reynard 0.1.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d703f01c7c60de48b48ae223cd9c2e3a94a8639bb866eca234298331ad746d2a
4
- data.tar.gz: 9e88b9e21dc1b1d00357fcf8bdf2b6952676c5237369d7d950e97b379727e731
3
+ metadata.gz: ee7b44612f3b8e0fec7a0a3d1e18bc30366d1bea3470a8b3eb46a8d6180c85cd
4
+ data.tar.gz: e6c9629439a36c50ff214f932d5997ce004b21d12a1c71a6c727ab7ef38d722a
5
5
  SHA512:
6
- metadata.gz: b1cccaf532a955569236a07c5c8861519cf58c0c2abe3cdf218a8a57619a41d650e9f7226ccdb1a6ef1fddfe412cd9780032dcf8f68ecf995cd2176bb8774642
7
- data.tar.gz: 56acfbbabfea8a77e76cbee676bd0b397a608994f03f774022fd54d4e65fad06730de36aa9b122767edddb229f854eff9e8819d8a93d506ee5af66d799d18e82
6
+ metadata.gz: 159960b7bb4e38673c00baa531e3586135dcabedadd50dbd12da55eee31b77ea4d4d7d182974144adf602128b664e5d28f3c15c9e6d376f96774e78a45cdba2c
7
+ data.tar.gz: 5a7cbcc61c772858283855289f9b3a119b716c78994d16a4de6d5f34bc6921c633f023d362417657253885e6c35f157cb98d2771228e2790a0dff4df69bcc7f2
data/README.md CHANGED
@@ -51,7 +51,7 @@ reynard.base_url(base_url)
51
51
  Assuming there is an operation called `employeeByUuid` you can it as shown below.
52
52
 
53
53
  ```ruby
54
- employee = reynard.
54
+ response = reynard.
55
55
  operation('employeeByUuid').
56
56
  params(uuid: uuid).
57
57
  execute
@@ -60,12 +60,43 @@ employee = reynard.
60
60
  When an operation requires a body, you can add it as structured data.
61
61
 
62
62
  ```ruby
63
- employee = reynard.
63
+ response = reynard.
64
64
  operation('createEmployee').
65
65
  body(name: 'Sam Seven').
66
66
  execute
