reynard 0.1.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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: []