openapi_contracts 0.7.1 → 0.8.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.
- checksums.yaml +4 -4
- data/README.md +6 -3
- data/lib/openapi_contracts/doc/parameter.rb +86 -0
- data/lib/openapi_contracts/doc/path.rb +36 -1
- data/lib/openapi_contracts/doc.rb +8 -2
- data/lib/openapi_contracts/validators/body.rb +2 -2
- data/lib/openapi_contracts.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 25a37c81bfa9cb9d1d26f26a25b7c81f0675a153c199223639bdd48c76537cb6
|
4
|
+
data.tar.gz: 48529ed541f4ad72568d222d2a57e30ef7f1a7b5e59fbb447961a58d57eafe26
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1cc4ee62cea4e015834ae6a493bc771f13670b553cfdc1085b7dac79ea5a347583786ec4cd1db31c9054d88a010dd5d779c64f13df4388a3f55a3eae07ef577a
|
7
|
+
data.tar.gz: 9991124beb074d92107b1026e6ffd529cca988d8154eac6c6cc92aa5103df5de11cf917d1809545e26810de07959202f295a5d23a1a15077e639d2212bbc6286
|
data/README.md
CHANGED
@@ -47,8 +47,9 @@ it 'responds with 200 and matches the doc' do
|
|
47
47
|
|
48
48
|
The `match_openapi_doc($doc)` method allows passing options as a 2nd argument.
|
49
49
|
This allows overriding the default request.path lookup in case this does not find
|
50
|
-
the correct response definition in your schema. This
|
51
|
-
dynamic
|
50
|
+
the correct response definition in your schema. This can be usefull when there is
|
51
|
+
dynamic parameters in the path and the matcher fails to resolve the request path to
|
52
|
+
an endpoint in the openapi specification.
|
52
53
|
|
53
54
|
Example:
|
54
55
|
|
@@ -67,7 +68,9 @@ raise result.errors.merge("/n") unless result.valid?
|
|
67
68
|
|
68
69
|
### How it works
|
69
70
|
|
70
|
-
It uses the `request.path`, `request.method`, `status` and `headers` on the test subject
|
71
|
+
It uses the `request.path`, `request.method`, `status` and `headers` on the test subject
|
72
|
+
(which must be the response) to find the response schema in the OpenAPI document.
|
73
|
+
Then it does the following checks:
|
71
74
|
|
72
75
|
* The response is documented
|
73
76
|
* Required headers are present
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module OpenapiContracts
|
2
|
+
class Doc::Parameter
|
3
|
+
attr_reader :schema
|
4
|
+
|
5
|
+
def initialize(options)
|
6
|
+
@name = options[:name]
|
7
|
+
@in = options[:in]
|
8
|
+
@required = options[:required]
|
9
|
+
@schema = options[:schema]
|
10
|
+
end
|
11
|
+
|
12
|
+
def matches?(value)
|
13
|
+
case schema['type']
|
14
|
+
when 'integer'
|
15
|
+
integer_parameter_matches?(value)
|
16
|
+
when 'number'
|
17
|
+
number_parameter_matches?(value)
|
18
|
+
when 'string'
|
19
|
+
string_parameter_matches?(value)
|
20
|
+
else
|
21
|
+
# Not yet implemented
|
22
|
+
false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def integer_parameter_matches?(value)
|
29
|
+
return false unless /^-?\d+$/.match?(value)
|
30
|
+
|
31
|
+
parsed = value.to_i
|
32
|
+
return false unless minimum_number_matches?(parsed)
|
33
|
+
return false unless maximum_number_matches?(parsed)
|
34
|
+
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
def number_parameter_matches?(value)
|
39
|
+
return false unless /^-?(\d+\.)?\d+$/.match?(value)
|
40
|
+
|
41
|
+
parsed = value.to_f
|
42
|
+
return false unless minimum_number_matches?(parsed)
|
43
|
+
return false unless maximum_number_matches?(parsed)
|
44
|
+
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
def minimum_number_matches?(value)
|
49
|
+
if (min = schema['minimum'])
|
50
|
+
if schema['exclusiveMinimum']
|
51
|
+
return false if value <= min
|
52
|
+
elsif value < min
|
53
|
+
return false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
true
|
57
|
+
end
|
58
|
+
|
59
|
+
def maximum_number_matches?(value)
|
60
|
+
if (max = schema['maximum'])
|
61
|
+
if schema['exclusiveMaximum']
|
62
|
+
return false if value >= max
|
63
|
+
elsif value > max
|
64
|
+
return false
|
65
|
+
end
|
66
|
+
end
|
67
|
+
true
|
68
|
+
end
|
69
|
+
|
70
|
+
def string_parameter_matches?(value)
|
71
|
+
if (pat = schema['pattern'])
|
72
|
+
Regexp.new(pat).match?(value)
|
73
|
+
else
|
74
|
+
if (min = schema['minLength']) && (value.length < min)
|
75
|
+
return false
|
76
|
+
end
|
77
|
+
|
78
|
+
if (max = schema['maxLength']) && (value.length > max)
|
79
|
+
return false
|
80
|
+
end
|
81
|
+
|
82
|
+
true
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module OpenapiContracts
|
2
2
|
class Doc::Path
|
3
|
-
def initialize(schema)
|
3
|
+
def initialize(path, schema)
|
4
|
+
@path = path
|
4
5
|
@schema = schema
|
5
6
|
|
6
7
|
@methods = (known_http_methods & @schema.keys).to_h do |method|
|
@@ -8,16 +9,50 @@ module OpenapiContracts
|
|
8
9
|
end
|
9
10
|
end
|
10
11
|
|
12
|
+
def dynamic?
|
13
|
+
@path.include?('{')
|
14
|
+
end
|
15
|
+
|
16
|
+
def matches?(path)
|
17
|
+
@path == path || regexp_path.match(path) do |m|
|
18
|
+
m.named_captures.each do |k, v|
|
19
|
+
return false unless parameter_matches?(k, v)
|
20
|
+
end
|
21
|
+
true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
11
25
|
def methods
|
12
26
|
@methods.each_value
|
13
27
|
end
|
14
28
|
|
29
|
+
def static?
|
30
|
+
!dynamic?
|
31
|
+
end
|
32
|
+
|
15
33
|
def with_method(method)
|
16
34
|
@methods[method]
|
17
35
|
end
|
18
36
|
|
19
37
|
private
|
20
38
|
|
39
|
+
def parameter_matches?(name, value)
|
40
|
+
parameter = @schema['parameters']
|
41
|
+
&.find { |p| p['name'] == name && p['in'] == 'path' }
|
42
|
+
&.then { |s| Doc::Parameter.new(s.with_indifferent_access) }
|
43
|
+
|
44
|
+
return false unless parameter
|
45
|
+
|
46
|
+
parameter.matches?(value)
|
47
|
+
end
|
48
|
+
|
49
|
+
def regexp_path
|
50
|
+
re = /\{(\S+)\}/
|
51
|
+
@path.gsub(re) { |placeholder|
|
52
|
+
placeholder.match(re) { |m| "(?<#{m[1]}>[^/]*)" }
|
53
|
+
}.then { |str| Regexp.new(str) }
|
54
|
+
end
|
55
|
+
|
21
56
|
def known_http_methods
|
22
57
|
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods
|
23
58
|
%w(get head post put delete connect options trace patch).freeze
|
@@ -4,6 +4,7 @@ module OpenapiContracts
|
|
4
4
|
autoload :FileParser, 'openapi_contracts/doc/file_parser'
|
5
5
|
autoload :Method, 'openapi_contracts/doc/method'
|
6
6
|
autoload :Parser, 'openapi_contracts/doc/parser'
|
7
|
+
autoload :Parameter, 'openapi_contracts/doc/parameter'
|
7
8
|
autoload :Path, 'openapi_contracts/doc/path'
|
8
9
|
autoload :Response, 'openapi_contracts/doc/response'
|
9
10
|
autoload :Schema, 'openapi_contracts/doc/schema'
|
@@ -17,8 +18,9 @@ module OpenapiContracts
|
|
17
18
|
def initialize(schema)
|
18
19
|
@schema = Schema.new(schema)
|
19
20
|
@paths = @schema['paths'].to_h do |path, _|
|
20
|
-
[path, Path.new(@schema.at_pointer(['paths', path]))]
|
21
|
+
[path, Path.new(path, @schema.at_pointer(['paths', path]))]
|
21
22
|
end
|
23
|
+
@dynamic_paths = paths.select(&:dynamic?)
|
22
24
|
end
|
23
25
|
|
24
26
|
# Returns an Enumerator over all paths
|
@@ -42,7 +44,11 @@ module OpenapiContracts
|
|
42
44
|
end
|
43
45
|
|
44
46
|
def with_path(path)
|
45
|
-
@paths
|
47
|
+
if @paths.key?(path)
|
48
|
+
@paths[path]
|
49
|
+
else
|
50
|
+
@dynamic_paths.find { |p| p.matches?(path) }
|
51
|
+
end
|
46
52
|
end
|
47
53
|
end
|
48
54
|
end
|
@@ -23,9 +23,9 @@ module OpenapiContracts::Validators
|
|
23
23
|
|
24
24
|
def error_to_message(error)
|
25
25
|
if error.key?('details')
|
26
|
-
error['details'].to_a.map
|
26
|
+
error['details'].to_a.map { |(key, val)|
|
27
27
|
"#{key.humanize}: #{val} at #{error['data_pointer']}"
|
28
|
-
|
28
|
+
}.to_sentence
|
29
29
|
else
|
30
30
|
"#{error['data'].inspect} at #{error['data_pointer']} does not match the schema"
|
31
31
|
end
|
data/lib/openapi_contracts.rb
CHANGED
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.
|
4
|
+
version: 0.8.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-
|
11
|
+
date: 2023-07-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -141,6 +141,7 @@ files:
|
|
141
141
|
- lib/openapi_contracts/doc/file_parser.rb
|
142
142
|
- lib/openapi_contracts/doc/header.rb
|
143
143
|
- lib/openapi_contracts/doc/method.rb
|
144
|
+
- lib/openapi_contracts/doc/parameter.rb
|
144
145
|
- lib/openapi_contracts/doc/parser.rb
|
145
146
|
- lib/openapi_contracts/doc/path.rb
|
146
147
|
- lib/openapi_contracts/doc/response.rb
|