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 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