openapi_contracts 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +41 -20
  3. data/lib/openapi_contracts/doc/operation.rb +27 -0
  4. data/lib/openapi_contracts/doc/parameter.rb +22 -59
  5. data/lib/openapi_contracts/doc/path.rb +23 -38
  6. data/lib/openapi_contracts/doc/pointer.rb +81 -0
  7. data/lib/openapi_contracts/doc/request.rb +17 -0
  8. data/lib/openapi_contracts/doc/response.rb +5 -5
  9. data/lib/openapi_contracts/doc/schema.rb +44 -10
  10. data/lib/openapi_contracts/doc/with_parameters.rb +9 -0
  11. data/lib/openapi_contracts/doc.rb +17 -20
  12. data/lib/openapi_contracts/match.rb +34 -10
  13. data/lib/openapi_contracts/operation_router.rb +33 -0
  14. data/lib/openapi_contracts/parser/transformers/base.rb +15 -0
  15. data/lib/openapi_contracts/parser/transformers/nullable.rb +10 -0
  16. data/lib/openapi_contracts/parser/transformers/pointer.rb +34 -0
  17. data/lib/openapi_contracts/parser/transformers.rb +5 -0
  18. data/lib/openapi_contracts/parser.rb +61 -0
  19. data/lib/openapi_contracts/payload_parser.rb +39 -0
  20. data/lib/openapi_contracts/rspec.rb +2 -2
  21. data/lib/openapi_contracts/validators/base.rb +5 -1
  22. data/lib/openapi_contracts/validators/documented.rb +12 -5
  23. data/lib/openapi_contracts/validators/headers.rb +4 -0
  24. data/lib/openapi_contracts/validators/http_status.rb +2 -6
  25. data/lib/openapi_contracts/validators/request_body.rb +26 -0
  26. data/lib/openapi_contracts/validators/response_body.rb +28 -0
  27. data/lib/openapi_contracts/validators/schema_validation.rb +40 -0
  28. data/lib/openapi_contracts/validators.rb +9 -6
  29. data/lib/openapi_contracts.rb +10 -5
  30. metadata +30 -20
  31. data/lib/openapi_contracts/doc/file_parser.rb +0 -85
  32. data/lib/openapi_contracts/doc/method.rb +0 -18
  33. data/lib/openapi_contracts/doc/parser.rb +0 -44
  34. data/lib/openapi_contracts/validators/body.rb +0 -38
@@ -1,11 +1,18 @@
1
1
  module OpenapiContracts
2
2
  class Match
3
+ DEFAULT_OPTIONS = {request_body: false}.freeze
4
+ MIN_REQUEST_ANCESTORS = %w(Rack::Request::Env Rack::Request::Helpers).freeze
5
+ MIN_RESPONSE_ANCESTORS = %w(Rack::Response::Helpers).freeze
6
+
3
7
  attr_reader :errors
4
8
 
5
9
  def initialize(doc, response, options = {})
6
10
  @doc = doc
7
11
  @response = response
8
- @options = options
12
+ @request = options.delete(:request) { response.request }
13
+ @options = DEFAULT_OPTIONS.merge(options)
14
+ raise ArgumentError, "#{@response} must be compatible with Rack::Response::Helpers" unless response_compatible?
15
+ raise ArgumentError, "#{@request} must be compatible with Rack::Request::{Env,Helpers}" unless request_compatible?
9
16
  end
10
17
 
11
18
  def valid?
@@ -17,18 +24,35 @@ module OpenapiContracts
17
24
 
18
25
  private
19
26
 