67
67
  ```
68
68
 
69
+ In case the response matches a response in the specification it will attempt to build an object using the specified schema.
70
+
71
+ ```ruby
72
+ response.object.name #=> 'Sam Seven'
73
+ ```
74
+
75
+ The response object shared much of its interface with `Net::HTTP::Response`.
76
+
77
+ ```ruby
78
+ response.code #=> '200'
79
+ response.content_type #=> 'application/json'
80
+ response['Content-Type'] #=> 'application/json'
81
+ response.body #=> '{"name":"Sam Seven"}'
82
+ ```
83
+
84
+ ## Logging
85
+
86
+ When you want to know what the Reynard client is doing you can enable logging.
87
+
88
+ ```ruby
89
+ logger = Logger.new($stdout)
90
+ logger.level = Logger::INFO
91
+ reynard.logger(logger).execute
92
+ ```
93
+
94
+ The logging should be compatible with the Ruby on Rails logger.
95
+
96
+ ```ruby
97
+ reynard.logger(Rails.logger).execute
98
+ ```
99
+
69
100
  ## Mocking
70
101
 
71
102
  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.
@@ -82,4 +113,4 @@ end
82
113
 
83
114
  ## Copyright and other legal
84
115
 
85
- See LICENCE.
116
+ See LICENCE.
@@ -42,8 +42,12 @@ class Reynard
42
42
  copy(headers: @request_context.headers.merge(headers))
43
43
  end
44
44
 
45
+ def logger(logger)
46
+ copy(logger: logger)
47
+ end
48
+
45
49
  def execute
46
- build_object(build_request.perform)
50
+ build_response(build_request.perform)
47
51
  end
48
52
 
49
53
  private
@@ -63,32 +67,12 @@ class Reynard
63
67
  Reynard::Http::Request.new(request_context: @request_context)
64
68
  end
65
69
 
66
- def build_object(http_response)
67
- media_type = @specification.media_type(
68
- @request_context.operation.node,
69
- http_response.code,
70
- http_response.content_type
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)
80
- ObjectBuilder.new(
81
- media_type: media_type,
82
- schema: @specification.schema(media_type.node),
70
+ def build_response(http_response)
71
+ Reynard::Http::Response.new(
72
+ specification: @specification,
73
+ request_context: @request_context,
83
74
  http_response: http_response
84
- ).call
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
75
+ )
92
76
  end
93
77
  end
94
78
  end
@@ -15,50 +15,23 @@ class Reynard
15
15
  end
16
16
 
17
17
  def perform
18
+ @request_context.logger.info { "#{@request_context.verb.upcase} #{uri}" }
18
19
  Reynard.http.request(uri, build_request)
19
20
  end
20
21
 
21
22
  private
22
23
 
23
- def build_request
24
- case @request_context.verb
25
- when 'get'
26
- build_http_get
27
- when 'post'
28
- build_http_post
29
- when 'put'
30
- build_http_put
31
- when 'patch'
32
- build_http_patch
33
- when 'delete'
34
- build_http_delete
35
- end
36
- end
37
-
38
- def build_http_get
39
- Net::HTTP::Get.new(uri, @request_context.headers)
40
- end
41
-
42
- def build_http_post
43
- post = Net::HTTP::Post.new(uri, @request_context.headers)
44
- post.body = @request_context.body
45
- post
24
+ def request_class
25
+ Net::HTTP.const_get(@request_context.verb.capitalize)
46
26
  end
47
27
 
48
- def build_http_put
49
- put = Net::HTTP::Put.new(uri, @request_context.headers)
50
- put.body = @request_context.body
51
- put
52
- end
53
-
54
- def build_http_patch
55
- patch = Net::HTTP::Patch.new(uri, @request_context.headers)
56
- patch.body = @request_context.body
57
- patch
58
- end
59
-
60
- def build_http_delete
61
- Net::HTTP::Delete.new(uri, @request_context.headers)
28
+ def build_request
29
+ request = request_class.new(uri, @request_context.headers)
30
+ if @request_context.body
31
+ @request_context.logger.debug { @request_context.body }
32
+ request.body = @request_context.body
33
+ end
34
+ request
62
35
  end
63
36
  end
64
37
  end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Reynard
4
+ class Http
5
+ # Wraps an HTTP response and returns an object when it can find a definition for the response
6
+ # in the specification.
7
+ class Response
8
+ extend Forwardable
9
+ def_delegators :@http_response, :code, :content_type, :[], :body
10
+
11
+ def initialize(specification:, request_context:, http_response:)
12
+ @specification = specification
13
+ @request_context = request_context
14
+ @http_response = http_response
15
+ end
16
+
17
+ # Instantiates an object based on the schema that fits the response.
18
+ def object
19
+ return @object if defined?(@object)
20
+
21
+ @object = build_object
22
+ end
23
+
24
+ private
25
+
26
+ def build_object
27
+ media_type = @specification.media_type(
28
+ @request_context.operation.node,
29
+ @http_response.code,
30
+ @http_response.content_type
31
+ )
32
+ if media_type
33
+ build_object_with_media_type(media_type)
34
+ else
35
+ build_object_without_media_type
36
+ end
37
+ end
38
+
39
+ def build_object_with_media_type(media_type)
40
+ ObjectBuilder.new(
41
+ media_type: media_type,
42
+ schema: @specification.schema(media_type.node),
43
+ http_response: @http_response
44
+ ).call
45
+ end
46
+
47
+ def build_object_without_media_type
48
+ # Try to parse the response as JSON and give up otherwise.
49
+ Reynard::Model.new(MultiJson.load(@http_response.body))
50
+ rescue StandardError
51
+ @http_response.body
52
+ end
53
+ end
54
+ end
55
+ end
data/lib/reynard/http.rb CHANGED
@@ -3,5 +3,6 @@
3
3
  class Reynard
4
4
  class Http
5
5
  autoload :Request, 'reynard/http/request'
6
+ autoload :Response, 'reynard/http/response'
6
7
  end
7
8
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ class Reynard
6
+ # Proxy class for a Logger object. Makes irrelevant logging actions a no-op.
7
+ class Logger
8
+ def initialize(logger)
9
+ @logger = logger
10
+ end
11
+
12
+ def debug(&block)
13
+ return unless @logger
14
+ return if @logger.level > ::Logger::DEBUG
15
+
16
+ @logger.debug(block.call)
17
+ end
18
+
19
+ def info(&block)
20
+ return unless @logger
21
+ return if @logger.level > ::Logger::INFO
22
+
23
+ @logger.info(block.call)
24
+ end
25
+ end
26
+ end
@@ -15,7 +15,7 @@ class Reynard
15
15
  if @media_type.schema_name
16
16
  self.class.model_class(@media_type.schema_name, @schema.object_type)
17
17
  else
18
- OpenStruct
18
+ Reynard::Model
19
19
  end
20
20
  end
21
21
 
@@ -23,7 +23,7 @@ class Reynard
23
23
  if @schema.item_schema_name
24
24
  self.class.model_class(@schema.item_schema_name, 'object')
25
25
  else
26
- OpenStruct
26
+ Reynard::Model
27
27
  end
28
28
  end
29
29
 
@@ -10,6 +10,14 @@ class Reynard
10
10
  :body,
11
11
  keyword_init: true
12
12
  ) do
13
+ attr_writer :logger
14
+
15
+ def logger
16
+ @logger = nil unless defined?(@logger)
17
+
18
+ Reynard::Logger.new(@logger)
19
+ end
20
+
13
21
  def verb
14
22
  operation&.verb
15
23
  end
@@ -5,6 +5,8 @@ require 'rack'
5
5
  class Reynard
6
6
  # Wraps the YAML representation of an OpenAPI specification.
7
7
  class Specification
8
+ VERBS = %w[get put post delete options head patch trace].freeze
9
+
8
10
  def initialize(filename:)
9
11
  @filename = filename
10
12
  @data = read
@@ -32,7 +34,15 @@ class Reynard
32
34
  def build_grouped_params(operation_node, params)
33
35
  return {} unless params
34
36
 
35
- GroupedParameters.new(dig(*operation_node, 'parameters'), params).to_h
37
+ GroupedParameters.new(
38
+ [
39
+ # Parameters can be shared between methods on a path or be specific to an operation. The
40
+ # parameters on the operation level override those at the path level.
41
+ dig(*operation_node, 'parameters'),
42
+ dig(*operation_node[..-2], 'parameters')
43
+ ].compact.flatten,
44
+ params
45
+ ).to_h
36
46
  end
37
47
 
38
48
  # Returns a serialized body instance to serialize a request body and figure out the request
@@ -43,7 +53,7 @@ class Reynard
43
53
 
44
54
  def operation(operation_name)
45
55
  dig('paths').each do |path, operations|
46
- operations.each do |verb, operation|
56
+ operations.slice(*VERBS).each do |verb, operation|
47
57
  return Operation.new(node: ['paths', path, verb]) if operation_name == operation['operationId']
48
58
  end
49
59
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Reynard
4
- VERSION = '0.1.0'
4
+ VERSION = '0.4.0'
5
5
  end
data/lib/reynard.rb CHANGED
@@ -17,6 +17,7 @@ class Reynard
17
17
  autoload :Context, 'reynard/context'
18
18
  autoload :GroupedParameters, 'reynard/grouped_parameters'
19
19
  autoload :Http, 'reynard/http'
20
+ autoload :Logger, 'reynard/logger'
20
21
  autoload :MediaType, 'reynard/media_type'
21
22
  autoload :Model, 'reynard/model'
22
23
  autoload :Models, 'reynard/models'
@@ -34,9 +35,9 @@ class Reynard
34
35
  @specification = Specification.new(filename: filename)
35
36
  end
36
37
 
37
- # Assign an object that follows Reynard's internal request interface to mock requests or use a
38
- # different HTTP client.
39
38
  class << self
39
+ # Assign an object that follows Reynard's internal request interface to mock requests or use a
40
+ # different HTTP client.
40
41
  attr_writer :http
41
42
  end
42
43
 
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.1.0
4
+ version: 0.4.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: 2021-11-05 00:00:00.000000000 Z
11
+ date: 2022-04-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: multi_json
@@ -124,6 +124,8 @@ files:
124
124
  - lib/reynard/grouped_parameters.rb
125
125
  - lib/reynard/http.rb
126
126
  - lib/reynard/http/request.rb
127
+ - lib/reynard/http/response.rb
128
+ - lib/reynard/logger.rb
127
129
  - lib/reynard/media_type.rb
128
130
  - lib/reynard/model.rb
129
131
  - lib/reynard/models.rb
@@ -139,8 +141,9 @@ files:
139
141
  homepage: https://github.com/Manfred/reynard
140
142
  licenses:
141
143
  - MIT
142
- metadata: {}
143
- post_install_message:
144
+ metadata:
145
+ rubygems_mfa_required: 'true'
146
+ post_install_message:
144
147
  rdoc_options: []
145
148
  require_paths:
146
149
  - lib
@@ -156,7 +159,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
156
159
  version: '0'
157
160
  requirements: []
158
161
  rubygems_version: 3.1.6
159
- signing_key:
162
+ signing_key:
160
163
  specification_version: 4
161
164
  summary: Minimal OpenAPI client.
162
165
  test_files: []