class RSpec::Rails::Api::OpenApiRenderer

Class to render metadatas. Example:

```rb
renderer = RSpec::Rails::Api::OpenApiRenderer.new
renderer.merge_context(example_context)
renderer.write_files
```

Attributes

api_description[W]
api_servers[W]
api_title[W]
api_tos[W]
api_version[W]

Public Class Methods

new() click to toggle source
# File lib/rspec/rails/api/open_api_renderer.rb, line 19
def initialize
  @metadata = { resources: {}, entities: {} }
  @api_infos = {}
  @api_servers = []
  @api_paths = {}
  @api_components = {}
  @api_tags       = []
  @api_contact    = {}
  @api_license    = {}
end

Public Instance Methods

api_contact=(name: nil, email: nil, url: nil) click to toggle source
# File lib/rspec/rails/api/open_api_renderer.rb, line 64
def api_contact=(name: nil, email: nil, url: nil)
  @api_contact[:name]  = name if name
  @api_contact[:email] = email if email
  @api_contact[:url]   = url if url
end
api_license=(name: nil, url: nil) click to toggle source
# File lib/rspec/rails/api/open_api_renderer.rb, line 70
def api_license=(name: nil, url: nil)
  @api_license[:name] = name if name
  @api_license[:url] = url if url
end
merge_context(context, dump_metadata: false) click to toggle source
# File lib/rspec/rails/api/open_api_renderer.rb, line 30
def merge_context(context, dump_metadata: false)
  @metadata[:resources].deep_merge! context[:resources]
  @metadata[:entities].deep_merge! context[:entities]

  # Save context for debug and fixtures
  File.write ::Rails.root.join('tmp', 'rra_metadata.yaml'), context.to_yaml if dump_metadata
end
prepare_metadata() click to toggle source
# File lib/rspec/rails/api/open_api_renderer.rb, line 51
def prepare_metadata
  # Example: https://github.com/OAI/OpenAPI-Specification/blob/master/examples/v3.0/petstore-expanded.yaml
  extract_metadatas
  {
    openapi:    '3.0.0',
    info:       @api_infos,
    servers:    @api_servers,
    paths:      @api_paths,
    components: @api_components,
    tags:       @api_tags,
  }.deep_stringify_keys
end
write_files(path = nil, only: %i[yaml json]) click to toggle source
# File lib/rspec/rails/api/open_api_renderer.rb, line 38
def write_files(path = nil, only: %i[yaml json])
  content = prepare_metadata
  path ||= ::Rails.root.join('tmp', 'rspec_api_rails')

  file_types = %i[yaml json]

  only.each do |type|
    next unless file_types.include? type

    File.write "#{path}.#{type}", JSON.parse(JSON.pretty_generate(content)).send("to_#{type}")
  end
end

Private Instance Methods

api_infos() click to toggle source
# File lib/rspec/rails/api/open_api_renderer.rb, line 225
def api_infos
  @api_infos = {
    title:   @api_title || 'Some sample app',
    version: @api_version || '1.0',
  }
  @api_infos[:description]    = @api_description if @api_description
  @api_infos[:termsOfService] = @api_tos if @api_tos
  @api_infos[:contact]        = @api_contact if @api_contact[:name]
  @api_infos[:license]        = @api_license if @api_license[:name]

  @api_infos
end
api_servers() click to toggle source
# File lib/rspec/rails/api/open_api_renderer.rb, line 238
def api_servers
  @api_servers || [
    { url: 'http://api.example.com' },
  ]
end
escape_operation_id(string) click to toggle source
# File lib/rspec/rails/api/open_api_renderer.rb, line 221
def escape_operation_id(string)
  string.downcase.gsub(/[^\w]+/, '_')
end
extract_from_resources() click to toggle source
# File lib/rspec/rails/api/open_api_renderer.rb, line 83
def extract_from_resources
  @metadata[:resources].each do |resource_key, resource|
    @api_tags.push(
      name:        resource_key.to_s,
      description: resource[:description]
    )
    process_resource resource: resource_key, resource_config: resource
  end
end
extract_metadatas() click to toggle source
# File lib/rspec/rails/api/open_api_renderer.rb, line 77
def extract_metadatas
  extract_from_resources
  api_infos
  api_servers
