oas_rails 0.4.5 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5285e5cf1a83ddb32b2d4b74a79d5694175fa2e7959211f264ab58a0b9dadf36
4
- data.tar.gz: d16c39e306c501bda6fd38d842df96820cc7621bf29b76026e4a0ea8e1070588
3
+ metadata.gz: 1f833de18481bb481d9f698a2f10a3b7f1a1512623cd335932659736f94b2161
4
+ data.tar.gz: 343243f10ae846b8eb42ca75cdd3d69332fe19b41192959bb355603fa2148242
5
5
  SHA512:
6
- metadata.gz: 638314dfa65ce344c4fdb8c7e725503f6556a7276aa4839c406d738a7d06b9bfd8533e68dcf720bf95573e4b75f7d91f0600528b4fd00670e2c94990a92f5a04
7
- data.tar.gz: 606103ec7b8640f5fbbcc1afcbd9c56d5671df6dbacd89e7ab1c30ead4b2480f583cb2f6bc31a6cf4a731399d77e101e1a5b4d7f8465c548da49307ad170f4d3
6
+ metadata.gz: 3e41ce594245affa26d85f05bba1fbdb1b6dacbb1d112c42aaa63c0837654a56d4afdd9eb442bc7000f3ceda90fb254aea5712a5e2d3aeff58d1505d869957a3
7
+ data.tar.gz: 1be4b42da76e635d82ee95f663ea9ca950873b2c6037779598cfa0c284076a022a05aaeb772dd66f5a08b76bedbac7be3ac27453db0a07a4be8e7e0b3213bca9
data/README.md CHANGED
@@ -29,7 +29,7 @@ OasRails is a Rails engine for generating **automatic interactive documentation
29
29
 
30
30
  After experiencing the interactive documentation in Python's fast-api framework, I sought similar functionality in Ruby on Rails. Unable to find a suitable solution, I [asked on Stack Overflow](https://stackoverflow.com/questions/71947018/is-there-a-way-to-generate-an-interactive-documentation-for-rails-apis) years ago. Now, with some free time while freelancing as an API developer, I decided to build my own tool.
31
31
 
32
- **Note**: This is not yet a production-ready solution. The code may be rough and behave unexpectedly, but I am actively working on improving it. If you like the idea, please consider contributing to its development.
32
+ **Note: This is not yet a production-ready solution. The code may be rough and behave unexpectedly, but I am actively working on improving it. If you like the idea, please consider contributing to its development.**
33
33
 
