reynard 0.0.7 → 0.2.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: 464bcfde66e61c85b7dd949995fc3acd2db91048b80828bb4c8161c1bd5170d8
4
- data.tar.gz: e219398e59d21c45e42ece44f1cadcbdb6c0ef4b5a5927cb627bafa249138b6a
3
+ metadata.gz: 95ca8eb7744c1550bfcf705a92d97331d9c558b08dec5f2369b166b285b38edc
4
+ data.tar.gz: a140a0dbe5f74c95f43d2aa785dd3c96a322eab2893b2d8a629abd3b9859724e
5
5
  SHA512:
6
- metadata.gz: b3fb63f88b99b653541e92e948ba5e8cadb6edbbdb45bf6d939452d98663b7d40bf9782f5cb816665d847a0c807777e3604fef279430e30025e0228600c280e9
7
- data.tar.gz: 616c0ad91e77e8d69fa66d48000079cfc530563f276e85c4bdca96a83f17e9a273a8f88ff5d8d2b5bef60160d1241263f30a83fb3be2ff065ef87974d997fadd
6
+ metadata.gz: 77148ca6897e1e7b0479348cfe536ff94afc5c1741b6fe59afea7940b442d7c46a71914868f572f3588b7d85d95f38446bd8d28885a22a032c91a86e71e3da28
7
+ data.tar.gz: eab59f1738c99af0f2c8fba6a4fb09f15a5a26f31f86de91125d9f299d2d50844060bd3c13db33b260f267a82e269175e0c69d2cdeb4f32d397e99bca74e9694
data/README.md CHANGED
@@ -40,7 +40,7 @@ reynard.base_url('http://test.example.com/v1')
40
40
  You also have access to all servers in the specification so you can automatically select one however you want.
41
41
 
42
42
  ```ruby
43
- base_url = @reynard.servers.map(&:url).find do |url|
43
+ base_url = reynard.servers.map(&:url).find do |url|
44
44
  /staging/.match(url)
45
45
  end
46
46
  reynard.base_url(base_url)
@@ -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,41 @@ 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
+ ## Mocking
85
+
86
+ 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.
87
+
88
+ ```ruby
89
+ Reynard.http = MyMock.new
90
+
91
+ class MyMock
92
+ def request(uri, net_http_request)
93
+ Net::HTTPResponse::CODE_TO_OBJ['404'].new('HTTP/1.1', '200', 'OK')
94
+ end
95
+ end
96
+ ```
97
+
69
98
  ## Copyright and other legal
70
99
 
71
100
  See LICENCE.
@@ -43,7 +43,7 @@ class Reynard
43
43
  end
44
44
 
45
45
  def execute
46
- build_object(build_request.perform)
46
+ build_response(build_request.perform)
47
47
  end
48
48
 
49
49
  private
@@ -63,32 +63,12 @@ class Reynard
63
63
  Reynard::Http::Request.new(request_context: @request_context)
64
64
  end
65
65
 
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),
66
+ def build_response(http_response)
67
+ Reynard::Http::Response.new(
68
+ specification: @specification,
69
+ request_context: @request_context,
83
70
  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
71
+ )
92
72
  end
93
73
  end
94
74
  end
@@ -26,6 +26,12 @@ class Reynard
26
26
  build_http_get
27
27
  when 'post'
28
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
29
35
  end
30
36
  end
31
37
 
@@ -38,6 +44,22 @@ class Reynard
38
44
  post.body = @request_context.body
39
45
  post
40
46
  end
47
+
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)
62
+ end
41
63
  end
42
64
  end
43
65
  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
+ OpenStruct.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
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'rack'
4
+
3
5
  class Reynard
4
6
  # Wraps the YAML representation of an OpenAPI specification.
5
7
  class Specification
@@ -88,11 +90,20 @@ class Reynard
88
90
  false
89
91
  end
90
92
 
93
+ def self.normalize_model_name(name)
94
+ # 1. Unescape encoded characters to create an UTF-8 string
95
+ # 2. Replace all non-alphabetic characters with a space (not allowed in Ruby constant)
96
+ # 3. Camelcase
97
+ Rack::Utils.unescape_path(name)
98
+ .gsub(/[^[:alpha:]]/, ' ')
99
+ .gsub(/(\s+)([[:alpha:]])/) { Regexp.last_match(2).upcase }
100
+ end
101
+
91
102
  private
92
103
 
93
104
  def read
94
105
  File.open(@filename, encoding: 'UTF-8') do |file|
95
- YAML.safe_load(file)
106
+ YAML.safe_load(file, aliases: true)
96
107
  end
97
108
  end
98
109
 
@@ -105,7 +116,7 @@ class Reynard
105
116
  next unless cursor.respond_to?(:key?) && cursor&.key?('$ref')
106
117
 
107
118
  # We currenly only supply references inside the document starting with #/.
108
- path = cursor['$ref'][2..].split('/') + path
119
+ path = Rack::Utils.unescape_path(cursor['$ref'][2..]).split('/') + path
109
120
  cursor = data
110
121
  end
111
122
  cursor
@@ -113,16 +124,16 @@ class Reynard
113
124
 
114
125
  def schema_name(response)
115
126
  ref = response.dig('schema', '$ref')
116
- ref&.split('/')&.last
127
+ return unless ref
128
+
129
+ self.class.normalize_model_name(ref&.split('/')&.last)
117
130
  end
118
131
 
119
132
  def item_schema_name(schema)
120
133
  ref = schema.dig('items', '$ref')
121
- ref&.split('/')&.last
122
- end
134
+ return unless ref
123
135
 
124
- def object_name(_schema)
125
- 'Book'
136
+ self.class.normalize_model_name(ref&.split('/')&.last)
126
137
  end
127
138
  end
128
139
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Reynard
4
- VERSION = '0.0.7'
4
+ VERSION = '0.2.0'
5
5
  end
data/lib/reynard.rb CHANGED
@@ -34,6 +34,14 @@ class Reynard
34
34
  @specification = Specification.new(filename: filename)
35
35
  end
36
36
 
37
+ # Assign an object that follows Reynard's internal request interface to mock requests or use a
38
+ # different HTTP client.
39
+ class << self
40
+ attr_writer :http
41
+ end
42
+
43
+ # Returns Reynard's global request interface. This is a global object to allow persistent
44
+ # connections, caching, and other features that need a persistent object in the process.
37
45
  def self.http
38
46
  @http ||= begin
39
47
  http = Net::HTTP::Persistent.new(name: 'Reynard')
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.7
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Manfred Stienstra
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-17 00:00:00.000000000 Z
11
+ date: 2021-11-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: multi_json
@@ -124,6 +124,7 @@ 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
127
128
  - lib/reynard/media_type.rb
128
129
  - lib/reynard/model.rb
129
130
  - lib/reynard/models.rb
@@ -139,7 +140,8 @@ files:
139
140
  homepage: https://github.com/Manfred/reynard
140
141
  licenses:
141
142
  - MIT
142
- metadata: {}
143
+ metadata:
144
+ rubygems_mfa_required: 'true'
143
145
  post_install_message:
144
146
  rdoc_options: []
145
147
  require_paths: