reynard 0.0.3 → 0.0.7
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 +9 -0
- data/lib/reynard/context.rb +32 -3
- data/lib/reynard/http/request.rb +1 -7
- data/lib/reynard/model.rb +5 -1
- data/lib/reynard/object_builder.rb +32 -8
- data/lib/reynard/request_context.rb +1 -0
- data/lib/reynard/schema.rb +3 -2
- data/lib/reynard/serialized_body.rb +23 -0
- data/lib/reynard/specification.rb +35 -3
- data/lib/reynard/version.rb +1 -1
- data/lib/reynard.rb +11 -1
- metadata +35 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 464bcfde66e61c85b7dd949995fc3acd2db91048b80828bb4c8161c1bd5170d8
|
4
|
+
data.tar.gz: e219398e59d21c45e42ece44f1cadcbdb6c0ef4b5a5927cb627bafa249138b6a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b3fb63f88b99b653541e92e948ba5e8cadb6edbbdb45bf6d939452d98663b7d40bf9782f5cb816665d847a0c807777e3604fef279430e30025e0228600c280e9
|
7
|
+
data.tar.gz: 616c0ad91e77e8d69fa66d48000079cfc530563f276e85c4bdca96a83f17e9a273a8f88ff5d8d2b5bef60160d1241263f30a83fb3be2ff065ef87974d997fadd
|
data/README.md
CHANGED
@@ -57,6 +57,15 @@ employee = reynard.
|
|
57
57
|
execute
|
58
58
|
```
|
59
59
|
|
60
|
+
When an operation requires a body, you can add it as structured data.
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
employee = reynard.
|
64
|
+
operation('createEmployee').
|
65
|
+
body(name: 'Sam Seven').
|
66
|
+
execute
|
67
|
+
```
|
68
|
+
|
60
69
|
## Copyright and other legal
|
61
70
|
|
62
71
|
See LICENCE.
|
data/lib/reynard/context.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'ostruct'
|
4
|
+
|
3
5
|
class Reynard
|
4
6
|
# Exposes a public interface to build a request context.
|
5
7
|
class Context
|
@@ -24,8 +26,20 @@ class Reynard
|
|
24
26
|
copy(params: @specification.build_grouped_params(@request_context.operation.node, params))
|
25
27
|
end
|
26
28
|
|
29
|
+
def body(data)
|
30
|
+
return unless @request_context.operation
|
31
|
+
|
32
|
+
serialized_body = @specification.build_body(@request_context.operation.node, data)
|
33
|
+
return unless serialized_body
|
34
|
+
|
35
|
+
copy(
|
36
|
+
headers: @request_context.headers.merge(serialized_body.headers),
|
37
|
+
body: serialized_body.to_s
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
27
41
|
def headers(headers)
|
28
|
-
copy(headers: headers)
|
42
|
+
copy(headers: @request_context.headers.merge(headers))
|
29
43
|
end
|
30
44
|
|
31
45
|
def execute
|
@@ -35,7 +49,7 @@ class Reynard
|
|
35
49
|
private
|
36
50
|
|
37
51
|
def build_request_context
|
38
|
-
RequestContext.new(base_url: @specification.default_base_url)
|
52
|
+
RequestContext.new(base_url: @specification.default_base_url, headers: {})
|
39
53
|
end
|
40
54
|
|
41
55
|
def copy(**properties)
|
@@ -53,13 +67,28 @@ class Reynard
|
|
53
67
|
media_type = @specification.media_type(
|
54
68
|
@request_context.operation.node,
|
55
69
|
http_response.code,
|
56
|
-
http_response
|
70
|
+
http_response.content_type
|
57
71
|
)
|
72
|
+
if media_type
|
73
|
+
build_object_with_media_type(http_response, media_type)
|
74
|
+
else
|
75
|
+
build_object_without_media_type(http_response)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def build_object_with_media_type(http_response, media_type)
|
58
80
|
ObjectBuilder.new(
|
59
81
|
media_type: media_type,
|
60
82
|
schema: @specification.schema(media_type.node),
|
61
83
|
http_response: http_response
|
62
84
|
).call
|
63
85
|
end
|
86
|
+
|
87
|
+
def build_object_without_media_type(http_response)
|
88
|
+
# Try to parse the response as JSON and give up otherwise.
|
89
|
+
OpenStruct.new(MultiJson.load(http_response.body))
|
90
|
+
rescue StandardError
|
91
|
+
http_response.body
|
92
|
+
end
|
64
93
|
end
|
65
94
|
end
|
data/lib/reynard/http/request.rb
CHANGED
@@ -15,7 +15,7 @@ class Reynard
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def perform
|
18
|
-
|
18
|
+
Reynard.http.request(uri, build_request)
|
19
19
|
end
|
20
20
|
|
21
21
|
private
|
@@ -29,12 +29,6 @@ class Reynard
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
-
def build_http
|
33
|
-
http = Net::HTTP.new(uri.hostname, uri.port)
|
34
|
-
http.set_debug_output($stderr) if ENV['DEBUG']
|
35
|
-
http
|
36
|
-
end
|
37
|
-
|
38
32
|
def build_http_get
|
39
33
|
Net::HTTP::Get.new(uri, @request_context.headers)
|
40
34
|
end
|
data/lib/reynard/model.rb
CHANGED
@@ -16,10 +16,14 @@ class Reynard
|
|
16
16
|
# Until we can set accessors based on the schema
|
17
17
|
def method_missing(attribute_name, *)
|
18
18
|
instance_variable_get("@#{attribute_name}")
|
19
|
+
rescue NameError
|
20
|
+
raise NoMethodError, "undefined method `#{attribute_name}' for #{inspect}"
|
19
21
|
end
|
20
22
|
|
21
23
|
def respond_to_missing?(attribute_name, *)
|
22
|
-
|
24
|
+
!instance_variable_get("@#{attribute_name}").nil?
|
25
|
+
rescue NameError
|
26
|
+
false
|
23
27
|
end
|
24
28
|
end
|
25
29
|
end
|
@@ -11,28 +11,52 @@ class Reynard
|
|
11
11
|
@http_response = http_response
|
12
12
|
end
|
13
13
|
|
14
|
-
# Object.const_set(@media_type.schema_name, Class.new(Reynard::Model))
|
15
14
|
def object_class
|
16
15
|
if @media_type.schema_name
|
17
|
-
self.class.model_class(@media_type.schema_name)
|
16
|
+
self.class.model_class(@media_type.schema_name, @schema.object_type)
|
17
|
+
else
|
18
|
+
OpenStruct
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def item_object_class
|
23
|
+
if @schema.item_schema_name
|
24
|
+
self.class.model_class(@schema.item_schema_name, 'object')
|
18
25
|
else
|
19
26
|
OpenStruct
|
20
27
|
end
|
21
28
|
end
|
22
29
|
|
23
30
|
def call
|
24
|
-
|
25
|
-
|
26
|
-
|
31
|
+
if @schema.object_type == 'array'
|
32
|
+
array = object_class.new
|
33
|
+
data.each { |attributes| array << item_object_class.new(attributes) }
|
34
|
+
array
|
27
35
|
else
|
28
|
-
|
36
|
+
object_class.new(data)
|
29
37
|
end
|
30
38
|
end
|
31
39
|
|
32
|
-
def
|
40
|
+
def data
|
41
|
+
@data ||= MultiJson.load(@http_response.body)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.model_class(name, object_type)
|
45
|
+
model_class_get(name) || model_class_set(name, object_type)
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.model_class_get(name)
|
33
49
|
Reynard::Models.const_get(name)
|
34
50
|
rescue NameError
|
35
|
-
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.model_class_set(name, object_type)
|
55
|
+
if object_type == 'array'
|
56
|
+
Reynard::Models.const_set(name, Class.new(Array))
|
57
|
+
else
|
58
|
+
Reynard::Models.const_set(name, Class.new(Reynard::Model))
|
59
|
+
end
|
36
60
|
end
|
37
61
|
end
|
38
62
|
end
|
data/lib/reynard/schema.rb
CHANGED
@@ -3,11 +3,12 @@
|
|
3
3
|
class Reynard
|
4
4
|
# Holds reference and object type for a schema in the API specification.
|
5
5
|
class Schema
|
6
|
-
attr_reader :node, :object_type
|
6
|
+
attr_reader :node, :object_type, :item_schema_name
|
7
7
|
|
8
|
-
def initialize(node:, object_type:)
|
8
|
+
def initialize(node:, object_type:, item_schema_name:)
|
9
9
|
@node = node
|
10
10
|
@object_type = object_type
|
11
|
+
@item_schema_name = item_schema_name
|
11
12
|
end
|
12
13
|
end
|
13
14
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Reynard
|
4
|
+
# Serializes a request body and returns headers appropriate for the request.
|
5
|
+
class SerializedBody
|
6
|
+
def initialize(content, data)
|
7
|
+
@content = content
|
8
|
+
@data = data
|
9
|
+
end
|
10
|
+
|
11
|
+
def content_type
|
12
|
+
'application/json'
|
13
|
+
end
|
14
|
+
|
15
|
+
def headers
|
16
|
+
{ 'Content-Type' => content_type }
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
MultiJson.dump(@data)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -33,6 +33,12 @@ class Reynard
|
|
33
33
|
GroupedParameters.new(dig(*operation_node, 'parameters'), params).to_h
|
34
34
|
end
|
35
35
|
|
36
|
+
# Returns a serialized body instance to serialize a request body and figure out the request
|
37
|
+
# headers.
|
38
|
+
def build_body(operation_node, data)
|
39
|
+
SerializedBody.new(dig(*operation_node, 'requestBody', 'content'), data)
|
40
|
+
end
|
41
|
+
|
36
42
|
def operation(operation_name)
|
37
43
|
dig('paths').each do |path, operations|
|
38
44
|
operations.each do |verb, operation|
|
@@ -44,8 +50,8 @@ class Reynard
|
|
44
50
|
|
45
51
|
def media_type(operation_node, response_code, media_type)
|
46
52
|
responses = dig(*operation_node, 'responses')
|
47
|
-
response_code = responses.key?(response_code)
|
48
|
-
response = responses
|
53
|
+
response_code = 'default' unless responses.key?(response_code)
|
54
|
+
response, media_type = media_type_response(responses, response_code, media_type)
|
49
55
|
return unless response
|
50
56
|
|
51
57
|
MediaType.new(
|
@@ -54,11 +60,32 @@ class Reynard
|
|
54
60
|
)
|
55
61
|
end
|
56
62
|
|
63
|
+
def media_type_response(responses, response_code, media_type)
|
64
|
+
defined_responses = responses.dig(response_code, 'content')
|
65
|
+
return unless defined_responses&.any?
|
66
|
+
|
67
|
+
defined_responses.each do |expression, response|
|
68
|
+
return response, expression if self.class.media_type_matches?(media_type, expression)
|
69
|
+
end
|
70
|
+
nil
|
71
|
+
end
|
72
|
+
|
57
73
|
def schema(media_type_node)
|
58
74
|
schema = dig(*media_type_node, 'schema')
|
59
75
|
return unless schema
|
60
76
|
|
61
|
-
Schema.new(
|
77
|
+
Schema.new(
|
78
|
+
node: [*media_type_node, 'schema'],
|
79
|
+
object_type: schema['type'],
|
80
|
+
item_schema_name: item_schema_name(schema)
|
81
|
+
)
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.media_type_matches?(media_type, expression)
|
85
|
+
return true unless media_type
|
86
|
+
return true if expression == media_type
|
87
|
+
|
88
|
+
false
|
62
89
|
end
|
63
90
|
|
64
91
|
private
|
@@ -89,6 +116,11 @@ class Reynard
|
|
89
116
|
ref&.split('/')&.last
|
90
117
|
end
|
91
118
|
|
119
|
+
def item_schema_name(schema)
|
120
|
+
ref = schema.dig('items', '$ref')
|
121
|
+
ref&.split('/')&.last
|
122
|
+
end
|
123
|
+
|
92
124
|
def object_name(_schema)
|
93
125
|
'Book'
|
94
126
|
end
|
data/lib/reynard/version.rb
CHANGED
data/lib/reynard.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'net/http/persistent'
|
3
4
|
require 'forwardable'
|
4
5
|
require 'multi_json'
|
5
6
|
require 'rack'
|
@@ -14,6 +15,7 @@ class Reynard
|
|
14
15
|
def_delegators :@specification, :servers
|
15
16
|
|
16
17
|
autoload :Context, 'reynard/context'
|
18
|
+
autoload :GroupedParameters, 'reynard/grouped_parameters'
|
17
19
|
autoload :Http, 'reynard/http'
|
18
20
|
autoload :MediaType, 'reynard/media_type'
|
19
21
|
autoload :Model, 'reynard/model'
|
@@ -22,16 +24,24 @@ class Reynard
|
|
22
24
|
autoload :Operation, 'reynard/operation'
|
23
25
|
autoload :RequestContext, 'reynard/request_context'
|
24
26
|
autoload :Schema, 'reynard/schema'
|
27
|
+
autoload :SerializedBody, 'reynard/serialized_body'
|
25
28
|
autoload :Server, 'reynard/server'
|
26
29
|
autoload :Specification, 'reynard/specification'
|
27
30
|
autoload :Template, 'reynard/template'
|
28
|
-
autoload :GroupedParameters, 'reynard/grouped_parameters'
|
29
31
|
autoload :VERSION, 'reynard/version'
|
30
32
|
|
31
33
|
def initialize(filename:)
|
32
34
|
@specification = Specification.new(filename: filename)
|
33
35
|
end
|
34
36
|
|
37
|
+
def self.http
|
38
|
+
@http ||= begin
|
39
|
+
http = Net::HTTP::Persistent.new(name: 'Reynard')
|
40
|
+
http.debug_output = $stderr if ENV['DEBUG']
|
41
|
+
http
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
35
45
|
private
|
36
46
|
|
37
47
|
def build_context
|
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.0.
|
4
|
+
version: 0.0.7
|
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: 2021-
|
11
|
+
date: 2021-09-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: multi_json
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: net-http-persistent
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: rack
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,6 +94,20 @@ dependencies:
|
|
80
94
|
- - ">="
|
81
95
|
- !ruby/object:Gem::Version
|
82
96
|
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: webrick
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
83
111
|
description: |2
|
84
112
|
Reynard is an OpenAPI client for Ruby. It operates directly on the OpenAPI specification without
|
85
113
|
the need to generate any source code.
|
@@ -103,6 +131,7 @@ files:
|
|
103
131
|
- lib/reynard/operation.rb
|
104
132
|
- lib/reynard/request_context.rb
|
105
133
|
- lib/reynard/schema.rb
|
134
|
+
- lib/reynard/serialized_body.rb
|
106
135
|
- lib/reynard/server.rb
|
107
136
|
- lib/reynard/specification.rb
|
108
137
|
- lib/reynard/template.rb
|
@@ -111,7 +140,7 @@ homepage: https://github.com/Manfred/reynard
|
|
111
140
|
licenses:
|
112
141
|
- MIT
|
113
142
|
metadata: {}
|
114
|
-
post_install_message:
|
143
|
+
post_install_message:
|
115
144
|
rdoc_options: []
|
116
145
|
require_paths:
|
117
146
|
- lib
|
@@ -126,8 +155,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
126
155
|
- !ruby/object:Gem::Version
|
127
156
|
version: '0'
|
128
157
|
requirements: []
|
129
|
-
rubygems_version: 3.
|
130
|
-
signing_key:
|
158
|
+
rubygems_version: 3.1.6
|
159
|
+
signing_key:
|
131
160
|
specification_version: 4
|
132
161
|
summary: Minimal OpenAPI client.
|
133
162
|
test_files: []
|