34
34
  The goal is to minimize the effort required to create comprehensive documentation. By following REST principles in Rails, we believe this is achievable. You can enhance the documentation using [Yard](https://yardoc.org/) tags.
35
35
 
@@ -120,7 +120,7 @@ Then fill it with your data. Below are the available configuration options:
120
120
 
121
121
  - `config.possible_default_responses`: Array with possible default errors.(Some will be added depending on the endpoint, example: not_found only works with show/update/delete). Default: [:not_found, :unauthorized, :forbidden]. It should be HTTP status code symbols from the list: `[:not_found, :unauthorized, :forbidden, :internal_server_error, :unprocessable_entity]`
122
122
 
123
- - `config.response_body_of_default`: body for use in default responses. It must be a Hash. Default: { message: String }
123
+ - `config.response_body_of_default`: body for use in default responses. It must be a String hash like the used in request body tags. Default: "{ message: String }"
124
124
 
125
125
  ## Usage
126
126
 
@@ -158,12 +158,19 @@ Represents a parameter for the endpoint. The position can be: `header`, `path`,
158
158
  <details>
159
159
  <summary style="font-weight: bold; font-size: 1.2em;">@request_body</summary>
160
160
 
161
- **Structure**: `@request_body text [type] structure`
161
+ **Structure**: `@request_body text [type<structure>]`
162
162
 
163
163
  Documents the request body needed by the endpoint. The structure is optional if you provide a valid Active Record class. Use `!` to indicate a required request body.
164
164
 
165
165
  **Example**:
166
- `# @request_body The user to be created [Hash] {user: {name: String, age: Integer, password: String}}`
166
+
167
+ `# @request_body The user to be created [!Hash{user: {name: String, age: Integer, password: String}}]`
168
+
169
+ `# @request_body The user to be created [!User]`
170
+
171
+ `# @request_body The user to be created [User]`
172
+
173
+ `# @request_body The user to be created [!Hash{user: {name: String, age: Integer, password: String, surnames: Array<String>, coords: Hash{lat: String, lng: String}}}]`
167
174
 
168
175
  </details>
169
176
 
@@ -182,12 +189,30 @@ Adds examples to the provided request body.
182
189
  <details>
183
190
  <summary style="font-weight: bold; font-size: 1.2em;">@response</summary>
184
191
 
185
- **Structure**: `@response text(code) [type] structure`
192
+ **Structure**: `@response text(code) [type<structure>]`
186
193
 
187
194
  Documents the responses of the endpoint and overrides the default responses found by the engine.
188
195
 
189
196
  **Example**:
190
- `# @response User not found by the provided Id(404) [Hash] {success: Boolean, message: String}`
197
+
198
+ `# @response User not found by the provided Id(404) [Hash{success: Boolean, message: String}]`
199
+
200
+ `# @response Validation errors(422) [Hash{success: Boolean, errors: Array<Hash{field: String, type: String, detail: Array<String>}>}]`
201
+
202
+ </details>
203
+
204
+ <details>
205
+ <summary style="font-weight: bold; font-size: 1.2em;">@response_example</summary>
206
+
207
+ **Structure**: `@response_example text(code) [String Hash]`
208
+
209
+ Documents response examples of the endpoint associated to a response code.
210
+
211
+ **Example**:
212
+
213
+ `# @response_example Invalida Email(422) [{success: "false", errors: [{field: "email", type: "email", detail: ["Invalid email"]}] }]`
214
+
215
+ `# @response_example Id not exists (404) [{success: "false", message: "Nothing found with the provided ID." }]`
191
216
 
192
217
  </details>
193
218
 
@@ -250,9 +275,9 @@ class UsersController < ApplicationController
250
275
  # This method show a User by ID. The id must exist of other way it will be returning a **`404`**.
251
276
  #
252
277
  # @parameter id(path) [Integer] Used for identify the user.
253
- # @response Requested User(200) [Hash] {user: {name: String, email: String, created_at: DateTime }}
254
- # @response User not found by the provided Id(404) [Hash] {success: Boolean, message: String}
255
- # @response You don't have the right permission for access to this resource(403) [Hash] {success: Boolean, message: String}
278
+ # @response Requested User(200) [Hash{user: {name: String, email: String, created_at: DateTime }}]
279
+ # @response User not found by the provided Id(404) [Hash{success: Boolean, message: String}]
280
+ # @response You don't have the right permission for access to this resource(403) [Hash{success: Boolean, message: String}]
256
281
  def show
257
282
  render json: @user
258
283
  end
@@ -260,7 +285,7 @@ class UsersController < ApplicationController
260
285
  # @summary Create a User
261
286
  # @no_auth
262
287
  #
263
- # @request_body The user to be created. At least include an `email`. [User!]
288
+ # @request_body The user to be created. At least include an `email`. [!User]
264
289
  # @request_body_example basic user [Hash] {user: {name: "Luis", email: "luis@gmail.ocom"}}
265
290
  def create
266
291
  @user = User.new(user_params)
@@ -276,7 +301,7 @@ class UsersController < ApplicationController
276
301
  # - There is no option
277
302
  # - It must work
278
303
  # @tags users, update
279
- # @request_body User to be created [Hash] {user: { name: String, email: String, age: Integer}}
304
+ # @request_body User to be created [!Hash{user: { name: String, email: !String, age: Integer, available_dates: Array<Date>}}]
280
305
  # @request_body_example Update user [Hash] {user: {name: "Luis", email: "luis@gmail.com"}}
281
306
  # @request_body_example Complete User [Hash] {user: {name: "Luis", email: "luis@gmail.com", age: 21}}
282
307
  def update
@@ -8,14 +8,17 @@ module OasRails
8
8
 
9
9
  def from_oas_route(oas_route)
10
10
  oas_route.docstring.tags(:response).each do |tag|
11
- @responses.add_response(ResponseBuilder.new(@specification).from_tag(tag).build)
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
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)
12
15
  end
