openapi_first 0.11.0.alpha → 0.12.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/.rubocop.yml +6 -0
- data/CHANGELOG.md +19 -1
- data/Gemfile.lock +8 -7
- data/README.md +122 -89
- data/benchmarks/Gemfile.lock +6 -6
- data/benchmarks/apps/openapi_first.ru +1 -1
- data/examples/app.rb +6 -1
- data/lib/openapi_first.rb +46 -8
- data/lib/openapi_first/app.rb +5 -8
- data/lib/openapi_first/definition.rb +0 -12
- data/lib/openapi_first/find_handler.rb +25 -21
- data/lib/openapi_first/operation.rb +12 -7
- data/lib/openapi_first/request_validation.rb +24 -13
- data/lib/openapi_first/{operation_resolver.rb → responder.rb} +18 -4
- data/lib/openapi_first/response_validation.rb +25 -78
- data/lib/openapi_first/response_validator.rb +6 -47
- data/lib/openapi_first/router.rb +29 -31
- data/lib/openapi_first/router_required.rb +13 -0
- data/lib/openapi_first/validation_format.rb +3 -1
- data/lib/openapi_first/version.rb +1 -1
- data/openapi_first.gemspec +7 -7
- metadata +6 -5
@@ -3,61 +3,20 @@
|
|
3
3
|
require 'json_schemer'
|
4
4
|
require 'multi_json'
|
5
5
|
require_relative 'validation'
|
6
|
+
require_relative 'router'
|
6
7
|
|
7
8
|
module OpenapiFirst
|
8
9
|
class ResponseValidator
|
9
10
|
def initialize(spec)
|
10
11
|
@spec = spec
|
12
|
+
@router = Router.new(->(_env) {}, spec: spec, raise_error: true)
|
13
|
+
@response_validation = ResponseValidation.new(->(response) { response.to_a })
|
11
14
|
end
|
12
15
|
|
13
16
|
def validate(request, response)
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
Validation.new([e.message])
|
18
|
-
end
|
19
|
-
|
20
|
-
def validate_operation(request, response)
|
21
|
-
errors = validation_errors(request, response)
|
22
|
-
Validation.new(errors || [])
|
23
|
-
rescue OasParser::ResponseCodeNotFound, OasParser::MethodNotFound => e
|
24
|
-
Validation.new([e.message])
|
25
|
-
end
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
def validation_errors(request, response)
|
30
|
-
content = response_for(request, response)&.content
|
31
|
-
return unless content
|
32
|
-
|
33
|
-
content_type = content[response.content_type]
|
34
|
-
return ["Content type not found: '#{response.content_type}'"] unless content_type
|
35
|
-
|
36
|
-
response_schema = content_type['schema']
|
37
|
-
return unless response_schema
|
38
|
-
|
39
|
-
response_data = MultiJson.load(response.body)
|
40
|
-
validate_json_schema(response_schema, response_data)
|
41
|
-
end
|
42
|
-
|
43
|
-
def validate_json_schema(schema, data)
|
44
|
-
JSONSchemer.schema(schema).validate(data).to_a.map do |error|
|
45
|
-
format_error(error)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def format_error(error)
|
50
|
-
ValidationFormat.error_details(error)
|
51
|
-
.merge!(
|
52
|
-
data_pointer: error['data_pointer'],
|
53
|
-
schema_pointer: error['schema_pointer']
|
54
|
-
)
|
55
|
-
end
|
56
|
-
|
57
|
-
def response_for(request, response)
|
58
|
-
@spec
|
59
|
-
.find_operation!(request)
|
60
|
-
&.response_by_code(response.status.to_s, use_default: true)
|
17
|
+
env = request.env.dup
|
18
|
+
@router.call(env)
|
19
|
+
@response_validation.validate(response, env[OPERATION])
|
61
20
|
end
|
62
21
|
end
|
63
22
|
end
|
data/lib/openapi_first/router.rb
CHANGED
@@ -6,52 +6,49 @@ require_relative 'utils'
|
|
6
6
|
|
7
7
|
module OpenapiFirst
|
8
8
|
class Router
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
def initialize(
|
10
|
+
app,
|
11
|
+
spec:,
|
12
|
+
raise_error: false,
|
13
|
+
parent_app: nil
|
14
|
+
)
|
13
15
|
@app = app
|
14
|
-
@parent_app =
|
15
|
-
@raise =
|
16
|
-
@failure_app = find_failure_app(options[:not_found])
|
17
|
-
if @failure_app.nil?
|
18
|
-
raise ArgumentError,
|
19
|
-
'not_found must be nil, :continue or must respond to call'
|
20
|
-
end
|
21
|
-
spec = options.fetch(:spec)
|
16
|
+
@parent_app = parent_app
|
17
|
+
@raise = raise_error
|
22
18
|
@filepath = spec.filepath
|
23
19
|
@router = build_router(spec.operations)
|
24
20
|
end
|
25
21
|
|
26
22
|
def call(env)
|
27
|
-
|
28
|
-
|
23
|
+
env[OPERATION] = nil
|
24
|
+
response = call_router(env)
|
25
|
+
status = response[0]
|
26
|
+
if UNKNOWN_ROUTE_STATUS.include?(status) && env[OPERATION].nil?
|
27
|
+
return @parent_app.call(env) if @parent_app # This should only happen if used via OpenapiFirst.middlware
|
29
28
|
|
30
|
-
|
31
|
-
req = Rack::Request.new(env)
|
32
|
-
msg = "Could not find definition for #{req.request_method} '#{req.path}' in API description #{@filepath}"
|
33
|
-
raise NotFoundError, msg
|
29
|
+
raise_error(env) if @raise
|
34
30
|
end
|
35
|
-
|
36
|
-
|
37
|
-
@failure_app.call(env)
|
31
|
+
response
|
38
32
|
end
|
39
33
|
|
40
|
-
|
34
|
+
UNKNOWN_ROUTE_STATUS = [404, 405].freeze
|
35
|
+
ORIGINAL_PATH = 'openapi_first.path_info'
|
41
36
|
|
42
|
-
|
43
|
-
return DEFAULT_NOT_FOUND_APP if option.nil?
|
44
|
-
return @app if option == :continue
|
37
|
+
private
|
45
38
|
|
46
|
-
|
39
|
+
def raise_error(env)
|
40
|
+
req = Rack::Request.new(env)
|
41
|
+
msg = "Could not find definition for #{req.request_method} '#{req.path}' in API description #{@filepath}"
|
42
|
+
raise NotFoundError, msg
|
47
43
|
end
|
48
44
|
|
49
|
-
def
|
50
|
-
|
45
|
+
def call_router(env)
|
46
|
+
# Changing and restoring PATH_INFO is needed, because Hanami::Router does not respect existing script_path
|
47
|
+
env[ORIGINAL_PATH] = env[Rack::PATH_INFO]
|
51
48
|
env[Rack::PATH_INFO] = Rack::Request.new(env).path
|
52
|
-
@router.
|
49
|
+
@router.call(env)
|
53
50
|
ensure
|
54
|
-
env[Rack::PATH_INFO] =
|
51
|
+
env[Rack::PATH_INFO] = env.delete(ORIGINAL_PATH) if env[ORIGINAL_PATH]
|
55
52
|
end
|
56
53
|
|
57
54
|
def build_router(operations) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
@@ -67,7 +64,8 @@ module OpenapiFirst
|
|
67
64
|
normalized_path,
|
68
65
|
to: lambda do |env|
|
69
66
|
env[OPERATION] = operation
|
70
|
-
env[PARAMETERS] =
|
67
|
+
env[PARAMETERS] = env['router.params']
|
68
|
+
env[Rack::PATH_INFO] = env.delete(ORIGINAL_PATH)
|
71
69
|
@app.call(env)
|
72
70
|
end
|
73
71
|
)
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OpenapiFirst
|
4
|
+
module RouterRequired
|
5
|
+
def call(env)
|
6
|
+
unless env.key?(OPERATION)
|
7
|
+
raise 'OpenapiFirst::Router missing in middleware stack. Did you forget adding OpenapiFirst::Router?'
|
8
|
+
end
|
9
|
+
|
10
|
+
super
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -5,6 +5,7 @@ module OpenapiFirst
|
|
5
5
|
SIMPLE_TYPES = %w[string integer].freeze
|
6
6
|
|
7
7
|
# rubocop:disable Metrics/MethodLength
|
8
|
+
# rubocop:disable Metrics/AbcSize
|
8
9
|
def self.error_details(error)
|
9
10
|
if error['type'] == 'pattern'
|
10
11
|
{
|
@@ -23,9 +24,10 @@ module OpenapiFirst
|
|
23
24
|
elsif error['schema'] == false
|
24
25
|
{ title: 'unknown fields are not allowed' }
|
25
26
|
else
|
26
|
-
{ title:
|
27
|
+
{ title: "is not valid: #{error['data'].inspect}" }
|
27
28
|
end
|
28
29
|
end
|
29
30
|
# rubocop:enable Metrics/MethodLength
|
31
|
+
# rubocop:enable Metrics/AbcSize
|
30
32
|
end
|
31
33
|
end
|
data/openapi_first.gemspec
CHANGED
@@ -32,13 +32,13 @@ Gem::Specification.new do |spec|
|
|
32
32
|
spec.bindir = 'exe'
|
33
33
|
spec.require_paths = ['lib']
|
34
34
|
|
35
|
-
spec.
|
36
|
-
spec.
|
37
|
-
spec.
|
38
|
-
spec.
|
39
|
-
spec.
|
40
|
-
spec.
|
41
|
-
spec.
|
35
|
+
spec.add_runtime_dependency 'deep_merge', '>= 1.2.1'
|
36
|
+
spec.add_runtime_dependency 'hanami-router', '~> 2.0.alpha3'
|
37
|
+
spec.add_runtime_dependency 'hanami-utils', '~> 2.0.alpha1'
|
38
|
+
spec.add_runtime_dependency 'json_schemer', '~> 0.2'
|
39
|
+
spec.add_runtime_dependency 'multi_json', '~> 1.14'
|
40
|
+
spec.add_runtime_dependency 'oas_parser', '~> 0.25.1'
|
41
|
+
spec.add_runtime_dependency 'rack', '~> 2.2'
|
42
42
|
|
43
43
|
spec.add_development_dependency 'bundler', '~> 2'
|
44
44
|
spec.add_development_dependency 'rack-test', '~> 1'
|
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.12.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andreas Haller
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-06-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: deep_merge
|
@@ -206,12 +206,13 @@ files:
|
|
206
206
|
- lib/openapi_first/find_handler.rb
|
207
207
|
- lib/openapi_first/inbox.rb
|
208
208
|
- lib/openapi_first/operation.rb
|
209
|
-
- lib/openapi_first/operation_resolver.rb
|
210
209
|
- lib/openapi_first/request_validation.rb
|
210
|
+
- lib/openapi_first/responder.rb
|
211
211
|
- lib/openapi_first/response_object.rb
|
212
212
|
- lib/openapi_first/response_validation.rb
|
213
213
|
- lib/openapi_first/response_validator.rb
|
214
214
|
- lib/openapi_first/router.rb
|
215
|
+
- lib/openapi_first/router_required.rb
|
215
216
|
- lib/openapi_first/utils.rb
|
216
217
|
- lib/openapi_first/validation.rb
|
217
218
|
- lib/openapi_first/validation_format.rb
|
@@ -235,9 +236,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
235
236
|
version: '0'
|
236
237
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
237
238
|
requirements:
|
238
|
-
- - "
|
239
|
+
- - ">="
|
239
240
|
- !ruby/object:Gem::Version
|
240
|
-
version:
|
241
|
+
version: '0'
|
241
242
|
requirements: []
|
242
243
|
rubygems_version: 3.1.2
|
243
244
|
signing_key:
|