oas_rails 0.3.0 → 0.4.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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +9 -0
  3. data/app/controllers/oas_rails/oas_rails_controller.rb +1 -1
  4. data/lib/generators/oas_rails/config/templates/oas_rails_initializer.rb +11 -0
  5. data/lib/oas_rails/builders/content_builder.rb +55 -0
  6. data/lib/oas_rails/builders/operation_builder.rb +32 -0
  7. data/lib/oas_rails/builders/parameter_builder.rb +28 -0
  8. data/lib/oas_rails/builders/parameters_builder.rb +39 -0
  9. data/lib/oas_rails/builders/path_item_builder.rb +22 -0
  10. data/lib/oas_rails/builders/request_body_builder.rb +60 -0
  11. data/lib/oas_rails/builders/response_builder.rb +40 -0
  12. data/lib/oas_rails/builders/responses_builder.rb +58 -0
  13. data/lib/oas_rails/configuration.rb +17 -5
  14. data/lib/oas_rails/extractors/oas_route_extractor.rb +66 -0
  15. data/lib/oas_rails/extractors/render_response_extractor.rb +11 -36
  16. data/lib/oas_rails/oas_route.rb +0 -5
  17. data/lib/oas_rails/spec/components.rb +85 -0
  18. data/lib/oas_rails/spec/contact.rb +18 -0
  19. data/lib/oas_rails/spec/hashable.rb +39 -0
  20. data/lib/oas_rails/{info.rb → spec/info.rb} +30 -24
  21. data/lib/oas_rails/spec/license.rb +18 -0
  22. data/lib/oas_rails/spec/media_type.rb +84 -0
  23. data/lib/oas_rails/spec/operation.rb +25 -0
  24. data/lib/oas_rails/spec/parameter.rb +34 -0
  25. data/lib/oas_rails/spec/path_item.rb +33 -0
  26. data/lib/oas_rails/spec/paths.rb +26 -0
  27. data/lib/oas_rails/spec/reference.rb +16 -0
  28. data/lib/oas_rails/spec/request_body.rb +21 -0
  29. data/lib/oas_rails/spec/response.rb +20 -0
  30. data/lib/oas_rails/spec/responses.rb +25 -0
  31. data/lib/oas_rails/spec/server.rb +17 -0
  32. data/lib/oas_rails/spec/specable.rb +51 -0
  33. data/lib/oas_rails/spec/specification.rb +50 -0
  34. data/lib/oas_rails/spec/tag.rb +18 -0
  35. data/lib/oas_rails/utils.rb +39 -0
  36. data/lib/oas_rails/version.rb +1 -1
  37. data/lib/oas_rails.rb +41 -16
  38. metadata +29 -17
  39. data/lib/oas_rails/contact.rb +0 -12
  40. data/lib/oas_rails/license.rb +0 -11
  41. data/lib/oas_rails/media_type.rb +0 -102
  42. data/lib/oas_rails/oas_base.rb +0 -30
  43. data/lib/oas_rails/operation.rb +0 -134
  44. data/lib/oas_rails/parameter.rb +0 -47
  45. data/lib/oas_rails/path_item.rb +0 -25
  46. data/lib/oas_rails/paths.rb +0 -19
  47. data/lib/oas_rails/request_body.rb +0 -29
  48. data/lib/oas_rails/response.rb +0 -12
  49. data/lib/oas_rails/responses.rb +0 -20
  50. data/lib/oas_rails/server.rb +0 -10
  51. data/lib/oas_rails/specification.rb +0 -72
  52. data/lib/oas_rails/tag.rb +0 -17
@@ -0,0 +1,85 @@
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 = Hashable.generate_hash(schema)
48
+ @schemas[key] = schema if @schemas[key].nil?
49
+
50
+ schema_reference(key)
51
+ end
52
+
53
+ def add_example(example)
54
+ key = Hashable.generate_hash(example)
55
+ @examples[key] = example if @examples[key].nil?
56
+
57
+ example_reference(key)
58
+ end
59
+
60
+ def create_reference(type, name)
61
+ "#/components/#{type}/#{name}"
62
+ end
63
+
64
+ def schema_reference(name)
65
+ Reference.new(create_reference('schemas', name))
66
+ end
67
+
68
+ def response_reference(name)
69
+ Reference.new(create_reference('responses', name))
70
+ end
71
+
72
+ def parameter_reference(name)
73
+ Reference.new(create_reference('parameters', name))
74
+ end
75
+
76
+ def example_reference(name)
77
+ Reference.new(create_reference('examples', name))
78
+ end
79
+
80
+ def request_body_reference(name)
81
+ Reference.new(create_reference('requestBodies', name))
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,18 @@
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
@@ -0,0 +1,39 @@
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,28 +1,33 @@
1
1
  module OasRails