13
16
 
14
17
  self
15
18
  end
16
19
 
17
20
  def add_autodiscovered_responses(oas_route)
18
- return self unless OasRails.config.autodiscover_responses
21
+ return self if !OasRails.config.autodiscover_responses || oas_route.docstring.tags(:response).any?
19
22
 
20
23
  new_responses = Extractors::RenderResponseExtractor.extract_responses_from_source(@specification, source: oas_route.source_string)
21
24
 
@@ -29,7 +32,7 @@ module OasRails
29
32
  def add_default_responses(oas_route, security)
30
33
  return self unless OasRails.config.set_default_responses
31
34
 
32
- content = ContentBuilder.new(@specification, :outgoing).with_schema(Utils.hash_to_json_schema(OasRails.config.response_body_of_default)).build
35
+ content = ContentBuilder.new(@specification, :outgoing).with_schema(JsonSchemaGenerator.process_string(OasRails.config.response_body_of_default)[:json_schema]).build
33
36
  common_errors = []
34
37
  common_errors.push(:unauthorized, :forbidden) if security
35
38
 
@@ -26,7 +26,7 @@ module OasRails
26
26
  @security_schemas = {}
27
27
  @set_default_responses = true
28
28
  @possible_default_responses = [:not_found, :unauthorized, :forbidden]
29
- @response_body_of_default = { message: String }
29
+ @response_body_of_default = "Hash{ success: !Boolean, message: String }"
30
30
  end
31
31
 
32
32
  def security_schema=(value)
@@ -0,0 +1,127 @@
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
+ else
33
+ { type: type.downcase.to_sym, required: }
34
+ end
35
+ end
36
+
37
+ # Parses the properties of an object type from a string.
38
+ #
39
+ # @param str [String] The string representation of the object's properties.
40
+ # @return [Hash] A hash where keys are property names and values are their JSON schema types.
41
+ def self.parse_object_properties(str)
42
+ properties = {}
43
+ stack = []
44
+ current_key = ''
45
+ current_value = ''
46
+
47
+ str.each_char.with_index do |char, index|
48
+ case char
49
+ when '{', '<'
50
+ stack.push(char)
51
+ current_value += char
52
+ when '}', '>'
53
+ stack.pop
54
+ current_value += char
55
+ when ','
56
+ if stack.empty?
57
+ properties[current_key.strip.to_sym] = parse_type(current_value.strip)
58
+ current_key = ''
59
+ current_value = ''
60
+ else
61
+ current_value += char
62
+ end
63
+ when ':'
64
+ if stack.empty?
65
+ current_key = current_value
66
+ current_value = ''
67
+ else
68
+ current_value += char
69
+ end
70
+ else
71
+ current_value += char
72
+ end
73
+
74
+ properties[current_key.strip.to_sym] = parse_type(current_value.strip) if index == str.length - 1 && !current_key.empty?
75
+ end
76
+
77
+ properties
78
+ end
79
+
80
+ # Converts a parsed data type into a JSON schema format.
81
+ #
82
+ # @param parsed [Hash] The parsed data type hash.
83
+ # @return [Hash] The JSON schema representation of the parsed data type.
84
+ def self.to_json_schema(parsed)
85
+ case parsed[:type]
86
+ when :object
87
+ schema = {
88
+ type: 'object',
89
+ properties: {}
90
+ }
91
+ required_props = []
92
+ parsed[:properties].each do |key, value|
93
+ schema[:properties][key] = to_json_schema(value)
94
+ required_props << key.to_s if value[:required]
95
+ end
96
+ schema[:required] = required_props unless required_props.empty?
97
+ schema
98
+ when :array
99
+ {
100
+ type: 'array',
101
+ items: to_json_schema(parsed[:items])
102
+ }
103
+ else
104
+ ruby_type_to_json_schema_type(parsed[:type])
105
+ end
106
+ end
107
+
108
+ # Converts a Ruby data type into its corresponding JSON schema type.
109
+ #
110
+ # @param type [Symbol, String] The Ruby data type.
111
+ # @return [Hash, String] The JSON schema type or a hash with additional format information.
112
+ def self.ruby_type_to_json_schema_type(type)
113
+ case type.to_s.downcase
114
+ when 'string' then { type: "string" }
115
+ when 'integer' then { type: "integer" }
116
+ when 'float' then { type: "float" }
117
+ when 'boolean' then { type: "boolean" }
118
+ when 'array' then { type: "array" }
119
+ when 'hash' then { type: "hash" }
120
+ when 'nil' then { type: "null" }
121
+ when 'date' then { type: "string", format: "date" }
122
+ when 'datetime' then { type: "string", format: "date-time" }
123
+ else type.to_s.downcase
124
+ end
125
+ end
126
+ end
127
+ end
@@ -1,3 +1,3 @@
1
1
  module OasRails
