oas_rails 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -1
  3. data/lib/oas_rails/builders/oas_route_builder.rb +2 -3
  4. data/lib/oas_rails/configuration.rb +14 -127
  5. data/lib/oas_rails/engine.rb +0 -5
  6. data/lib/oas_rails/version.rb +1 -1
  7. data/lib/oas_rails.rb +18 -64
  8. metadata +4 -54
  9. data/lib/oas_rails/builders/content_builder.rb +0 -62
  10. data/lib/oas_rails/builders/operation_builder.rb +0 -32
  11. data/lib/oas_rails/builders/parameter_builder.rb +0 -28
  12. data/lib/oas_rails/builders/parameters_builder.rb +0 -39
  13. data/lib/oas_rails/builders/path_item_builder.rb +0 -24
  14. data/lib/oas_rails/builders/request_body_builder.rb +0 -61
  15. data/lib/oas_rails/builders/response_builder.rb +0 -40
  16. data/lib/oas_rails/builders/responses_builder.rb +0 -81
  17. data/lib/oas_rails/extractors/oas_route_extractor.rb +0 -66
  18. data/lib/oas_rails/json_schema_generator.rb +0 -132
  19. data/lib/oas_rails/oas_route.rb +0 -21
  20. data/lib/oas_rails/spec/components.rb +0 -96
  21. data/lib/oas_rails/spec/contact.rb +0 -18
  22. data/lib/oas_rails/spec/hashable.rb +0 -39
  23. data/lib/oas_rails/spec/info.rb +0 -66
  24. data/lib/oas_rails/spec/license.rb +0 -18
  25. data/lib/oas_rails/spec/media_type.rb +0 -24
  26. data/lib/oas_rails/spec/operation.rb +0 -25
  27. data/lib/oas_rails/spec/parameter.rb +0 -37
  28. data/lib/oas_rails/spec/path_item.rb +0 -33
  29. data/lib/oas_rails/spec/paths.rb +0 -26
  30. data/lib/oas_rails/spec/reference.rb +0 -16
  31. data/lib/oas_rails/spec/request_body.rb +0 -21
  32. data/lib/oas_rails/spec/response.rb +0 -20
  33. data/lib/oas_rails/spec/responses.rb +0 -25
  34. data/lib/oas_rails/spec/server.rb +0 -17
  35. data/lib/oas_rails/spec/specable.rb +0 -54
  36. data/lib/oas_rails/spec/specification.rb +0 -50
  37. data/lib/oas_rails/spec/tag.rb +0 -18
  38. data/lib/oas_rails/yard/example_tag.rb +0 -12
  39. data/lib/oas_rails/yard/oas_rails_factory.rb +0 -169
  40. data/lib/oas_rails/yard/parameter_tag.rb +0 -14
  41. data/lib/oas_rails/yard/request_body_example_tag.rb +0 -11
  42. data/lib/oas_rails/yard/request_body_tag.rb +0 -16
  43. data/lib/oas_rails/yard/response_example_tag.rb +0 -12
  44. data/lib/oas_rails/yard/response_tag.rb +0 -13
