openapi_first 0.12.5 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +6 -46
- data/CHANGELOG.md +16 -1
- data/Gemfile.lock +60 -59
- data/README.md +25 -10
- data/benchmarks/Gemfile +2 -1
- data/benchmarks/Gemfile.lock +58 -58
- data/benchmarks/apps/committee.ru +14 -18
- data/benchmarks/apps/hanami_api.ru +21 -0
- data/benchmarks/apps/hanami_router.ru +1 -1
- data/benchmarks/apps/sinatra.ru +1 -1
- data/benchmarks/apps/syro.ru +3 -3
- data/benchmarks/benchmarks.rb +11 -14
- data/lib/openapi_first.rb +9 -4
- data/lib/openapi_first/app.rb +3 -5
- data/lib/openapi_first/{find_handler.rb → default_operation_resolver.rb} +5 -13
- data/lib/openapi_first/definition.rb +9 -8
- data/lib/openapi_first/operation.rb +65 -25
- data/lib/openapi_first/request_validation.rb +29 -30
- data/lib/openapi_first/responder.rb +4 -4
- data/lib/openapi_first/response_validation.rb +2 -1
- data/lib/openapi_first/router.rb +6 -4
- data/lib/openapi_first/schema_validation.rb +36 -0
- data/lib/openapi_first/utils.rb +7 -14
- data/lib/openapi_first/validation_format.rb +22 -0
- data/lib/openapi_first/version.rb +1 -1
- data/openapi_first.gemspec +4 -2
- metadata +13 -11
@@ -2,12 +2,12 @@
|
|
2
2
|
|
3
3
|
require 'rack'
|
4
4
|
require_relative 'inbox'
|
5
|
-
require_relative '
|
5
|
+
require_relative 'default_operation_resolver'
|
6
6
|
|
7
7
|
module OpenapiFirst
|
8
8
|
class Responder
|
9
|
-
def initialize(
|
10
|
-
@resolver = resolver
|
9
|
+
def initialize(namespace: nil, resolver: nil)
|
10
|
+
@resolver = resolver || DefaultOperationResolver.new(namespace)
|
11
11
|
@namespace = namespace
|
12
12
|
end
|
13
13
|
|
@@ -24,7 +24,7 @@ module OpenapiFirst
|
|
24
24
|
private
|
25
25
|
|
26
26
|
def find_handler(operation)
|
27
|
-
handler = @resolver
|
27
|
+
handler = @resolver.call(operation)
|
28
28
|
raise NotImplementedError, "Could not find handler for #{operation.name}" unless handler
|
29
29
|
|
30
30
|
handler
|
@@ -41,7 +41,8 @@ module OpenapiFirst
|
|
41
41
|
full_body = +''
|
42
42
|
response.each { |chunk| full_body << chunk }
|
43
43
|
data = full_body.empty? ? {} : load_json(full_body)
|
44
|
-
errors =
|
44
|
+
errors = schema.validate(data)
|
45
|
+
errors = errors.to_a.map! do |error|
|
45
46
|
error_message_for(error)
|
46
47
|
end
|
47
48
|
raise ResponseBodyInvalidError, errors.join(', ') if errors.any?
|
data/lib/openapi_first/router.rb
CHANGED
@@ -40,7 +40,10 @@ module OpenapiFirst
|
|
40
40
|
|
41
41
|
def raise_error(env)
|
42
42
|
req = Rack::Request.new(env)
|
43
|
-
msg =
|
43
|
+
msg =
|
44
|
+
"Could not find definition for #{req.request_method} '#{
|
45
|
+
req.path
|
46
|
+
}' in API description #{@filepath}"
|
44
47
|
raise NotFoundError, msg
|
45
48
|
end
|
46
49
|
|
@@ -53,13 +56,12 @@ module OpenapiFirst
|
|
53
56
|
env[Rack::PATH_INFO] = env.delete(ORIGINAL_PATH) if env[ORIGINAL_PATH]
|
54
57
|
end
|
55
58
|
|
56
|
-
def build_router(operations) # rubocop:disable Metrics/AbcSize
|
57
|
-
router = Hanami::Router.new
|
59
|
+
def build_router(operations) # rubocop:disable Metrics/AbcSize
|
60
|
+
router = Hanami::Router.new
|
58
61
|
operations.each do |operation|
|
59
62
|
normalized_path = operation.path.gsub('{', ':').gsub('}', '')
|
60
63
|
if operation.operation_id.nil?
|
61
64
|
warn "operationId is missing in '#{operation.method} #{operation.path}'. I am ignoring this operation."
|
62
|
-
next
|
63
65
|
end
|
64
66
|
router.public_send(
|
65
67
|
operation.method,
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json_schemer'
|
4
|
+
|
5
|
+
module OpenapiFirst
|
6
|
+
class SchemaValidation
|
7
|
+
attr_reader :raw_schema
|
8
|
+
|
9
|
+
def initialize(schema, write: true)
|
10
|
+
@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
|
+
@schemer = JSONSchemer.schema(
|
15
|
+
schema,
|
16
|
+
keywords: custom_keywords,
|
17
|
+
before_property_validation: proc do |data, property, property_schema, parent|
|
18
|
+
convert_nullable(data, property, property_schema, parent)
|
19
|
+
end
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
def validate(input)
|
24
|
+
@schemer.validate(input)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def convert_nullable(_data, _property, property_schema, _parent)
|
30
|
+
return unless property_schema.is_a?(Hash) && property_schema['nullable'] && property_schema['type']
|
31
|
+
|
32
|
+
property_schema['type'] = [*property_schema['type'], 'null']
|
33
|
+
property_schema.delete('nullable')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/openapi_first/utils.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'hanami/utils/string'
|
4
|
+
require 'hanami/utils/hash'
|
4
5
|
require 'deep_merge/core'
|
5
6
|
|
6
7
|
module OpenapiFirst
|
@@ -17,20 +18,12 @@ module OpenapiFirst
|
|
17
18
|
Hanami::Utils::String.classify(string)
|
18
19
|
end
|
19
20
|
|
20
|
-
def self.
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
when Array
|
27
|
-
value.map do |item|
|
28
|
-
item.is_a?(::Hash) ? deep_stringify(item) : item
|
29
|
-
end
|
30
|
-
else
|
31
|
-
value
|
32
|
-
end
|
33
|
-
end
|
21
|
+
def self.deep_symbolize(hash)
|
22
|
+
Hanami::Utils::Hash.deep_symbolize(hash)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.deep_stringify(hash)
|
26
|
+
Hanami::Utils::Hash.deep_stringify(hash)
|
34
27
|
end
|
35
28
|
end
|
36
29
|
end
|
@@ -6,17 +6,37 @@ module OpenapiFirst
|
|
6
6
|
|
7
7
|
# rubocop:disable Metrics/MethodLength
|
8
8
|
# rubocop:disable Metrics/AbcSize
|
9
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
10
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
9
11
|
def self.error_details(error)
|
10
12
|
if error['type'] == 'pattern'
|
11
13
|
{
|
12
14
|
title: 'is not valid',
|
13
15
|
detail: "does not match pattern '#{error['schema']['pattern']}'"
|
14
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
|
+
}
|
15
27
|
elsif error['type'] == 'required'
|
16
28
|
missing_keys = error['details']['missing_keys']
|
17
29
|
{
|
18
30
|
title: "is missing required properties: #{missing_keys.join(', ')}"
|
19
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
|
+
}
|
20
40
|
elsif SIMPLE_TYPES.include?(error['type'])
|
21
41
|
{
|
22
42
|
title: "should be a #{error['type']}"
|
@@ -29,5 +49,7 @@ module OpenapiFirst
|
|
29
49
|
end
|
30
50
|
# rubocop:enable Metrics/MethodLength
|
31
51
|
# rubocop:enable Metrics/AbcSize
|
52
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
53
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
32
54
|
end
|
33
55
|
end
|
data/openapi_first.gemspec
CHANGED
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.metadata['changelog_uri'] = 'https://github.com/ahx/openapi_first/blob/master/CHANGELOG.md'
|
21
21
|
else
|
22
22
|
raise 'RubyGems 2.0 or newer is required to protect against ' \
|
23
|
-
|
23
|
+
'public gem pushes.'
|
24
24
|
end
|
25
25
|
|
26
26
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
@@ -32,10 +32,12 @@ Gem::Specification.new do |spec|
|
|
32
32
|
spec.bindir = 'exe'
|
33
33
|
spec.require_paths = ['lib']
|
34
34
|
|
35
|
+
spec.required_ruby_version = '>= 2.6.0'
|
36
|
+
|
35
37
|
spec.add_runtime_dependency 'deep_merge', '>= 1.2.1'
|
36
38
|
spec.add_runtime_dependency 'hanami-router', '~> 2.0.alpha3'
|
37
39
|
spec.add_runtime_dependency 'hanami-utils', '~> 2.0.alpha1'
|
38
|
-
spec.add_runtime_dependency 'json_schemer', '~> 0.2'
|
40
|
+
spec.add_runtime_dependency 'json_schemer', '~> 0.2.16'
|
39
41
|
spec.add_runtime_dependency 'multi_json', '~> 1.14'
|
40
42
|
spec.add_runtime_dependency 'oas_parser', '~> 0.25.1'
|
41
43
|
spec.add_runtime_dependency 'rack', '~> 2.2'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: openapi_first
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.14.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andreas Haller
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-08-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: deep_merge
|
@@ -58,14 +58,14 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
61
|
+
version: 0.2.16
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
68
|
+
version: 0.2.16
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: multi_json
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -164,7 +164,7 @@ dependencies:
|
|
164
164
|
- - "~>"
|
165
165
|
- !ruby/object:Gem::Version
|
166
166
|
version: '3'
|
167
|
-
description:
|
167
|
+
description:
|
168
168
|
email:
|
169
169
|
- andreas.haller@posteo.de
|
170
170
|
executables: []
|
@@ -187,6 +187,7 @@ files:
|
|
187
187
|
- benchmarks/Gemfile.lock
|
188
188
|
- benchmarks/apps/committee.ru
|
189
189
|
- benchmarks/apps/grape.ru
|
190
|
+
- benchmarks/apps/hanami_api.ru
|
190
191
|
- benchmarks/apps/hanami_router.ru
|
191
192
|
- benchmarks/apps/openapi.yaml
|
192
193
|
- benchmarks/apps/openapi_first.ru
|
@@ -202,8 +203,8 @@ files:
|
|
202
203
|
- lib/openapi_first.rb
|
203
204
|
- lib/openapi_first/app.rb
|
204
205
|
- lib/openapi_first/coverage.rb
|
206
|
+
- lib/openapi_first/default_operation_resolver.rb
|
205
207
|
- lib/openapi_first/definition.rb
|
206
|
-
- lib/openapi_first/find_handler.rb
|
207
208
|
- lib/openapi_first/inbox.rb
|
208
209
|
- lib/openapi_first/operation.rb
|
209
210
|
- lib/openapi_first/request_validation.rb
|
@@ -213,6 +214,7 @@ files:
|
|
213
214
|
- lib/openapi_first/response_validator.rb
|
214
215
|
- lib/openapi_first/router.rb
|
215
216
|
- lib/openapi_first/router_required.rb
|
217
|
+
- lib/openapi_first/schema_validation.rb
|
216
218
|
- lib/openapi_first/utils.rb
|
217
219
|
- lib/openapi_first/validation.rb
|
218
220
|
- lib/openapi_first/validation_format.rb
|
@@ -225,7 +227,7 @@ metadata:
|
|
225
227
|
https://github.com/ahx/openapi_first: https://github.com/ahx/openapi_first
|
226
228
|
source_code_uri: https://github.com/ahx/openapi_first
|
227
229
|
changelog_uri: https://github.com/ahx/openapi_first/blob/master/CHANGELOG.md
|
228
|
-
post_install_message:
|
230
|
+
post_install_message:
|
229
231
|
rdoc_options: []
|
230
232
|
require_paths:
|
231
233
|
- lib
|
@@ -233,15 +235,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
233
235
|
requirements:
|
234
236
|
- - ">="
|
235
237
|
- !ruby/object:Gem::Version
|
236
|
-
version:
|
238
|
+
version: 2.6.0
|
237
239
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
238
240
|
requirements:
|
239
241
|
- - ">="
|
240
242
|
- !ruby/object:Gem::Version
|
241
243
|
version: '0'
|
242
244
|
requirements: []
|
243
|
-
rubygems_version: 3.
|
244
|
-
signing_key:
|
245
|
+
rubygems_version: 3.2.3
|
246
|
+
signing_key:
|
245
247
|
specification_version: 4
|
246
248
|
summary: Implement REST APIs based on OpenApi.
|
247
249
|
test_files: []
|