openapi_contracts 0.7.1 → 0.9.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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +43 -19
  3. data/lib/openapi_contracts/doc/operation.rb +27 -0
  4. data/lib/openapi_contracts/doc/parameter.rb +49 -0
  5. data/lib/openapi_contracts/doc/path.rb +32 -12
  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 -14
  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 +11 -5
  30. metadata +31 -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
@@ -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
@@ -1,19 +1,25 @@
1
1
  require 'active_support'
2
2
  require 'active_support/core_ext/array'
3
+ require 'active_support/core_ext/hash'
3
4
  require 'active_support/core_ext/class'
4
5
  require 'active_support/core_ext/module'
5
6
  require 'active_support/core_ext/string'
7
+ require 'rubygems/version'
6
8
 
7
9
  require 'json_schemer'
10
+ require 'rack'
8
11
  require 'yaml'
9
12
 
10
13
  module OpenapiContracts
11
- autoload :Doc, 'openapi_contracts/doc'
12
- autoload :Helper, 'openapi_contracts/helper'
13
- autoload :Match, 'openapi_contracts/match'
14
- 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'
15
21
 
16
- Env = Struct.new(:spec, :response, :expected_status)
22
+ Env = Struct.new(:operation, :options, :request, :response, keyword_init: true)
17
23
 
18
24
  module_function
19
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.7.1
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-06-15 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,22 +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
144
- - lib/openapi_contracts/doc/parser.rb
142
+ - lib/openapi_contracts/doc/operation.rb
143
+ - lib/openapi_contracts/doc/parameter.rb
145
144
  - lib/openapi_contracts/doc/path.rb
145
+ - lib/openapi_contracts/doc/pointer.rb
146
+ - lib/openapi_contracts/doc/request.rb
146
147
  - lib/openapi_contracts/doc/response.rb
147
148
  - lib/openapi_contracts/doc/schema.rb
149
+ - lib/openapi_contracts/doc/with_parameters.rb
148
150
  - lib/openapi_contracts/helper.rb
149
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
150
159
  - lib/openapi_contracts/rspec.rb
151
160
  - lib/openapi_contracts/validators.rb
152
161
  - lib/openapi_contracts/validators/base.rb
153
- - lib/openapi_contracts/validators/body.rb
154
162
  - lib/openapi_contracts/validators/documented.rb
155
163
  - lib/openapi_contracts/validators/headers.rb
156
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
157
168
  homepage: https://github.com/mkon/openapi_contracts
158
169
  licenses:
159
170
  - MIT
@@ -167,7 +178,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
167
178
  requirements:
168
179
  - - ">="
169
180
  - !ruby/object:Gem::Version
170
- version: '2.7'
181
+ version: '3.0'
171
182
  - - "<"
172
183
  - !ruby/object:Gem::Version
173
184
  version: '3.3'
@@ -1,85 +0,0 @@
1
- module OpenapiContracts
2
- class Doc::FileParser
3
- Result = Struct.new(:data, :path) do
4
- def to_mergable_hash
5
- d = data
6
- path.ascend do |p|
7
- d = {p.basename.to_s => d}
8
- end
9
- d
10
- end
11
- end
12
-
13
- def self.parse(rootfile, pathname)
14
- new(rootfile, pathname).call
15
- end
16
-
17
- def initialize(rootfile, pathname)
18
- @root = rootfile.parent
19
- @rootfile = rootfile
20
- @pathname = pathname.relative? ? @root.join(pathname) : pathname
21
- end
22
-
23
- def call
24
- schema = YAML.safe_load(File.read(@pathname))
25
- schema = transform_hash(schema)
26
- Result.new(schema, @pathname.relative_path_from(@root).sub_ext(''))
27
- end
28
-
29
- private
30
-
31
- def transform_hash(data)
32
- data.each_with_object({}) do |(key, val), m|
33
- if val.is_a?(Array)
34
- m[key] = transform_array(val)
35
- elsif val.is_a?(Hash)
36
- m[key] = transform_hash(val)
37
- elsif key == '$ref'
38
- m.merge! transform_pointer(key, val)
39
- else
40
- m[key] = val
41
- end
42
- end
43
- end
44
-
45
- def transform_array(data)
46
- data.each_with_object([]) do |item, m|
47
- case item
48
- when Hash
49
- m.push transform_hash(item)
50
- when Array
51
- m.push transform_array(item)
52
- else
53
- m.push item
54
- end
55
- end
56
- end
57
-
58
- def transform_pointer(key, target)
59
- if %r{^#/(?<pointer>.*)} =~ target
60
- # A JSON Pointer
61
- {key => generate_absolute_pointer(pointer)}
62
- elsif %r{^(?<relpath>[^#]+)(?:#/(?<pointer>.*))?} =~ target
63
- if relpath.start_with?('paths') # path description file pointer
64
- # Inline the file contents
65
- self.class.parse(@rootfile, Pathname(relpath)).data
66
- else # A file pointer with potential JSON sub-pointer
67
- tgt = @pathname.parent.relative_path_from(@root).join(relpath).sub_ext('')
68
- tgt = tgt.join(pointer) if pointer
69
- {key => "#/#{tgt}"}
70
- end
71
- else
72
- {key => target}
73
- end
74
- end
75
-
76
- # A JSON pointer to the currently parsed file as seen from the root openapi file
77
- def generate_absolute_pointer(json_pointer)
78
- if @rootfile == @pathname
79
- "#/#{json_pointer}"
80
- else
81
- "#/#{@pathname.relative_path_from(@root).sub_ext('').join(json_pointer)}"
82
- end
83
- end
84
- end
85
- end
@@ -1,18 +0,0 @@
1
- module OpenapiContracts
2
- class Doc::Method
3
- def initialize(schema)
4
- @schema = schema
5
- @responses = schema['responses'].to_h do |status, _|
6
- [status, Doc::Response.new(schema.navigate('responses', status))]
7
- end
8
- end
9
-
10
- def responses
11
- @responses.each_value
12
- end
13
-
14
- def with_status(status)
15
- @responses[status]
16
- end
17
- end
18
- end
@@ -1,44 +0,0 @@
1
- module OpenapiContracts
2
- class Doc::Parser
3
- def self.call(dir, filename)
4
- new(dir.join(filename)).parse
5
- end
6
-
7
- def initialize(rootfile)
8
- @rootfile = rootfile
9
- end
10
-
11
- def parse
12
- file = Doc::FileParser.parse(@rootfile, @rootfile)
13
- data = file.data
14
- data.deep_merge! merge_components
15
- nullable_to_type!(data)
16
- # debugger
17
- end
18
-
19
- private
20
-
21
- def merge_components
22
- data = {}
23
- Dir[File.expand_path('components/**/*.yaml', @rootfile.parent)].each do |file|
24
- result = Doc::FileParser.parse(@rootfile, Pathname(file))
25
- data.deep_merge!(result.to_mergable_hash)
26
- end
27
- data
28
- end
29
-
30
- def nullable_to_type!(object)
31
- case object
32
- when Hash
33
- if object['type'] && object['nullable']
34
- object['type'] = [object['type'], 'null']
35
- object.delete 'nullable'
36
- else
37
- object.each_value { |o| nullable_to_type! o }
38
- end
39
- when Array
40
- object.each { |o| nullable_to_type! o }
41
- end
42
- end
43
- end
44
- end