openapi_contracts 0.7.1 → 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 +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