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.
@@ -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
- errors = validation_errors(request, response)
15
- Validation.new(errors || [])
16
- rescue OasParser::ResponseCodeNotFound, OasParser::MethodNotFound => e
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
@@ -6,52 +6,49 @@ require_relative 'utils'
6
6
 
7
7
  module OpenapiFirst
8
8
  class Router
9
- NOT_FOUND = Rack::Response.new('', 404).finish.freeze
10
- DEFAULT_NOT_FOUND_APP = ->(_env) { NOT_FOUND }
11
-
12
- def initialize(app, options) # rubocop:disable Metrics/MethodLength
9
+ def initialize(
10
+ app,
11
+ spec:,
12
+ raise_error: false,
13
+ parent_app: nil
14
+ )
13
15
  @app = app
14
- @parent_app = options.fetch(:parent_app, nil)
15
- @raise = options.fetch(:raise, false)
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
- endpoint = find_endpoint(env)
28
- return endpoint.call(env) if endpoint
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
- if @raise
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
- return @parent_app.call(env) if @parent_app
36
-
37
- @failure_app.call(env)
31
+ response
38
32
  end
39
33
 
40
- private
34
+ UNKNOWN_ROUTE_STATUS = [404, 405].freeze
35
+ ORIGINAL_PATH = 'openapi_first.path_info'
41
36
 
42
- def find_failure_app(option)
43
- return DEFAULT_NOT_FOUND_APP if option.nil?
44
- return @app if option == :continue
37
+ private
45
38
 
46
- option if option.respond_to?(:call)
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 find_endpoint(env)
50
- original_path_info = env[Rack::PATH_INFO]
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.recognize(env).endpoint
49
+ @router.call(env)
53
50
  ensure
54
- env[Rack::PATH_INFO] = original_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] = Utils.deep_stringify(env['router.params'])
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: 'is not valid' }
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OpenapiFirst
4
- VERSION = '0.11.0.alpha'
4
+ VERSION = '0.12.1'
5
5
  end
@@ -32,13 +32,13 @@ Gem::Specification.new do |spec|
32
32
  spec.bindir = 'exe'
33
33
  spec.require_paths = ['lib']
34
34
 
35
- spec.add_dependency 'deep_merge', '>= 1.2.1'
36
- spec.add_dependency 'hanami-router', '~> 2.0.alpha3'
37
- spec.add_dependency 'hanami-utils', '~> 2.0.alpha1'
38
- spec.add_dependency 'json_schemer', '~> 0.2'
39
- spec.add_dependency 'multi_json', '~> 1.14'
40
- spec.add_dependency 'oas_parser', '~> 0.25.1'
41
- spec.add_dependency 'rack', '~> 2.2'
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.11.0.alpha
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-05-24 00:00:00.000000000 Z
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: 1.3.1
241
+ version: '0'
241
242
  requirements: []
242
243
  rubygems_version: 3.1.2
243
244
  signing_key: