class Rack::Kibo

Rack Middleware which presents a clean API to JSON responses

Constants

API_PARSE_REGEX

Regex to locate an API version in the path. Able to find the following

  • /path/api/1/something => 1

  • /path/api/V2/something => 2

  • /api/v13/omething => 13

  • /something/else/134/ => 0

JSON_CONTENT_TYPE

Standard JSON content-type

JSON_CONTENT_TYPE_REGEX
VERSION

Public Class Methods

new(app, options={}) click to toggle source

Creates a new instance of the CleanApi middleware app: The rack app options: Hash of options to pass to the middleware

# File lib/rack/kibo/kibo.rb, line 24
def initialize(app, options={})
  @app = app
  @options = options
end

Public Instance Methods

call(env) click to toggle source

Rack Middleware entry-point

# File lib/rack/kibo/kibo.rb, line 31
def call(env)
  result = @app.call(env)
  wrap_response env, result
rescue StandardError => e
  wrap_response env, create_error_response(e, env, result)
end

Private Instance Methods

create_error_json(error, data) click to toggle source

Generates an error response payload

# File lib/rack/kibo/kibo.rb, line 121
def create_error_json(error, data)
  result = {
    :error => {
      :message => 'Error'
    }
  }
  if @options[:expose_errors]
    result[:error][:message] = error.message
    result[:error][:data] = data
  end
  result.to_json
end
create_error_response(error, env, result) click to toggle source

Convert exceptions into a proper response

# File lib/rack/kibo/kibo.rb, line 82
def create_error_response(error, env, result)
  rsp_env = {}
  response = 'Error'
  if should_wrap_response?(env, result)
    server_result = nil
    if result
      env = result[1]
      server_result = result[2]
    end
    response = create_error_json(error, server_result)
    # should send json content-type
    rsp_env["Content-Type"] = JSON_CONTENT_TYPE
  end
  error_result = [500, rsp_env, [response]]
end
create_payload(response_data) click to toggle source

Generates the 'payload' element of the API response

# File lib/rack/kibo/kibo.rb, line 100
def create_payload(response_data)
  payload = []
  response_data.each do |data|
    payload << JSON.parse(data)
  end

  nil if payload.length == 0
  payload if payload.length > 1
  payload[0] if payload.length == 1
end
is_successful_http_status_code?(code) click to toggle source

Determines if the HTTP Status code is considered a successful response

# File lib/rack/kibo/kibo.rb, line 137
def is_successful_http_status_code?(code)
  code < 400
end
is_supported_content_type?(content_type) click to toggle source

Determines if the content_type dictates that the response should be wrapped in the CleanApi

# File lib/rack/kibo/kibo.rb, line 161
def is_supported_content_type?(content_type)
  # TODO: Should we allow user-defined accept-encodings?
  content_type =~ JSON_CONTENT_TYPE_REGEX ? true : false
end
parse_api_version(env) click to toggle source

Parses the API version from the request path

# File lib/rack/kibo/kibo.rb, line 113
def parse_api_version(env)
  matches = API_PARSE_REGEX.match(request_path(env))
  return 0 unless matches
  matches.captures[0].to_i
end
request_accept(env) click to toggle source

Fetches the 'HTTP_ACCEPT' option from env

# File lib/rack/kibo/kibo.rb, line 42
def request_accept(env)
  env["HTTP_ACCEPT"]
end
request_path(env) click to toggle source

Fetches the 'PATH_INFO' rack variable from env Optionally will find and prioritize the 'REQUEST_PATH' if available

# File lib/rack/kibo/kibo.rb, line 50
def request_path(env)
  env["REQUEST_PATH"] || env["PATH_INFO"]
end
response_should_be_json?(response) click to toggle source

Determines if the response Content-Type is supposed to be wrapped in the CleanApi object

# File lib/rack/kibo/kibo.rb, line 153
def response_should_be_json?(response)
  return false unless response
  is_supported_content_type? response[1]["Content-Type"]
end
should_wrap_response?(env, response) click to toggle source

Determines if the response should be wrapped in the CleanApi object

# File lib/rack/kibo/kibo.rb, line 144
def should_wrap_response?(env, response)
  client_request = is_supported_content_type? request_accept(env)
  server_response = response_should_be_json? response
  server_response || client_request
end
wrap_response(env, response) click to toggle source

Takes the output from the rack app and optionally wraps the response with a simple JSON API structure

# File lib/rack/kibo/kibo.rb, line 57
def wrap_response(env, response)
  return response unless should_wrap_response?(env, response)
  rs = {
    :success => is_successful_http_status_code?(response[0]),
    :responded_at => Time.now.utc,
    :version => parse_api_version(env),
    :location => request_path(env),
    :body => create_payload(response[2])
  }
  response[2] = [ rs.to_json ]

  # Let Rack compute the content-length
  #response[1]["Content-Length"] = response[2][0].length.to_s
  response[1].delete("Content-Length")
  # TODO: support an option to allow user to emit original status codes
  
  # if there is an error, then respond with a 200
  # so browser promises return as success responses
  # instead of errors
  response[0] = 200 unless rs[:success]
  response
end