Skip to content

Commit

Permalink
refactor: move OAS creation logic to builders and replace common objects
Browse files Browse the repository at this point in the history
by references to components.
  • Loading branch information
a-chacon committed Aug 8, 2024
1 parent 71a1515 commit 64d7922
Show file tree
Hide file tree
Showing 27 changed files with 701 additions and 369 deletions.
20 changes: 19 additions & 1 deletion lib/oas_rails.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,22 @@ module OasRails
autoload :Utils, "oas_rails/utils"
autoload :EsquemaBuilder, "oas_rails/esquema_builder"

module Builders
autoload :OperationBuilder, "oas_rails/builders/operation_builder"
autoload :PathItemBuilder, "oas_rails/builders/path_item_builder"
autoload :ResponseBuilder, "oas_rails/builders/response_builder"
autoload :ResponsesBuilder, "oas_rails/builders/responses_builder"
autoload :ContentBuilder, "oas_rails/builders/content_builder"
autoload :ParametersBuilder, "oas_rails/builders/parameters_builder"
autoload :ParameterBuilder, "oas_rails/builders/parameter_builder"
autoload :RequestBodyBuilder, "oas_rails/builders/request_body_builder"
end

# This module contains all the clases that represent a part of the OAS file.
module Spec
autoload :Hashable, "oas_rails/spec/hashable"
autoload :Specable, "oas_rails/spec/specable"
autoload :Components, "oas_rails/spec/components"
autoload :Parameter, "oas_rails/spec/parameter"
autoload :License, "oas_rails/spec/license"
autoload :Response, "oas_rails/spec/response"
Expand All @@ -28,6 +41,7 @@ module Spec
autoload :Server, "oas_rails/spec/server"
autoload :Tag, "oas_rails/spec/tag"
autoload :Specification, "oas_rails/spec/specification"
autoload :Reference, "oas_rails/spec/reference"
end

module YARD
Expand All @@ -37,11 +51,15 @@ module YARD
module Extractors
autoload :RenderResponseExtractor, 'oas_rails/extractors/render_response_extractor'
autoload :RouteExtractor, "oas_rails/extractors/route_extractor"
autoload :OasRouteExtractor, "oas_rails/extractors/oas_route_extractor"
end

class << self
def build
Spec::Specification.new.to_spec
oas = Spec::Specification.new
oas.build

oas.to_spec
end

# Configurations for make the OasRails engine Work.
Expand Down
55 changes: 55 additions & 0 deletions lib/oas_rails/builders/content_builder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
module OasRails
module Builders
class ContentBuilder
def initialize(specification, context)
@context = context || :incoming
@specification = specification
@media_type = Spec::MediaType.new(specification)
end

def with_schema(schema)
@media_type.schema = @specification.components.add_schema(schema)

self
end

def with_examples(examples)
@media_type.examples = @specification.components.add_example(examples)

self
end

def with_examples_from_tags(tags)
@media_type.examples = @media_type.examples.merge(tags.each_with_object({}).with_index(1) do |(example, result), _index|
key = example.text.downcase.gsub(' ', '_')
value = {
"summary" => example.text,
"value" => example.content
}
result[key] = @specification.components.add_example(value)
end)

self
end

def from_model_class(klass)
return self unless klass.ancestors.include? ActiveRecord::Base

model_schema = EsquemaBuilder.send("build_#{@context}_schema", klass:)
model_schema["required"] = []
schema = { type: "object", properties: { klass.to_s.downcase => model_schema } }
examples = Spec::MediaType.search_for_examples_in_tests(klass, context: @context)
@media_type.schema = @specification.components.add_schema(schema)
@media_type.examples = @media_type.examples.merge(examples)

self
end

def build
{
"application/json": @media_type
}
end
end
end
end
29 changes: 29 additions & 0 deletions lib/oas_rails/builders/operation_builder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
module OasRails
module Builders
class OperationBuilder
include Extractors::OasRouteExtractor

def initialize(specification)
@specification = specification
@operation = Spec::Operation.new(specification)
end

def from_oas_route(oas_route)
@operation.summary = extract_summary(oas_route:)
@operation.operation_id = extract_operation_id(oas_route:)
@operation.description = oas_route.docstring
@operation.tags = extract_tags(oas_route:)
@operation.security = extract_security(oas_route:)
@operation.parameters = ParametersBuilder.new(@specification).from_oas_route(oas_route).build
@operation.request_body = RequestBodyBuilder.new(@specification).from_oas_route(oas_route).reference
@operation.responses = ResponsesBuilder.new(@specification).from_oas_route(oas_route).add_autodiscovered_responses(oas_route).build

self
end

def build
@operation
end
end
end
end
28 changes: 28 additions & 0 deletions lib/oas_rails/builders/parameter_builder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module OasRails
module Builders
class ParameterBuilder
def initialize(specification)
@specification = specification
@parameter = Spec::Parameter.new(specification)
end

