class Apia::Rack

Public Class Methods

new(app, api, namespace, **options) click to toggle source
# File lib/apia/rack.rb, line 11
def initialize(app, api, namespace, **options)
  @app = app
  @api = api
  @namespace = '/' + namespace.sub(/\A\/+/, '').sub(/\/+\z/, '')
  @options = options
end

Private Class Methods

error_triplet(code, description: nil, detail: {}, status: 500, headers: {}) click to toggle source

Return a triplet for a given error using the standard error schema

@param code [String] @param description [String] @param detail [Hash] @param status [Integer] @param headers [Hash] @return [Array]

# File lib/apia/rack.rb, line 171
def error_triplet(code, description: nil, detail: {}, status: 500, headers: {})
  json_triplet({
                 error: {
                   code: code,
                   description: description,
                   detail: detail
                 }
               }, status: status, headers: headers.merge('x-api-schema' => 'json-error'))
end
json_triplet(body, status: 200, headers: {}) click to toggle source

Return a JSON-ready triplet for the given body.

@param body [Hash, Array] @param status [Integer] @param headers [Hash] @return [Array]

# File lib/apia/rack.rb, line 154
def json_triplet(body, status: 200, headers: {})
  body_as_json = body.to_json
  [
    status,
    headers.merge('content-type' => 'application/json', 'content-length' => body_as_json.bytesize.to_s),
    [body_as_json]
  ]
end

Public Instance Methods

api() click to toggle source

Return the API object

@return [Apia::API]

# File lib/apia/rack.rb, line 45
def api
  return Object.const_get(@api) if @api.is_a?(String) && development?
  return @cached_api ||= Object.const_get(@api) if @api.is_a?(String)

  @api
end
call(env) click to toggle source

Actually make the request

@param env [Hash] @return [Array] a rack triplet

# File lib/apia/rack.rb, line 56
def call(env)
  if @options[:hosts]&.none? { |host| host == env['HTTP_HOST'] }
    return @app.call(env)
  end

  unless env['PATH_INFO'] =~ /\A#{Regexp.escape(@namespace)}\/([a-z].*)\z/i
    return @app.call(env)
  end

  api_path = Regexp.last_match(1)

  triplet = handle_request(env, api_path)
  add_cors_headers(env, triplet)
  triplet
end
development?() click to toggle source

Is this supposed to be running in development? This will validate the whole API on each request as well as being more verbose about internal server errors that are encountered.

@return [Boolean]

# File lib/apia/rack.rb, line 23
def development?
  env_is_dev = ENV['RACK_ENV'] == 'development'
  return true if env_is_dev && @options[:development].nil?

  @options[:development] == true
end
find_route(method, path) click to toggle source

Parse a given full path and return nil if it doesn't match our namespace or return a hash with the controller and endpoint named as available.

@param path [String] /core/v1/controller/endpoint @return [nil, Hash]

# File lib/apia/rack.rb, line 36
def find_route(method, path)
  return if api.nil?

  api.definition.route_set.find(method.to_s.downcase.to_sym, path).first
end

Private Instance Methods

add_cors_headers(env, triplet) click to toggle source

Add cross origin headers to the response triplet

@param env [Hash] @param triplet [Array] @return [void]

# File lib/apia/rack.rb, line 136
def add_cors_headers(env, triplet)
  triplet[1]['Access-Control-Allow-Origin'] = '*'
  triplet[1]['Access-Control-Allow-Methods'] = '*'
  if env['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']
    triplet[1]['Access-Control-Allow-Headers'] = env['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']
  end

  true
end
handle_request(env, api_path) click to toggle source
# File lib/apia/rack.rb, line 74
def handle_request(env, api_path)
  if env['REQUEST_METHOD'].upcase == 'OPTIONS'
    return [204, {}, ['']]
  end

  validate_api if development?

  route = find_route(env['REQUEST_METHOD'], api_path)
  if route.nil?
    raise RackError.new(404, 'route_not_found', "No route matches '#{api_path}' for #{env['REQUEST_METHOD']}")
  end

  request = Apia::Request.new(env)
  request.api_path = api_path
  request.namespace = @namespace
  request.api = api
  request.controller = route.controller
  request.endpoint = route.endpoint
  request.route = route

  response = request.endpoint.execute(request)
  response.rack_triplet
rescue ::StandardError => e
  if e.is_a?(RackError) || e.is_a?(Apia::ManifestError)
    return e.triplet
  end

  api.definition.exception_handlers.call(e, {
    env: env,
    api: api,
    request: defined?(request) ? request : nil
  })

  if development?
    return triplet_for_exception(e)
  end

  self.class.error_triplet('unhandled_exception', status: 500)
end
triplet_for_exception(exception) click to toggle source
# File lib/apia/rack.rb, line 118
def triplet_for_exception(exception)
  self.class.error_triplet(
    'unhandled_exception',
    description: 'This is an exception that has occurred and not been handled.',
    detail: {
      class: exception.class.name,
      message: exception.message,
      backtrace: exception.backtrace
    },
    status: 500
  )
end
validate_api() click to toggle source
# File lib/apia/rack.rb, line 114
def validate_api
  api.validate_all.raise_if_needed
end