reynard 0.4.0 → 0.5.1

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: ee7b44612f3b8e0fec7a0a3d1e18bc30366d1bea3470a8b3eb46a8d6180c85cd
4
- data.tar.gz: e6c9629439a36c50ff214f932d5997ce004b21d12a1c71a6c727ab7ef38d722a
3
+ metadata.gz: 891b142e2ca908078c960db4b36040b407974da43395d02b7ee3f768a1c758b4
4
+ data.tar.gz: 5deefbad34c0364cfd3c3a04910b882499c3b96d7c4d642fdb10fbdcc3e9898f
5
5
  SHA512:
6
- metadata.gz: 159960b7bb4e38673c00baa531e3586135dcabedadd50dbd12da55eee31b77ea4d4d7d182974144adf602128b664e5d28f3c15c9e6d376f96774e78a45cdba2c
7
- data.tar.gz: 5a7cbcc61c772858283855289f9b3a119b716c78994d16a4de6d5f34bc6921c633f023d362417657253885e6c35f157cb98d2771228e2790a0dff4df69bcc7f2
6
+ metadata.gz: 393b2cab7867c45c53b761f42b03e609e653600981aa1283cf3f7c0213379adb9a1eeb1530b3a0f826e07d1ceb5c3fab0d8afc114a2001ec773f71d2484bc109
7
+ data.tar.gz: c14ebcc55481c202c0c3c7b7dd36e68eb5b72f0297c0339867e6c298d4a3605284dff2e041d33b074c8c9e3b8064f450fdf481fa686e56c1e764af59a1050593
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack'
4
+
5
+ class Reynard
6
+ # Loads external references.
7
+ class External
8
+ def initialize(path:, ref:)
9
+ @path = path
10
+ @relative_path, @anchor = ref.split('#', 2)
11
+ end
12
+
13
+ def path
14
+ return [] unless @anchor
15
+
16
+ @anchor.split('/')[1..]
17
+ end
18
+
19
+ def data
20
+ File.open(filename, encoding: 'UTF-8') do |file|
21
+ YAML.safe_load(file, aliases: true)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def filename
28
+ filename = File.expand_path(@relative_path, @path)
29
+ return filename if filename.start_with?(@path)
30
+
31
+ raise 'You are only allowed to reference files relative to the specification file.'
32
+ end
33
+ end
34
+ end
@@ -15,7 +15,7 @@ class Reynard
15
15
  end
16
16
 
17
17
  def perform
18
- @request_context.logger.info { "#{@request_context.verb.upcase} #{uri}" }
18
+ @request_context.logger&.info { "#{@request_context.verb.upcase} #{uri}" }
19
19
  Reynard.http.request(uri, build_request)
20
20
  end
21
21
 
@@ -28,7 +28,7 @@ class Reynard
28
28
  def build_request
29
29
  request = request_class.new(uri, @request_context.headers)
30
30
  if @request_context.body
31
- @request_context.logger.debug { @request_context.body }
31
+ @request_context.logger&.debug { @request_context.body }
32
32
  request.body = @request_context.body
33
33
  end
34
34
  request
@@ -14,6 +14,8 @@ class Reynard
14
14
  def object_class
15
15
  if @media_type.schema_name
16
16
  self.class.model_class(@media_type.schema_name, @schema.object_type)
17
+ elsif @schema.object_type == 'array'
18
+ Array
17
19
  else
18
20
  Reynard::Model
19
21
  end
@@ -46,7 +48,7 @@ class Reynard
46
48
  end
47
49
 
48
50
  def self.model_class_get(name)
49
- Reynard::Models.const_get(name)
51
+ Kernel.const_get("::Reynard::Models::#{name}")
50
52
  rescue NameError
51
53
  nil
52
54
  end
@@ -3,6 +3,7 @@
3
3
  class Reynard
4
4
  # Value class for details about the request.
5
5
  RequestContext = Struct.new(
6
+ :logger,
6
7
  :base_url,
7
8
  :operation,
8
9
  :headers,
@@ -10,14 +11,6 @@ class Reynard
10
11
  :body,
11
12
  keyword_init: true
12
13
  ) do
13
- attr_writer :logger
14
-
15
- def logger
16
- @logger = nil unless defined?(@logger)
17
-
18
- Reynard::Logger.new(@logger)
19
- end
20
-
21
14
  def verb
22
15
  operation&.verb
23
16
  end
@@ -102,11 +102,22 @@ class Reynard
102
102
 
103
103
  def self.normalize_model_name(name)
104
104
  # 1. Unescape encoded characters to create an UTF-8 string
105
- # 2. Replace all non-alphabetic characters with a space (not allowed in Ruby constant)
106
- # 3. Camelcase
105
+ # 2. Remove extensions for regularly used external schema files
106
+ # 3. Replace all non-alphabetic characters with a space (not allowed in Ruby constant)
107
+ # 4. Camelcase
107
108
  Rack::Utils.unescape_path(name)
109
+ .gsub(/(.yml|.yaml|.json)\Z/, '')
108
110
  .gsub(/[^[:alpha:]]/, ' ')
109
111
  .gsub(/(\s+)([[:alpha:]])/) { Regexp.last_match(2).upcase }