def from_path(path, param)
@parameter.name = param
@parameter.in = 'path'
@parameter.description = "#{param.split('_')[-1].titleize} of existing #{extract_word_before(path, param).singularize}."

self
end

def extract_word_before(string, param)
regex = %r{/(\w+)/\{#{param}\}}
match = string.match(regex)
match ? match[1] : nil
end

def build
@parameter
end
end
end
end
39 changes: 39 additions & 0 deletions lib/oas_rails/builders/parameters_builder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
module OasRails
module Builders
class ParametersBuilder
def initialize(specification)
@specification = specification
@parameters = []
end

def from_oas_route(oas_route)
parameters_from_tags(tags: oas_route.docstring.tags(:parameter))
oas_route.path_params.try(:map) do |p|
@parameters << ParameterBuilder.new(@specification).from_path(oas_route.path, p).build unless @parameters.any? { |param| param.name.to_s == p.to_s }
end

self
end

def parameters_from_tags(tags:)
tags.each do |t|
parameter = Spec::Parameter.new(@specification)
parameter.name = t.name
parameter.in = t.location
parameter.required = t.required
parameter.schema = t.schema
parameter.description = t.text
@parameters << parameter
end

self
end

def build
@parameters.map do |p|
@specification.components.add_parameter(p)
end
end
end
end
end
22 changes: 22 additions & 0 deletions lib/oas_rails/builders/path_item_builder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module OasRails
module Builders
class PathItemBuilder
def initialize(specification)
@specification = specification
@path_item = Spec::PathItem.new(specification)
end

def from_path(path, route_extractor: Extractors::RouteExtractor)
route_extractor.host_routes_by_path(path).each do |oas_route|
@path_item.add_operation(oas_route.verb.downcase, OperationBuilder.new(@specification).from_oas_route(oas_route).build)
end

self
end

def build
@path_item
end
end
end
end
60 changes: 60 additions & 0 deletions lib/oas_rails/builders/request_body_builder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
module OasRails
module Builders
class RequestBodyBuilder
def initialize(specification)
@specification = specification
@request_body = Spec::RequestBody.new(specification)
end

def from_oas_route(oas_route)
tag_request_body = oas_route.docstring.tags(:request_body).first
if tag_request_body.nil? && OasRails.config.autodiscover_request_body
detect_request_body(oas_route) if %w[create update].include? oas_route.method
elsif !tag_request_body.nil?
from_tags(tag: tag_request_body, examples_tags: oas_route.docstring.tags(:request_body_example))
end

self
end

def from_tags(tag:, examples_tags: [])
if tag.klass.ancestors.include? ActiveRecord::Base
from_model_class(klass: tag.klass, description: tag.text, required: tag.required, examples_tags:)
else
@request_body.description = tag.text
@request_body.content = ContentBuilder.new(@specification, :incoming).with_schema(tag.schema).with_examples_from_tags(examples_tags).build
@request_body.required = tag.required
end

self
end

def from_model_class(klass:, **kwargs)
@request_body.description = kwargs[:description] || klass.to_s
@request_body.content = ContentBuilder.new(@specification, :incoming).from_model_class(klass).with_examples_from_tags(kwargs[:examples_tags] || {}).build
@request_body.required = kwargs[:required]

self
end

def build
return {} if @request_body.content == {}

@request_body
end

def reference
return {} if @request_body.content == {}

@specification.components.add_request_body(@request_body)
end

private

def detect_request_body(oas_route)
klass = oas_route.controller.singularize.camelize.constantize
from_model_class(klass:, required: true)
end
end
end
end
40 changes: 40 additions & 0 deletions lib/oas_rails/builders/response_builder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
module OasRails
module Builders
class ResponseBuilder
def initialize(specification)
@specification = specification
@response = Spec::Response.new(specification)
end

def with_description(description)
@response.description = description

self
end

def with_content(content)
@response.content = content

self
end

def with_code(code)
@response.code = code

self
end

def from_tag(tag)
@response.code = tag.name.to_i
@response.description = tag.text
@response.content = ContentBuilder.new(@specification, :outgoing).with_schema(tag.schema).build

self
end

def build
@response
end
end
end
end
34 changes: 34 additions & 0 deletions lib/oas_rails/builders/responses_builder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
module OasRails
module Builders
class ResponsesBuilder
def initialize(specification)
@specification = specification
@responses = Spec::Responses.new(specification)
end

def from_oas_route(oas_route)
oas_route.docstring.tags(:response).each do |tag|
@responses.add_response(ResponseBuilder.new(@specification).from_tag(tag).build)
end

self
end

def add_autodiscovered_responses(oas_route)
return unless OasRails.config.autodiscover_responses

new_responses = Extractors::RenderResponseExtractor.extract_responses_from_source(@specification, source: oas_route.source_string)

new_responses.each do |new_response|
@responses.add_response(new_response) if @responses.responses[new_response.code].blank?
end

self
end

def build
@responses
end
end
end
end
Loading

0 comments on commit 64d7922

Please sign in to comment.