@@ -1,61 +0,0 @@
1
- module OasRails
2
- module Builders
3
- class RequestBodyBuilder
4
- def initialize(specification)
5
- @specification = specification
6
- @request_body = Spec::RequestBody.new(specification)
7
- end
8
-
9
- def from_oas_route(oas_route)
10
- tag_request_body = oas_route.tags(:request_body).first
11
- if tag_request_body.nil? && OasRails.config.autodiscover_request_body
12
- detect_request_body(oas_route) if %w[create update].include? oas_route.method
13
- elsif !tag_request_body.nil?
14
- from_tags(tag: tag_request_body, examples_tags: oas_route.tags(:request_body_example))
15
- end
16
-
17
- self
18
- end
19
-
20
- def from_tags(tag:, examples_tags: [])
21
- if Utils.active_record_class?(tag.klass)
22
- from_model_class(klass: tag.klass, description: tag.text, required: tag.required, content_type: tag.content_type, examples_tags:)
23
- else
24
- @request_body.description = tag.text
25
- @request_body.content = ContentBuilder.new(@specification, :incoming).with_schema(tag.schema).with_examples_from_tags(examples_tags).with_content_type(tag.content_type).build
26
- @request_body.required = tag.required
27
- end
28
-
29
- self
30
- end
31
-
32
- def from_model_class(klass:, **kwargs)
33
- @request_body.description = kwargs[:description] || klass.to_s
34
- @request_body.content = ContentBuilder.new(@specification, :incoming).from_model_class(klass).with_examples_from_tags(kwargs[:examples_tags] || {}).with_content_type(kwargs[:content_type]).build
35
- @request_body.required = kwargs[:required]
36
-
37
- self
38
- end
39
-
40
- def build
41
- return {} if @request_body.content == {}
42
-
43
- @request_body
44
- end
45
-
46
- def reference
47
- return {} if @request_body.content == {}
48
-
49
- @specification.components.add_request_body(@request_body)
50
- end
51
-
52
- private
53
-
54
- def detect_request_body(oas_route)
55
- return unless (klass = Utils.find_model_from_route(oas_route.controller))
56
-
57
- from_model_class(klass:, required: true)
58
- end
59
- end
60
- end
61
- end
@@ -1,40 +0,0 @@
1
- module OasRails
2
- module Builders
3
- class ResponseBuilder
4
- def initialize(specification)
5
- @specification = specification
6
- @response = Spec::Response.new(specification)
7
- end
8
-
9
- def with_description(description)
10
- @response.description = description
11
-
12
- self
13
- end
14
-
15
- def with_content(content)
16
- @response.content = content
17
-
18
- self
19
- end
20
-
21
- def with_code(code)
22
- @response.code = code
23
-
24
- self
25
- end
26
-
27
- def from_tag(tag)
28
- @response.code = tag.name.to_i
29
- @response.description = tag.text
30
- @response.content = ContentBuilder.new(@specification, :outgoing).with_schema(tag.schema).build
31
-
32
- self
33
- end
34
-
35
- def build
36
- @response
37
- end
38
- end
39
- end
40
- end
@@ -1,81 +0,0 @@
1
- module OasRails
2
- module Builders
3
- class ResponsesBuilder
4
- def initialize(specification)
5
- @specification = specification
6
- @responses = Spec::Responses.new(specification)
7
- end
8
-
9
- def from_oas_route(oas_route)
10
- oas_route.tags(:response).each do |tag|
11
- content = ContentBuilder.new(@specification, :outgoing).with_schema(tag.schema).with_examples_from_tags(oas_route.tags(:response_example).filter { |re| re.code == tag.name }).build
12
- response = ResponseBuilder.new(@specification).with_code(tag.name.to_i).with_description(tag.text).with_content(content).build
13
-
14
- @responses.add_response(response)
15
- end
16
-
17
- self
18
- end
19
-
20
- def add_autodiscovered_responses(oas_route)
21
- return self if !OasRails.config.autodiscover_responses || oas_route.tags(:response).any?
22
-
23
- new_responses = Extractors::RenderResponseExtractor.extract_responses_from_source(@specification, source: oas_route.source_string)
24
-
25
- new_responses.each do |new_response|
26
- @responses.add_response(new_response) if @responses.responses[new_response.code].blank?
27
- end
28
-
29
- self
30
- end
31
-
32
- def add_default_responses(oas_route, security)
33
- return self unless OasRails.config.set_default_responses
34
-
35
- common_errors = determine_common_errors(oas_route, security)
36
- add_responses_for_errors(common_errors)
37
-
38
- self
39
- end
40
-
41
- def build
42
- @responses
43
- end
44
-
45
- private
46
-
47
- def determine_common_errors(oas_route, security)
48
- common_errors = []
49
- common_errors.push(:unauthorized, :forbidden, :internal_server_error) if security
50
-
51
- case oas_route.method
52
- when "show", "destroy"
53
- common_errors.push(:not_found)
54
- when "create"
55
- common_errors.push(:unprocessable_entity)
56
- when "update"
57
- common_errors.push(:not_found, :unprocessable_entity)
58
- end
59
-
60
- OasRails.config.possible_default_responses & common_errors
61
- end
62
-
63
- def add_responses_for_errors(errors)
64
- errors.each do |error|
65
- response_body = resolve_response_body(error)
66
- content = ContentBuilder.new(@specification, :outgoing).with_schema(JsonSchemaGenerator.process_string(response_body)[:json_schema]).build
67
- code = Utils.status_to_integer(error)
68
- response = ResponseBuilder.new(@specification).with_code(code).with_description(Utils.get_definition(code)).with_content(content).build
69
-
70
- @responses.add_response(response) if @responses.responses[response.code].blank?
71
- end
72
- end
73
-
74
- def resolve_response_body(error)
75
- OasRails.config.public_send("response_body_of_#{error}")
76
- rescue StandardError
77
- OasRails.config.response_body_of_default
78
- end
79
- end
80
- end
81
- end
@@ -1,66 +0,0 @@
1
- module OasRails
2
- module Extractors
3
- module OasRouteExtractor
4
- def extract_summary(oas_route:)
5
- oas_route.tags(:summary).first.try(:text) || generate_crud_name(oas_route.method, oas_route.controller.downcase) || "#{oas_route.verb} #{oas_route.path}"
6
- end
7
-
8
- def extract_operation_id(oas_route:)
9
- "#{oas_route.method}#{oas_route.path.gsub('/', '_').gsub(/[{}]/, '')}"
10
- end
11
-
12
- def extract_tags(oas_route:)
13
- tags = oas_route.tags(:tags).first
14
- if tags.nil?
15
- default_tags(oas_route:)
16
- else
17
- tags.text.split(",").map(&:strip).map(&:titleize)
18
- end
19
- end
20
-
21
- def default_tags(oas_route:)
22
- tags = []
23
- if OasRails.config.default_tags_from == :namespace
24
- tag = oas_route.path.split('/').reject(&:empty?).first.try(:titleize)
25
- tags << tag unless tag.nil?
26
- else
27
- tags << oas_route.controller.gsub("/", " ").titleize
28
- end
29
- tags
30
- end
31
-
32
- def extract_security(oas_route:)
33
- return [] if oas_route.tags(:no_auth).any?
34
-
35
- if (methods = oas_route.tags(:auth).first)
36
- OasRails.config.security_schemas.keys.map { |key| { key => [] } }.select do |schema|
37
- methods.types.include?(schema.keys.first.to_s)
38
- end
39
- elsif OasRails.config.authenticate_all_routes_by_default
40
- OasRails.config.security_schemas.keys.map { |key| { key => [] } }
41
- else
42
- []
43
- end
44
- end
45
-
46
- private
47
-
48
- def generate_crud_name(method, controller)
49
- controller_name = controller.to_s.underscore.humanize.downcase.pluralize
50
-
51
- case method.to_sym
52
- when :index
53
- "List #{controller_name}"
54
- when :show
55
- "View #{controller_name.singularize}"
56
- when :create
57
- "Create new #{controller_name.singularize}"
58
- when :update
59
- "Update #{controller_name.singularize}"
60
- when :destroy
61
- "Delete #{controller_name.singularize}"
62
- end
63
- end
64
- end
65
- end
66
- end
@@ -1,132 +0,0 @@
1
- require 'json'
2
-
3
- module OasRails
4
- # The JsonSchemaGenerator module provides methods to transform string representations
5
- # of data types into JSON schema formats.
6
- module JsonSchemaGenerator
7
- # Processes a string representing a data type and converts it into a JSON schema.
8
- #
9
- # @param str [String] The string representation of a data type.
10
- # @return [Hash] A hash containing the required flag and the JSON schema.
11
- def self.process_string(str)
12
- parsed = parse_type(str)
13
- {
14
- required: parsed[:required],
15
- json_schema: to_json_schema(parsed)
16
- }
17
- end
18
-
19
- # Parses a string representing a data type and determines its JSON schema type.
20
- #
21
- # @param str [String] The string representation of a data type.
22
- # @return [Hash] A hash containing the type, whether it's required, and any additional properties.
23
- def self.parse_type(str)
24
- required = str.start_with?('!')
25
- type = str.sub(/^!/, '').strip
26
-
27
- case type
28
- when /^Hash\{(.+)\}$/i
29
- { type: :object, required:, properties: parse_object_properties(::Regexp.last_match(1)) }
30
- when /^Array<(.+)>$/i
31
- { type: :array, required:, items: parse_type(::Regexp.last_match(1)) }
32
- when ->(t) { Utils.active_record_class?(t) }
33
- Builders::EsquemaBuilder.build_outgoing_schema(klass: type.constantize)
34
- else
35
- { type: type.downcase.to_sym, required: }
36
- end
37
- end
38
-
39
- # Parses the properties of an object type from a string.
40
- #
41
- # @param str [String] The string representation of the object's properties.
42
- # @return [Hash] A hash where keys are property names and values are their JSON schema types.
43
- def self.parse_object_properties(str)
44
- properties = {}
45
- stack = []
46
- current_key = ''
47
- current_value = ''
48
-
49
- str.each_char.with_index do |char, index|
50
- case char
51
- when '{', '<'
52
- stack.push(char)
53
- current_value += char
54
- when '}', '>'
55
- stack.pop
56
- current_value += char
57
- when ','
58
- if stack.empty?
59
- properties[current_key.strip.to_sym] = parse_type(current_value.strip)
60
- current_key = ''
61
- current_value = ''
62
- else
63
- current_value += char
64
- end
65
- when ':'
66
- if stack.empty?
67
- current_key = current_value
68
- current_value = ''
69
- else
70
- current_value += char
71
- end
72
- else
73
- current_value += char
74
- end
75
-
76
- properties[current_key.strip.to_sym] = parse_type(current_value.strip) if index == str.length - 1 && !current_key.empty?
77
- end
78
-
79
- properties
80
- end
81
-
82
- # Converts a parsed data type into a JSON schema format.
83
- #
84
- # @param parsed [Hash] The parsed data type hash.
85
- # @return [Hash] The JSON schema representation of the parsed data type.
86
- def self.to_json_schema(parsed)
87
- case parsed[:type]
88
- when :object
89
- schema = {
90
- type: 'object',
91
- properties: {}
92
- }
93
- required_props = []
94
- parsed[:properties].each do |key, value|
95
- schema[:properties][key] = to_json_schema(value)
96
- required_props << key.to_s if value[:required]
97
- end
98
- schema[:required] = required_props unless required_props.empty?
99
- schema
100
- when :array
101
- {
102
- type: 'array',
103
- items: to_json_schema(parsed[:items])
104
- }
105
- when nil
106
- parsed
107
- else
108
- ruby_type_to_json_schema_type(parsed[:type])
109
- end
110
- end
111
-
112
- # Converts a Ruby data type into its corresponding JSON schema type.
113
- #
114
- # @param type [Symbol, String] The Ruby data type.
115
- # @return [Hash, String] The JSON schema type or a hash with additional format information.
116
- def self.ruby_type_to_json_schema_type(type)
117
- case type.to_s.downcase
118
- when 'string' then { type: "string" }
119
- when 'integer' then { type: "integer" }
120
- when 'float' then { type: "float" }
121
- when 'boolean' then { type: "boolean" }
122
- when 'array' then { type: "array" }
123
- when 'hash' then { type: "hash" }
124
- when 'nil' then { type: "null" }
125
- when 'date' then { type: "string", format: "date" }
126
- when 'datetime' then { type: "string", format: "date-time" }
127
- when 'file' then { type: "string", format: "binary" }
128
- else type.to_s.downcase
129
- end
130
- end
131
- end
132
- end
@@ -1,21 +0,0 @@
1
- module OasRails
2
- class OasRoute
3
- attr_accessor :controller_class, :controller_action, :controller, :controller_path, :method, :verb, :path,
4
- :rails_route, :docstring, :source_string
5
- attr_writer :tags
6
-
7
- def initialize(attributes = {})
8
- attributes.each { |key, value| send("#{key}=", value) }
9
- end
10
-
11
- def path_params
12
- @rails_route.path.spec.to_s.scan(/:(\w+)/).flatten.reject! { |e| e == 'format' }
13
- end
14
-
15
- def tags(name = nil)
16
- return @tags if name.nil?
17
-
18
- @tags.select { |tag| tag.tag_name.to_s == name.to_s }
19
- end
20
- end
21
- end
@@ -1,96 +0,0 @@
1
- module OasRails
2
- module Spec
3
- class Components
4
- include Specable
5
-
6
- attr_accessor :schemas, :parameters, :security_schemes, :request_bodies, :responses, :headers, :examples, :links, :callbacks
7
-
8
- def initialize(specification)
9
- @specification = specification
10
- @schemas = {}
11
- @parameters = {}
12
- @security_schemes = OasRails.config.security_schemas
13
- @request_bodies = {}
14
- @responses = {}
15
- @headers = {}
16
- @examples = {}
17
- @links = {}
18
- @callbacks = {}
19
- end
20
-
21
- def oas_fields
22
- [:request_bodies, :examples, :responses, :schemas, :parameters, :security_schemes]
23
- end
24
-
25
- def add_response(response)
26
- key = response.hash_key
27
- @responses[key] = response unless @responses.key? key
28
-
29
- response_reference(key)
30
- end
31
-
32
- def add_parameter(parameter)
33
- key = parameter.hash_key
34
- @parameters[key] = parameter unless @parameters.key? key
35
-
36
- parameter_reference(key)
37
- end
38
-
39
- def add_request_body(request_body)
40
- key = request_body.hash_key
41
- @request_bodies[key] = request_body unless @request_bodies.key? key
42
-
43
- request_body_reference(key)
44
- end
45
-
46
- def add_schema(schema)
47
- key = nil
48
- if OasRails.config.use_model_names
49
- if schema[:type] == 'array'
50
- arr_schema = schema[:items]
51
- arr_key = arr_schema['title']
52
- key = "#{arr_key}List" unless arr_key.nil?
53
- else
54
- key = schema['title']
55
- end
56
- end
57
-
58
- key = Hashable.generate_hash(schema) if key.nil?
59
-
60
- @schemas[key] = schema if @schemas[key].nil?
61
- schema_reference(key)
62
- end
63
-
64
- def add_example(example)
65
- key = Hashable.generate_hash(example)
66
- @examples[key] = example if @examples[key].nil?
67
-
68
- example_reference(key)
69
- end
70
-
71
- def create_reference(type, name)
72
- "#/components/#{type}/#{name}"
73
- end
74
-
75
- def schema_reference(name)
76
- Reference.new(create_reference('schemas', name))
77
- end
78
-
79
- def response_reference(name)
80
- Reference.new(create_reference('responses', name))
81
- end
82
-
83
- def parameter_reference(name)
84
- Reference.new(create_reference('parameters', name))
85
- end
86
-
87
- def example_reference(name)
88
- Reference.new(create_reference('examples', name))
89
- end
90
-
91
- def request_body_reference(name)
92
- Reference.new(create_reference('requestBodies', name))
93
- end
94
- end
95
- end
96
- end
@@ -1,18 +0,0 @@
1
- module OasRails
2
- module Spec
3
- class Contact
4
- include Specable
5
- attr_accessor :name, :url, :email
6
-
7
- def initialize(**kwargs)
8
- @name = kwargs[:name] || ''
9
- @url = kwargs[:url] || ''
10
- @email = kwargs[:email] || ''
11
- end
12
-
13
- def oas_fields
14
- [:name, :url, :email]
15
- end
16
- end
17
- end
18
- end
@@ -1,39 +0,0 @@
1
- module OasRails
2
- module Spec
3
- require 'digest'
4
-
5
- module Hashable
6
- def hash_key
7
- Hashable.generate_hash(hash_representation)
8
- end
9
-
10
- def hash_representation
11
- public_instance_variables.sort.to_h { |var| [var, instance_variable_get(var)] }
12
- end
13
-
14
- def self.generate_hash(obj)
15
- Digest::MD5.hexdigest(hash_representation_recursive(obj).to_s)
16
- end
17
-
18
- def public_instance_variables
19
- instance_variables.select do |var|
20
- method_name = var.to_s.delete('@')
21
- respond_to?(method_name) || respond_to?("#{method_name}=")
22
- end
23
- end
24
-
25
- def self.hash_representation_recursive(obj)
26
- case obj
27
- when Hash
28
- obj.transform_values { |v| hash_representation_recursive(v) }
29
- when Array
30
- obj.map { |v| hash_representation_recursive(v) }
31
- when Hashable
32
- obj.hash_representation
33
- else
34
- obj
35
- end
36
- end
37
- end
38
- end
39
- end
@@ -1,66 +0,0 @@
1
- module OasRails
2
- module Spec
3
- class Info
4
- include Specable
5
- attr_accessor :title, :summary, :description, :terms_of_service, :contact, :license, :version
6
-
7
- def initialize(**kwargs)
8
- @title = kwargs[:title] || default_title
9
- @summary = kwargs[:summary] || default_summary
10
- @description = kwargs[:description] || default_description
11
- @terms_of_service = kwargs[:terms_of_service] || ''
12
- @contact = Spec::Contact.new
13
- @license = Spec::License.new
14
- @version = kwargs[:version] || '0.0.1'
15
- end
16
-
17
- def oas_fields
18
- [:title, :summary, :description, :terms_of_service, :contact, :license, :version]
19
- end
20
-
21
- def default_title
22
- "OasRails #{VERSION}"
23
- end
24
-
25
- def default_summary
26
- "OasRails: Automatic Interactive API Documentation for Rails"
27
- end
28
-
29
- def default_description
30
- "# Welcome to OasRails
31
-
32
- OasRails automatically generates interactive documentation for your Rails APIs using the OpenAPI Specification 3.1 (OAS 3.1) and displays it with a nice UI.
33
-
34
- ## Getting Started
35
-
36
- You've successfully mounted the OasRails engine. This default documentation is based on your routes and automatically gathered information.
37
-
38
- ## Enhancing Your Documentation
39
-
40
- To customize and enrich your API documentation:
41
-
42
- 1. Generate an initializer file:
43
-
44
- ```
45
- rails generate oas_rails:config
46
- ```
47
- 2. Edit the created `config/initializers/oas_rails.rb` file to override default settings and add project-specific information.
48
-
49
- 3. Use Yard tags in your controller methods to provide detailed API endpoint descriptions.
50
-
51
- ## Features
52
-
53
- - Automatic OAS 3.1 document generation
54
- - [RapiDoc](https://github.com/rapi-doc/RapiDoc) integration for interactive exploration
55
- - Minimal setup required for basic documentation
56
- - Extensible through configuration and Yard tags
57
-
58
- Explore your API documentation and enjoy the power of OasRails!
59
-
60
- For more information and advanced usage, visit the [OasRails GitHub repository](https://github.com/a-chacon/oas_rails).
61
-
62
- "
63
- end
64
- end
65
- end
66
- end
@@ -1,18 +0,0 @@
1
- module OasRails
2
- module Spec
3
- class License
4
- include Specable
5
-
6
- attr_accessor :name, :url
7
-
8
- def initialize(**kwargs)
9
- @name = kwargs[:name] || 'GPL 3.0'
10
- @url = kwargs[:url] || 'https://www.gnu.org/licenses/gpl-3.0.html#license-text'
11
- end
12
-
13
- def oas_fields
14
- [:name, :url]
15
- end
16
- end
17
- end
18
- end
@@ -1,24 +0,0 @@
1
- module OasRails
2
- module Spec
3
- class MediaType
4
- include Specable
5
-
6
- attr_accessor :schema, :example, :examples, :encoding
7
-
8
- # Initializes a new MediaType object.
9
- #
10
- # @param schema [Hash] the schema of the media type.
11
- # @param kwargs [Hash] additional keyword arguments.
12
- def initialize(specification)
13
- @specification = specification
14
- @schema = {}
15
- @example = {}
16
- @examples = {}
17
- end
18
-
19
- def oas_fields
20
- [:schema, :example, :examples, :encoding]
21
- end
22
- end
23
- end
24
- end
@@ -1,25 +0,0 @@
1
- module OasRails
2
- module Spec
3
- class Operation
4
- include Specable
5
-
6
- attr_accessor :specification, :tags, :summary, :description, :operation_id, :parameters, :request_body, :responses, :security
7
-
8
- def initialize(specification)
9
- @specification = specification
10
- @summary = ""
11
- @operation_id = ""
12
- @tags = []
13
- @description = @summary
14
- @parameters = []
15
- @request_body = {}
16
- @responses = Spec::Responses.new(specification)
17
- @security = []
18
- end
19
-
20
- def oas_fields
21
- [:tags, :summary, :description, :operation_id, :parameters, :request_body, :responses, :security]
22
- end
23
- end
24
- end
25
- end