module RSpec module Apib class Recorder attr_reader :example, :request, :response, :routes, :apib def initialize(example, request, response, routes, apib) @example = example @request = request @response = response @routes = routes @apib = apib end def run return unless request return unless response document_request document_response end private # Request headers contained in the blacklist will not be included in the # API documentation def request_header_blacklist RSpec::Apib.config.request_header_blacklist end def request_param_blacklist RSpec::Apib.config.request_param_blacklist end # The routing object for the rails route of the request def route @route ||= begin route = routes.router.send(:find_routes, request).first route = route.third if route.present? route end end # The top level example group the example is contained in def example_group @example_group ||= begin example_group = example.metadata[:example_group] while example_group[:parent_example_group] example_group = example_group[:parent_example_group] end example_group end end def path @path ||= begin parts = route.parts tmp = {} parts.each do |part| if route.optional_parts.include?(part) tmp[part] = "(:#{part}:)" else tmp[part] = ":#{part}:" end end result = route.format(tmp).gsub(/:(.*?):/, '{\\1}') result = result.gsub('.(', '(.') result = result.gsub('/(', '(/') result end end def group @group ||= begin apib[resource_name] ||= {} end end def resource_type @resource_type ||= begin route.required_parts.include?(:id) ? 'resource' : 'collection' end end def resource_name @resource_name ||= example_group[:description].to_s.singularize end def action @action ||= begin name = "#{request.method} #{path}" group[name] ||= { request: { _status: 600 }, response: [] } end end def document_request document_request_params return if response.status >= action[:request][:_status] action[:request][:_status] = response.status action[:request][:path] = request.path action[:request][:body] = request.body.read document_request_header end def document_request_header action[:request][:headers] = {} request.headers.each do |k, v| next unless k.starts_with?('HTTP_') header = k.gsub('HTTP_', '').downcase next if request_header_blacklist.include? header action[:request][:headers][header] = v end end def document_request_params return if (200..299).exclude?(response.status) params = request.params.symbolize_keys request_param_blacklist.each { |param| params.except!(param) } action[:request][:params] ||= {} params.each do |name, value| action[:request][:params][name] ||= { value: value, path: route.parts.include?(name), required: route.required_parts.include?(name) } end end def document_response data = {} return if response_exists? data[:description] = example.description data[:status] = response.status data[:content_type] = response.content_type.to_s data[:body] = response.body data[:headers] = response.headers action[:response] << data end def response_exists? action[:response].any? { |r| r[:status] == response.status } end end end end