openapi_first 1.0.0.beta1 → 1.0.0.beta4
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/.github/workflows/ruby.yml +8 -20
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +23 -0
- data/Gemfile +4 -1
- data/Gemfile.lock +39 -51
- data/README.md +27 -25
- data/benchmarks/Gemfile.lock +28 -33
- data/benchmarks/apps/openapi_first_with_hanami_api.ru +1 -2
- data/benchmarks/apps/openapi_first_with_plain_rack.ru +32 -0
- data/benchmarks/apps/openapi_first_with_response_validation.ru +14 -11
- data/benchmarks/apps/openapi_first_with_sinatra.ru +29 -0
- data/lib/openapi_first/body_parser_middleware.rb +1 -1
- data/lib/openapi_first/config.rb +19 -0
- data/lib/openapi_first/default_error_response.rb +47 -0
- data/lib/openapi_first/definition.rb +8 -1
- data/lib/openapi_first/error_response.rb +31 -0
- data/lib/openapi_first/errors.rb +3 -40
- data/lib/openapi_first/operation.rb +33 -14
- data/lib/openapi_first/operation_schemas.rb +52 -0
- data/lib/openapi_first/plugins.rb +17 -0
- data/lib/openapi_first/request_body_validator.rb +41 -0
- data/lib/openapi_first/request_validation.rb +66 -84
- data/lib/openapi_first/response_validation.rb +38 -7
- data/lib/openapi_first/response_validator.rb +1 -1
- data/lib/openapi_first/router.rb +15 -14
- data/lib/openapi_first/schema_validation.rb +22 -21
- data/lib/openapi_first/string_keyed_hash.rb +20 -0
- data/lib/openapi_first/validation_result.rb +15 -0
- data/lib/openapi_first/version.rb +1 -1
- data/lib/openapi_first.rb +30 -21
- data/openapi_first.gemspec +4 -12
- metadata +20 -117
- data/benchmarks/apps/openapi_first.ru +0 -22
- data/lib/openapi_first/utils.rb +0 -35
- data/lib/openapi_first/validation_format.rb +0 -55
- /data/benchmarks/apps/{committee.ru → committee_with_hanami_api.ru} +0 -0
data/lib/openapi_first/router.rb
CHANGED
@@ -7,6 +7,9 @@ require_relative 'body_parser_middleware'
|
|
7
7
|
|
8
8
|
module OpenapiFirst
|
9
9
|
class Router
|
10
|
+
# The unconverted path parameters before they are converted to the types defined in the API description
|
11
|
+
RAW_PATH_PARAMS = 'openapi.raw_path_params'
|
12
|
+
|
10
13
|
def initialize(
|
11
14
|
app,
|
12
15
|
options
|
@@ -63,17 +66,16 @@ module OpenapiFirst
|
|
63
66
|
env[Rack::PATH_INFO] = env.delete(ORIGINAL_PATH) if env[ORIGINAL_PATH]
|
64
67
|
end
|
65
68
|
|
66
|
-
def handle_body_parsing_error(
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
).finish
|
69
|
+
def handle_body_parsing_error(_exception)
|
70
|
+
message = 'Failed to parse body as application/json'
|
71
|
+
raise RequestInvalidError, message if @raise
|
72
|
+
|
73
|
+
error = {
|
74
|
+
status: 400,
|
75
|
+
title: message
|
76
|
+
}
|
77
|
+
|
78
|
+
ErrorResponse::Default.new(**error).finish
|
77
79
|
end
|
78
80
|
|
79
81
|
def build_router(operations)
|
@@ -89,7 +91,7 @@ module OpenapiFirst
|
|
89
91
|
end
|
90
92
|
raise_error = @raise
|
91
93
|
Rack::Builder.app do
|
92
|
-
use
|
94
|
+
use(BodyParserMiddleware, raise_error:)
|
93
95
|
run router
|
94
96
|
end
|
95
97
|
end
|
@@ -99,8 +101,7 @@ module OpenapiFirst
|
|
99
101
|
env[OPERATION] = operation
|
100
102
|
path_info = env.delete(ORIGINAL_PATH)
|
101
103
|
env[REQUEST_BODY] = env.delete(ROUTER_PARSED_BODY) if env.key?(ROUTER_PARSED_BODY)
|
102
|
-
|
103
|
-
env[PARAMS] = OpenapiParameters::Path.new(operation.path_parameters).unpack(route_params)
|
104
|
+
env[RAW_PATH_PARAMS] = env['router.params']
|
104
105
|
env[Rack::PATH_INFO] = path_info
|
105
106
|
@app.call(env)
|
106
107
|
end
|
@@ -1,46 +1,47 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'json_schemer'
|
4
|
+
require_relative 'validation_result'
|
4
5
|
|
5
6
|
module OpenapiFirst
|
6
7
|
class SchemaValidation
|
7
8
|
attr_reader :raw_schema
|
8
9
|
|
9
|
-
|
10
|
+
SCHEMAS = {
|
11
|
+
'3.1' => 'https://spec.openapis.org/oas/3.1/dialect/base',
|
12
|
+
'3.0' => 'json-schemer://openapi30/schema'
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
def initialize(schema, openapi_version:, write: true)
|
10
16
|
@raw_schema = schema
|
11
|
-
custom_keywords = {}
|
12
|
-
custom_keywords['writeOnly'] = proc { |data| !data } unless write
|
13
|
-
custom_keywords['readOnly'] = proc { |data| !data } if write
|
14
17
|
@schemer = JSONSchemer.schema(
|
15
18
|
schema,
|
16
|
-
|
19
|
+
access_mode: write ? 'write' : 'read',
|
20
|
+
meta_schema: SCHEMAS.fetch(openapi_version),
|
17
21
|
insert_property_defaults: true,
|
18
|
-
|
19
|
-
|
20
|
-
binary_format(data, property, property_schema, parent)
|
21
|
-
end
|
22
|
+
output_format: 'detailed',
|
23
|
+
before_property_validation: method(:before_property_validation)
|
22
24
|
)
|
23
25
|
end
|
24
26
|
|
25
|
-
def validate(
|
26
|
-
|
27
|
+
def validate(data)
|
28
|
+
ValidationResult.new(
|
29
|
+
output: @schemer.validate(data),
|
30
|
+
schema: raw_schema,
|
31
|
+
data:
|
32
|
+
)
|
27
33
|
end
|
28
34
|
|
29
35
|
private
|
30
36
|
|
31
|
-
def
|
32
|
-
|
33
|
-
|
34
|
-
property_schema['type'] = 'object'
|
35
|
-
property_schema.delete('format')
|
36
|
-
data[property].transform_keys!(&:to_s)
|
37
|
+
def before_property_validation(data, property, property_schema, parent)
|
38
|
+
binary_format(data, property, property_schema, parent)
|
37
39
|
end
|
38
40
|
|
39
|
-
def
|
40
|
-
return unless property_schema.is_a?(Hash) && property_schema['
|
41
|
+
def binary_format(data, property, property_schema, _parent)
|
42
|
+
return unless property_schema.is_a?(Hash) && property_schema['format'] == 'binary'
|
41
43
|
|
42
|
-
|
43
|
-
property_schema.delete('nullable')
|
44
|
+
data[property] = data[property][:tempfile].read
|
44
45
|
end
|
45
46
|
end
|
46
47
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OpenapiFirst
|
4
|
+
class StringKeyedHash
|
5
|
+
extend Forwardable
|
6
|
+
def_delegators :@orig, :empty?
|
7
|
+
|
8
|
+
def initialize(original)
|
9
|
+
@orig = original
|
10
|
+
end
|
11
|
+
|
12
|
+
def key?(key)
|
13
|
+
@orig.key?(key.to_sym)
|
14
|
+
end
|
15
|
+
|
16
|
+
def [](key)
|
17
|
+
@orig[key.to_sym]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OpenapiFirst
|
4
|
+
ValidationResult = Struct.new(:output, :schema, :data, keyword_init: true) do
|
5
|
+
def valid? = output['valid']
|
6
|
+
def error? = !output['valid']
|
7
|
+
|
8
|
+
# Returns a message that is used in exception messages.
|
9
|
+
def message
|
10
|
+
return if valid?
|
11
|
+
|
12
|
+
(output['errors']&.map { |e| e['error'] }&.join('. ') || output['error'])&.concat('.')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/openapi_first.rb
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
require 'yaml'
|
4
4
|
require 'json_refs'
|
5
|
+
require_relative 'openapi_first/config'
|
6
|
+
require_relative 'openapi_first/plugins'
|
5
7
|
require_relative 'openapi_first/definition'
|
6
8
|
require_relative 'openapi_first/version'
|
7
9
|
require_relative 'openapi_first/errors'
|
@@ -9,15 +11,40 @@ require_relative 'openapi_first/router'
|
|
9
11
|
require_relative 'openapi_first/request_validation'
|
10
12
|
require_relative 'openapi_first/response_validator'
|
11
13
|
require_relative 'openapi_first/response_validation'
|
14
|
+
require_relative 'openapi_first/default_error_response'
|
12
15
|
|
13
16
|
module OpenapiFirst
|
17
|
+
# The OpenAPI operation for the current request
|
14
18
|
OPERATION = 'openapi.operation'
|
19
|
+
|
20
|
+
# Merged parsed path and query parameters
|
15
21
|
PARAMS = 'openapi.params'
|
22
|
+
|
23
|
+
# Parsed query parameters
|
24
|
+
QUERY_PARAMS = 'openapi.query'
|
25
|
+
|
26
|
+
# Parsed path parameters
|
27
|
+
PATH_PARAMS = 'openapi.path_params'
|
28
|
+
|
29
|
+
# Parsed header parameters, except for Content-Type, Accept and Authorization
|
30
|
+
HEADER_PARAMS = 'openapi.headers'
|
31
|
+
|
32
|
+
# Parsed cookie parameter values
|
33
|
+
COOKIE_PARAMS = 'openapi.cookies'
|
34
|
+
|
35
|
+
# The parsed request body
|
16
36
|
REQUEST_BODY = 'openapi.parsed_request_body'
|
17
|
-
HANDLER = 'openapi_first.handler'
|
18
37
|
|
19
|
-
|
20
|
-
|
38
|
+
class << self
|
39
|
+
# Throws an error in the middle of the request validation to stop validation and send a response.
|
40
|
+
def error!(status, location = nil, title: nil, validation_result: nil)
|
41
|
+
throw :error, {
|
42
|
+
status:,
|
43
|
+
location:,
|
44
|
+
title: title || validation_result&.output&.fetch('error') || Rack::Utils::HTTP_STATUS_CODES[status],
|
45
|
+
validation_result:
|
46
|
+
}
|
47
|
+
end
|
21
48
|
end
|
22
49
|
|
23
50
|
def self.load(spec_path, only: nil)
|
@@ -28,22 +55,4 @@ module OpenapiFirst
|
|
28
55
|
resolved['paths'].filter!(&->(key, _) { only.call(key) }) if only
|
29
56
|
Definition.new(resolved, spec_path)
|
30
57
|
end
|
31
|
-
|
32
|
-
def self.app(
|
33
|
-
spec,
|
34
|
-
namespace:,
|
35
|
-
router_raise_error: false,
|
36
|
-
request_validation_raise_error: false,
|
37
|
-
response_validation: false
|
38
|
-
)
|
39
|
-
spec = OpenapiFirst.load(spec) unless spec.is_a?(Definition)
|
40
|
-
App.new(
|
41
|
-
nil,
|
42
|
-
spec,
|
43
|
-
namespace: namespace,
|
44
|
-
router_raise_error: router_raise_error,
|
45
|
-
request_validation_raise_error: request_validation_raise_error,
|
46
|
-
response_validation: response_validation
|
47
|
-
)
|
48
|
-
end
|
49
58
|
end
|
data/openapi_first.gemspec
CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
11
11
|
spec.email = ['andreas.haller@posteo.de']
|
12
12
|
spec.licenses = ['MIT']
|
13
13
|
|
14
|
-
spec.summary = 'Implement REST APIs based on OpenApi.'
|
14
|
+
spec.summary = 'Implement REST APIs based on OpenApi 3.x'
|
15
15
|
spec.homepage = 'https://github.com/ahx/openapi_first'
|
16
16
|
|
17
17
|
if spec.respond_to?(:metadata)
|
@@ -32,22 +32,14 @@ Gem::Specification.new do |spec|
|
|
32
32
|
spec.bindir = 'exe'
|
33
33
|
spec.require_paths = ['lib']
|
34
34
|
|
35
|
-
spec.required_ruby_version = '>= 3.
|
35
|
+
spec.required_ruby_version = '>= 3.1.1'
|
36
36
|
|
37
|
-
spec.add_runtime_dependency 'deep_merge', '>= 1.2.1'
|
38
37
|
spec.add_runtime_dependency 'hanami-router', '~> 2.0.0'
|
39
|
-
spec.add_runtime_dependency 'hanami-utils', '~> 2.0.0'
|
40
38
|
spec.add_runtime_dependency 'json_refs', '~> 0.1', '>= 0.1.7'
|
41
|
-
spec.add_runtime_dependency 'json_schemer', '~> 0.
|
39
|
+
spec.add_runtime_dependency 'json_schemer', '~> 2.0.0'
|
42
40
|
spec.add_runtime_dependency 'multi_json', '~> 1.14'
|
43
|
-
spec.add_runtime_dependency '
|
41
|
+
spec.add_runtime_dependency 'openapi_parameters', '~> 0.2.2'
|
44
42
|
spec.add_runtime_dependency 'rack', '>= 2.2', '< 4.0'
|
45
|
-
|
46
|
-
spec.add_development_dependency 'bundler', '~> 2'
|
47
|
-
spec.add_development_dependency 'openapi_parameters', '~> 0.2', '<= 2.0.0'
|
48
|
-
spec.add_development_dependency 'rack-test', '~> 1'
|
49
|
-
spec.add_development_dependency 'rake', '~> 13'
|
50
|
-
spec.add_development_dependency 'rspec', '~> 3'
|
51
43
|
spec.metadata = {
|
52
44
|
'rubygems_mfa_required' => 'true'
|
53
45
|
}
|
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: openapi_first
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.
|
4
|
+
version: 1.0.0.beta4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andreas Haller
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-10-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: deep_merge
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - ">="
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: 1.2.1
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - ">="
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: 1.2.1
|
27
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: hanami-router
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -38,20 +24,6 @@ dependencies:
|
|
38
24
|
- - "~>"
|
39
25
|
- !ruby/object:Gem::Version
|
40
26
|
version: 2.0.0
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: hanami-utils
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: 2.0.0
|
48
|
-
type: :runtime
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: 2.0.0
|
55
27
|
- !ruby/object:Gem::Dependency
|
56
28
|
name: json_refs
|
57
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -78,14 +50,14 @@ dependencies:
|
|
78
50
|
requirements:
|
79
51
|
- - "~>"
|
80
52
|
- !ruby/object:Gem::Version
|
81
|
-
version: 0.
|
53
|
+
version: 2.0.0
|
82
54
|
type: :runtime
|
83
55
|
prerelease: false
|
84
56
|
version_requirements: !ruby/object:Gem::Requirement
|
85
57
|
requirements:
|
86
58
|
- - "~>"
|
87
59
|
- !ruby/object:Gem::Version
|
88
|
-
version: 0.
|
60
|
+
version: 2.0.0
|
89
61
|
- !ruby/object:Gem::Dependency
|
90
62
|
name: multi_json
|
91
63
|
requirement: !ruby/object:Gem::Requirement
|
@@ -101,19 +73,19 @@ dependencies:
|
|
101
73
|
- !ruby/object:Gem::Version
|
102
74
|
version: '1.14'
|
103
75
|
- !ruby/object:Gem::Dependency
|
104
|
-
name:
|
76
|
+
name: openapi_parameters
|
105
77
|
requirement: !ruby/object:Gem::Requirement
|
106
78
|
requirements:
|
107
79
|
- - "~>"
|
108
80
|
- !ruby/object:Gem::Version
|
109
|
-
version:
|
81
|
+
version: 0.2.2
|
110
82
|
type: :runtime
|
111
83
|
prerelease: false
|
112
84
|
version_requirements: !ruby/object:Gem::Requirement
|
113
85
|
requirements:
|
114
86
|
- - "~>"
|
115
87
|
- !ruby/object:Gem::Version
|
116
|
-
version:
|
88
|
+
version: 0.2.2
|
117
89
|
- !ruby/object:Gem::Dependency
|
118
90
|
name: rack
|
119
91
|
requirement: !ruby/object:Gem::Requirement
|
@@ -134,82 +106,6 @@ dependencies:
|
|
134
106
|
- - "<"
|
135
107
|
- !ruby/object:Gem::Version
|
136
108
|
version: '4.0'
|
137
|
-
- !ruby/object:Gem::Dependency
|
138
|
-
name: bundler
|
139
|
-
requirement: !ruby/object:Gem::Requirement
|
140
|
-
requirements:
|
141
|
-
- - "~>"
|
142
|
-
- !ruby/object:Gem::Version
|
143
|
-
version: '2'
|
144
|
-
type: :development
|
145
|
-
prerelease: false
|
146
|
-
version_requirements: !ruby/object:Gem::Requirement
|
147
|
-
requirements:
|
148
|
-
- - "~>"
|
149
|
-
- !ruby/object:Gem::Version
|
150
|
-
version: '2'
|
151
|
-
- !ruby/object:Gem::Dependency
|
152
|
-
name: openapi_parameters
|
153
|
-
requirement: !ruby/object:Gem::Requirement
|
154
|
-
requirements:
|
155
|
-
- - "~>"
|
156
|
-
- !ruby/object:Gem::Version
|
157
|
-
version: '0.2'
|
158
|
-
- - "<="
|
159
|
-
- !ruby/object:Gem::Version
|
160
|
-
version: 2.0.0
|
161
|
-
type: :development
|
162
|
-
prerelease: false
|
163
|
-
version_requirements: !ruby/object:Gem::Requirement
|
164
|
-
requirements:
|
165
|
-
- - "~>"
|
166
|
-
- !ruby/object:Gem::Version
|
167
|
-
version: '0.2'
|
168
|
-
- - "<="
|
169
|
-
- !ruby/object:Gem::Version
|
170
|
-
version: 2.0.0
|
171
|
-
- !ruby/object:Gem::Dependency
|
172
|
-
name: rack-test
|
173
|
-
requirement: !ruby/object:Gem::Requirement
|
174
|
-
requirements:
|
175
|
-
- - "~>"
|
176
|
-
- !ruby/object:Gem::Version
|
177
|
-
version: '1'
|
178
|
-
type: :development
|
179
|
-
prerelease: false
|
180
|
-
version_requirements: !ruby/object:Gem::Requirement
|
181
|
-
requirements:
|
182
|
-
- - "~>"
|
183
|
-
- !ruby/object:Gem::Version
|
184
|
-
version: '1'
|
185
|
-
- !ruby/object:Gem::Dependency
|
186
|
-
name: rake
|
187
|
-
requirement: !ruby/object:Gem::Requirement
|
188
|
-
requirements:
|
189
|
-
- - "~>"
|
190
|
-
- !ruby/object:Gem::Version
|
191
|
-
version: '13'
|
192
|
-
type: :development
|
193
|
-
prerelease: false
|
194
|
-
version_requirements: !ruby/object:Gem::Requirement
|
195
|
-
requirements:
|
196
|
-
- - "~>"
|
197
|
-
- !ruby/object:Gem::Version
|
198
|
-
version: '13'
|
199
|
-
- !ruby/object:Gem::Dependency
|
200
|
-
name: rspec
|
201
|
-
requirement: !ruby/object:Gem::Requirement
|
202
|
-
requirements:
|
203
|
-
- - "~>"
|
204
|
-
- !ruby/object:Gem::Version
|
205
|
-
version: '3'
|
206
|
-
type: :development
|
207
|
-
prerelease: false
|
208
|
-
version_requirements: !ruby/object:Gem::Requirement
|
209
|
-
requirements:
|
210
|
-
- - "~>"
|
211
|
-
- !ruby/object:Gem::Version
|
212
|
-
version: '3'
|
213
109
|
description:
|
214
110
|
email:
|
215
111
|
- andreas.haller@posteo.de
|
@@ -231,16 +127,17 @@ files:
|
|
231
127
|
- benchmarks/Gemfile
|
232
128
|
- benchmarks/Gemfile.lock
|
233
129
|
- benchmarks/README.md
|
234
|
-
- benchmarks/apps/
|
130
|
+
- benchmarks/apps/committee_with_hanami_api.ru
|
235
131
|
- benchmarks/apps/committee_with_response_validation.ru
|
236
132
|
- benchmarks/apps/committee_with_sinatra.ru
|
237
133
|
- benchmarks/apps/grape.ru
|
238
134
|
- benchmarks/apps/hanami_api.ru
|
239
135
|
- benchmarks/apps/hanami_router.ru
|
240
136
|
- benchmarks/apps/openapi.yaml
|
241
|
-
- benchmarks/apps/openapi_first.ru
|
242
137
|
- benchmarks/apps/openapi_first_with_hanami_api.ru
|
138
|
+
- benchmarks/apps/openapi_first_with_plain_rack.ru
|
243
139
|
- benchmarks/apps/openapi_first_with_response_validation.ru
|
140
|
+
- benchmarks/apps/openapi_first_with_sinatra.ru
|
244
141
|
- benchmarks/apps/roda.ru
|
245
142
|
- benchmarks/apps/sinatra.ru
|
246
143
|
- benchmarks/apps/syro.ru
|
@@ -255,17 +152,23 @@ files:
|
|
255
152
|
- examples/openapi.yaml
|
256
153
|
- lib/openapi_first.rb
|
257
154
|
- lib/openapi_first/body_parser_middleware.rb
|
155
|
+
- lib/openapi_first/config.rb
|
156
|
+
- lib/openapi_first/default_error_response.rb
|
258
157
|
- lib/openapi_first/definition.rb
|
158
|
+
- lib/openapi_first/error_response.rb
|
259
159
|
- lib/openapi_first/errors.rb
|
260
160
|
- lib/openapi_first/operation.rb
|
161
|
+
- lib/openapi_first/operation_schemas.rb
|
162
|
+
- lib/openapi_first/plugins.rb
|
163
|
+
- lib/openapi_first/request_body_validator.rb
|
261
164
|
- lib/openapi_first/request_validation.rb
|
262
165
|
- lib/openapi_first/response_validation.rb
|
263
166
|
- lib/openapi_first/response_validator.rb
|
264
167
|
- lib/openapi_first/router.rb
|
265
168
|
- lib/openapi_first/schema_validation.rb
|
169
|
+
- lib/openapi_first/string_keyed_hash.rb
|
266
170
|
- lib/openapi_first/use_router.rb
|
267
|
-
- lib/openapi_first/
|
268
|
-
- lib/openapi_first/validation_format.rb
|
171
|
+
- lib/openapi_first/validation_result.rb
|
269
172
|
- lib/openapi_first/version.rb
|
270
173
|
- openapi_first.gemspec
|
271
174
|
homepage: https://github.com/ahx/openapi_first
|
@@ -281,7 +184,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
281
184
|
requirements:
|
282
185
|
- - ">="
|
283
186
|
- !ruby/object:Gem::Version
|
284
|
-
version: 3.
|
187
|
+
version: 3.1.1
|
285
188
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
286
189
|
requirements:
|
287
190
|
- - ">"
|
@@ -291,5 +194,5 @@ requirements: []
|
|
291
194
|
rubygems_version: 3.3.7
|
292
195
|
signing_key:
|
293
196
|
specification_version: 4
|
294
|
-
summary: Implement REST APIs based on OpenApi.
|
197
|
+
summary: Implement REST APIs based on OpenApi 3.x
|
295
198
|
test_files: []
|
@@ -1,22 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'multi_json'
|
4
|
-
require 'openapi_first'
|
5
|
-
|
6
|
-
namespace = Module.new do
|
7
|
-
def self.find_thing(params, _res)
|
8
|
-
{ hello: 'world', id: params.fetch('id') }
|
9
|
-
end
|
10
|
-
|
11
|
-
def self.find_things(_params, _res)
|
12
|
-
[{ hello: 'world' }]
|
13
|
-
end
|
14
|
-
|
15
|
-
def self.create_thing(_params, res)
|
16
|
-
res.status = 201
|
17
|
-
{ hello: 'world' }
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
oas_path = File.absolute_path('./openapi.yaml', __dir__)
|
22
|
-
run OpenapiFirst.app(oas_path, namespace: namespace)
|
data/lib/openapi_first/utils.rb
DELETED
@@ -1,35 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'hanami/utils/string'
|
4
|
-
require 'hanami/utils/hash'
|
5
|
-
require 'deep_merge/core'
|
6
|
-
|
7
|
-
module OpenapiFirst
|
8
|
-
module Utils
|
9
|
-
def self.deep_merge!(dest, source)
|
10
|
-
DeepMerge.deep_merge!(source, dest)
|
11
|
-
end
|
12
|
-
|
13
|
-
def self.underscore(string)
|
14
|
-
Hanami::Utils::String.underscore(string)
|
15
|
-
end
|
16
|
-
|
17
|
-
def self.classify(string)
|
18
|
-
Hanami::Utils::String.classify(string)
|
19
|
-
end
|
20
|
-
|
21
|
-
class StringKeyedHash
|
22
|
-
def initialize(original)
|
23
|
-
@orig = original
|
24
|
-
end
|
25
|
-
|
26
|
-
def key?(key)
|
27
|
-
@orig.key?(key.to_sym)
|
28
|
-
end
|
29
|
-
|
30
|
-
def [](key)
|
31
|
-
@orig[key.to_sym]
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
@@ -1,55 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module OpenapiFirst
|
4
|
-
module ValidationFormat
|
5
|
-
SIMPLE_TYPES = %w[string integer].freeze
|
6
|
-
|
7
|
-
# rubocop:disable Metrics/MethodLength
|
8
|
-
# rubocop:disable Metrics/AbcSize
|
9
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
10
|
-
# rubocop:disable Metrics/PerceivedComplexity
|
11
|
-
def self.error_details(error)
|
12
|
-
if error['type'] == 'pattern'
|
13
|
-
{
|
14
|
-
title: 'is not valid',
|
15
|
-
detail: "does not match pattern '#{error['schema']['pattern']}'"
|
16
|
-
}
|
17
|
-
elsif error['type'] == 'format'
|
18
|
-
{
|
19
|
-
title: "has not a valid #{error.dig('schema', 'format')} format",
|
20
|
-
detail: "#{error['data'].inspect} is not a valid #{error.dig('schema', 'format')} format"
|
21
|
-
}
|
22
|
-
elsif error['type'] == 'enum'
|
23
|
-
{
|
24
|
-
title: "value #{error['data'].inspect} is not defined in enum",
|
25
|
-
detail: "value can be one of #{error.dig('schema', 'enum')&.join(', ')}"
|
26
|
-
}
|
27
|
-
elsif error['type'] == 'required'
|
28
|
-
missing_keys = error['details']['missing_keys']
|
29
|
-
{
|
30
|
-
title: "is missing required properties: #{missing_keys.join(', ')}"
|
31
|
-
}
|
32
|
-
elsif error['type'] == 'readOnly'
|
33
|
-
{
|
34
|
-
title: 'appears in request, but is read-only'
|
35
|
-
}
|
36
|
-
elsif error['type'] == 'writeOnly'
|
37
|
-
{
|
38
|
-
title: 'write-only field appears in response:'
|
39
|
-
}
|
40
|
-
elsif SIMPLE_TYPES.include?(error['type'])
|
41
|
-
{
|
42
|
-
title: "should be a #{error['type']}"
|
43
|
-
}
|
44
|
-
elsif error['schema'] == false
|
45
|
-
{ title: 'unknown fields are not allowed' }
|
46
|
-
else
|
47
|
-
{ title: "is not valid: #{error['data'].inspect}" }
|
48
|
-
end
|
49
|
-
end
|
50
|
-
# rubocop:enable Metrics/MethodLength
|
51
|
-
# rubocop:enable Metrics/AbcSize
|
52
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
53
|
-
# rubocop:enable Metrics/PerceivedComplexity
|
54
|
-
end
|
55
|
-
end
|
File without changes
|