2
- VERSION = "0.4.5"
2
+ VERSION = "0.6.0"
3
3
  end
@@ -0,0 +1,12 @@
1
+ module OasRails
2
+ module YARD
3
+ class ExampleTag < ::YARD::Tags::Tag
4
+ attr_accessor :content
5
+
6
+ def initialize(tag_name, text, content: {})
7
+ super(tag_name, text, nil, nil)
8
+ @content = content
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,176 @@
1
+ module OasRails
2
+ module YARD
3
+ class OasRailsFactory < ::YARD::Tags::DefaultFactory
4
+ # Parses a tag that represents a request body.
5
+ # @param tag_name [String] The name of the tag.
6
+ # @param text [String] The tag text to parse.
7
+ # @return [RequestBodyTag] The parsed request body tag object.
8
+ def parse_tag_with_request_body(tag_name, text)
9
+ description, klass, schema, required = extract_description_and_schema(text)
10
+ RequestBodyTag.new(tag_name, description, klass, schema:, required:)
11
+ end
12
+
13
+ # Parses a tag that represents a request body example.
14
+ # @param tag_name [String] The name of the tag.
15
+ # @param text [String] The tag text to parse.
16
+ # @return [RequestBodyExampleTag] The parsed request body example tag object.
17
+ def parse_tag_with_request_body_example(tag_name, text)
18
+ description, _, hash = extract_description_type_and_content(text, process_content: true)
19
+ RequestBodyExampleTag.new(tag_name, description, content: hash)
20
+ end
21
+
22
+ # Parses a tag that represents a parameter.
23
+ # @param tag_name [String] The name of the tag.
24
+ # @param text [String] The tag text to parse.
25
+ # @return [ParameterTag] The parsed parameter tag object.
26
+ def parse_tag_with_parameter(tag_name, text)
27
+ name, location, schema, required, description = extract_name_location_schema_and_description(text)
28
+ ParameterTag.new(tag_name, name, description, schema, location, required:)
29
+ end
30
+
31
+ # Parses a tag that represents a response.
32
+ # @param tag_name [String] The name of the tag.
33
+ # @param text [String] The tag text to parse.
34
+ # @return [ResponseTag] The parsed response tag object.
35
+ def parse_tag_with_response(tag_name, text)
36
+ name, code, schema = extract_name_code_and_schema(text)
37
+ ResponseTag.new(tag_name, code, name, schema)
38
+ end
39
+
40
+ # Parses a tag that represents a response example.
41
+ # @param tag_name [String] The name of the tag.
42
+ # @param text [String] The tag text to parse.
43
+ # @return [ResponseExampleTag] The parsed response example tag object.
44
+ def parse_tag_with_response_example(tag_name, text)
45
+ description, code, hash = extract_name_code_and_hash(text)
46
+ ResponseExampleTag.new(tag_name, description, content: hash, code:)
47
+ end
48
+
49
+ private
50
+
51
+ # Reusable method for extracting description, type, and content with an option to process content.
52
+ # @param text [String] The text to parse.
53
+ # @param process_content [Boolean] Whether to evaluate the content as a hash.
54
+ # @return [Array] An array containing the description, type, and content or remaining text.
55
+ def extract_description_type_and_content(text, process_content: false)
56
+ match = text.match(/^(.*?)\s*\[(.*)\]\s*(.*)$/)
57
+ raise ArgumentError, "Invalid tag format: #{text}" if match.nil?
58
+
59
+ description = match[1].strip
60
+ type = match[2].strip
61
+ content = process_content ? eval_content(match[3].strip) : match[3].strip
62
+
63
+ [description, type, content]
64
+ end
65
+
66
+ # Specific method to extract description and schema for request body tags.
67
+ # @param text [String] The text to parse.
68
+ # @return [Array] An array containing the description, class, schema, and required flag.
69
+ def extract_description_and_schema(text)
70
+ description, type, = extract_description_type_and_content(text)
71
+ klass, schema, required = type_text_to_schema(type)
72
+ [description, klass, schema, required]
73
+ end
74
+
75
+ # Specific method to extract name, location, and schema for parameters.
76
+ # @param text [String] The text to parse.
77
+ # @return [Array] An array containing the name, location, schema, and required flag.
78
+ def extract_name_location_schema_and_description(text)
79
+ match = text.match(/^(.*?)\s*\[(.*?)\]\s*(.*)$/)
80
+ name, location = extract_text_and_parentheses_content(match[1].strip)
81
+ schema, required = type_text_to_schema(match[2].strip)[1..]
82
+ description = match[3].strip
83
+ [name, location, schema, required, description]
84
+ end
85
+
86
+ # Specific method to extract name, code, and schema for responses.
87
+ # @param text [String] The text to parse.
88
+ # @return [Array] An array containing the name, code, and schema.
89
+ def extract_name_code_and_schema(text)
90
+ name, code = extract_text_and_parentheses_content(text)
91
+ _, type, = extract_description_type_and_content(text)
92
+ schema = type_text_to_schema(type)[1]
93
+ [name, code, schema]
94
+ end
95
+
96
+ # Specific method to extract name, code, and hash for responses examples.
97
+ # @param text [String] The text to parse.
98
+ # @return [Array] An array containing the name, code, and schema.
99
+ def extract_name_code_and_hash(text)
100
+ name, code = extract_text_and_parentheses_content(text)
101
+ _, type, = extract_description_type_and_content(text)
102
+ hash = eval_content(type)
103
+ [name, code, hash]
104
+ end
105
+
106
+ # Evaluates a string as a hash, handling errors gracefully.
107
+ # @param content [String] The content string to evaluate.
108
+ # @return [Hash] The evaluated hash, or an empty hash if an error occurs.
109
+ # rubocop:disable Security/Eval
110
+ def eval_content(content)
111
+ eval(content)
112
+ rescue StandardError
113
+ {}
114
+ end
115
+ # rubocop:enable Security/Eval
116
+
117
+ # Parses the position name and location from input text.
118
+ # @param input [String] The input text to parse.
119
+ # @return [Array] An array containing the name and location.
120
+ def extract_text_and_parentheses_content(input)
121
+ return unless input =~ /^(.+?)\(([^)]+)\)/
122
+
123
+ text = ::Regexp.last_match(1).strip
124
+ parenthesis_content = ::Regexp.last_match(2).strip
125
+ [text, parenthesis_content]
126
+ end
127
+
128
+ # Extracts the text and whether it's required.
129
+ # @param text [String] The text to parse.
130
+ # @return [Array] An array containing the text and a required flag.
131
+ def text_and_required(text)
132
+ if text.start_with?('!')
133
+ [text.sub(/^!/, ''), true]
134
+ else
135
+ [text, false]
136
+ end
137
+ end
138
+
139
+ # Matches and validates a description and type from text.
140
+ # @param text [String] The text to parse.
141
+ # @return [MatchData] The match data from the regex.
142
+ def description_and_type(text)
143
+ match = text.match(/^(.*?)\s*\[(.*?)\]\s*(.*)$/)
144
+ raise ArgumentError, "The request body tag is not valid: #{text}" if match.nil?
145
+
146
+ match
147
+ end
148
+
149
+ # Checks if a given text refers to an ActiveRecord class.
150
+ # @param text [String] The text to check.
151
+ # @return [Boolean] True if the text refers to an ActiveRecord class, false otherwise.
152
+ def active_record_class?(text)
153
+ klass = text.constantize
154
+ klass.ancestors.include? ActiveRecord::Base
155
+ rescue StandardError
156
+ false
157
+ end
158
+
159
+ # Converts type text to a schema, checking if it's an ActiveRecord class.
160
+ # @param text [String] The type text to convert.
161
+ # @return [Array] An array containing the class, schema, and required flag.
162
+ def type_text_to_schema(text)
163
+ type_text, required = text_and_required(text)
164
+
165
+ if active_record_class?(type_text)
166
+ klass = type_text.constantize
167
+ schema = Builders::EsquemaBuilder.build_outgoing_schema(klass:)
168
+ else
169
+ schema = JsonSchemaGenerator.process_string(type_text)[:json_schema]
170
+ klass = Object
171
+ end
172
+ [klass, schema, required]
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,14 @@
1
+ module OasRails
2
+ module YARD
3
+ class ParameterTag < ::YARD::Tags::Tag
4
+ attr_accessor :schema, :required, :location
5
+
6
+ def initialize(tag_name, name, text, schema, location, required: false)
7
+ super(tag_name, text, nil, name)
8
+ @schema = schema
9
+ @required = required
10
+ @location = location
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ module OasRails
2
+ module YARD
3
+ class RequestBodyExampleTag < ExampleTag
4
+ attr_accessor :content
5
+
6
+ def initialize(tag_name, text, content: {})
7
+ super
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ module OasRails
2
+ module YARD
3
+ class RequestBodyTag < ::YARD::Tags::Tag
4
+ attr_accessor :klass, :schema, :required
5
+
6
+ def initialize(tag_name, text, klass, schema: {}, required: false)
7
+ # initialize(tag_name, text, types = nil, name = nil)
8
+ super(tag_name, text, nil, nil)
9
+ @klass = klass
10
+ @schema = schema
11
+ @required = required
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ module OasRails
2
+ module YARD
3
+ class ResponseExampleTag < ExampleTag
4
+ attr_accessor :code
5
+
6
+ def initialize(tag_name, text, content: {}, code: 200)
7
+ super(tag_name, text, content:)
8
+ @code = code
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ module OasRails
2
+ module YARD
3
+ class ResponseTag < ::YARD::Tags::Tag
4
+ attr_accessor :schema
5
+
6
+ # TODO: name == code. The name MUST be changed to code for better understanding
7
+ def initialize(tag_name, name, text, schema)
8
+ super(tag_name, text, nil, name)
9
+ @schema = schema
10
+ end
11
+ end
12
+ end
13
+ end
data/lib/oas_rails.rb CHANGED
@@ -9,6 +9,7 @@ module OasRails
9
9
  autoload :Configuration, "oas_rails/configuration"
