openapi-ruby 3.0.3 → 3.1.1
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 +0 -1
- data/lib/generators/openapi_ruby/install/templates/initializer.rb.tt +0 -3
- data/lib/openapi_ruby/adapters/minitest.rb +1 -1
- data/lib/openapi_ruby/adapters/rspec.rb +44 -7
- data/lib/openapi_ruby/components/loader.rb +19 -1
- data/lib/openapi_ruby/components/registry.rb +17 -6
- data/lib/openapi_ruby/configuration.rb +22 -17
- data/lib/openapi_ruby/core/document_builder.rb +7 -1
- data/lib/openapi_ruby/dsl/context.rb +2 -4
- data/lib/openapi_ruby/engine.rb +2 -3
- data/lib/openapi_ruby/generator/schema_writer.rb +10 -4
- data/lib/openapi_ruby/middleware/path_matcher.rb +5 -1
- data/lib/openapi_ruby/middleware/request_validation.rb +34 -4
- data/lib/openapi_ruby/middleware/schema_resolver.rb +8 -4
- data/lib/openapi_ruby/version.rb +1 -1
- data/lib/tasks/openapi_ruby.rake +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 953f7f56ecdec3d8ee0f1f1261e49328e53d0aab5041b43f47a6e1138a2e880f
|
|
4
|
+
data.tar.gz: bec8a51340348604307f17791824d9327e7ceee7b58a8f35533ef7036c12156f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c62b49da036fe1dbb27b3f7ce2937a044427ea89ded288bdab1c33dab6fc041e31c89ecb75fd2e2419731cd0f803428baed9ed5152cdf01d7cd77b42b004642e
|
|
7
|
+
data.tar.gz: 5e2bbbcf02c286120207fc06a9f86c66b2618c9766a78b4b94c215641d0c2a5173aa4c45705e4fd2e6ed909271c63000a65182d159f22f3d716a4742dcbc6bae
|
data/README.md
CHANGED
|
@@ -63,7 +63,6 @@ OpenapiRuby.configure do |config|
|
|
|
63
63
|
config.camelize_keys = true
|
|
64
64
|
config.schema_output_dir = "swagger"
|
|
65
65
|
config.schema_output_format = :yaml
|
|
66
|
-
config.validate_responses_in_tests = true
|
|
67
66
|
|
|
68
67
|
# Runtime middleware (disabled by default)
|
|
69
68
|
config.request_validation = :disabled # :enabled, :disabled, :warn_only
|
|
@@ -20,9 +20,6 @@ OpenapiRuby.configure do |config|
|
|
|
20
20
|
config.schema_output_dir = "swagger"
|
|
21
21
|
config.schema_output_format = :yaml
|
|
22
22
|
|
|
23
|
-
# Validate response bodies in tests against defined schemas
|
|
24
|
-
config.validate_responses_in_tests = true
|
|
25
|
-
|
|
26
23
|
# Runtime request/response validation middleware
|
|
27
24
|
# Options: :disabled, :enabled, :warn_only
|
|
28
25
|
config.request_validation = :disabled
|
|
@@ -87,7 +87,7 @@ module OpenapiRuby
|
|
|
87
87
|
assert_equal expected_status, response.status,
|
|
88
88
|
"Expected status #{expected_status}, got #{response.status}\nResponse body: #{response.body}"
|
|
89
89
|
|
|
90
|
-
if
|
|
90
|
+
if response_ctx.schema_definition
|
|
91
91
|
validator = Testing::ResponseValidator.new
|
|
92
92
|
body_data = parse_response_body
|
|
93
93
|
errors = validator.validate(
|
|
@@ -26,7 +26,6 @@ module OpenapiRuby
|
|
|
26
26
|
define_method(method) do |summary = nil, &block|
|
|
27
27
|
path_ctx = metadata[:openapi_path_context]
|
|
28
28
|
op_context = DSL::OperationContext.new(method, summary)
|
|
29
|
-
path_ctx.path_parameters.each { |p| op_context.parameter(p) }
|
|
30
29
|
path_ctx.operations[method.to_s] = op_context
|
|
31
30
|
|
|
32
31
|
describe "#{method.to_s.upcase} #{path_ctx.path_template}" do
|
|
@@ -118,6 +117,7 @@ module OpenapiRuby
|
|
|
118
117
|
# Merge individual parameter let values
|
|
119
118
|
operation&.parameters&.each do |param|
|
|
120
119
|
name = param["name"]
|
|
120
|
+
next unless name
|
|
121
121
|
val = resolve_let(name.to_sym)
|
|
122
122
|
next if val.nil?
|
|
123
123
|
|
|
@@ -144,6 +144,12 @@ module OpenapiRuby
|
|
|
144
144
|
accept = resolve_let(:Accept)
|
|
145
145
|
headers["Accept"] = accept || "application/json"
|
|
146
146
|
|
|
147
|
+
# Always append query params to the URL so the middleware sees them
|
|
148
|
+
# (Rails sends params as request body for non-GET methods).
|
|
149
|
+
if params.any?
|
|
150
|
+
path = "#{path}?#{Rack::Utils.build_nested_query(params)}"
|
|
151
|
+
end
|
|
152
|
+
|
|
147
153
|
if body
|
|
148
154
|
content_type = operation&.request_body_definition&.dig("content")&.keys&.first || "application/json"
|
|
149
155
|
request_args = if content_type.include?("form-data") || content_type.include?("x-www-form-urlencoded")
|
|
@@ -154,13 +160,8 @@ module OpenapiRuby
|
|
|
154
160
|
headers: headers.merge("Content-Type" => content_type)
|
|
155
161
|
}
|
|
156
162
|
end
|
|
157
|
-
# Append query params to path when body is present
|
|
158
|
-
if params.any?
|
|
159
|
-
query_string = params.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join("&")
|
|
160
|
-
path = "#{path}?#{query_string}"
|
|
161
|
-
end
|
|
162
163
|
else
|
|
163
|
-
request_args = {
|
|
164
|
+
request_args = {headers: headers}
|
|
164
165
|
end
|
|
165
166
|
|
|
166
167
|
send(method.to_sym, path, **request_args)
|
|
@@ -177,6 +178,19 @@ module OpenapiRuby
|
|
|
177
178
|
"Expected status #{expected_status}, got #{actual_status}\n" \
|
|
178
179
|
"Response body: #{response.body}"
|
|
179
180
|
end
|
|
181
|
+
|
|
182
|
+
if response_ctx.schema_definition
|
|
183
|
+
schema_name = find_in_metadata(metadata, :openapi_schema_name)
|
|
184
|
+
validator = Testing::ResponseValidator.new(OpenapiRuby::Adapters::RSpec.validation_document_for(schema_name))
|
|
185
|
+
errors = validator.validate(
|
|
186
|
+
response_body: parsed_response_body,
|
|
187
|
+
status_code: response.status,
|
|
188
|
+
response_context: response_ctx
|
|
189
|
+
)
|
|
190
|
+
unless errors.empty?
|
|
191
|
+
raise "Response body validation failed:\n#{errors.join("\n")}\nResponse body: #{response.body}"
|
|
192
|
+
end
|
|
193
|
+
end
|
|
180
194
|
end
|
|
181
195
|
|
|
182
196
|
private
|
|
@@ -269,6 +283,29 @@ module OpenapiRuby
|
|
|
269
283
|
end
|
|
270
284
|
end
|
|
271
285
|
|
|
286
|
+
# Build the OpenAPI document hash for a given schema name and cache it.
|
|
287
|
+
# Used by response body validation so $ref schemas can be resolved.
|
|
288
|
+
def self.validation_document_for(schema_name)
|
|
289
|
+
return nil unless schema_name
|
|
290
|
+
|
|
291
|
+
key = schema_name.to_sym
|
|
292
|
+
@validation_documents ||= {}
|
|
293
|
+
@validation_documents[key] ||= begin
|
|
294
|
+
config = OpenapiRuby.configuration
|
|
295
|
+
schema_config = config.schemas[key] || config.schemas[schema_name.to_s]
|
|
296
|
+
return nil unless schema_config
|
|
297
|
+
|
|
298
|
+
builder = OpenapiRuby::Core::DocumentBuilder.new(schema_config)
|
|
299
|
+
OpenapiRuby::DSL::MetadataStore.contexts_for(schema_name).each do |context|
|
|
300
|
+
builder.add_path(context.path_template, context.to_openapi)
|
|
301
|
+
end
|
|
302
|
+
scope = schema_config[:component_scope]
|
|
303
|
+
loader = OpenapiRuby::Components::Loader.new(scope: scope)
|
|
304
|
+
builder.merge_components(loader.to_openapi_hash)
|
|
305
|
+
builder.build.data
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
|
|
272
309
|
def self.install!
|
|
273
310
|
::RSpec.configure do |config|
|
|
274
311
|
config.extend ExampleGroupHelpers, type: :openapi
|
|
@@ -13,10 +13,12 @@ module OpenapiRuby
|
|
|
13
13
|
def load!
|
|
14
14
|
define_namespace_modules!
|
|
15
15
|
load_component_files!
|
|
16
|
+
@@loaded = true # rubocop:disable Style/ClassVars
|
|
16
17
|
self
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
def to_openapi_hash
|
|
21
|
+
ensure_loaded!
|
|
20
22
|
Registry.instance.to_openapi_hash(scope: @scope)
|
|
21
23
|
end
|
|
22
24
|
|
|
@@ -158,6 +160,11 @@ module OpenapiRuby
|
|
|
158
160
|
if inferred_scope == :shared
|
|
159
161
|
klass._component_scopes = []
|
|
160
162
|
klass._component_scopes_explicitly_set = true
|
|
163
|
+
elsif inferred_scope.is_a?(Array)
|
|
164
|
+
Registry.instance.unregister(klass)
|
|
165
|
+
klass._component_scopes = inferred_scope
|
|
166
|
+
klass._component_scopes_explicitly_set = true
|
|
167
|
+
Registry.instance.register(klass)
|
|
161
168
|
else
|
|
162
169
|
Registry.instance.unregister(klass)
|
|
163
170
|
klass._component_scopes = [inferred_scope]
|
|
@@ -181,14 +188,25 @@ module OpenapiRuby
|
|
|
181
188
|
|
|
182
189
|
def infer_scope(relative_path, scope_paths)
|
|
183
190
|
scope_paths.sort_by { |prefix, _| -prefix.length }.each do |prefix, scope|
|
|
184
|
-
|
|
191
|
+
if relative_path.start_with?("#{prefix}/")
|
|
192
|
+
return scope.is_a?(Array) ? scope.map(&:to_sym) : scope&.to_sym
|
|
193
|
+
end
|
|
185
194
|
end
|
|
186
195
|
nil
|
|
187
196
|
end
|
|
188
197
|
|
|
189
198
|
def filter_type(type)
|
|
199
|
+
ensure_loaded!
|
|
190
200
|
to_openapi_hash[type.to_s] || {}
|
|
191
201
|
end
|
|
202
|
+
|
|
203
|
+
@@loaded = false # rubocop:disable Style/ClassVars
|
|
204
|
+
|
|
205
|
+
def ensure_loaded!
|
|
206
|
+
return if @@loaded
|
|
207
|
+
|
|
208
|
+
load!
|
|
209
|
+
end
|
|
192
210
|
end
|
|
193
211
|
end
|
|
194
212
|
end
|
|
@@ -89,16 +89,18 @@ module OpenapiRuby
|
|
|
89
89
|
|
|
90
90
|
def to_openapi_hash(scope: nil)
|
|
91
91
|
result = {}
|
|
92
|
+
# Track which components are scope-specific vs multi-scope/shared
|
|
93
|
+
# so scope-specific ones take precedence on name collisions.
|
|
94
|
+
specificity = {}
|
|
95
|
+
|
|
92
96
|
@components.each do |type, components|
|
|
93
97
|
type_key = type.to_s
|
|
94
98
|
result[type_key] = {}
|
|
99
|
+
specificity[type_key] = {}
|
|
100
|
+
|
|
95
101
|
components.each_value do |klass|
|
|
96
102
|
next if klass._schema_hidden
|
|
97
|
-
|
|
98
|
-
# - Components with matching scope: included
|
|
99
|
-
# - Components explicitly marked as shared (empty scopes + explicitly_set): included
|
|
100
|
-
# - Components with non-matching scope: excluded
|
|
101
|
-
# - Components with no scope assigned (empty scopes + NOT explicitly_set): excluded
|
|
103
|
+
|
|
102
104
|
if scope
|
|
103
105
|
if klass._component_scopes.empty?
|
|
104
106
|
next unless klass._component_scopes_explicitly_set
|
|
@@ -107,7 +109,16 @@ module OpenapiRuby
|
|
|
107
109
|
end
|
|
108
110
|
end
|
|
109
111
|
|
|
110
|
-
|
|
112
|
+
name = klass.component_name
|
|
113
|
+
is_specific = klass._component_scopes.length == 1 && klass._component_scopes.include?(scope)
|
|
114
|
+
|
|
115
|
+
# Scope-specific components take precedence over shared/multi-scope
|
|
116
|
+
if specificity[type_key][name] && !is_specific
|
|
117
|
+
next
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
result[type_key][name] = klass.to_openapi
|
|
121
|
+
specificity[type_key][name] = is_specific
|
|
111
122
|
end
|
|
112
123
|
result.delete(type_key) if result[type_key].empty?
|
|
113
124
|
end
|
|
@@ -9,46 +9,51 @@ module OpenapiRuby
|
|
|
9
9
|
# Components
|
|
10
10
|
attr_accessor :component_paths
|
|
11
11
|
attr_accessor :component_scope_paths
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
attr_accessor :
|
|
12
|
+
|
|
13
|
+
# Output / formatting
|
|
14
|
+
attr_accessor :camelize_keys, :schema_output_format, :schema_output_dir
|
|
15
15
|
attr_accessor :auto_validation_error_response
|
|
16
16
|
attr_accessor :validation_error_schema
|
|
17
17
|
|
|
18
18
|
# Middleware (runtime validation)
|
|
19
|
-
attr_accessor :request_validation
|
|
19
|
+
attr_accessor :request_validation, :response_validation, :coerce_params
|
|
20
20
|
|
|
21
|
-
#
|
|
22
|
-
|
|
21
|
+
# OpenAPI meta-schema validation of generated specs and middleware-loaded
|
|
22
|
+
# documents. One of :disabled, :enabled (raise on errors), :warn_only
|
|
23
|
+
# (default, log warnings). Boolean values are accepted for backwards
|
|
24
|
+
# compatibility: `true` → :warn_only, `false` → :disabled.
|
|
25
|
+
attr_reader :strict_reference_validation
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
def strict_reference_validation=(value)
|
|
28
|
+
@strict_reference_validation = case value
|
|
29
|
+
when true, :warn_only then :warn_only
|
|
30
|
+
when false, :disabled then :disabled
|
|
31
|
+
when :enabled then :enabled
|
|
32
|
+
else
|
|
33
|
+
raise ConfigurationError,
|
|
34
|
+
"strict_reference_validation must be :disabled, :enabled, :warn_only, or a boolean"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
26
37
|
|
|
27
|
-
#
|
|
28
|
-
attr_accessor :
|
|
38
|
+
# UI (optional)
|
|
39
|
+
attr_accessor :ui_enabled, :ui_path, :ui_config
|
|
29
40
|
|
|
30
41
|
def initialize
|
|
31
42
|
@schemas = {}
|
|
32
43
|
@component_paths = ["app/api_components"]
|
|
33
44
|
@component_scope_paths = {}
|
|
34
45
|
@camelize_keys = true
|
|
35
|
-
@key_transform = nil
|
|
36
46
|
@request_validation = :disabled
|
|
37
47
|
@response_validation = :disabled
|
|
38
|
-
@strict_query_params = false
|
|
39
48
|
@coerce_params = true
|
|
40
|
-
@error_handler = nil
|
|
41
49
|
@schema_output_dir = "swagger"
|
|
42
50
|
@schema_output_format = :yaml
|
|
43
|
-
@validate_responses_in_tests = true
|
|
44
51
|
@ui_enabled = false
|
|
45
52
|
@ui_path = "/api-docs"
|
|
46
53
|
@ui_config = {}
|
|
47
|
-
@strict_reference_validation =
|
|
54
|
+
@strict_reference_validation = :warn_only
|
|
48
55
|
@auto_validation_error_response = true
|
|
49
56
|
@validation_error_schema = nil
|
|
50
|
-
@coverage_enabled = false
|
|
51
|
-
@coverage_report_path = "tmp/openapi_coverage.json"
|
|
52
57
|
end
|
|
53
58
|
|
|
54
59
|
def validate!
|
|
@@ -60,12 +60,18 @@ module OpenapiRuby
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
# Add 400 to
|
|
63
|
+
# Add 400 to operations that have parameters or a request body
|
|
64
64
|
@paths.each_value do |path_item|
|
|
65
|
+
path_params = path_item["parameters"]
|
|
66
|
+
|
|
65
67
|
path_item.each do |key, operation|
|
|
66
68
|
next unless operation.is_a?(Hash) && operation.key?("responses")
|
|
67
69
|
next if key == "parameters"
|
|
68
70
|
|
|
71
|
+
has_params = operation.key?("parameters") || path_params
|
|
72
|
+
has_body = operation.key?("requestBody")
|
|
73
|
+
next unless has_params || has_body
|
|
74
|
+
|
|
69
75
|
operation["responses"]["400"] ||= {"$ref" => "#/components/responses/SchemaValidationError"}
|
|
70
76
|
end
|
|
71
77
|
end
|
|
@@ -23,8 +23,6 @@ module OpenapiRuby
|
|
|
23
23
|
HTTP_METHODS.each do |method|
|
|
24
24
|
define_method(method) do |summary = nil, &block|
|
|
25
25
|
op = OperationContext.new(method, summary)
|
|
26
|
-
# Copy path-level parameters to operation
|
|
27
|
-
@path_parameters.each { |p| op.parameter(p) }
|
|
28
26
|
op.instance_eval(&block) if block
|
|
29
27
|
@operations[method.to_s] = op
|
|
30
28
|
op
|
|
@@ -34,8 +32,8 @@ module OpenapiRuby
|
|
|
34
32
|
def to_openapi
|
|
35
33
|
result = {}
|
|
36
34
|
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
result["parameters"] = @path_parameters if @path_parameters.any?
|
|
36
|
+
|
|
39
37
|
@operations.each do |verb, op|
|
|
40
38
|
result[verb] = op.to_openapi
|
|
41
39
|
end
|
data/lib/openapi_ruby/engine.rb
CHANGED
|
@@ -7,6 +7,8 @@ module OpenapiRuby
|
|
|
7
7
|
initializer "openapi_ruby.middleware" do |app|
|
|
8
8
|
config = OpenapiRuby.configuration
|
|
9
9
|
|
|
10
|
+
next if ENV["OPENAPI_RUBY_GENERATING"]
|
|
11
|
+
|
|
10
12
|
if config.request_validation != :disabled || config.response_validation != :disabled
|
|
11
13
|
config.schemas.each do |name, schema_config|
|
|
12
14
|
schema_path = resolve_schema_path(config, name)
|
|
@@ -36,9 +38,6 @@ module OpenapiRuby
|
|
|
36
38
|
end
|
|
37
39
|
end
|
|
38
40
|
|
|
39
|
-
# Components are loaded on demand via Components::Loader (e.g. in test helpers),
|
|
40
|
-
# not at boot time — avoids cross-file dependency ordering issues.
|
|
41
|
-
|
|
42
41
|
private
|
|
43
42
|
|
|
44
43
|
def resolve_schema_path(config, schema_name)
|
|
@@ -23,7 +23,7 @@ module OpenapiRuby
|
|
|
23
23
|
|
|
24
24
|
def write!
|
|
25
25
|
document = build_document
|
|
26
|
-
validate_document!(document)
|
|
26
|
+
validate_document!(document) unless OpenapiRuby.configuration.strict_reference_validation == :disabled
|
|
27
27
|
output_path = File.join(output_dir, filename)
|
|
28
28
|
FileUtils.mkdir_p(output_dir)
|
|
29
29
|
File.write(output_path, format_output(document))
|
|
@@ -38,9 +38,10 @@ module OpenapiRuby
|
|
|
38
38
|
builder.add_path(context.path_template, context.to_openapi)
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
# Merge components from registry
|
|
41
|
+
# Merge components from registry (Loader ensures files are loaded)
|
|
42
42
|
scope = @schema_config[:component_scope]
|
|
43
|
-
|
|
43
|
+
loader = Components::Loader.new(scope: scope)
|
|
44
|
+
components = loader.to_openapi_hash
|
|
44
45
|
builder.merge_components(components)
|
|
45
46
|
|
|
46
47
|
builder.build
|
|
@@ -62,7 +63,12 @@ module OpenapiRuby
|
|
|
62
63
|
return if errors.empty?
|
|
63
64
|
|
|
64
65
|
error_messages = errors.first(10).map { |e| e["error"] || e.to_s }
|
|
65
|
-
|
|
66
|
+
message = "[openapi_ruby] Generated schema '#{@schema_name}' has validation errors:\n#{error_messages.join("\n")}"
|
|
67
|
+
if OpenapiRuby.configuration.strict_reference_validation == :enabled
|
|
68
|
+
raise OpenapiRuby::ConfigurationError, message
|
|
69
|
+
else
|
|
70
|
+
warn message
|
|
71
|
+
end
|
|
66
72
|
end
|
|
67
73
|
|
|
68
74
|
def format_output(document)
|
|
@@ -21,7 +21,11 @@ module OpenapiRuby
|
|
|
21
21
|
private
|
|
22
22
|
|
|
23
23
|
def build_matchers(templates)
|
|
24
|
-
templates
|
|
24
|
+
# Sort templates so that static paths come before parameterized ones.
|
|
25
|
+
# This ensures `/admin_users/me` matches before `/admin_users/{id}`.
|
|
26
|
+
sorted = templates.sort_by { |t| [t.count("{"), t] }
|
|
27
|
+
|
|
28
|
+
sorted.map do |template|
|
|
25
29
|
pattern = Regexp.new("\\A" + template.gsub(/\{(\w+)\}/) { "(?<#{::Regexp.last_match(1)}>[^/]+)" } + "\\z")
|
|
26
30
|
[template, pattern]
|
|
27
31
|
end
|
|
@@ -139,8 +139,8 @@ module OpenapiRuby
|
|
|
139
139
|
errors = []
|
|
140
140
|
# Coerce string values for validation based on schema type
|
|
141
141
|
coerced = coerce_for_validation(value, schema)
|
|
142
|
-
|
|
143
|
-
|
|
142
|
+
schema_validator = resolve_schema(schema)
|
|
143
|
+
schema_validator.validate(coerced).each do |err|
|
|
144
144
|
msg = err["error"] || err["type"] || "validation failed"
|
|
145
145
|
errors << "Invalid #{context}: #{msg}"
|
|
146
146
|
end
|
|
@@ -170,9 +170,26 @@ module OpenapiRuby
|
|
|
170
170
|
end
|
|
171
171
|
|
|
172
172
|
def coerce_for_validation(value, schema)
|
|
173
|
-
|
|
173
|
+
resolved = resolve_schema_definition(schema)
|
|
174
|
+
|
|
175
|
+
if value.is_a?(Hash) && resolved.is_a?(Hash)
|
|
176
|
+
properties = resolved["properties"] || {}
|
|
177
|
+
value.each_with_object({}) do |(k, v), coerced|
|
|
178
|
+
prop_schema = properties[k] || properties[k.to_s]
|
|
179
|
+
coerced[k] = prop_schema ? coerce_for_validation(v, prop_schema) : v
|
|
180
|
+
end
|
|
181
|
+
elsif value.is_a?(String)
|
|
182
|
+
coerce_string(value, resolved)
|
|
183
|
+
else
|
|
184
|
+
value
|
|
185
|
+
end
|
|
186
|
+
rescue ArgumentError, TypeError
|
|
187
|
+
value
|
|
188
|
+
end
|
|
174
189
|
|
|
175
|
-
|
|
190
|
+
def coerce_string(value, schema)
|
|
191
|
+
type = schema.is_a?(Hash) ? schema["type"] : nil
|
|
192
|
+
case type
|
|
176
193
|
when "integer"
|
|
177
194
|
Integer(value)
|
|
178
195
|
when "number"
|
|
@@ -190,6 +207,19 @@ module OpenapiRuby
|
|
|
190
207
|
value
|
|
191
208
|
end
|
|
192
209
|
|
|
210
|
+
def resolve_schema_definition(schema)
|
|
211
|
+
return schema unless schema.is_a?(Hash) && schema["$ref"]
|
|
212
|
+
|
|
213
|
+
ref_path = schema["$ref"].sub("#/", "").split("/")
|
|
214
|
+
ref_path.reduce(document) { |doc, key| doc&.dig(key) } || schema
|
|
215
|
+
rescue
|
|
216
|
+
schema
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def document
|
|
220
|
+
@resolver.respond_to?(:document) ? @resolver.document : {}
|
|
221
|
+
end
|
|
222
|
+
|
|
193
223
|
def read_request_body(request)
|
|
194
224
|
return nil unless request.body
|
|
195
225
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module OpenapiRuby
|
|
4
4
|
module Middleware
|
|
5
5
|
class SchemaResolver
|
|
6
|
-
def initialize(spec_path: nil, document: nil, strict_reference_validation:
|
|
6
|
+
def initialize(spec_path: nil, document: nil, strict_reference_validation: :warn_only)
|
|
7
7
|
@spec_path = spec_path
|
|
8
8
|
@document = document
|
|
9
9
|
@strict_reference_validation = strict_reference_validation
|
|
@@ -41,15 +41,19 @@ module OpenapiRuby
|
|
|
41
41
|
private
|
|
42
42
|
|
|
43
43
|
def validate_document!(doc)
|
|
44
|
-
return
|
|
44
|
+
return if @strict_reference_validation == :disabled
|
|
45
45
|
|
|
46
46
|
schemer = JSONSchemer.openapi(doc)
|
|
47
47
|
errors = schemer.validate.to_a
|
|
48
48
|
return if errors.empty?
|
|
49
49
|
|
|
50
50
|
error_messages = errors.first(5).map { |e| e["error"] || e.to_s }
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
message = "OpenAPI document validation failed:\n#{error_messages.join("\n")}"
|
|
52
|
+
if @strict_reference_validation == :enabled
|
|
53
|
+
raise OpenapiRuby::ConfigurationError, message
|
|
54
|
+
else
|
|
55
|
+
warn message
|
|
56
|
+
end
|
|
53
57
|
end
|
|
54
58
|
|
|
55
59
|
def load_document
|
data/lib/openapi_ruby/version.rb
CHANGED
data/lib/tasks/openapi_ruby.rake
CHANGED
|
@@ -8,7 +8,7 @@ namespace :openapi_ruby do
|
|
|
8
8
|
|
|
9
9
|
# Spawn a subprocess so RAILS_ENV defaults to "test" cleanly,
|
|
10
10
|
# just like rswag did with RSpec::Core::RakeTask.
|
|
11
|
-
env = {"RAILS_ENV" => ENV.fetch("RAILS_ENV", "test")}
|
|
11
|
+
env = {"RAILS_ENV" => ENV.fetch("RAILS_ENV", "test"), "OPENAPI_RUBY_GENERATING" => "true"}
|
|
12
12
|
script = generate_script(framework, pattern)
|
|
13
13
|
command = "bundle exec ruby -e #{Shellwords.escape(script)}"
|
|
14
14
|
|