openapi_first 0.11.0.alpha → 0.12.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: