oas_rails 0.4.5 → 0.5.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: b3f5da00f122856b6cce2f73b5912013477726bda80f57f39fdbe4609bc551c7
4
+ data.tar.gz: 35def82214e4c8b01755ddb2b501140f45ab653d575512ec52730d5722f7b025
5
5
  SHA512:
6
- metadata.gz: 638314dfa65ce344c4fdb8c7e725503f6556a7276aa4839c406d738a7d06b9bfd8533e68dcf720bf95573e4b75f7d91f0600528b4fd00670e2c94990a92f5a04
7
- data.tar.gz: 606103ec7b8640f5fbbcc1afcbd9c56d5671df6dbacd89e7ab1c30ead4b2480f583cb2f6bc31a6cf4a731399d77e101e1a5b4d7f8465c548da49307ad170f4d3
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**: 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,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] 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, 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] {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}
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] {user: { name: String, email: String, age: Integer}}
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 unless OasRails.config.autodiscover_responses
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(Utils.hash_to_json_schema(OasRails.config.response_body_of_default)).build
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
@@ -1,3 +1,3 @@
1
1
  module OasRails
2
- VERSION = "0.4.5"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -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,12 @@
1
+ module OasRails
2
+ module YARD
3
+ class RequestBodyExampleTag < ::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,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 ResponseTag < ::YARD::Tags::Tag
4
+ attr_accessor :schema
5
+
6
+ def initialize(tag_name, name, text, schema)
7
+ super(tag_name, text, nil, name)
8
+ @schema = schema
9
+ end
10
+ end
11
+ end
12
+ 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 :OasYARDFactory, 'oas_rails/yard/oas_yard_factory'
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::OasYARDFactory
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.5
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-22 00:00:00.000000000 Z
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/oas_yard_factory.rb
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