oas_rails 0.4.5 → 0.5.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 +4 -4
- data/README.md +21 -11
- data/lib/oas_rails/builders/responses_builder.rb +2 -2
- data/lib/oas_rails/configuration.rb +1 -1
- data/lib/oas_rails/json_schema_generator.rb +127 -0
- data/lib/oas_rails/version.rb +1 -1
- data/lib/oas_rails/yard/oas_rails_factory.rb +157 -0
- data/lib/oas_rails/yard/parameter_tag.rb +14 -0
- data/lib/oas_rails/yard/request_body_example_tag.rb +12 -0
- data/lib/oas_rails/yard/request_body_tag.rb +15 -0
- data/lib/oas_rails/yard/response_tag.rb +12 -0
- data/lib/oas_rails.rb +7 -2
- metadata +8 -3
- data/lib/oas_rails/yard/oas_yard_factory.rb +0 -160
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b3f5da00f122856b6cce2f73b5912013477726bda80f57f39fdbe4609bc551c7
|
4
|
+
data.tar.gz: 35def82214e4c8b01755ddb2b501140f45ab653d575512ec52730d5722f7b025
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f6854cfb18a0770b11dae6e87da5a7a5d3521d3967bd27656cf6e69ac3f3dfb8342b045f04af2ba27ab969e1b5615dd757fb26e9632628811741f0876f473a94
|
7
|
+
data.tar.gz: 994dc70c3ba5b1a935686b9f7c90913bd59106d8981db340f788d38d048226f1acef0eb113d2e12534c5eec83d7a5ee8d9fd159da923bbcbd412e8d72a9eb758
|
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
|
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
|
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]
|
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
|
-
|
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,15 @@ 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]
|
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
|
-
|
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, erros: Array<Hash{field: String, type: String, detail: Array<String>}>}]`
|
191
201
|
|
192
202
|
</details>
|
193
203
|
|
@@ -250,9 +260,9 @@ class UsersController < ApplicationController
|
|
250
260
|
# This method show a User by ID. The id must exist of other way it will be returning a **`404`**.
|
251
261
|
#
|
252
262
|
# @parameter id(path) [Integer] Used for identify the user.
|
253
|
-
# @response Requested User(200) [Hash
|
254
|
-
# @response User not found by the provided Id(404) [Hash
|
255
|
-
# @response You don't have the right permission for access to this resource(403) [Hash
|
263
|
+
# @response Requested User(200) [Hash{user: {name: String, email: String, created_at: DateTime }}]
|
264
|
+
# @response User not found by the provided Id(404) [Hash{success: Boolean, message: String}]
|
265
|
+
# @response You don't have the right permission for access to this resource(403) [Hash{success: Boolean, message: String}]
|
256
266
|
def show
|
257
267
|
render json: @user
|
258
268
|
end
|
@@ -260,7 +270,7 @@ class UsersController < ApplicationController
|
|
260
270
|
# @summary Create a User
|
261
271
|
# @no_auth
|
262
272
|
#
|
263
|
-
# @request_body The user to be created. At least include an `email`. [User
|
273
|
+
# @request_body The user to be created. At least include an `email`. [!User]
|
264
274
|
# @request_body_example basic user [Hash] {user: {name: "Luis", email: "luis@gmail.ocom"}}
|
265
275
|
def create
|
266
276
|
@user = User.new(user_params)
|
@@ -276,7 +286,7 @@ class UsersController < ApplicationController
|
|
276
286
|
# - There is no option
|
277
287
|
# - It must work
|
278
288
|
# @tags users, update
|
279
|
-
# @request_body User to be created [Hash
|
289
|
+
# @request_body User to be created [!Hash{user: { name: String, email: !String, age: Integer, available_dates: Array<Date>}}]
|
280
290
|
# @request_body_example Update user [Hash] {user: {name: "Luis", email: "luis@gmail.com"}}
|
281
291
|
# @request_body_example Complete User [Hash] {user: {name: "Luis", email: "luis@gmail.com", age: 21}}
|
282
292
|
def update
|
@@ -15,7 +15,7 @@ module OasRails
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def add_autodiscovered_responses(oas_route)
|
18
|
-
return self
|
18
|
+
return self if !OasRails.config.autodiscover_responses || oas_route.docstring.tags(:response).any?
|
19
19
|
|
20
20
|
new_responses = Extractors::RenderResponseExtractor.extract_responses_from_source(@specification, source: oas_route.source_string)
|
21
21
|
|
@@ -29,7 +29,7 @@ module OasRails
|
|
29
29
|
def add_default_responses(oas_route, security)
|
30
30
|
return self unless OasRails.config.set_default_responses
|
31
31
|
|
32
|
-
content = ContentBuilder.new(@specification, :outgoing).with_schema(
|
32
|
+
content = ContentBuilder.new(@specification, :outgoing).with_schema(JsonSchemaGenerator.process_string(OasRails.config.response_body_of_default)[:json_schema]).build
|
33
33
|
common_errors = []
|
34
34
|
common_errors.push(:unauthorized, :forbidden) if security
|
35
35
|
|
@@ -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 = "{ 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
|
data/lib/oas_rails/version.rb
CHANGED
@@ -0,0 +1,157 @@
|
|
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
|
+
private
|
41
|
+
|
42
|
+
# Reusable method for extracting description, type, and content with an option to process content.
|
43
|
+
# @param text [String] The text to parse.
|
44
|
+
# @param process_content [Boolean] Whether to evaluate the content as a hash.
|
45
|
+
# @return [Array] An array containing the description, type, and content or remaining text.
|
46
|
+
def extract_description_type_and_content(text, process_content: false)
|
47
|
+
match = text.match(/^(.*?)\s*\[(.*?)\]\s*(.*)$/)
|
48
|
+
raise ArgumentError, "Invalid tag format: #{text}" if match.nil?
|
49
|
+
|
50
|
+
description = match[1].strip
|
51
|
+
type = match[2].strip
|
52
|
+
content = process_content ? eval_content(match[3].strip) : match[3].strip
|
53
|
+
|
54
|
+
[description, type, content]
|
55
|
+
end
|
56
|
+
|
57
|
+
# Specific method to extract description and schema for request body tags.
|
58
|
+
# @param text [String] The text to parse.
|
59
|
+
# @return [Array] An array containing the description, class, schema, and required flag.
|
60
|
+
def extract_description_and_schema(text)
|
61
|
+
description, type, = extract_description_type_and_content(text)
|
62
|
+
klass, schema, required = type_text_to_schema(type)
|
63
|
+
[description, klass, schema, required]
|
64
|
+
end
|
65
|
+
|
66
|
+
# Specific method to extract name, location, and schema for parameters.
|
67
|
+
# @param text [String] The text to parse.
|
68
|
+
# @return [Array] An array containing the name, location, schema, and required flag.
|
69
|
+
def extract_name_location_schema_and_description(text)
|
70
|
+
match = text.match(/^(.*?)\s*\[(.*?)\]\s*(.*)$/)
|
71
|
+
name, location = extract_text_and_parentheses_content(match[1].strip)
|
72
|
+
schema, required = type_text_to_schema(match[2].strip)[1..]
|
73
|
+
description = match[3].strip
|
74
|
+
[name, location, schema, required, description]
|
75
|
+
end
|
76
|
+
|
77
|
+
# Specific method to extract name, code, and schema for responses.
|
78
|
+
# @param text [String] The text to parse.
|
79
|
+
# @return [Array] An array containing the name, code, and schema.
|
80
|
+
def extract_name_code_and_schema(text)
|
81
|
+
name, code = extract_text_and_parentheses_content(text)
|
82
|
+
_, type, = extract_description_type_and_content(text)
|
83
|
+
schema = type_text_to_schema(type)[1]
|
84
|
+
[name, code, schema]
|
85
|
+
end
|
86
|
+
|
87
|
+
# Evaluates a string as a hash, handling errors gracefully.
|
88
|
+
# @param content [String] The content string to evaluate.
|
89
|
+
# @return [Hash] The evaluated hash, or an empty hash if an error occurs.
|
90
|
+
# rubocop:disable Security/Eval
|
91
|
+
def eval_content(content)
|
92
|
+
eval(content)
|
93
|
+
rescue StandardError
|
94
|
+
{}
|
95
|
+
end
|
96
|
+
# rubocop:enable Security/Eval
|
97
|
+
|
98
|
+
# Parses the position name and location from input text.
|
99
|
+
# @param input [String] The input text to parse.
|
100
|
+
# @return [Array] An array containing the name and location.
|
101
|
+
def extract_text_and_parentheses_content(input)
|
102
|
+
return unless input =~ /^(.+?)\(([^)]+)\)/
|
103
|
+
|
104
|
+
text = ::Regexp.last_match(1).strip
|
105
|
+
parenthesis_content = ::Regexp.last_match(2).strip
|
106
|
+
[text, parenthesis_content]
|
107
|
+
end
|
108
|
+
|
109
|
+
# Extracts the text and whether it's required.
|
110
|
+
# @param text [String] The text to parse.
|
111
|
+
# @return [Array] An array containing the text and a required flag.
|
112
|
+
def text_and_required(text)
|
113
|
+
if text.start_with?('!')
|
114
|
+
[text.sub(/^!/, ''), true]
|
115
|
+
else
|
116
|
+
[text, false]
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Matches and validates a description and type from text.
|
121
|
+
# @param text [String] The text to parse.
|
122
|
+
# @return [MatchData] The match data from the regex.
|
123
|
+
def description_and_type(text)
|
124
|
+
match = text.match(/^(.*?)\s*\[(.*?)\]\s*(.*)$/)
|
125
|
+
raise ArgumentError, "The request body tag is not valid: #{text}" if match.nil?
|
126
|
+
|
127
|
+
match
|
128
|
+
end
|
129
|
+
|
130
|
+
# Checks if a given text refers to an ActiveRecord class.
|
131
|
+
# @param text [String] The text to check.
|
132
|
+
# @return [Boolean] True if the text refers to an ActiveRecord class, false otherwise.
|
133
|
+
def active_record_class?(text)
|
134
|
+
klass = text.constantize
|
135
|
+
klass.ancestors.include? ActiveRecord::Base
|
136
|
+
rescue StandardError
|
137
|
+
false
|
138
|
+
end
|
139
|
+
|
140
|
+
# Converts type text to a schema, checking if it's an ActiveRecord class.
|
141
|
+
# @param text [String] The type text to convert.
|
142
|
+
# @return [Array] An array containing the class, schema, and required flag.
|
143
|
+
def type_text_to_schema(text)
|
144
|
+
type_text, required = text_and_required(text)
|
145
|
+
|
146
|
+
if active_record_class?(type_text)
|
147
|
+
klass = type_text.constantize
|
148
|
+
schema = Builders::EsquemaBuilder.build_outgoing_schema(klass:)
|
149
|
+
else
|
150
|
+
schema = JsonSchemaGenerator.process_string(type_text)[:json_schema]
|
151
|
+
klass = Object
|
152
|
+
end
|
153
|
+
[klass, schema, required]
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
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,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
|
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,11 @@ module OasRails
|
|
45
46
|
end
|
46
47
|
|
47
48
|
module YARD
|
48
|
-
autoload :
|
49
|
+
autoload :RequestBodyTag, 'oas_rails/yard/request_body_tag'
|
50
|
+
autoload :RequestBodyExampleTag, 'oas_rails/yard/request_body_example_tag'
|
51
|
+
autoload :ParameterTag, 'oas_rails/yard/parameter_tag'
|
52
|
+
autoload :ResponseTag, 'oas_rails/yard/response_tag'
|
53
|
+
autoload :OasRailsFactory, 'oas_rails/yard/oas_rails_factory'
|
49
54
|
end
|
50
55
|
|
51
56
|
module Extractors
|
@@ -73,7 +78,7 @@ module OasRails
|
|
73
78
|
end
|
74
79
|
|
75
80
|
def configure_yard!
|
76
|
-
::YARD::Tags::Library.default_factory = YARD::
|
81
|
+
::YARD::Tags::Library.default_factory = YARD::OasRailsFactory
|
77
82
|
yard_tags = {
|
78
83
|
'Request body' => [:request_body, :with_request_body],
|
79
84
|
'Request body Example' => [:request_body_example, :with_request_body_example],
|
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
|
+
version: 0.5.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-
|
11
|
+
date: 2024-08-23 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,11 @@ 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/
|
133
|
+
- lib/oas_rails/yard/oas_rails_factory.rb
|
134
|
+
- lib/oas_rails/yard/parameter_tag.rb
|
135
|
+
- lib/oas_rails/yard/request_body_example_tag.rb
|
136
|
+
- lib/oas_rails/yard/request_body_tag.rb
|
137
|
+
- lib/oas_rails/yard/response_tag.rb
|
133
138
|
homepage: https://github.com/a-chacon/oas_rails
|
134
139
|
licenses:
|
135
140
|
- 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
|