end
path_with_params(string) click to toggle source
# File lib/rspec/rails/api/open_api_renderer.rb, line 215
def path_with_params(string)
  string.gsub(/(?::(\w*))/) do |e|
    "{#{e.sub(':', '')}}"
  end
end
process_action(resource: nil, path: nil, path_config: nil, action_config: nil, parameters: nil) click to toggle source

rubocop:disable Metrics/AbcSize, Metrics/MethodLength

# File lib/rspec/rails/api/open_api_renderer.rb, line 140
def process_action(resource: nil, path: nil, path_config: nil, action_config: nil, parameters: nil)
  responses    = {}
  request_body = nil

  if %i[post put
        patch].include?(action_config) && path_config[:actions][action_config][:params].keys.count.positive?
    schema = path_config[:actions][action_config][:params]
    schema_ref   = escape_operation_id("#{action_config}_#{path}")
    examples     = process_examples(path_config[:actions][action_config][:statuses])
    request_body = process_request_body schema: schema, ref: schema_ref, examples: examples
  end

  path_config[:actions][action_config][:statuses].each do |status_key, status|
    content               = status[:example][:response]
    responses[status_key] = process_response status: status_key, status_config: status, content: content
  end

  description          = path_config[:actions][action_config][:description]
  action               = {
    description: description,
    operationId: "#{resource} #{description}".downcase.gsub(/[^\w]/, '_'),
    parameters:  parameters,
    responses:   responses,
    tags:        [resource.to_s],
  }

  action[:requestBody] = request_body if request_body

  action
end
process_examples(statuses) click to toggle source
# File lib/rspec/rails/api/open_api_renderer.rb, line 202
def process_examples(statuses)
  request_examples = {}

  statuses.each do |code, request|
    request_examples[code] = {
      summary: "Example for a #{code} code",
      value:   request[:example][:params],
    }
  end

  request_examples
end
process_path_param(name, param) click to toggle source
# File lib/rspec/rails/api/open_api_renderer.rb, line 123
def process_path_param(name, param) # rubocop:disable Metrics/MethodLength
  parameter = {
    name:        name.to_s,
    description: param[:description],
    required:    param[:required] || true,
    in:          param[:scope].to_s,
    schema:      {
      type: PARAM_TYPES[param[:type]][:type],
    },
  }

  parameter[:schema][:format] = PARAM_TYPES[param[:type]][:format] if PARAM_TYPES[param[:type]][:format]

  parameter
end
process_path_params(params) click to toggle source
# File lib/rspec/rails/api/open_api_renderer.rb, line 114
def process_path_params(params)
  parameters = []
  params.each do |name, param|
    parameters.push process_path_param name, param
  end

  parameters
end
process_request_body(schema: nil, ref: nil, examples: {}) click to toggle source

rubocop:enable Metrics/AbcSize, Metrics/MethodLength

# File lib/rspec/rails/api/open_api_renderer.rb, line 172
def process_request_body(schema: nil, ref: nil, examples: {})
  Utils.deep_set @api_components, "schemas.#{ref}", schema
  {
    # description: '',
    required: true,
    content:  {
      'application/json' => {
        schema:   { '$ref' => "#/components/schemas/#{ref}" },
        examples: examples,
      },
    },
  }
end
process_resource(resource: nil, resource_config: nil) click to toggle source
# File lib/rspec/rails/api/open_api_renderer.rb, line 93
def process_resource(resource: nil, resource_config: nil) # rubocop:disable Metrics/MethodLength
  http_verbs = %i[get post put patch delete]
  resource_config[:paths].each do |path_key, path|
    url        = path_with_params path_key.to_s
    actions    = {}
    parameters = path.key?(:path_params) ? process_path_params(path[:path_params]) : []

    path[:actions].each_key do |action|
      next unless http_verbs.include? action

      actions[action] = process_action resource:      resource,
                                       path:          path_key,
                                       path_config:   path,
                                       action_config: action,
                                       parameters:    parameters
    end

    @api_paths[url] = actions
  end
end
process_response(status: nil, status_config: nil, content: nil) click to toggle source
# File lib/rspec/rails/api/open_api_renderer.rb, line 186
def process_response(status: nil, status_config: nil, content: nil)
  response = {
    description: status_config[:description],
  }

  return response if status.to_s == '204' && content # No content

  response[:content] = {
    'application/json': {
      examples: { default: { value: JSON.parse(content) } },
    },
  }

  response
end