reynard 0.0.6 → 0.1.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: e0d45ac57b8de184f9386bde8d78ccd4cea04f430f809f19c5af6f64ef77ec63
4
- data.tar.gz: 6da06eac247b2fdc1f88ad8bfa76f11cf85d2c6852bec1d26ed73cb82dfb1edf
3
+ metadata.gz: d703f01c7c60de48b48ae223cd9c2e3a94a8639bb866eca234298331ad746d2a
4
+ data.tar.gz: 9e88b9e21dc1b1d00357fcf8bdf2b6952676c5237369d7d950e97b379727e731
5
5
  SHA512:
6
- metadata.gz: e9813aac2ddb3316fc4048bf8f058365f5549fb096b8241488092a1124915810e15cd9e49a713f7a3a979f2c2bf88e11ce34fea41e3cf24046bd44143cd1d665
7
- data.tar.gz: 77f1d12245ef452b312196e00918a60cfcca452fdb5d5658832406e370c5628e660813faf756b3404e0705dfe9e6b90f59248a1b35273d8b4dcafbe79ba66640
6
+ metadata.gz: b1cccaf532a955569236a07c5c8861519cf58c0c2abe3cdf218a8a57619a41d650e9f7226ccdb1a6ef1fddfe412cd9780032dcf8f68ecf995cd2176bb8774642
7
+ data.tar.gz: 56acfbbabfea8a77e76cbee676bd0b397a608994f03f774022fd54d4e65fad06730de36aa9b122767edddb229f854eff9e8819d8a93d506ee5af66d799d18e82
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)
@@ -66,6 +66,20 @@ employee = reynard.
66
66
  execute
67
67
  ```
68
68
 
69
+ ## Mocking
70
+
71
+ 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.
72
+
73
+ ```ruby
74
+ Reynard.http = MyMock.new
75
+
76
+ class MyMock
77
+ def request(uri, net_http_request)
78
+ Net::HTTPResponse::CODE_TO_OBJ['404'].new('HTTP/1.1', '200', 'OK')
79
+ end
80
+ end
81
+ ```
82
+
69
83
  ## Copyright and other legal
70
84
 
71
85
  See LICENCE.
@@ -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
@@ -67,11 +69,26 @@ class Reynard
67
69
  http_response.code,
68
70
  http_response.content_type
69
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)
70
80
  ObjectBuilder.new(
71
81
  media_type: media_type,
72
82
  schema: @specification.schema(media_type.node),
73
83
  http_response: http_response
74
84
  ).call
75
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
76
93
  end
77
94
  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
@@ -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
@@ -61,7 +63,10 @@ class Reynard
61
63
  end
62
64
 
63
65
  def media_type_response(responses, response_code, media_type)
64
- responses.dig(response_code, 'content').each do |expression, response|
66
+ defined_responses = responses.dig(response_code, 'content')
67
+ return unless defined_responses&.any?
68
+
69
+ defined_responses.each do |expression, response|
65
70
  return response, expression if self.class.media_type_matches?(media_type, expression)
66
71
  end
67
72
  nil
@@ -85,11 +90,20 @@ class Reynard
85
90
  false
86
91
  end
87
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
+
88
102
  private
89
103
 
90
104
  def read
91
105
  File.open(@filename, encoding: 'UTF-8') do |file|
92
- YAML.safe_load(file)
106
+ YAML.safe_load(file, aliases: true)
93
107
  end
94
108
  end
95
109
 
@@ -102,7 +116,7 @@ class Reynard
102
116
  next unless cursor.respond_to?(:key?) && cursor&.key?('$ref')
103
117
 
104
118
  # We currenly only supply references inside the document starting with #/.
105
- path = cursor['$ref'][2..].split('/') + path
119
+ path = Rack::Utils.unescape_path(cursor['$ref'][2..]).split('/') + path
106
120
  cursor = data
107
121
  end
108
122
  cursor
@@ -110,16 +124,16 @@ class Reynard
110
124
 
111
125
  def schema_name(response)
112
126
  ref = response.dig('schema', '$ref')
113
- ref&.split('/')&.last
127
+ return unless ref
128
+
129
+ self.class.normalize_model_name(ref&.split('/')&.last)
114
130
  end
115
131
 
116
132
  def item_schema_name(schema)
117
133
  ref = schema.dig('items', '$ref')
118
- ref&.split('/')&.last
119
- end
134
+ return unless ref
120
135
 
121
- def object_name(_schema)
122
- 'Book'
136
+ self.class.normalize_model_name(ref&.split('/')&.last)
123
137
  end
124
138
  end
125
139
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Reynard
4
- VERSION = '0.0.6'
4
+ VERSION = '0.1.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.6
4
+ version: 0.1.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-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: multi_json