oas_rails 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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