reynard 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +81 -0
- data/lib/reynard/context.rb +3 -3
- data/lib/reynard/http/response.rb +34 -3
- data/lib/reynard/media_type.rb +3 -12
- data/lib/reynard/model.rb +16 -2
- data/lib/reynard/object_builder.rb +53 -33
- data/lib/reynard/schema.rb +101 -5
- data/lib/reynard/specification.rb +4 -38
- data/lib/reynard/template.rb +1 -1
- data/lib/reynard/version.rb +1 -1
- data/lib/reynard.rb +1 -1
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 149366d0bcc24959907a263c6223a2bb75cc7004a06cf153d1ef2dc35e26b91d
|
4
|
+
data.tar.gz: 8f698ffa4c958564a902d1039807324c9b1182e0a6638dedb16fc6eb3b6eb00f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5de35106aeb06b3478d807d34f5da335684530f6f4f28c789fe52d8a0aafd7f13dab27154ac39cfdc354c4e65bd6da755ea16d669603eae280c027f12d54d152
|
7
|
+
data.tar.gz: ff64d0913efc4e8a2b7c5b8420f94cefcf13567633906d48459ce8a653f5a65f8bd9d8c24368c68a8445320012561a24ff42568065232703675af4944b59f301
|
data/README.md
CHANGED
@@ -79,8 +79,79 @@ response.code #=> '200'
|
|
79
79
|
response.content_type #=> 'application/json'
|
80
80
|
response['Content-Type'] #=> 'application/json'
|
81
81
|
response.body #=> '{"name":"Sam Seven"}'
|
82
|
+
response.parsed_body #=> { "name" => "Sam Seven" }
|
82
83
|
```
|
83
84
|
|
85
|
+
## Schema and models
|
86
|
+
|
87
|
+
Reynard has an object builder that allows you to get a value object backed by model classes based on the resource schema.
|
88
|
+
|
89
|
+
For example, when the schema for a response is something like this:
|
90
|
+
|
91
|
+
```yaml
|
92
|
+
book:
|
93
|
+
type: object
|
94
|
+
properties:
|
95
|
+
name:
|
96
|
+
type: string
|
97
|
+
author:
|
98
|
+
type: object
|
99
|
+
properties:
|
100
|
+
name:
|
101
|
+
type: string
|
102
|
+
```
|
103
|
+
|
104
|
+
And the parsed body from the response is:
|
105
|
+
|
106
|
+
```json
|
107
|
+
{
|
108
|
+
"name": "Erebus",
|
109
|
+
"author": { "name": "Palin" }
|
110
|
+
}
|
111
|
+
```
|
112
|
+
|
113
|
+
You should be able to access it using:
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
response.object.class #=> Reynard::Models::Book
|
117
|
+
response.object.author.class #=> Reynard::Models::Author
|
118
|
+
response.object.author.name #=> 'Palin'
|
119
|
+
```
|
120
|
+
|
121
|
+
### Model name
|
122
|
+
|
123
|
+
Model names are determined in order:
|
124
|
+
|
125
|
+
1. From the `title` attribute of a schema
|
126
|
+
2. From the `$ref` pointing to the schema
|
127
|
+
3. From the path to the definition of the schema
|
128
|
+
|
129
|
+
```yaml
|
130
|
+
application/json:
|
131
|
+
schema:
|
132
|
+
$ref: "#/components/schemas/Book"
|
133
|
+
components:
|
134
|
+
schemas:
|
135
|
+
Book:
|
136
|
+
type: object
|
137
|
+
title: LibraryBook
|
138
|
+
```
|
139
|
+
|
140
|
+
In this example it would use the `title` and the model name would be `LibraryBook`. Otherwise it would use `Book` from the end of the `$ref`.
|
141
|
+
|
142
|
+
If neither of those are available it would look at the full expanded path.
|
143
|
+
|
144
|
+
```
|
145
|
+
books:
|
146
|
+
type: array
|
147
|
+
items:
|
148
|
+
type: object
|
149
|
+
```
|
150
|
+
|
151
|
+
For example, in case of an array item it would look at `books` and singularize it to `Book`.
|
152
|
+
|
153
|
+
If you run into issues where Reynard doesn't properly build an object for a nested resource, it's probably because of a naming issue. It's advised to add a `title` property to the schema definition with a unique name in that case.
|
154
|
+
|
84
155
|
## Logging
|
85
156
|
|
86
157
|
When you want to know what the Reynard client is doing you can enable logging.
|
@@ -97,6 +168,16 @@ The logging should be compatible with the Ruby on Rails logger.
|
|
97
168
|
reynard.logger(Rails.logger).execute
|
98
169
|
```
|
99
170
|
|
171
|
+
## Debugging
|
172
|
+
|
173
|
+
You can turn on debug logging in `Net::HTTP` by setting the `DEBUG` environment variable. After setting this, all HTTP interaction will be written to STDERR.
|
174
|
+
|
175
|
+
```sh
|
176
|
+
env DEBUG=true ruby script.rb
|
177
|
+
```
|
178
|
+
|
179
|
+
Internally this will set `http.debug_output = $stderr` on the HTTP object in the client.
|
180
|
+
|
100
181
|
## Mocking
|
101
182
|
|
102
183
|
You can mock Reynard requests by changing the HTTP implementation. The class **must** implement a single `request` method that accepts an URI and net/http request object. It **must** return a net/http response object or an object with the exact same interface.
|
data/lib/reynard/context.rb
CHANGED
@@ -14,7 +14,7 @@ class Reynard
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def base_url(base_url)
|
17
|
-
copy(base_url:
|
17
|
+
copy(base_url:)
|
18
18
|
end
|
19
19
|
|
20
20
|
def operation(operation_name)
|
@@ -43,7 +43,7 @@ class Reynard
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def logger(logger)
|
46
|
-
copy(logger:
|
46
|
+
copy(logger:)
|
47
47
|
end
|
48
48
|
|
49
49
|
def execute
|
@@ -71,7 +71,7 @@ class Reynard
|
|
71
71
|
Reynard::Http::Response.new(
|
72
72
|
specification: @specification,
|
73
73
|
request_context: @request_context,
|
74
|
-
http_response:
|
74
|
+
http_response:
|
75
75
|
)
|
76
76
|
end
|
77
77
|
end
|
@@ -14,6 +14,38 @@ class Reynard
|
|
14
14
|
@http_response = http_response
|
15
15
|
end
|
16
16
|
|
17
|
+
# True when the response code is in the 1xx range.
|
18
|
+
def informational?
|
19
|
+
code.start_with?('1')
|
20
|
+
end
|
21
|
+
|
22
|
+
# True when the response code is in the 2xx range.
|
23
|
+
def success?
|
24
|
+
code.start_with?('2')
|
25
|
+
end
|
26
|
+
|
27
|
+
# True when the response code is in the 3xx range.
|
28
|
+
def redirection?
|
29
|
+
code.start_with?('3')
|
30
|
+
end
|
31
|
+
|
32
|
+
# True when the response code is in the 4xx range.
|
33
|
+
def client_error?
|
34
|
+
code.start_with?('4')
|
35
|
+
end
|
36
|
+
|
37
|
+
# True when the response code is in the 5xx range.
|
38
|
+
def server_error?
|
39
|
+
code.start_with?('5')
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the parsed response body.
|
43
|
+
def parsed_body
|
44
|
+
return @parsed_body if defined?(@parsed_body)
|
45
|
+
|
46
|
+
@parsed_body = MultiJson.load(@http_response.body)
|
47
|
+
end
|
48
|
+
|
17
49
|
# Instantiates an object based on the schema that fits the response.
|
18
50
|
def object
|
19
51
|
return @object if defined?(@object)
|
@@ -37,10 +69,9 @@ class Reynard
|
|
37
69
|
end
|
38
70
|
|
39
71
|
def build_object_with_media_type(media_type)
|
40
|
-
ObjectBuilder.new(
|
41
|
-
media_type: media_type,
|
72
|
+
::Reynard::ObjectBuilder.new(
|
42
73
|
schema: @specification.schema(media_type.node),
|
43
|
-
|
74
|
+
parsed_body:
|
44
75
|
).call
|
45
76
|
end
|
46
77
|
|
data/lib/reynard/media_type.rb
CHANGED
@@ -1,21 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class Reynard
|
4
|
-
# Holds node reference
|
4
|
+
# Holds node reference a media type in the API specification.
|
5
5
|
class MediaType
|
6
|
-
attr_reader :node
|
6
|
+
attr_reader :node
|
7
7
|
|
8
|
-
def initialize(node
|
8
|
+
def initialize(node:)
|
9
9
|
@node = node
|
10
|
-
@schema_name = schema_name
|
11
|
-
end
|
12
|
-
|
13
|
-
def media_type
|
14
|
-
@node[6]
|
15
|
-
end
|
16
|
-
|
17
|
-
def response_code
|
18
|
-
@node[4]
|
19
10
|
end
|
20
11
|
end
|
21
12
|
end
|
data/lib/reynard/model.rb
CHANGED
@@ -3,13 +3,18 @@
|
|
3
3
|
class Reynard
|
4
4
|
# Superclass for dynamic classes generated by the object builder.
|
5
5
|
class Model
|
6
|
+
class << self
|
7
|
+
# Holds references to the full schema for the model if available.
|
8
|
+
attr_accessor :schema
|
9
|
+
end
|
10
|
+
|
6
11
|
def initialize(attributes)
|
7
12
|
self.attributes = attributes
|
8
13
|
end
|
9
14
|
|
10
15
|
def attributes=(attributes)
|
11
16
|
attributes.each do |name, value|
|
12
|
-
instance_variable_set("@#{name}", value)
|
17
|
+
instance_variable_set("@#{name}", self.class.cast(name, value))
|
13
18
|
end
|
14
19
|
end
|
15
20
|
|
@@ -21,9 +26,18 @@ class Reynard
|
|
21
26
|
end
|
22
27
|
|
23
28
|
def respond_to_missing?(attribute_name, *)
|
24
|
-
|
29
|
+
instance_variable_defined?("@#{attribute_name}")
|
25
30
|
rescue NameError
|
26
31
|
false
|
27
32
|
end
|
33
|
+
|
34
|
+
def self.cast(name, value)
|
35
|
+
return value unless schema
|
36
|
+
|
37
|
+
property = schema.property_schema(name)
|
38
|
+
return value unless property
|
39
|
+
|
40
|
+
Reynard::ObjectBuilder.new(schema: property, parsed_body: value).call
|
41
|
+
end
|
28
42
|
end
|
29
43
|
end
|
@@ -5,58 +5,78 @@ require 'ostruct'
|
|
5
5
|
class Reynard
|
6
6
|
# Defines dynamic classes based on schema and instantiates them for a response.
|
7
7
|
class ObjectBuilder
|
8
|
-
|
9
|
-
|
8
|
+
attr_reader :schema, :parsed_body
|
9
|
+
|
10
|
+
def initialize(schema:, parsed_body:, model_name: nil)
|
10
11
|
@schema = schema
|
11
|
-
@
|
12
|
+
@parsed_body = parsed_body
|
13
|
+
@model_name = model_name
|
12
14
|
end
|
13
15
|
|
14
|
-
def
|
15
|
-
|
16
|
-
self.class.model_class(@media_type.schema_name, @schema.object_type)
|
17
|
-
else
|
18
|
-
Reynard::Model
|
19
|
-
end
|
16
|
+
def model_name
|
17
|
+
@model_name || @schema.model_name
|
20
18
|
end
|
21
19
|
|
22
|
-
def
|
23
|
-
if @
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
end
|
20
|
+
def model_class
|
21
|
+
return @model_class if defined?(@model_class)
|
22
|
+
|
23
|
+
@model_class =
|
24
|
+
self.class.model_class_get(model_name) || self.class.model_class_set(model_name, schema)
|
28
25
|
end
|
29
26
|
|
30
27
|
def call
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
28
|
+
case schema.type
|
29
|
+
when 'object'
|
30
|
+
model_class.new(parsed_body)
|
31
|
+
when 'array'
|
32
|
+
cast_array
|
35
33
|
else
|
36
|
-
|
34
|
+
parsed_body
|
37
35
|
end
|
38
36
|
end
|
39
37
|
|
40
|
-
def
|
41
|
-
|
38
|
+
def self.model_class_get(model_name)
|
39
|
+
Kernel.const_get("::Reynard::Models::#{model_name}")
|
40
|
+
rescue NameError
|
41
|
+
nil
|
42
42
|
end
|
43
43
|
|
44
|
-
def self.
|
45
|
-
|
44
|
+
def self.model_class_set(model_name, schema)
|
45
|
+
if schema.type == 'array'
|
46
|
+
array_model_class_set(model_name)
|
47
|
+
else
|
48
|
+
object_model_class_set(model_name, schema)
|
49
|
+
end
|
46
50
|
end
|
47
51
|
|
48
|
-
def self.
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
+
def self.array_model_class_set(model_name)
|
53
|
+
return Array unless model_name
|
54
|
+
|
55
|
+
::Reynard::Models.const_set(model_name, Class.new(Array))
|
52
56
|
end
|
53
57
|
|
54
|
-
def self.
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
58
|
+
def self.object_model_class_set(model_name, schema)
|
59
|
+
return Reynard::Model unless model_name
|
60
|
+
|
61
|
+
model_class = Class.new(Reynard::Model)
|
62
|
+
model_class.schema = schema
|
63
|
+
::Reynard::Models.const_set(model_name, model_class)
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def cast_array
|
69
|
+
return unless parsed_body
|
70
|
+
|
71
|
+
item_schema = schema.item_schema
|
72
|
+
array = model_class.new
|
73
|
+
parsed_body.each do |item|
|
74
|
+
array << self.class.new(
|
75
|
+
schema: item_schema,
|
76
|
+
parsed_body: item
|
77
|
+
).call
|
59
78
|
end
|
79
|
+
array
|
60
80
|
end
|
61
81
|
end
|
62
82
|
end
|
data/lib/reynard/schema.rb
CHANGED
@@ -1,14 +1,110 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class Reynard
|
4
|
-
# Holds
|
4
|
+
# Holds a references to a schema definition in the specification.
|
5
5
|
class Schema
|
6
|
-
attr_reader :node, :
|
6
|
+
attr_reader :node, :namespace
|
7
7
|
|
8
|
-
def initialize(
|
8
|
+
def initialize(specification:, node:, namespace: nil)
|
9
|
+
@specification = specification
|
9
10
|
@node = node
|
10
|
-
@
|
11
|
-
|
11
|
+
@namespace = namespace
|
12
|
+
end
|
13
|
+
|
14
|
+
def type
|
15
|
+
return @type if defined?(@type)
|
16
|
+
|
17
|
+
@type = @specification.dig(*node, 'type')
|
18
|
+
end
|
19
|
+
|
20
|
+
def model_name
|
21
|
+
return @model_name if defined?(@model_name)
|
22
|
+
|
23
|
+
@model_name = find_model_name
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns the schema for items when the current schema is an array.
|
27
|
+
def item_schema
|
28
|
+
return unless type == 'array'
|
29
|
+
|
30
|
+
self.class.new(
|
31
|
+
specification: @specification,
|
32
|
+
node: [*node, 'items'],
|
33
|
+
namespace: [*namespace, model_name]
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns the schema for a propery in the schema.
|
38
|
+
def property_schema(name)
|
39
|
+
property_node = [*node, 'properties', name.to_s]
|
40
|
+
return unless @specification.dig(*property_node)
|
41
|
+
|
42
|
+
self.class.new(
|
43
|
+
specification: @specification,
|
44
|
+
node: property_node,
|
45
|
+
namespace: [*namespace, model_name]
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.title_model_name(model_name)
|
50
|
+
return unless model_name
|
51
|
+
|
52
|
+
model_name
|
53
|
+
.gsub(/[^[:alpha:]]/, ' ')
|
54
|
+
.gsub(/\s{2,}/, ' ')
|
55
|
+
.gsub(/(\s+)([[:alpha:]])/) { Regexp.last_match(2).upcase }
|
56
|
+
.strip
|
57
|
+
end
|
58
|
+
|
59
|
+
# Extracts a model name from a ref when there is a usable value.
|
60
|
+
#
|
61
|
+
# ref_model_name("#/components/schemas/Library") => "Library"
|
62
|
+
def self.ref_model_name(ref)
|
63
|
+
return unless ref
|
64
|
+
|
65
|
+
normalize_ref_model_name(ref.split('/')&.last)
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.normalize_ref_model_name(model_name)
|
69
|
+
# 1. Unescape encoded characters to create an UTF-8 string
|
70
|
+
# 2. Remove extensions for regularly used external schema files
|
71
|
+
# 3. Replace all non-alphabetic characters with a space (not allowed in Ruby constant)
|
72
|
+
# 4. Camelcase
|
73
|
+
Rack::Utils.unescape_path(model_name)
|
74
|
+
.gsub(/(.yml|.yaml|.json)\Z/, '')
|
75
|
+
.gsub(/[^[:alpha:]]/, ' ')
|
76
|
+
.gsub(/(\s+)([[:alpha:]])/) { Regexp.last_match(2).upcase }
|
77
|
+
.gsub(/\A(.)/) { Regexp.last_match(1).upcase }
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
# Returns a model name based on the schema's title or $ref.
|
83
|
+
def find_model_name
|
84
|
+
title_model_name || ref_model_name || node_model_name
|
85
|
+
end
|
86
|
+
|
87
|
+
def title_model_name
|
88
|
+
title = @specification.dig(*node, 'title')
|
89
|
+
return unless title
|
90
|
+
|
91
|
+
self.class.title_model_name(title)
|
92
|
+
end
|
93
|
+
|
94
|
+
def ref_model_name
|
95
|
+
parent = @specification.dig(*node[..-2])
|
96
|
+
ref = parent.dig('schema', '$ref') || parent.dig('items', '$ref')
|
97
|
+
return unless ref
|
98
|
+
|
99
|
+
self.class.ref_model_name(ref)
|
100
|
+
end
|
101
|
+
|
102
|
+
def node_model_name
|
103
|
+
self.class.title_model_name(node_property_name.capitalize.gsub(/[_-]/, ' '))
|
104
|
+
end
|
105
|
+
|
106
|
+
def node_property_name
|
107
|
+
node.last == 'items' ? node.at(-2).chomp('s') : node.last
|
12
108
|
end
|
13
109
|
end
|
14
110
|
end
|
@@ -66,10 +66,7 @@ class Reynard
|
|
66
66
|
response, media_type = media_type_response(responses, response_code, media_type)
|
67
67
|
return unless response
|
68
68
|
|
69
|
-
MediaType.new(
|
70
|
-
node: [*operation_node, 'responses', response_code, 'content', media_type],
|
71
|
-
schema_name: schema_name(response)
|
72
|
-
)
|
69
|
+
MediaType.new(node: [*operation_node, 'responses', response_code, 'content', media_type])
|
73
70
|
end
|
74
71
|
|
75
72
|
def media_type_response(responses, response_code, media_type)
|
@@ -83,14 +80,9 @@ class Reynard
|
|
83
80
|
end
|
84
81
|
|
85
82
|
def schema(media_type_node)
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
Schema.new(
|
90
|
-
node: [*media_type_node, 'schema'],
|
91
|
-
object_type: schema['type'],
|
92
|
-
item_schema_name: item_schema_name(schema)
|
93
|
-
)
|
83
|
+
return unless dig(*media_type_node, 'schema')
|
84
|
+
|
85
|
+
Schema.new(specification: self, node: [*media_type_node, 'schema'])
|
94
86
|
end
|
95
87
|
|
96
88
|
def self.media_type_matches?(media_type, expression)
|
@@ -100,18 +92,6 @@ class Reynard
|
|
100
92
|
false
|
101
93
|
end
|
102
94
|
|
103
|
-
def self.normalize_model_name(name)
|
104
|
-
# 1. Unescape encoded characters to create an UTF-8 string
|
105
|
-
# 2. Remove extensions for regularly used external schema files
|
106
|
-
# 3. Replace all non-alphabetic characters with a space (not allowed in Ruby constant)
|
107
|
-
# 4. Camelcase
|
108
|
-
Rack::Utils.unescape_path(name)
|
109
|
-
.gsub(/(.yml|.yaml|.json)\Z/, '')
|
110
|
-
.gsub(/[^[:alpha:]]/, ' ')
|
111
|
-
.gsub(/(\s+)([[:alpha:]])/) { Regexp.last_match(2).upcase }
|
112
|
-
.gsub(/\A(.)/) { Regexp.last_match(1).upcase }
|
113
|
-
end
|
114
|
-
|
115
95
|
private
|
116
96
|
|
117
97
|
def read
|
@@ -148,19 +128,5 @@ class Reynard
|
|
148
128
|
# rubocop:enable Metrics/AbcSize
|
149
129
|
# rubocop:enable Metrics/CyclomaticComplexity
|
150
130
|
# rubocop:enable Metrics/MethodLength
|
151
|
-
|
152
|
-
def schema_name(response)
|
153
|
-
ref = response.dig('schema', '$ref')
|
154
|
-
return unless ref
|
155
|
-
|
156
|
-
self.class.normalize_model_name(ref&.split('/')&.last)
|
157
|
-
end
|
158
|
-
|
159
|
-
def item_schema_name(schema)
|
160
|
-
ref = schema.dig('items', '$ref')
|
161
|
-
return unless ref
|
162
|
-
|
163
|
-
self.class.normalize_model_name(ref&.split('/')&.last)
|
164
|
-
end
|
165
131
|
end
|
166
132
|
end
|
data/lib/reynard/template.rb
CHANGED
data/lib/reynard/version.rb
CHANGED
data/lib/reynard.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: reynard
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Manfred Stienstra
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-11-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: multi_json
|
@@ -143,7 +143,7 @@ licenses:
|
|
143
143
|
- MIT
|
144
144
|
metadata:
|
145
145
|
rubygems_mfa_required: 'true'
|
146
|
-
post_install_message:
|
146
|
+
post_install_message:
|
147
147
|
rdoc_options: []
|
148
148
|
require_paths:
|
149
149
|
- lib
|
@@ -151,15 +151,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
151
151
|
requirements:
|
152
152
|
- - ">"
|
153
153
|
- !ruby/object:Gem::Version
|
154
|
-
version: '
|
154
|
+
version: '3.1'
|
155
155
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
156
156
|
requirements:
|
157
157
|
- - ">="
|
158
158
|
- !ruby/object:Gem::Version
|
159
159
|
version: '0'
|
160
160
|
requirements: []
|
161
|
-
rubygems_version: 3.
|
162
|
-
signing_key:
|
161
|
+
rubygems_version: 3.3.7
|
162
|
+
signing_key:
|
163
163
|
specification_version: 4
|
164
164
|
summary: Minimal OpenAPI client.
|
165
165
|
test_files: []
|