openapi_first 0.12.5 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,12 +2,12 @@
2
2
 
3
3
  require 'rack'
4
4
  require_relative 'inbox'
5
- require_relative 'find_handler'
5
+ require_relative 'default_operation_resolver'
6
6
 
7
7
  module OpenapiFirst
8
8
  class Responder
9
- def initialize(spec:, namespace:, resolver: FindHandler.new(spec, namespace))
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[operation.operation_id]
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 = JSONSchemer.schema(schema).validate(data).to_a.map do |error|
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?
@@ -40,7 +40,10 @@ module OpenapiFirst
40
40
 
41
41
  def raise_error(env)
42
42
  req = Rack::Request.new(env)
43
- msg = "Could not find definition for #{req.request_method} '#{req.path}' in API description #{@filepath}"
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, Metrics/MethodLength
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
@@ -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.deep_stringify(params) # rubocop:disable Metrics/MethodLength
21
- params.each_with_object({}) do |(key, value), output|
22
- output[key.to_s] =
23
- case value
24
- when ::Hash
25
- deep_stringify(value)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OpenapiFirst
4
- VERSION = '0.12.5'
4
+ VERSION = '0.14.0'
5
5
  end
@@ -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
- 'public gem pushes.'
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.12.5
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: 2020-08-10 00:00:00.000000000 Z
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: '0.2'
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: '0.2'
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: '0'
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.1.2
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: []