oas_rails 0.12.0 → 0.14.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b929476e50a9620b4239738c1753cada50eaa6198ec363afe75dc2690f202504
4
- data.tar.gz: df7125a57463f2782254b316cd235a6b11e075e5010bc07d61e6aca8e555cd47
3
+ metadata.gz: 041c5783e2bbbedca71601a21756f2d90398bed275866c6214935965a47766f8
4
+ data.tar.gz: 4ea6110ed149c0d90b2c47e980f06abb8ef9c6efad7aa35699e23a8e95017fb6
5
5
  SHA512:
6
- metadata.gz: 95119f77419a24df6f3d22ef941e2dae351ad965ff2e8b84a7285a73932ab5e617d60c6c4ccfb08dfd44f555c58691ad1053b9983699d3f18b9f3c5a8a626693
7
- data.tar.gz: b4cce805735d6c2f0ba7f653bf778f59d7dd4c92be23128cac8bfd2ac0c185ccfd3b79e1f149cb1c17a0d083e8d7e83777b89f2bb96462c1acd2cfefdd6d25b9
6
+ metadata.gz: bd3d0eb79205462ca026002e5bbb64898704d1c2576320568a5332cb50dccf5e7044a7cc12aab06344d583b4d6a7aa0cdf6901f2a9ddbd0509ca305ef3f9dd96
7
+ data.tar.gz: fcaf3d373fed62a411de505665b6f1c288834902d009fc0d5c1d399693a0a7011b00e3df77a2fce6c77d10113044430e9ffe03ee87400976e8d9bdaca23fa631
data/README.md CHANGED
@@ -9,7 +9,15 @@
9
9
 
10
10
  OasRails is a Rails engine for generating **automatic interactive documentation for your Rails APIs**. It generates an **OAS 3.1** document and displays it using **[RapiDoc](https://rapidocweb.com)**.
11
11
 
12
- 🎬 A Demo Video Here:
12
+ ### 🚀 Demo App
13
+
14
+ Explore the interactive documentation live:
15
+
16
+ 🔗 **[Open Demo App](https://paso.fly.dev/api/docs)**
17
+ 👤 **Username**: `oasrails`
18
+ 🔑 **Password**: `oasrails`
19
+
20
+ 🎬 A Demo Installation/Usage Video:
13
21
  <https://vimeo.com/1013687332>
14
22
  🎬
15
23
 
@@ -56,3 +64,7 @@ If you plan a big feature, first open an issue to discuss it before any developm
56
64
  ## License
57
65
 
58
66
  The gem is available as open source under the terms of the [GPL-3.0](https://www.gnu.org/licenses/gpl-3.0.en.html#license-text).
67
+
68
+ ## Star History
69
+
70
+ [![Star History Chart](https://api.star-history.com/svg?repos=a-chacon/oas_rails&type=Date)](https://www.star-history.com/#a-chacon/oas_rails&Date)
@@ -26,6 +26,8 @@ OasRails.configure do |config|
26
26
 
27
27
  3. Use Yard tags in your controller methods to provide detailed API endpoint descriptions.
28
28
 
29
+ Docs: <https://a-chacon.com/oas_rails/>
30
+
29
31
  ## Features
30
32
 
31
33
  - Automatic OAS 3.1 document generation
@@ -105,6 +107,7 @@ OasRails.configure do |config|
105
107
  # Example, if you add forbidden then it will be added only if the endpoint requires authentication.
106
108
  # Example: not_found will be setted to the endpoint only if the operation is a show/update/destroy action.
107
109
  # config.set_default_responses = true
108
- # config.possible_default_responses = [:not_found, :unauthorized, :forbidden]
109
- # config.response_body_of_default = { message: String }
110
+ # config.possible_default_responses = [:not_found, :unauthorized, :forbidden, :internal_server_error, :unprocessable_entity]
111
+ # config.response_body_of_default = "Hash{ message: String }"
112
+ # config.response_body_of_unprocessable_entity= "Hash{ errors: Array<String> }"
110
113
  end
@@ -0,0 +1,65 @@
1
+ module OasRails
2
+ class ActiveRecordExampleFinder
3
+ def initialize(context: :incoming, utils: Utils, factory_bot: FactoryBot, erb: ERB, yaml: YAML, file: File)
4
+ @context = context
5
+ @utils = utils
6
+ @factory_bot = factory_bot
7
+ @erb = erb
8
+ @yaml = yaml
9
+ @file = file
10
+ @factory_examples = {}
11
+ end
12
+
13
+ def search(klass)
14
+ case @utils.detect_test_framework
15
+ when :factory_bot
16
+ fetch_factory_bot_examples(klass: klass)
17
+ when :fixtures
18
+ fetch_fixture_examples(klass: klass)
19
+ else
20
+ {}
21
+ end
22
+ end
23
+
24
+ # Fetches examples from FactoryBot for the provided class.
25
+ #
26
+ # @param klass [Class] the class to fetch examples for.
27
+ # @return [Hash] a hash containing examples data or an empty hash if no examples are found.
28
+ def fetch_factory_bot_examples(klass:)
29
+ klass_sym = @utils.class_to_symbol(klass)
30
+
31
+ begin
32
+ @factory_examples[klass_sym] = @factory_bot.build_stubbed_list(klass_sym, 1) if @factory_examples[klass_sym].nil?
33
+
34
+ @factory_examples[klass_sym].each_with_index.to_h do |obj, index|
35
+ ["#{klass_sym}#{index + 1}", { value: { klass_sym => clean_example_object(obj: obj.as_json) } }]
36
+ end.deep_symbolize_keys
37
+ rescue KeyError
38
+ {}
39
+ end
40
+ end
41
+
42
+ # Fetches examples from fixtures for the provided class.
43
+ #
44
+ # @param klass [Class] the class to fetch examples for.
45
+ # @return [Hash] a hash containing examples data or an empty hash if no examples are found.
46
+ def fetch_fixture_examples(klass:)
47
+ fixture_file = Rails.root.join('test', 'fixtures', "#{klass.to_s.pluralize.downcase}.yml")
48
+ begin
49
+ erb_result = @erb.new(@file.read(fixture_file)).result
50
+ fixture_data = @yaml.safe_load(
51
+ erb_result,
52
+ aliases: true,
53
+ permitted_classes: [Symbol, ActiveSupport::HashWithIndifferentAccess, Time]
54
+ ).with_indifferent_access
55
+ rescue Errno::ENOENT
56
+ return {}
57
+ end
58
+ fixture_data.transform_values { |attributes| { value: { klass.to_s.downcase => clean_example_object(obj: attributes) } } }.deep_symbolize_keys
59
+ end
60
+
61
+ def clean_example_object(obj:)
62
+ obj.reject { |key, _| OasRails.config.send("excluded_columns_#{@context}").include?(key.to_sym) }
63
+ end
64
+ end
65
+ end
@@ -33,12 +33,12 @@ module OasRails
33
33
  end
34
34
 
35
35
  def from_model_class(klass)
36
- return self unless klass.ancestors.map(&:to_s).include? 'ActiveRecord::Base'
36
+ return self unless Utils.active_record_class?(klass)
37
37
 
38
38
  model_schema = Builders::EsquemaBuilder.send("build_#{@context}_schema", klass:)
39
39
  model_schema["required"] = []
40
40
  schema = { type: "object", properties: { klass.to_s.downcase => model_schema } }
41
- examples = Spec::MediaType.search_for_examples_in_tests(klass, context: @context)
41
+ examples = ActiveRecordExampleFinder.new(context: @context).search(klass)
42
42
  @media_type.schema = @specification.components.add_schema(schema)
43
43
  @media_type.examples = @media_type.examples.merge(examples)
44
44
 
@@ -0,0 +1,90 @@
1
+ module OasRails
2
+ module Builders
3
+ class OasRouteBuilder
4
+ def self.build_from_rails_route(rails_route)
5
+ new(rails_route).build
6
+ end
7
+
8
+ def initialize(rails_route)
9
+ @rails_route = rails_route
10
+ end
11
+
12
+ def build
13
+ OasRoute.new(
14
+ controller_class: controller_class,
15
+ controller_action: controller_action,
16
+ controller: controller,
17
+ controller_path: controller_path,
18
+ method: method,
19
+ verb: verb,
20
+ path: path,
21
+ rails_route: @rails_route,
22
+ docstring: docstring,
23
+ source_string: source_string,
24
+ tags: tags
25
+ )
26
+ end
27
+
28
+ private
29
+
30
+ def controller_class
31
+ "#{@rails_route.defaults[:controller].camelize}Controller"
32
+ end
33
+
34
+ def controller_action
35
+ "#{controller_class}##{@rails_route.defaults[:action]}"
36
+ end
37
+
38
+ def controller
39
+ @rails_route.defaults[:controller]
40
+ end
41
+
42
+ def controller_path
43
+ Rails.root.join("app/controllers/#{controller}_controller.rb").to_s
44
+ end
45
+
46
+ def method
47
+ @rails_route.defaults[:action]
48
+ end
49
+
50
+ def verb
51
+ @rails_route.verb
52
+ end
53
+
54
+ def path
55
+ Extractors::RouteExtractor.clean_route(@rails_route.path.spec.to_s)
56
+ end
57
+
58
+ def source_string
59
+ controller_class.constantize.instance_method(method).source
60
+ end
61
+
62
+ def docstring
63
+ comment_lines = controller_class.constantize.instance_method(method).comment.lines
64
+ processed_lines = comment_lines.map { |line| line.sub(/^# /, '') }
65
+
66
+ filtered_lines = processed_lines.reject do |line|
67
+ line.include?('rubocop') ||
68
+ line.include?('TODO')
69
+ end
70
+
71
+ ::YARD::Docstring.parser.parse(filtered_lines.join).to_docstring
72
+ end
73
+
74
+ def tags
75
+ method_comment = controller_class.constantize.instance_method(method).comment
76
+ class_comment = controller_class.constantize.instance_method(method).class_comment
77
+
78
+ method_tags = parse_tags(method_comment)
79
+ class_tags = parse_tags(class_comment)
80
+
81
+ method_tags + class_tags
82
+ end
83
+
84
+ def parse_tags(comment)
85
+ lines = comment.lines.map { |line| line.sub(/^# /, '') }
86
+ ::YARD::Docstring.parser.parse(lines.join).tags
87
+ end
88
+ end
89
+ end
90
+ end
@@ -7,7 +7,7 @@ module OasRails
7
7
  end
8
8
 
9
9
  def from_oas_route(oas_route)
10
- parameters_from_tags(tags: oas_route.docstring.tags(:parameter))
10
+ parameters_from_tags(tags: oas_route.tags(:parameter))
11
11
  oas_route.path_params.try(:map) do |p|
12
12
  @parameters << ParameterBuilder.new(@specification).from_path(oas_route.path, p).build unless @parameters.any? { |param| param.name.to_s == p.to_s }
13
13
  end
@@ -7,18 +7,18 @@ module OasRails
7
7
  end
8
8
 
9
9
  def from_oas_route(oas_route)
10
- tag_request_body = oas_route.docstring.tags(:request_body).first
10
+ tag_request_body = oas_route.tags(:request_body).first
11
11
  if tag_request_body.nil? && OasRails.config.autodiscover_request_body
12
12
  detect_request_body(oas_route) if %w[create update].include? oas_route.method
13
13
  elsif !tag_request_body.nil?
14
- from_tags(tag: tag_request_body, examples_tags: oas_route.docstring.tags(:request_body_example))
14
+ from_tags(tag: tag_request_body, examples_tags: oas_route.tags(:request_body_example))
15
15
  end
16
16
 
17
17
  self
18
18
  end
19
19
 
20
20
  def from_tags(tag:, examples_tags: [])
21
- if tag.klass.ancestors.map(&:to_s).include? 'ActiveRecord::Base'
21
+ if Utils.active_record_class?(tag.klass)
22
22
  from_model_class(klass: tag.klass, description: tag.text, required: tag.required, examples_tags:)
23
23
  else
24
24
  @request_body.description = tag.text
@@ -7,8 +7,8 @@ module OasRails
7
7
  end
8
8
 
9
9
  def from_oas_route(oas_route)
10
- oas_route.docstring.tags(:response).each do |tag|
11
- content = ContentBuilder.new(@specification, :outgoing).with_schema(tag.schema).with_examples_from_tags(oas_route.docstring.tags(:response_example).filter { |re| re.code == tag.name }).build
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
12
  response = ResponseBuilder.new(@specification).with_code(tag.name.to_i).with_description(tag.text).with_content(content).build
13
13
 
14
14
  @responses.add_response(response)
@@ -18,7 +18,7 @@ module OasRails
18
18
  end
19
19
 
20
20
  def add_autodiscovered_responses(oas_route)
21
- return self if !OasRails.config.autodiscover_responses || oas_route.docstring.tags(:response).any?
21
+ return self if !OasRails.config.autodiscover_responses || oas_route.tags(:response).any?
22
22
 
23
23
  new_responses = Extractors::RenderResponseExtractor.extract_responses_from_source(@specification, source: oas_route.source_string)
24
24
 
@@ -32,29 +32,49 @@ module OasRails
32
32
  def add_default_responses(oas_route, security)
33
33
  return self unless OasRails.config.set_default_responses
34
34
 
35
- content = ContentBuilder.new(@specification, :outgoing).with_schema(JsonSchemaGenerator.process_string(OasRails.config.response_body_of_default)[:json_schema]).build
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)
36
48
  common_errors = []
37
- common_errors.push(:unauthorized, :forbidden) if security
49
+ common_errors.push(:unauthorized, :forbidden, :internal_server_error) if security
38
50
 
39
51
  case oas_route.method
40
- when "show", "update", "destroy"
52
+ when "show", "destroy"
41
53
  common_errors.push(:not_found)
42
- when "create", "index"
43
- # possible errors for this methods?
54
+ when "create"
55
+ common_errors.push(:unprocessable_entity)
56
+ when "update"
57
+ common_errors.push(:not_found, :unprocessable_entity)
44
58
  end
45
59
 
46
- (OasRails.config.possible_default_responses & common_errors).each do |e|
47
- code = Utils.status_to_integer(e)
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)
48
68
  response = ResponseBuilder.new(@specification).with_code(code).with_description(Utils.get_definition(code)).with_content(content).build
49
69
 
50
70
  @responses.add_response(response) if @responses.responses[response.code].blank?
51
71
  end
52
-
53
- self
54
72
  end
55
73
 
56
- def build
57
- @responses
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
58
78
  end
59
79
  end
60
80
  end
@@ -11,12 +11,11 @@ module OasRails
11
11
  :authenticate_all_routes_by_default,
12
12
  :set_default_responses,
13
13
  :possible_default_responses,
14
- :response_body_of_default,
15
14
  :http_verbs,
16
15
  :use_model_names,
17
16
  :rapidoc_theme
18
17
 
19
- attr_reader :servers, :tags, :security_schema, :include_mode
18
+ attr_reader :servers, :tags, :security_schema, :include_mode, :response_body_of_default
20
19
 
21
20
  def initialize
22
21
  @info = Spec::Info.new
@@ -33,12 +32,28 @@ module OasRails
33
32
  @security_schema = nil
34
33
  @security_schemas = {}
35
34
  @set_default_responses = true
36
- @possible_default_responses = [:not_found, :unauthorized, :forbidden]
35
+ @possible_default_responses = [:not_found, :unauthorized, :forbidden, :internal_server_error, :unprocessable_entity]
37
36
  @http_verbs = [:get, :post, :put, :patch, :delete]
38
- @response_body_of_default = "Hash{ success: !Boolean, message: String }"
37
+ @response_body_of_default = "Hash{ status: !Integer, error: String }"
39
38
  @use_model_names = false
40
39
  @rapidoc_theme = :rails
41
40
  @include_mode = :all
41
+
42
+ @possible_default_responses.each do |response|
43
+ method_name = "response_body_of_#{response}="
44
+ variable_name = "@response_body_of_#{response}"
45
+
46
+ define_singleton_method(method_name) do |value|
47
+ raise ArgumentError, "#{method_name} must be a String With a valid object" unless value.is_a?(String)
48
+
49
+ OasRails::JsonSchemaGenerator.parse_type(value)
50
+ instance_variable_set(variable_name, value)
51
+ end
52
+
53
+ define_singleton_method("response_body_of_#{response}") do
54
+ instance_variable_get(variable_name) || @response_body_of_default
55
+ end
56
+ end
42
57
  end
43
58
 
44
59
  def security_schema=(value)
@@ -73,6 +88,13 @@ module OasRails
73
88
 
74
89
  @include_mode = value
75
90
  end
91
+
92
+ def response_body_of_default=(value)
93
+ raise ArgumentError, "response_body_of_default must be a String With a valid object" unless value.is_a?(String)
94
+
95
+ OasRails::JsonSchemaGenerator.parse_type(value)
96
+ @response_body_of_default = value
97
+ end
76
98
  end
77
99
 
78
100
  DEFAULT_SECURITY_SCHEMES = {
@@ -2,7 +2,7 @@ module OasRails
2
2
  module Extractors
3
3
  module OasRouteExtractor
4
4
  def extract_summary(oas_route:)
5
- oas_route.docstring.tags(:summary).first.try(:text) || generate_crud_name(oas_route.method, oas_route.controller.downcase) || "#{oas_route.verb} #{oas_route.path}"
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
6
  end
7
7
 
8
8
  def extract_operation_id(oas_route:)
@@ -10,7 +10,7 @@ module OasRails
10
10
  end
11
11
 
12
12
  def extract_tags(oas_route:)
13
- tags = oas_route.docstring.tags(:tags).first
13
+ tags = oas_route.tags(:tags).first
14
14
  if tags.nil?
15
15
  default_tags(oas_route:)
16
16
  else
@@ -30,9 +30,9 @@ module OasRails
30
30
  end
31
31
 
32
32
  def extract_security(oas_route:)
33
- return [] if oas_route.docstring.tags(:no_auth).any?
33
+ return [] if oas_route.tags(:no_auth).any?
34
34
 
35
- if (methods = oas_route.docstring.tags(:auth).first)
35
+ if (methods = oas_route.tags(:auth).first)
36
36
  OasRails.config.security_schemas.keys.map { |key| { key => [] } }.select do |schema|
37
37
  methods.types.include?(schema.keys.first.to_s)
38
38
  end
@@ -60,7 +60,7 @@ module OasRails
60
60
  maybe_a_model, errors = content.gsub('@', "").split(".")
61
61
  klass = maybe_a_model.singularize.camelize(:upper).constantize
62
62
 
63
- if klass.ancestors.map(&:to_s).include? 'ActiveRecord::Base'
63
+ if Utils.active_record_class?(klass)
64
64
  schema = Builders::EsquemaBuilder.build_outgoing_schema(klass:)
65
65
  if test_singularity(maybe_a_model)
66
66
  build_singular_model_schema_and_examples(maybe_a_model, errors, klass, schema)
@@ -81,7 +81,7 @@ module OasRails
81
81
  # @return [Array<Hash, Hash>] An array where the first element is the schema and the second is the examples.
82
82
  def build_singular_model_schema_and_examples(_maybe_a_model, errors, klass, schema)
83
83
  if errors.nil?
84
- [schema, Spec::MediaType.search_for_examples_in_tests(klass, context: :outgoing)]
84
+ [schema, ActiveRecordExampleFinder.new(context: :outgoing).search(klass)]
85
85
  else
86
86
  # TODO: this is not building the real schema.
87
87
  [
@@ -110,7 +110,7 @@ module OasRails
110
110
  # @param schema [Hash] The schema for the model.
111
111
  # @return [Array<Hash, Hash>] An array where the first element is the schema and the second is the examples.
112
112
  def build_array_model_schema_and_examples(maybe_a_model, klass, schema)
113
- examples = { maybe_a_model => { value: Spec::MediaType.search_for_examples_in_tests(klass, context: :outgoing).values.map { |p| p.dig(:value, maybe_a_model.singularize.to_sym) } } }
113
+ examples = { maybe_a_model => { value: ActiveRecordExampleFinder.new(context: :outgoing).search(klass).values.map { |p| p.dig(:value, maybe_a_model.singularize.to_sym) } } }
114
114
  [{ type: "array", items: schema }, examples]
115
115
  end
116
116
 
@@ -48,10 +48,10 @@ module OasRails
48
48
  private
49
49
 
50
50
  def extract_host_routes
51
- routes = valid_routes.map { |r| OasRoute.new_from_rails_route(rails_route: r) }
51
+ routes = valid_routes.map { |r| Builders::OasRouteBuilder.build_from_rails_route(r) }
52
52
 
53
- routes.select! { |route| route.docstring.tags.any? } if OasRails.config.include_mode == :with_tags
54
- routes.select! { |route| route.docstring.tags.any? { |t| t.tag_name == "oas_include" } } if OasRails.config.include_mode == :explicit
53
+ routes.select! { |route| route.tags.any? } if OasRails.config.include_mode == :with_tags
54
+ routes.select! { |route| route.tags.any? { |t| t.tag_name == "oas_include" } } if OasRails.config.include_mode == :explicit
55
55
  routes
56
56
  end
57
57
 
@@ -29,6 +29,8 @@ module OasRails
29
29
  { type: :object, required:, properties: parse_object_properties(::Regexp.last_match(1)) }
30
30
  when /^Array<(.+)>$/i
31
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)
32
34
  else
33
35
  { type: type.downcase.to_sym, required: }
34
36
  end
@@ -100,6 +102,8 @@ module OasRails
100
102
  type: 'array',
101
103
  items: to_json_schema(parsed[:items])
102
104
  }
105
+ when nil
106
+ parsed
103
107
  else
104
108
  ruby_type_to_json_schema_type(parsed[:type])
105
109
  end
@@ -1,47 +1,21 @@
1
1
  module OasRails
2
2
  class OasRoute
3
- attr_accessor(:controller_class, :controller_action, :controller, :controller_path, :method, :verb, :path,
4
- :rails_route, :docstring, :source_string)
3
+ attr_accessor :controller_class, :controller_action, :controller, :controller_path, :method, :verb, :path,
4
+ :rails_route, :docstring, :source_string
5
+ attr_writer :tags
5
6
 
6
- def initialize; end
7
-
8
- def self.new_from_rails_route(rails_route: ActionDispatch::Journey::Route)
9
- instance = new
10
- instance.rails_route = rails_route
11
- instance.extract_rails_route_data
12
- instance
13
- end
14
-
15
- def extract_rails_route_data
16
- @controller_action = "#{@rails_route.defaults[:controller].camelize}Controller##{@rails_route.defaults[:action]}"
17
- @controller_class = "#{@rails_route.defaults[:controller].camelize}Controller"
18
- @controller = @rails_route.defaults[:controller]
19
- @controller_path = controller_path_extractor(@rails_route.defaults[:controller])
20
- @method = @rails_route.defaults[:action]
21
- @verb = @rails_route.verb
22
- @path = Extractors::RouteExtractor.clean_route(@rails_route.path.spec.to_s)
23
- @docstring = extract_docstring
24
- @source_string = extract_source_string
25
- end
26
-
27
- def extract_docstring
28
- comment_lines = controller_class.constantize.instance_method(method).comment.lines
29
- processed_lines = comment_lines.map do |line|
30
- line.sub(/^# /, '')
31
- end
32
- ::YARD::Docstring.parser.parse(processed_lines.join).to_docstring
33
- end
34
-
35
- def extract_source_string
36
- @controller_class.constantize.instance_method(method).source
7
+ def initialize(attributes = {})
8
+ attributes.each { |key, value| send("#{key}=", value) }
37
9
  end
38
10
 
39
11
  def path_params
40
12
  @rails_route.path.spec.to_s.scan(/:(\w+)/).flatten.reject! { |e| e == 'format' }
41
13
  end
42
14
 
43
- def controller_path_extractor(controller)
44
- Rails.root.join("app/controllers/#{controller}_controller.rb").to_s
15
+ def tags(name = nil)
16
+ return @tags if name.nil?
17
+
18
+ @tags.select { |tag| tag.tag_name.to_s == name.to_s }
45
19
  end
46
20
  end
47
21
  end
@@ -5,9 +5,6 @@ module OasRails
5
5
 
6
6
  attr_accessor :schema, :example, :examples, :encoding
7
7
 
8
- @context = :incoming
9
- @factory_examples = {}
10
-
11
8
  # Initializes a new MediaType object.
12
9
  #
13
10
  # @param schema [Hash] the schema of the media type.
@@ -22,68 +19,6 @@ module OasRails
22
19
  def oas_fields
23
20
  [:schema, :example, :examples, :encoding]
24
21
  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
- erb_result = ERB.new(File.read(fixture_file)).result
72
- fixture_data = YAML.safe_load(
73
- erb_result,
74
- aliases: true,
75
- permitted_classes: [Symbol, ActiveSupport::HashWithIndifferentAccess, Time]
76
- ).with_indifferent_access
77
- rescue Errno::ENOENT
78
- return {}
79
- end
80
- fixture_data.transform_values { |attributes| { value: { klass.to_s.downcase => clean_example_object(obj: attributes) } } }
81
- end
82
-
83
- def clean_example_object(obj:)
84
- obj.reject { |key, _| OasRails.config.send("excluded_columns_#{@context}").include?(key.to_sym) }
85
- end
86
- end
87
22
  end
88
23
  end
89
24
  end
@@ -14,6 +14,8 @@ module OasRails
14
14
  }.freeze
15
15
 
16
16
  HTTP_STATUS_DEFINITIONS = {
17
+ 200 => "The request has succeeded.",
18
+ 201 => "The request has been fulfilled and resulted in a new resource being created.",
17
19
  404 => "The requested resource could not be found.",
18
20
  401 => "You are not authorized to access this resource. You need to authenticate yourself first.",
19
21
  403 => "You are not allowed to access this resource. You do not have the necessary permissions.",
@@ -34,19 +36,6 @@ module OasRails
34
36
  end
35
37
  end
36
38
 
37
- # TODO: check if it is in use
38
- def type_to_schema(type_string)
39
- if type_string.start_with?('Array<')
40
- inner_type = type_string[/Array<(.+)>$/, 1]
41
- {
42
- "type" => "array",
43
- "items" => type_to_schema(inner_type)
44
- }
45
- else
46
- { "type" => TYPE_MAPPING.fetch(type_string, 'string') }
47
- end
48
- end
49
-
50
39
  def hash_to_json_schema(hash)
51
40
  {
52
41
  type: 'object',
@@ -116,6 +105,16 @@ module OasRails
116
105
 
117
106
  nil # Return nil if no matching constant is found
118
107
  end
108
+
109
+ # Checks if a given text refers to an ActiveRecord class.
110
+ # @param text [String] The text to check.
111
+ # @return [Boolean] True if the text refers to an ActiveRecord class, false otherwise.
112
+ def active_record_class?(klass_or_string)
113
+ klass = klass_or_string.is_a?(Class) ? klass_or_string : klass_or_string.constantize
114
+ klass.ancestors.map(&:to_s).include? 'ActiveRecord::Base'
115
+ rescue StandardError
116
+ false
117
+ end
119
118
  end
120
119
  end
121
120
  end
@@ -1,3 +1,3 @@
1
1
  module OasRails
2
- VERSION = "0.12.0"
2
+ VERSION = "0.14.0"
3
3
  end
@@ -147,23 +147,13 @@ module OasRails
147
147
  match
148
148
  end
149
149
 
150
- # Checks if a given text refers to an ActiveRecord class.
151
- # @param text [String] The text to check.
152
- # @return [Boolean] True if the text refers to an ActiveRecord class, false otherwise.
153
- def active_record_class?(text)
154
- klass = text.constantize
155
- klass.ancestors.map(&:to_s).include? 'ActiveRecord::Base'
156
- rescue StandardError
157
- false
158
- end
159
-
160
150
  # Converts type text to a schema, checking if it's an ActiveRecord class.
161
151
  # @param text [String] The type text to convert.
162
152
  # @return [Array] An array containing the class, schema, and required flag.
163
153
  def type_text_to_schema(text)
164
154
  type_text, required = text_and_required(text)
165
155
 
166
- if active_record_class?(type_text)
156
+ if Utils.active_record_class?(type_text)
167
157
  klass = type_text.constantize
168
158
  schema = Builders::EsquemaBuilder.build_outgoing_schema(klass:)
169
159
  else
data/lib/oas_rails.rb CHANGED
@@ -10,6 +10,7 @@ module OasRails
10
10
  autoload :OasRoute, "oas_rails/oas_route"
11
11
  autoload :Utils, "oas_rails/utils"
12
12
  autoload :JsonSchemaGenerator, "oas_rails/json_schema_generator"
13
+ autoload :ActiveRecordExampleFinder, "oas_rails/active_record_example_finder"
13
14
 
14
15
  module Builders
15
16
  autoload :OperationBuilder, "oas_rails/builders/operation_builder"
@@ -21,6 +22,7 @@ module OasRails
21
22
  autoload :ParameterBuilder, "oas_rails/builders/parameter_builder"
22
23
  autoload :RequestBodyBuilder, "oas_rails/builders/request_body_builder"
23
24
  autoload :EsquemaBuilder, "oas_rails/builders/esquema_builder"
25
+ autoload :OasRouteBuilder, "oas_rails/builders/oas_route_builder"
24
26
  end
25
27
 
26
28
  # This module contains all the clases that represent a part of the OAS file.
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oas_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.0
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - a-chacon
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-04-13 00:00:00.000000000 Z
11
+ date: 2025-05-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: easy_talk
14
+ name: easy_talk_two
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.0'
19
+ version: 1.1.2
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.0'
26
+ version: 1.1.2
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: method_source
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -80,8 +80,10 @@ files:
80
80
  - lib/generators/oas_rails/config/config_generator.rb
81
81
  - lib/generators/oas_rails/config/templates/oas_rails_initializer.rb
82
82
  - lib/oas_rails.rb
83
+ - lib/oas_rails/active_record_example_finder.rb
83
84
  - lib/oas_rails/builders/content_builder.rb
84
85
  - lib/oas_rails/builders/esquema_builder.rb
86
+ - lib/oas_rails/builders/oas_route_builder.rb
85
87
  - lib/oas_rails/builders/operation_builder.rb
86
88
  - lib/oas_rails/builders/parameter_builder.rb
87
89
  - lib/oas_rails/builders/parameters_builder.rb