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.
@@ -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: []