10
10
  autoload :OasRoute, "oas_rails/oas_route"
11
11
  autoload :Utils, "oas_rails/utils"
12
+ autoload :JsonSchemaGenerator, "oas_rails/json_schema_generator"
12
13
 
13
14
  module Builders
14
15
  autoload :OperationBuilder, "oas_rails/builders/operation_builder"
@@ -45,7 +46,13 @@ module OasRails
45
46
  end
46
47
 
47
48
  module YARD
48
- autoload :OasYARDFactory, 'oas_rails/yard/oas_yard_factory'
49
+ autoload :RequestBodyTag, 'oas_rails/yard/request_body_tag'
50
+ autoload :ExampleTag, 'oas_rails/yard/example_tag'
51
+ autoload :RequestBodyExampleTag, 'oas_rails/yard/request_body_example_tag'
52
+ autoload :ParameterTag, 'oas_rails/yard/parameter_tag'
53
+ autoload :ResponseTag, 'oas_rails/yard/response_tag'
54
+ autoload :ResponseExampleTag, 'oas_rails/yard/response_example_tag'
55
+ autoload :OasRailsFactory, 'oas_rails/yard/oas_rails_factory'
49
56
  end
50
57
 
51
58
  module Extractors
@@ -73,12 +80,13 @@ module OasRails
73
80
  end
74
81
 
75
82
  def configure_yard!
76
- ::YARD::Tags::Library.default_factory = YARD::OasYARDFactory
83
+ ::YARD::Tags::Library.default_factory = YARD::OasRailsFactory
77
84
  yard_tags = {
78
85
  'Request body' => [:request_body, :with_request_body],
79
86
  'Request body Example' => [:request_body_example, :with_request_body_example],
80
87
  'Parameter' => [:parameter, :with_parameter],
81
88
  'Response' => [:response, :with_response],
89
+ 'Response Example' => [:response_example, :with_response_example],
82
90
  'Endpoint Tags' => [:tags],
83
91
  'Summary' => [:summary],
84
92
  'No Auth' => [:no_auth],
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oas_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.5
4
+ version: 0.6.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: 2024-08-22 00:00:00.000000000 Z
11
+ date: 2024-08-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: method_source
@@ -108,6 +108,7 @@ files:
108
108
  - lib/oas_rails/extractors/oas_route_extractor.rb
109
109
  - lib/oas_rails/extractors/render_response_extractor.rb
110
110
  - lib/oas_rails/extractors/route_extractor.rb
111
+ - lib/oas_rails/json_schema_generator.rb
111
112
  - lib/oas_rails/oas_route.rb
112
113
  - lib/oas_rails/spec/components.rb
113
114
  - lib/oas_rails/spec/contact.rb
@@ -129,7 +130,13 @@ files:
129
130
  - lib/oas_rails/spec/tag.rb
130
131
  - lib/oas_rails/utils.rb
131
132
  - lib/oas_rails/version.rb
132
- - lib/oas_rails/yard/oas_yard_factory.rb
133
+ - lib/oas_rails/yard/example_tag.rb
134
+ - lib/oas_rails/yard/oas_rails_factory.rb
135
+ - lib/oas_rails/yard/parameter_tag.rb
136
+ - lib/oas_rails/yard/request_body_example_tag.rb
137
+ - lib/oas_rails/yard/request_body_tag.rb
138
+ - lib/oas_rails/yard/response_example_tag.rb
139
+ - lib/oas_rails/yard/response_tag.rb
133
140
  homepage: https://github.com/a-chacon/oas_rails
134
141
  licenses:
135
142
  - GPL-3.0-only
@@ -1,160 +0,0 @@
1
- module OasRails
2
- module YARD
3
- class RequestBodyTag < ::YARD::Tags::Tag
4
- attr_accessor :klass, :schema, :required
5
-
6
- def initialize(tag_name, text, klass, schema: {}, required: false)
7
- # initialize(tag_name, text, types = nil, name = nil)
8
- super(tag_name, text, nil, nil)
9
- @klass = klass
10
- @schema = schema
11
- @required = required
12
- end
13
- end
14
-
15
- class RequestBodyExampleTag < ::YARD::Tags::Tag
16
- attr_accessor :content
17
-
18
- def initialize(tag_name, text, content: {})
19
- super(tag_name, text, nil, nil)
20
- @content = content
21
- end
22
- end
23
-
24
- class ParameterTag < ::YARD::Tags::Tag
25
- attr_accessor :schema, :required, :location
26
-
27
- def initialize(tag_name, name, text, schema, location, required: false)
28
- super(tag_name, text, nil, name)
29
- @schema = schema
30
- @required = required
31
- @location = location
32
- end
33
- end
34
-
35
- class ResponseTag < ::YARD::Tags::Tag
36
- attr_accessor :schema
37
-
38
- def initialize(tag_name, name, text, schema)
39
- super(tag_name, text, nil, name)
40
- @schema = schema
41
- end
42
- end
43
-
44
- class OasYARDFactory < ::YARD::Tags::DefaultFactory
45
- ## parse_tag is a prefix used by YARD
46
-
47
- def parse_tag_with_request_body(tag_name, text)
48
- match = text.match(/^(.*?)\s*\[(.*?)\]\s*(.*)$/)
49
- if !match.nil?
50
- text = match[1].strip
51
- type, required = parse_type(match[2].strip)
52
-
53
- if type.constantize == Hash
54
- hash_string = match[3].strip
55
-
56
- begin
57
- hash = eval(hash_string)
58
- hash = Utils.hash_to_json_schema(hash)
59
- rescue StandardError
60
- hash = {}
61
- end
62
- end
63
-
64
- RequestBodyTag.new(tag_name, text, type.constantize, schema: hash, required:)
65
- else
66
- Rails.logger.debug("Error parsing request_body tag: #{text}")
67
- end
68
- end
69
-
70
- def parse_tag_with_request_body_example(tag_name, text)
71
- match = text.match(/^(.*?)\s*\[(.*?)\]\s*(.*)$/)
72
- if !match.nil?
73
- text = match[1].strip
74
- type = match[2]
75
-
76
- if type.constantize == Hash
77
- hash_string = match[3].strip
78
-
79
- begin
80
- hash = eval(hash_string)
81
- rescue StandardError
82
- hash = {}
83
- end
84
- end
85
- RequestBodyExampleTag.new(tag_name, text, content: hash)
86
- else
87
- Rails.logger.debug("Error parsing request body example tag: #{text}")
88
- end
89
- end
90
-
91
- def parse_tag_with_parameter(tag_name, text)
92
- match = text.match(/^(.*?)\s*\[(.*?)\]\s*(.*)$/)
93
- if !match.nil?
94
- text = match[1].strip
95
- name, location = parse_position_name(text)
96
- type, required = parse_type(match[2].strip)
97
- schema = Utils.type_to_schema(type)
98
-
99
- ParameterTag.new(tag_name, name, match[3].strip, schema, location, required:)
100
- else
101
- Rails.logger.debug("Error parsing request body example tag: #{text}")
102
- end
103
- end
104
-
105
- def parse_tag_with_response(tag_name, text)
106
- match = text.match(/^(.*?)\s*\[(.*?)\]\s*(.*)$/)
107
- if !match.nil?
108
- name, code = parse_position_name(match[1].strip)
109
-
110
- begin
111
- hash = parse_str_to_hash(match[3].strip)
112
- hash = Utils.hash_to_json_schema(hash)
113
- rescue StandardError
114
- hash = {}
115
- end
116
-
117
- ResponseTag.new(tag_name, code, name, hash)
118
- else
119
- Rails.logger.debug("Error parsing request body example tag: #{text}")
120
- end
121
- end
122
-
123
- def parse_position_name(input)
124
- return unless input =~ /^([^(]+)\((.*)\)$/
125
-
126
- name = ::Regexp.last_match(1)
127
- location = ::Regexp.last_match(2)
128
- [name, location]
129
- end
130
-
131
- def parse_type(type_string)
132
- if type_string.end_with?('!')
133
- [type_string.chomp('!'), true]
134
- else
135
- [type_string, false]
136
- end
137
- end
138
-
139
- def parse_str_to_hash(str)
140
- str = str.gsub(/^\{|\}$/, '') # Remove leading { and trailing }
141
- pairs = str.split(',').map(&:strip)
142
-
143
- pairs.each_with_object({}) do |pair, hash|
144
- key, value = pair.split(':').map(&:strip)
145
- key = key.to_sym
146
- hash[key] = case value
147
- when 'String' then String
148
- when 'Integer' then Integer
149
- when 'Float' then Float
150
- when 'Boolean' then [TrueClass, FalseClass]
151
- when 'Array' then Array
152
- when 'Hash' then Hash
153
- when 'DateTime' then DateTime
154
- else value
155
- end
156
- end
157
- end
158
- end
159
- end
160
- end