20
- def lookup_api_spec
21
- @doc.response_for(
22
- @options.fetch(:path, @response.request.path),
23
- @response.request.request_method.downcase,
24
- @response.status.to_s
27
+ def matchers
28
+ env = Env.new(
29
+ options: @options,
30
+ operation: operation,
31
+ request: @request,
32
+ response: @response
25
33
  )
34
+ validators = Validators::ALL.dup
35
+ validators.delete(Validators::HttpStatus) unless @options[:status]
36
+ validators.delete(Validators::RequestBody) unless @options[:request_body]
37
+ validators.reverse
38
+ .reduce(->(err) { err }) { |s, m| m.new(s, env) }
26
39
  end
27
40
 
28
- def matchers
29
- env = Env.new(lookup_api_spec, @response, @options[:status])
30
- Validators::ALL.reverse
31
- .reduce(->(err) { err }) { |s, m| m.new(s, env) }
41
+ def operation
42
+ @doc.operation_for(
43
+ @options.fetch(:path, @request.path),
44
+ @request.request_method.downcase
45
+ )
46
+ end
47
+
48
+ def request_compatible?
49
+ ancestors = @request.class.ancestors.map(&:to_s)
50
+ MIN_REQUEST_ANCESTORS.all? { |s| ancestors.include?(s) }
51
+ end
52
+
53
+ def response_compatible?
54
+ ancestors = @response.class.ancestors.map(&:to_s)
55
+ MIN_RESPONSE_ANCESTORS.all? { |s| ancestors.include?(s) }
32
56
  end
33
57
  end
34
58
  end
@@ -0,0 +1,33 @@
1
+ module OpenapiContracts
2
+ class OperationRouter
3
+ def initialize(doc)
4
+ @doc = doc
5
+ @dynamic_paths = doc.paths.select(&:dynamic?)
6
+ end
7
+
8
+ def route(actual_path, method)
9
+ @doc.with_path(actual_path)&.then { |p| return p.with_method(method) }
10
+
11
+ @dynamic_paths.each do |path|
12
+ next unless path.supports_method?(method)
13
+ next unless m = path.path_regexp.match(actual_path)
14
+
15
+ operation = path.with_method(method)
16
+ parameters = (path.parameters + operation.parameters).select(&:in_path?)
17
+
18
+ return operation if parameter_match?(m.named_captures, parameters)
19
+ end
20
+
21
+ nil
22
+ end
23
+
24
+ private
25
+
26
+ def parameter_match?(actual_params, parameters)
27
+ actual_params.each do |k, v|
28
+ return false unless parameters&.find { |s| s.name == k }&.matches?(v)
29
+ end
30
+ true
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,15 @@
1
+ module OpenapiContracts::Parser::Transformers
2
+ class Base
3
+ def initialize(parser, cwd, pointer)
4
+ @parser = parser
5
+ @cwd = cwd
6
+ @pointer = pointer
7
+ end
8
+
9
+ # :nocov:
10
+ def call
11
+ raise NotImplementedError
12
+ end
13
+ # :nocov:
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ module OpenapiContracts::Parser::Transformers
2
+ class Nullable < Base
3
+ def call(object)
4
+ return unless object['type'].present? && object['nullable'] == true
5
+
6
+ object.delete('nullable')
7
+ object['type'] = [object['type'], 'null']
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,34 @@
1
+ module OpenapiContracts::Parser::Transformers
2
+ class Pointer < Base
3
+ def call(object)
4
+ return unless object['$ref'].present?
5
+
6
+ object['$ref'] = transform_pointer(object['$ref'])
7
+ end
8
+
9
+ private
10
+
11
+ def transform_pointer(target)
12
+ if %r{^#/(?<pointer>.*)} =~ target
13
+ # A JSON Pointer
14
+ generate_absolute_pointer(pointer)
15
+ elsif %r{^(?<relpath>[^#]+)(?:#/(?<pointer>.*))?} =~ target
16
+ ptr = @parser.filenesting[@cwd.join(relpath)]
17
+ tgt = ptr.to_json_pointer
18
+ tgt += "/#{pointer}" if pointer
19
+ tgt
20
+ else
21
+ target
22
+ end
23
+ end
24
+
25
+ # A JSON pointer to the currently parsed file as seen from the root openapi file
26
+ def generate_absolute_pointer(json_pointer)
27
+ if @pointer.empty?
28
+ "#/#{json_pointer}"
29
+ else
30
+ "#{@pointer.to_json_pointer}/#{json_pointer}"
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ module OpenapiContracts::Parser::Transformers
2
+ autoload :Base, 'openapi_contracts/parser/transformers/base'
3
+ autoload :Nullable, 'openapi_contracts/parser/transformers/nullable'
4
+ autoload :Pointer, 'openapi_contracts/parser/transformers/pointer'
5
+ end
@@ -0,0 +1,61 @@
1
+ module OpenapiContracts
2
+ class Parser
3
+ autoload :Transformers, 'openapi_contracts/parser/transformers'
4
+
5
+ TRANSFORMERS = [Transformers::Nullable, Transformers::Pointer].freeze
6
+
7
+ def self.call(dir, filename)
8
+ new(dir.join(filename)).parse
9
+ end
10
+
11
+ attr_reader :filenesting, :rootfile
12
+
13
+ def initialize(rootfile)
14
+ @cwd = rootfile.parent
15
+ @rootfile = rootfile
16
+ @filenesting = {}
17
+ end
18
+
19
+ def parse
20
+ @filenesting = build_file_list
21
+ @filenesting.each_with_object({}) do |(path, pointer), schema|
22
+ target = pointer.to_a.reduce(schema) { |d, k| d[k] ||= {} }
23
+ target.delete('$ref') # ref file pointers must be replaced
24
+ target.merge! file_to_data(path, pointer)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def build_file_list
31
+ list = {@rootfile.relative_path_from(@cwd) => Doc::Pointer[]}
32
+ Dir[File.expand_path('components/**/*.yaml', @cwd)].each do |file|
33
+ pathname = Pathname(file).relative_path_from(@cwd)
34
+ pointer = Doc::Pointer.from_path pathname.sub_ext('')
35
+ list.merge! pathname => pointer
36
+ end
37
+ YAML.safe_load_file(@rootfile).fetch('paths') { {} }.each_pair do |k, v|
38
+ next unless v['$ref'] && !v['$ref'].start_with?('#')
39
+
40
+ list.merge! Pathname(v['$ref']) => Doc::Pointer['paths', k]
41
+ end
42
+ list
43
+ end
44
+
45
+ def file_to_data(pathname, pointer)
46
+ YAML.safe_load_file(@cwd.join(pathname)).tap do |data|
47
+ transform_objects!(data, pathname.parent, pointer)
48
+ end
49
+ end
50
+
51
+ def transform_objects!(object, cwd, pointer)
52
+ case object
53
+ when Hash
54
+ object.each_value { |v| transform_objects!(v, cwd, pointer) }
55
+ TRANSFORMERS.map { |t| t.new(self, cwd, pointer) }.each { |t| t.call(object) }
56
+ when Array
57
+ object.each { |o| transform_objects!(o, cwd, pointer) }
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,39 @@
1
+ require 'singleton'
2
+
3
+ module OpenapiContracts
4
+ class PayloadParser
5
+ include Singleton
6
+
7
+ class << self
8
+ delegate :parse, :register, to: :instance
9
+ end
10
+
11
+ Entry = Struct.new(:matcher, :parser) do
12
+ def call(raw)
13
+ parser.call(raw)
14
+ end
15
+
16
+ def match?(media_type)
17
+ matcher == media_type || matcher.match?(media_type)
18
+ end
19
+ end
20
+
21
+ def initialize
22
+ @parsers = []
23
+ end
24
+
25
+ def parse(media_type, payload)
26
+ parser = @parsers.find { |e| e.match?(media_type) }
27
+ raise ArgumentError, "#{media_type.inspect} is not supported yet" unless parser
28
+
29
+ parser.call(payload)
30
+ end
31
+
32
+ def register(matcher, parser)
33
+ @parsers << Entry.new(matcher, parser)
34
+ end
35
+ end
36
+
37
+ PayloadParser.register(%r{(/|\+)json$}, ->(raw) { JSON(raw) })
38
+ PayloadParser.register('application/x-www-form-urlencoded', ->(raw) { Rack::Utils.parse_nested_query(raw) })
39
+ end
@@ -14,12 +14,12 @@ RSpec::Matchers.define :match_openapi_doc do |doc, options = {}| # rubocop:disab
14
14
  end
15
15
 
16
16
  description do
17
- desc = 'to match the openapi schema'
17
+ desc = 'match the openapi schema'
18
18
  desc << " with #{http_status_desc(@status)}" if @status
19
19
  desc
20
20
  end
21
21
 
22
- failure_message do |_reponse|
22
+ failure_message do |_response|
23
23
  @errors.map { |e| "* #{e}" }.join("\n")
24
24
  end
25
25
 
@@ -22,7 +22,11 @@ module OpenapiContracts::Validators
22
22
 
23
23
  private
24
24
 
25
- delegate :expected_status, :response, :spec, to: :@env
25
+ delegate :operation, :options, :request, :response, to: :@env
26
+
27
+ def response_desc
28
+ "#{request.request_method} #{request.path}"
29
+ end
26
30
 
27
31
  # :nocov:
28
32
  def validate
@@ -1,18 +1,25 @@
1
1
  module OpenapiContracts::Validators
2
+ # Purpose of this validator
3
+ # * ensure the operation is documented (combination http-method + path)
4
+ # * ensure the response-status is documented on the operation
2
5
  class Documented < Base
3
6
  self.final = true
4
7
 
5
8
  private
6
9
 
7
10
  def validate
8
- return if spec
11
+ return operation_missing unless operation
9
12
 
10
- status_desc = http_status_desc(response.status)
11
- @errors << "Undocumented request/response for #{response_desc.inspect} with #{status_desc}"
13
+ response_missing unless operation.response_for_status(response.status)
14
+ end
15
+
16
+ def operation_missing
17
+ @errors << "Undocumented operation for #{response_desc.inspect}"
12
18
  end
13
19
 
14
- def response_desc
15
- "#{response.request.request_method} #{response.request.path}"
20
+ def response_missing
21
+ status_desc = http_status_desc(response.status)
22
+ @errors << "Undocumented response for #{response_desc.inspect} with #{status_desc}"
16
23
  end
17
24
  end
18
25
  end
@@ -2,6 +2,10 @@ module OpenapiContracts::Validators
2
2
  class Headers < Base
3
3
  private
4
4
 
5
+ def spec
6
+ @spec ||= operation.response_for_status(response.status)
7
+ end
8
+
5
9
  def validate
6
10
  spec.headers.each do |header|
7
11
  value = response.headers[header.name]
@@ -5,13 +5,9 @@ module OpenapiContracts::Validators
5
5
  private
6
6
 
7
7
  def validate
8
- return if expected_status.blank? || expected_status == response.status
8
+ return if options[:status] == response.status
9
9
 
10
- @errors << "Response has #{http_status_desc}"
11
- end
12
-
13
- def http_status_desc
14
- "http status #{Rack::Utils::HTTP_STATUS_CODES[response.status]} (#{response.status})"
10
+ @errors << "Response has #{http_status_desc(response.status)}"
15
11
  end
16
12
  end
17
13
  end
@@ -0,0 +1,26 @@
1
+ module OpenapiContracts::Validators
2
+ class RequestBody < Base
3
+ include SchemaValidation
4
+
5
+ private
6
+
7
+ delegate :media_type, to: :request
8
+ delegate :request_body, to: :operation
9
+
10
+ def data_for_validation
11
+ request.body.rewind
12
+ raw = request.body.read
13
+ OpenapiContracts::PayloadParser.parse(media_type, raw)
14
+ end
15
+
16
+ def validate
17
+ if !request_body
18
+ @errors << "Undocumented request body for #{response_desc.inspect}"
19
+ elsif !request_body.supports_media_type?(media_type)
20
+ @errors << "Undocumented request with media-type #{media_type.inspect}"
21
+ else
22
+ @errors += validate_schema(request_body.schema_for(media_type), data_for_validation)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,28 @@
1
+ module OpenapiContracts::Validators
2
+ class ResponseBody < Base
3
+ include SchemaValidation
4
+
5
+ private
6
+
7
+ delegate :media_type, to: :response
8
+
9
+ def data_for_validation
10
+ # ActionDispatch::Response body is a plain string, while Rack::Response returns an array
11
+ OpenapiContracts::PayloadParser.parse(media_type, Array.wrap(response.body).join)
12
+ end
13
+
14
+ def spec
15
+ @spec ||= operation.response_for_status(response.status)
16
+ end
17
+
18
+ def validate
19
+ if spec.no_content?
20
+ @errors << 'Expected empty response body' if Array.wrap(response.body).any?(&:present?)
21
+ elsif !spec.supports_media_type?(media_type)
22
+ @errors << "Undocumented response with content-type #{media_type.inspect}"
23
+ else
24
+ @errors += validate_schema(spec.schema_for(media_type), data_for_validation)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,40 @@
1
+ module OpenapiContracts::Validators
2
+ module SchemaValidation
3
+ module_function
4
+
5
+ def build_validation_schema(schema)
6
+ schema.raw.merge(
7
+ '$ref' => schema.fragment,
8
+ '$schema' => schema_draft_version(schema)
9
+ )
10
+ end
11
+
12
+ def error_to_message(error)
13
+ pointer = " at #{error['data_pointer']}" if error['data_pointer'].present?
14
+ if error.key?('details')
15
+ error['details'].to_a.map { |(key, val)|
16
+ "#{key.humanize}: #{val}#{pointer}"
17
+ }.to_sentence
18
+ else
19
+ "#{error['data'].inspect}#{pointer} does not match the schema"
20
+ end
21
+ end
22
+
23
+ def schema_draft_version(schema)
24
+ if schema.openapi_version.blank? || schema.openapi_version < Gem::Version.new('3.1')
25
+ # Closest compatible version is actually draft 5 but not supported by json-schemer
26
+ 'http://json-schema.org/draft-04/schema#'
27
+ else
28
+ # >= 3.1 is actually comptable with 2020-12 but not yet supported by json-schemer
29
+ 'http://json-schema.org/draft-07/schema#'
30
+ end
31
+ end
32
+
33
+ def validate_schema(schema, data)
34
+ schemer = JSONSchemer.schema(build_validation_schema(schema))
35
+ schemer.validate(data).map do |err|
36
+ error_to_message(err)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -1,16 +1,19 @@
1
1
  module OpenapiContracts
2
2
  module Validators
3
- autoload :Base, 'openapi_contracts/validators/base'
4
- autoload :Body, 'openapi_contracts/validators/body'
5
- autoload :Documented, 'openapi_contracts/validators/documented'
6
- autoload :Headers, 'openapi_contracts/validators/headers'
7
- autoload :HttpStatus, 'openapi_contracts/validators/http_status'
3
+ autoload :Base, 'openapi_contracts/validators/base'
4
+ autoload :Documented, 'openapi_contracts/validators/documented'
5
+ autoload :Headers, 'openapi_contracts/validators/headers'
6
+ autoload :HttpStatus, 'openapi_contracts/validators/http_status'
7
+ autoload :RequestBody, 'openapi_contracts/validators/request_body'
8
+ autoload :ResponseBody, 'openapi_contracts/validators/response_body'
9
+ autoload :SchemaValidation, 'openapi_contracts/validators/schema_validation'
8
10
 
9
11
  # Defines order of matching
10
12
  ALL = [
11
13
  Documented,
12
14
  HttpStatus,
13
- Body,
15
+ RequestBody,
16
+ ResponseBody,
14
17
  Headers
15
18
  ].freeze
16
19
  end
@@ -4,17 +4,22 @@ require 'active_support/core_ext/hash'
4
4
  require 'active_support/core_ext/class'
5
5
  require 'active_support/core_ext/module'
6
6
  require 'active_support/core_ext/string'
7
+ require 'rubygems/version'
7
8
 
8
9
  require 'json_schemer'
10
+ require 'rack'
9
11
  require 'yaml'
10
12
 
11
13
  module OpenapiContracts
12
- autoload :Doc, 'openapi_contracts/doc'
13
- autoload :Helper, 'openapi_contracts/helper'
14
- autoload :Match, 'openapi_contracts/match'
15
- autoload :Validators, 'openapi_contracts/validators'
14
+ autoload :Doc, 'openapi_contracts/doc'
15
+ autoload :Helper, 'openapi_contracts/helper'
16
+ autoload :Match, 'openapi_contracts/match'
17
+ autoload :OperationRouter, 'openapi_contracts/operation_router'
18
+ autoload :Parser, 'openapi_contracts/parser'
19
+ autoload :PayloadParser, 'openapi_contracts/payload_parser'
20
+ autoload :Validators, 'openapi_contracts/validators'
16
21
 
17
- Env = Struct.new(:spec, :response, :expected_status)
22
+ Env = Struct.new(:operation, :options, :request, :response, keyword_init: true)
18
23
 
19
24
  module_function
20
25
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openapi_contracts
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - mkon
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-07-07 00:00:00.000000000 Z
11
+ date: 2023-08-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -36,42 +36,42 @@ dependencies:
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: 0.2.20
39
+ version: 1.0.3
40
40
  type: :runtime
41
41
  prerelease: false
42
42
  version_requirements: !ruby/object:Gem::Requirement
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: 0.2.20
46
+ version: 1.0.3
47
47
  - !ruby/object:Gem::Dependency
48
- name: json_spec
48
+ name: rack
49
49
  requirement: !ruby/object:Gem::Requirement
50
50
  requirements:
51
- - - "~>"
51
+ - - ">="
52
52
  - !ruby/object:Gem::Version
53
- version: 1.1.5
54
- type: :development
53
+ version: 2.0.0
54
+ type: :runtime
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  requirements:
58
- - - "~>"
58
+ - - ">="
59
59
  - !ruby/object:Gem::Version
60
- version: 1.1.5
60
+ version: 2.0.0
61
61
  - !ruby/object:Gem::Dependency
62
- name: rack
62
+ name: json_spec
63
63
  requirement: !ruby/object:Gem::Requirement
64
64
  requirements:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
- version: 3.0.0
67
+ version: 1.1.5
68
68
  type: :development
69
69
  prerelease: false
70
70
  version_requirements: !ruby/object:Gem::Requirement
71
71
  requirements:
72
72
  - - "~>"
73
73
  - !ruby/object:Gem::Version
74
- version: 3.0.0
74
+ version: 1.1.5
75
75
  - !ruby/object:Gem::Dependency
76
76
  name: rspec
77
77
  requirement: !ruby/object:Gem::Requirement
@@ -92,14 +92,14 @@ dependencies:
92
92
  requirements:
93
93
  - - '='
94
94
  - !ruby/object:Gem::Version
95
- version: 1.52.0
95
+ version: 1.54.1
96
96
  type: :development
97
97
  prerelease: false
98
98
  version_requirements: !ruby/object:Gem::Requirement
99
99
  requirements:
100
100
  - - '='
101
101
  - !ruby/object:Gem::Version
102
- version: 1.52.0
102
+ version: 1.54.1
103
103
  - !ruby/object:Gem::Dependency
104
104
  name: rubocop-rspec
105
105
  requirement: !ruby/object:Gem::Requirement
@@ -138,23 +138,33 @@ files:
138
138
  - README.md
139
139
  - lib/openapi_contracts.rb
140
140
  - lib/openapi_contracts/doc.rb
141
- - lib/openapi_contracts/doc/file_parser.rb
142
141
  - lib/openapi_contracts/doc/header.rb
143
- - lib/openapi_contracts/doc/method.rb
142
+ - lib/openapi_contracts/doc/operation.rb
144
143
  - lib/openapi_contracts/doc/parameter.rb
145
- - lib/openapi_contracts/doc/parser.rb
146
144
  - lib/openapi_contracts/doc/path.rb
145
+ - lib/openapi_contracts/doc/pointer.rb
146
+ - lib/openapi_contracts/doc/request.rb
147
147
  - lib/openapi_contracts/doc/response.rb
148
148
  - lib/openapi_contracts/doc/schema.rb
149
+ - lib/openapi_contracts/doc/with_parameters.rb
149
150
  - lib/openapi_contracts/helper.rb
150
151
  - lib/openapi_contracts/match.rb
152
+ - lib/openapi_contracts/operation_router.rb
153
+ - lib/openapi_contracts/parser.rb
154
+ - lib/openapi_contracts/parser/transformers.rb
155
+ - lib/openapi_contracts/parser/transformers/base.rb
156
+ - lib/openapi_contracts/parser/transformers/nullable.rb
157
+ - lib/openapi_contracts/parser/transformers/pointer.rb
158
+ - lib/openapi_contracts/payload_parser.rb
151
159
  - lib/openapi_contracts/rspec.rb
152
160
  - lib/openapi_contracts/validators.rb
153
161
  - lib/openapi_contracts/validators/base.rb
154
- - lib/openapi_contracts/validators/body.rb
155
162
  - lib/openapi_contracts/validators/documented.rb
156
163
  - lib/openapi_contracts/validators/headers.rb
157
164
  - lib/openapi_contracts/validators/http_status.rb
165
+ - lib/openapi_contracts/validators/request_body.rb
166
+ - lib/openapi_contracts/validators/response_body.rb
167
+ - lib/openapi_contracts/validators/schema_validation.rb
158
168
  homepage: https://github.com/mkon/openapi_contracts
159
169
  licenses:
160
170
  - MIT
@@ -168,7 +178,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
168
178
  requirements:
169
179
  - - ">="
170
180
  - !ruby/object:Gem::Version
171
- version: '2.7'
181
+ version: '3.0'
172
182
  - - "<"
173
183
  - !ruby/object:Gem::Version
174
184
  version: '3.3'