openapi_contracts 0.8.0 → 0.9.1

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 +49 -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 +32 -22
  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