openapi_contracts 0.8.0 → 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 +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,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
@@ -1,38 +0,0 @@
1
- module OpenapiContracts::Validators
2
- class Body < Base
3
- private
4
-
5
- def validate
6
- if spec.no_content?
7
- @errors << 'Expected empty response body' if response.body.present?
8
- elsif !spec.supports_content_type?(response_content_type)
9
- @errors << "Undocumented response with content-type #{response_content_type.inspect}"
10
- else
11
- validate_schema
12
- end
13
- end
14
-
15
- def validate_schema
16
- schema = spec.schema_for(response_content_type)
17
- # Trick JSONSchemer into validating only against the response schema
18
- schemer = JSONSchemer.schema(schema.raw.merge('$ref' => schema.fragment))
19
- schemer.validate(JSON(response.body)).each do |err|
20
- @errors << error_to_message(err)
21
- end
22
- end
23
-
24
- def error_to_message(error)
25
- if error.key?('details')
26
- error['details'].to_a.map { |(key, val)|
27
- "#{key.humanize}: #{val} at #{error['data_pointer']}"
28
- }.to_sentence
29
- else
30
- "#{error['data'].inspect} at #{error['data_pointer']} does not match the schema"
31
- end
32
- end
33
-
34
- def response_content_type
35
- response.headers['Content-Type'].split(';').first
36
- end
37
- end
38
- end