2
- class Info < OasBase
3
- attr_accessor :title, :summary, :description, :terms_of_service, :contact, :license, :version
4
-
5
- def initialize(**kwargs)
6
- super()
7
- @title = kwargs[:title] || default_title
8
- @summary = kwargs[:summary] || default_summary
9
- @description = kwargs[:description] || default_description
10
- @terms_of_service = kwargs[:terms_of_service] || ''
11
- @contact = Contact.new
12
- @license = License.new
13
- @version = kwargs[:version] || '0.0.1'
14
- end
15
-
16
- def default_title
17
- "OasRails #{VERSION}"
18
- end
19
-
20
- def default_summary
21
- "OasRails: Automatic Interactive API Documentation for Rails"
22
- end
23
-
24
- def default_description
25
- "# Welcome to 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
26
31
 
27
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.
28
33
 
@@ -55,6 +60,7 @@ Explore your API documentation and enjoy the power of OasRails!
55
60
  For more information and advanced usage, visit the [OasRails GitHub repository](https://github.com/a-chacon/oas_rails).
56
61
 
57
62
  "
63
+ end
58
64
  end
59
65
  end
60
66
  end
@@ -0,0 +1,18 @@
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
@@ -0,0 +1,84 @@
1
+ module OasRails
2
+ module Spec
3
+ class MediaType
4
+ include Specable
5
+
6
+ attr_accessor :schema, :example, :examples, :encoding
7
+
8
+ @context = :incoming
9
+ @factory_examples = {}
10
+
11
+ # Initializes a new MediaType object.
12
+ #
13
+ # @param schema [Hash] the schema of the media type.
14
+ # @param kwargs [Hash] additional keyword arguments.
15
+ def initialize(specification)
16
+ @specification = specification
17
+ @schema = {}
18
+ @example = {}
19
+ @examples = {}
20
+ end
21
+
22
+ def oas_fields
23
+ [:schema, :example, :examples, :encoding]
24
+ end
25
+
26
+ class << self
27
+ # Searches for examples in test files based on the provided class and test framework.
28
+ #
29
+ # @param klass [Class] the class to search examples for.
30
+ # @param utils [Module] a utility module that provides the `detect_test_framework` method. Defaults to `Utils`.
31
+ # @return [Hash] a hash containing examples data or an empty hash if no examples are found.
32
+ def search_for_examples_in_tests(klass, context: :incoming, utils: Utils)
33
+ @context = context
34
+ case utils.detect_test_framework
35
+ when :factory_bot
36
+ fetch_factory_bot_examples(klass:)
37
+ when :fixtures
38
+ fetch_fixture_examples(klass:)
39
+ else
40
+ {}
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ # Fetches examples from FactoryBot for the provided class.
47
+ #
48
+ # @param klass [Class] the class to fetch examples for.
49
+ # @return [Hash] a hash containing examples data or an empty hash if no examples are found.
50
+ def fetch_factory_bot_examples(klass:)
51
+ klass_sym = Utils.class_to_symbol(klass)
52
+
53
+ begin
54
+ @factory_examples[klass_sym] = FactoryBot.build_stubbed_list(klass_sym, 1) if @factory_examples[klass_sym].nil?
55
+
56
+ @factory_examples[klass_sym].each_with_index.to_h do |obj, index|
57
+ ["#{klass_sym}#{index + 1}", { value: { klass_sym => clean_example_object(obj: obj.as_json) } }]
58
+ end
59
+ rescue KeyError
60
+ {}
61
+ end
62
+ end
63
+
64
+ # Fetches examples from fixtures for the provided class.
65
+ #
66
+ # @param klass [Class] the class to fetch examples for.
67
+ # @return [Hash] a hash containing examples data or an empty hash if no examples are found.
68
+ def fetch_fixture_examples(klass:)
69
+ fixture_file = Rails.root.join('test', 'fixtures', "#{klass.to_s.pluralize.downcase}.yml")
70
+ begin
71
+ fixture_data = YAML.load_file(fixture_file).with_indifferent_access
72
+ rescue Errno::ENOENT
73
+ return {}
74
+ end
75
+ fixture_data.transform_values { |attributes| { value: { klass.to_s.downcase => clean_example_object(obj: attributes) } } }
76
+ end
77
+
78
+ def clean_example_object(obj:)
79
+ obj.reject { |key, _| OasRails.config.send("excluded_columns_#{@context}").include?(key.to_sym) }
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,25 @@
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
@@ -0,0 +1,34 @@
1
+ module OasRails
2
+ module Spec
3
+ class Parameter
4
+ include Specable
5
+ include Hashable
6
+
7
+ STYLE_DEFAULTS = { query: 'form', path: 'simple', header: 'simple', cookie: 'form' }.freeze
8
+
9
+ attr_accessor :name, :in, :style, :description, :required, :schema
10
+
11
+ def initialize(specification)
12
+ @specification = specification
13
+ @name = ""
14
+ @in = ""
15
+ @description = ""
16
+ @required = false
17
+ @style = ""
18
+ @schema = { type: 'string' }
19
+ end
20
+
21
+ def default_from_in
22
+ STYLE_DEFAULTS[@in.to_sym]
23
+ end
24
+
25
+ def required?
26
+ @in == 'path'
27
+ end
28
+
29
+ def oas_fields
30
+ [:name, :in, :description, :required, :schema, :style]
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,33 @@
1
+ module OasRails
2
+ module Spec
3
+ class PathItem
4
+ include Specable
5
+ attr_reader :get, :post, :put, :patch, :delete
6
+
7
+ def initialize(specification)
8
+ @specification = specification
9
+ @get = nil
10
+ @post = nil
11
+ @put = nil
12
+ @patch = nil
13
+ @delete = nil
14
+ end
15
+
16
+ def fill_from(path, route_extractor: Extractors::RouteExtractor)
17
+ route_extractor.host_routes_by_path(path).each do |oas_route|
18
+ add_operation(oas_route.verb.downcase, Spec::Operation.new(@specification).fill_from(oas_route))
19
+ end
20
+
21
+ self
22
+ end
23
+
24
+ def add_operation(http_method, operation)
25
+ instance_variable_set("@#{http_method}", operation)
26
+ end
27
+
28
+ def oas_fields
29
+ [:get, :post, :put, :patch, :delete]
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,26 @@
1
+ module OasRails
2
+ module Spec
3
+ class Paths
4
+ include Specable
5
+
6
+ attr_accessor :path_items
7
+
8
+ def initialize(specification)
9
+ @specification = specification
10
+ @path_items = {}
11
+ end
12
+
13
+ def add_path(path)
14
+ @path_items[path] = Builders::PathItemBuilder.new(@specification).from_path(path).build
15
+ end
16
+
17
+ def to_spec
18
+ paths_hash = {}
19
+ @path_items.each do |path, path_object|
20
+ paths_hash[path] = path_object.to_spec
21
+ end
22
+ paths_hash
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,16 @@
1
+ module OasRails
2
+ module Spec
3
+ class Reference
4
+ include Specable
5
+ attr_reader :ref
6
+
7
+ def initialize(ref)
8
+ @ref = ref
9
+ end
10
+
11
+ def to_spec
12
+ { '$ref' => @ref }
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,21 @@
1
+ module OasRails
2
+ module Spec
3
+ class RequestBody
4
+ include Specable
5
+ include Hashable
6
+
7
+ attr_accessor :description, :content, :required
8
+
9
+ def initialize(specification)
10
+ @specification = specification
11
+ @description = ""
12
+ @content = {} # a hash with media type objects
13
+ @required = false
14
+ end
15
+
16
+ def oas_fields
17
+ [:description, :content, :required]
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ module OasRails
2
+ module Spec
3
+ class Response
4
+ include Specable
5
+ include Hashable
6
+
7
+ attr_accessor :code, :description, :content
8
+
9
+ def initialize(specification)
10
+ @specification = specification
11
+ @description = ""
12
+ @content = {} # Hash with {content: MediaType}
13
+ end
14
+
15
+ def oas_fields
16
+ [:description, :content]
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,25 @@
1
+ module OasRails
2
+ module Spec
3
+ class Responses
4
+ include Specable
5
+ attr_accessor :responses
6
+
7
+ def initialize(specification)
8
+ @specification = specification
9
+ @responses = {}
10
+ end
11
+
12
+ def add_response(response)
13
+ @responses[response.code] = @specification.components.add_response(response)
14
+ end
15
+
16
+ def to_spec
17
+ spec = {}
18
+ @responses.each do |key, value|
19
+ spec[key] = value.to_spec
20
+ end
21
+ spec
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,17 @@
1
+ module OasRails
2
+ module Spec
3
+ class Server
4
+ include Specable
5
+ attr_accessor :url, :description
6
+
7
+ def initialize(url:, description:)
8
+ @url = url
9
+ @description = description
10
+ end
11
+
12
+ def oas_fields
13
+ [:url, :description]
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,51 @@
1
+ module OasRails
2
+ module Spec
3
+ module Specable
4
+ def oas_fields
5
+ []
6
+ end
7
+
8
+ def to_spec
9
+ hash = {}
10
+ oas_fields.each do |var|
11
+ key = var.to_s
12
+
13
+ camel_case_key = key.camelize(:lower).to_sym
14
+ value = send(var)
15
+
16
+ processed_value = if value.respond_to?(:to_spec)
17
+ value.to_spec
18
+ elsif value.is_a?(Array) && value.all? { |elem| elem.respond_to?(:to_spec) }
19
+ value.map(&:to_spec)
20
+ # elsif value.is_a?(Hash)
21
+ # p "Here"
22
+ # p value
23
+ # hash = {}
24
+ # value.each do |key, object|
25
+ # hash[key] = object.to_spec
26
+ # end
27
+ # hash
28
+ else
29
+ value
30
+ end
31
+
32
+ # hash[camel_case_key] = processed_value unless (processed_value.is_a?(Hash) || processed_value.is_a?(Array)) && processed_value.empty?
33
+ hash[camel_case_key] = processed_value unless processed_value.nil?
34
+ end
35
+ hash
36
+ end
37
+
38
+ def as_json
39
+ to_spec
40
+ end
41
+
42
+ private
43
+
44
+ def snake_to_camel(snake_str)
45
+ words = snake_str.to_s.split('_')
46
+ words[1..].map!(&:capitalize)
47
+ (words[0] + words[1..].join).to_sym
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,50 @@
1
+ require 'json'
2
+
3
+ module OasRails
4
+ module Spec
5
+ class Specification
6
+ include Specable
7
+ attr_accessor :components, :info, :openapi, :servers, :tags, :external_docs, :paths
8
+
9
+ # Initializes a new Specification object.
10
+ # Clears the cache if running in the development environment.
11
+ def initialize
12
+ clear_cache unless Rails.env.production?
13
+
14
+ @components = Components.new(self)
15
+ @info = OasRails.config.info
16
+ @openapi = '3.1.0'
17
+ @servers = OasRails.config.servers
18
+ @tags = OasRails.config.tags
19
+ @external_docs = {}
20
+ @paths = Spec::Paths.new(self)
21
+ end
22
+
23
+ def build(route_extractor: Extractors::RouteExtractor)
24
+ route_extractor.host_paths.each do |path|
25
+ @paths.add_path(path)
26
+ end
27
+ end
28
+
29
+ # Clears the cache for MethodSource and RouteExtractor.
30
+ #
31
+ # @return [void]
32
+ def clear_cache
33
+ MethodSource.clear_cache
34
+ Extractors::RouteExtractor.clear_cache
35
+ end
36
+
37
+ def oas_fields
38
+ [:openapi, :info, :servers, :paths, :components, :security, :tags, :external_docs]
39
+ end
40
+
41
+ # Create the Security Requirement Object.
42
+ # @see https://spec.openapis.org/oas/latest.html#security-requirement-object
43
+ def security
44
+ return [] unless OasRails.config.authenticate_all_routes_by_default
45
+
46
+ OasRails.config.security_schemas.map { |key, _| { key => [] } }
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,18 @@
1
+ module OasRails
2
+ module Spec
3
+ class Tag
4
+ include Specable
5
+
6
+ attr_accessor :name, :description
7
+
8
+ def initialize(name:, description:)
9
+ @name = name.titleize
10
+ @description = description
11
+ end
12
+
13
+ def oas_fields
14
+ [:name, :description]
15
+ end
16
+ end
17
+ end
18
+ end