112
+ .gsub(/\A(.)/) { Regexp.last_match(1).upcase }
113
+ end
114
+
115
+ def self.normalize_model_title(title)
116
+ title
117
+ .gsub(/[^[:alpha:]]/, ' ')
118
+ .gsub(/\s{2,}/, ' ')
119
+ .gsub(/(\s+)([[:alpha:]])/) { Regexp.last_match(2).upcase }
120
+ .strip
110
121
  end
111
122
 
112
123
  private
@@ -117,6 +128,9 @@ class Reynard
117
128
  end
118
129
  end
119
130
 
131
+ # rubocop:disable Metrics/AbcSize
132
+ # rubocop:disable Metrics/CyclomaticComplexity
133
+ # rubocop:disable Metrics/MethodLength
120
134
  def dig_into(data, cursor, path)
121
135
  while path.length.positive?
122
136
  cursor = cursor[path.first]
@@ -125,25 +139,44 @@ class Reynard
125
139
  path.shift
126
140
  next unless cursor.respond_to?(:key?) && cursor&.key?('$ref')
127
141
 
128
- # We currenly only supply references inside the document starting with #/.
129
- path = Rack::Utils.unescape_path(cursor['$ref'][2..]).split('/') + path
130
- cursor = data
142
+ case cursor['$ref']
143
+ # References another element in the current specification.
144
+ when %r{\A#/}
145
+ path = Rack::Utils.unescape_path(cursor['$ref'][2..]).split('/') + path
146
+ cursor = data
147
+ # References another file, with an optional anchor to an element in the data.
148
+ when %r{\A\./}
149
+ external = External.new(path: File.dirname(@filename), ref: cursor['$ref'])
150
+ path = external.path + path
151
+ cursor = external.data
152
+ end
131
153
  end
132
154
  cursor
133
155
  end
156
+ # rubocop:enable Metrics/AbcSize
157
+ # rubocop:enable Metrics/CyclomaticComplexity
158
+ # rubocop:enable Metrics/MethodLength
134
159
 
135
160
  def schema_name(response)
136
- ref = response.dig('schema', '$ref')
137
- return unless ref
138
-
139
- self.class.normalize_model_name(ref&.split('/')&.last)
161
+ extract_schema_name(response['schema'])
140
162
  end
141
163
 
142
164
  def item_schema_name(schema)
143
- ref = schema.dig('items', '$ref')
144
- return unless ref
165
+ if schema['type'] == 'array'
166
+ extract_schema_name(schema['items'])
167
+ else
168
+ extract_schema_name(schema)
169
+ end
170
+ end
171
+
172
+ def extract_schema_name(definition)
173
+ ref = definition['$ref']
174
+ return self.class.normalize_model_name(ref&.split('/')&.last) if ref
175
+
176
+ title = definition['title']
177
+ return unless title
145
178
 
146
- self.class.normalize_model_name(ref&.split('/')&.last)
179
+ self.class.normalize_model_title(title)
147
180
  end
148
181
  end
149
182
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Reynard
4
- VERSION = '0.4.0'
4
+ VERSION = '0.5.1'
5
5
  end
data/lib/reynard.rb CHANGED
@@ -11,10 +11,11 @@ require 'uri'
11
11
  # OpenAPI specification.
12
12
  class Reynard
13
13
  extend Forwardable
14
- def_delegators :build_context, :base_url, :operation, :headers, :params
14
+ def_delegators :build_context, :logger, :base_url, :operation, :headers, :params
15
15
  def_delegators :@specification, :servers
16
16
 
17
17
  autoload :Context, 'reynard/context'
18
+ autoload :External, 'reynard/external'
18
19
  autoload :GroupedParameters, 'reynard/grouped_parameters'
19
20
  autoload :Http, 'reynard/http'
20
21
  autoload :Logger, 'reynard/logger'
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.4.0
4
+ version: 0.5.1
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: 2022-04-05 00:00:00.000000000 Z
11
+ date: 2022-08-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: multi_json
@@ -121,11 +121,11 @@ files:
121
121
  - README.md
122
122
  - lib/reynard.rb
123
123
  - lib/reynard/context.rb
124
+ - lib/reynard/external.rb
124
125
  - lib/reynard/grouped_parameters.rb
125
126
  - lib/reynard/http.rb
126
127
  - lib/reynard/http/request.rb
127
128
  - lib/reynard/http/response.rb
128
- - lib/reynard/logger.rb
129
129
  - lib/reynard/media_type.rb
130
130
  - lib/reynard/model.rb
131
131
  - lib/reynard/models.rb
@@ -143,7 +143,7 @@ licenses:
143
143
  - MIT
144
144
  metadata:
145
145
  rubygems_mfa_required: 'true'
146
- post_install_message:
146
+ post_install_message:
147
147
  rdoc_options: []
148
148
  require_paths:
149
149
  - lib
@@ -159,7 +159,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
159
159
  version: '0'
160
160
  requirements: []
161
161
  rubygems_version: 3.1.6
162
- signing_key:
162
+ signing_key:
163
163
  specification_version: 4
164
164
  summary: Minimal OpenAPI client.
165
165
  test_files: []
@@ -1,26 +0,0 @@
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