ur 0.0.2 → 0.2.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.
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'faraday'
2
4
 
3
- class Ur
5
+ module Ur
4
6
  module Faraday
5
7
  autoload :YieldUr, 'ur/faraday/yield_ur'
6
8
  end
@@ -1,18 +1,12 @@
1
- class Ur
2
- module Faraday
3
- class YieldUr < ::Faraday::Middleware
4
- def initialize(app, options = {}, &block)
5
- @app = app
6
- @options = options
7
- @yield_to = block
8
- end
1
+ # frozen_string_literal: true
9
2
 
10
- def call(request_env)
11
- ur = Scorpio::Ur.from_faraday_request(request_env)
12
- ur.logger = @options[:logger]
13
- ur.faraday_on_complete(@app, request_env) do |response_env|
14
- @yield_to.call(ur)
15
- end
3
+ module Ur
4
+ module Faraday
5
+ # Faraday middleware which yields an Ur to the given block
6
+ class YieldUr < ::Ur::FaradayMiddleware
7
+ def initialize(app, **options, &block)
8
+ raise(ArgumentError, "no block given to yield ur") unless block
9
+ super(app, options.merge(after_response: block))
16
10
  end
17
11
  end
18
12
  end
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'ur' unless Object.const_defined?(:Ur)
2
4
 
3
- class Ur
4
- class Processing
5
+ module Ur
6
+ module Metadata
5
7
  include SubUr
6
8
 
7
9
  def began_at
@@ -13,10 +15,13 @@ class Ur
13
15
 
14
16
  attr_accessor :began_at_ns
15
17
 
18
+ # sets began_at from the current time
16
19
  def begin!
17
- self.began_at ||= Time.now
18
- self.began_at_ns ||= Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
20
+ self.began_at = Time.now
21
+ self.began_at_ns = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
19
22
  end
23
+
24
+ # sets the duration from the current time and began_at
20
25
  def finish!
21
26
  return if duration
22
27
  if began_at_ns
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'ur' unless Object.const_defined?(:Ur)
2
4
 
3
- class Ur
5
+ module Ur
4
6
  module Middleware
5
7
  def initialize(app, options = {})
6
8
  @app = app
@@ -9,6 +11,15 @@ class Ur
9
11
  attr_reader :app
10
12
  attr_reader :options
11
13
 
14
+ def begin_request(ur)
15
+ ur.metadata.begin!
16
+ end
17
+
18
+ def finish_request(ur)
19
+ ur.logger_tags(@options[:logger])
20
+ ur.metadata.finish!
21
+ end
22
+
12
23
  def invoke_callback(name, *a, &b)
13
24
  if @options[name]
14
25
  @options[name].call(*a, &b)
@@ -19,10 +30,11 @@ class Ur
19
30
  class FaradayMiddleware < ::Faraday::Middleware
20
31
  include Ur::Middleware
21
32
  def call(request_env)
22
- ur = Ur.from_faraday_request(request_env)
33
+ ur = Ur.from_faraday_request(request_env, @options.select { |k, _| [:schemas].include?(k) })
23
34
  invoke_callback(:before_request, ur)
24
- ur.logger = options[:logger] if options[:logger]
35
+ begin_request(ur)
25
36
  ur.faraday_on_complete(@app, request_env) do |response_env|
37
+ finish_request(ur)
26
38
  invoke_callback(:after_response, ur)
27
39
  end
28
40
  end
@@ -31,10 +43,11 @@ class Ur
31
43
  class RackMiddleware
32
44
  include Ur::Middleware
33
45
  def call(env)
34
- ur = Ur.from_rack_request(env)
46
+ ur = Ur.from_rack_request(env, @options.select { |k, _| [:schemas].include?(k) })
35
47
  invoke_callback(:before_request, ur)
36
- ur.logger = options[:logger] if options[:logger]
48
+ begin_request(ur)
37
49
  ur.with_rack_response(@app, env) do
50
+ finish_request(ur)
38
51
  invoke_callback(:after_response, ur)
39
52
  end
40
53
  end
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'ur' unless Object.const_defined?(:Ur)
2
4
 
3
- class Ur
4
- class Request
5
+ module Ur
6
+ module Request
5
7
  include RequestAndResponse
6
8
  include SubUr
7
9
 
@@ -1,8 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'ur' unless Object.const_defined?(:Ur)
2
4
 
3
- class Ur
5
+ module Ur
6
+ # functionality common to Request and Response
4
7
  module RequestAndResponse
8
+ # functionality for handling request/response entities from Faraday
5
9
  module FaradayEntity
10
+ # @param env [Faraday::Env] faraday env passed to middleware #call
6
11
  def set_body_from_faraday(env)
7
12
  if env[:raw_body].respond_to?(:to_str)
8
13
  self.body = env[:raw_body].to_str.dup
@@ -20,20 +25,34 @@ class Ur
20
25
  end
21
26
  include FaradayEntity
22
27
 
23
- def content_type_attrs
24
- return @content_type_attrs if instance_variable_defined?(:@content_type_attrs)
25
- @content_type_attrs = ContentTypeAttrs.new(content_type)
26
- end
27
-
28
+ # @return [Ur::ContentType] the string value of the content type header. returns an
29
+ # {Ur::ContentType}, a subclass of String which additionally parses the Content-Type
30
+ # according to relevant RFCs.
28
31
  def content_type
29
32
  headers.each do |k, v|
30
- return v if k =~ /\Acontent[-_]type\z/i
33
+ return ContentType.new(v) if k =~ /\Acontent[-_]type\z/i
31
34
  end
32
35
  nil
33
36
  end
34
37
 
38
+ # the media type of the content type
35
39
  def media_type
36
- content_type_attrs.media_type
40
+ content_type ? content_type.media_type : nil
41
+ end
42
+
43
+ # @return [Boolean] is our content type JSON?
44
+ def json?
45
+ content_type && content_type.json?
46
+ end
47
+
48
+ # @return [Boolean] is our content type XML?
49
+ def xml?
50
+ content_type && content_type.xml?
51
+ end
52
+
53
+ # @return [Boolean] is our content type x-www-form-urlencoded?
54
+ def form_urlencoded?
55
+ content_type && content_type.form_urlencoded?
37
56
  end
38
57
  end
39
58
  end
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'ur' unless Object.const_defined?(:Ur)
2
4
 
3
- class Ur
4
- class Response
5
+ module Ur
6
+ module Response
5
7
  include RequestAndResponse
6
8
  include SubUr
7
9
 
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'ur' unless Object.const_defined?(:Ur)
2
4
 
3
- class Ur
5
+ module Ur
4
6
  module SubUr
5
7
  def ur
6
- parents.detect { |p| p.is_a?(::Ur) }
8
+ parent_jsis.detect { |p| p.is_a?(::Ur) }
7
9
  end
8
10
  end
9
11
  end
@@ -1 +1 @@
1
- UR_VERSION = "0.0.2"
1
+ UR_VERSION = "0.2.0".freeze
@@ -0,0 +1,50 @@
1
+ $id: https://schemas.ur.unth.net/ur
2
+ type: object
3
+ properties:
4
+ bound:
5
+ type: string
6
+ description: '[RFC7230] The terms "inbound" and "outbound" are used to describe directional requirements in relation to the request route: "inbound" means toward the origin server and "outbound" means toward the user agent.'
7
+ enum:
8
+ - inbound
9
+ - outbound
10
+ request:
11
+ type: object
12
+ properties:
13
+ method:
14
+ type: string
15
+ description: '[RFC7230] The method token indicates the request method to be performed on the target resource.'
16
+ example: POST
17
+ uri:
18
+ type: string
19
+ format: uri
20
+ example: https://example.com/foo?bar=baz
21
+ headers:
22
+ type: object
23
+ body:
24
+ type: string
25
+ response:
26
+ type: object
27
+ properties:
28
+ status:
29
+ type: integer
30
+ description: >
31
+ The status-code element is a 3-digit integer code describing the
32
+ result of the server's attempt to understand and satisfy the client's
33
+ corresponding request.
34
+ example: 200
35
+ headers:
36
+ type: object
37
+ body:
38
+ type: string
39
+ metadata:
40
+ type: object
41
+ properties:
42
+ began_at_s:
43
+ type: string
44
+ format: date-time
45
+ duration:
46
+ type: number
47
+ tags:
48
+ type: array
49
+ items:
50
+ type: string
@@ -0,0 +1,342 @@
1
+ require_relative 'test_helper'
2
+
3
+ describe 'Ur::ContentType' do
4
+ let(:content_type) { Ur::ContentType.new(content_type_str) }
5
+ describe 'application/vnd.github+json; charset="utf-8"' do
6
+ let(:content_type_str) { 'application/vnd.github+json; charset="utf-8"' }
7
+ it 'parses' do
8
+ assert_equal(content_type, content_type_str)
9
+
10
+ assert_equal('application/vnd.github+json', content_type.media_type)
11
+
12
+ assert_equal('application', content_type.type)
13
+ assert(content_type.type?('Application'))
14
+ assert(content_type.type_application?)
15
+
16
+ assert_equal('vnd.github+json', content_type.subtype)
17
+ assert_equal('vnd', content_type.facet)
18
+ assert_equal('json', content_type.suffix)
19
+
20
+ assert_equal({'charset' => 'utf-8'}, content_type.parameters)
21
+ assert_equal('utf-8', content_type.parameters['CharSet'])
22
+ end
23
+ end
24
+ describe 'no subtype' do
25
+ let(:content_type_str) { 'application; charset="utf-8"' }
26
+ it 'will allow it' do
27
+ assert_equal(content_type, content_type_str)
28
+
29
+ assert_equal('application', content_type.media_type)
30
+
31
+ assert_equal('application', content_type.type)
32
+ assert(content_type.type?('Application'))
33
+
34
+ assert_equal(nil, content_type.subtype)
35
+ assert_equal(nil, content_type.facet)
36
+ assert_equal(nil, content_type.suffix)
37
+
38
+ assert_equal({'charset' => 'utf-8'}, content_type.parameters)
39
+ assert_equal('utf-8', content_type.parameters['CharSet'])
40
+ end
41
+ end
42
+ describe 'no facet' do
43
+ let(:content_type_str) { 'application/github+json; charset="utf-8"' }
44
+ it 'parses' do
45
+ assert_equal(content_type, content_type_str)
46
+
47
+ assert_equal('application/github+json', content_type.media_type)
48
+
49
+ assert_equal('application', content_type.type)
50
+ assert(content_type.type?('Application'))
51
+
52
+ assert_equal('github+json', content_type.subtype)
53
+ assert_equal(nil, content_type.facet)
54
+ assert_equal('json', content_type.suffix)
55
+
56
+ assert_equal({'charset' => 'utf-8'}, content_type.parameters)
57
+ assert_equal('utf-8', content_type.parameters['CharSet'])
58
+ end
59
+ end
60
+ describe 'no suffix' do
61
+ let(:content_type_str) { 'application/vnd.github.json; charset="utf-8"' }
62
+ it 'parses' do
63
+ assert_equal(content_type, content_type_str)
64
+
65
+ assert_equal('application/vnd.github.json', content_type.media_type)
66
+
67
+ assert_equal('application', content_type.type)
68
+ assert(content_type.type?('Application'))
69
+
70
+ assert_equal('vnd.github.json', content_type.subtype)
71
+ assert_equal('vnd', content_type.facet)
72
+ assert_equal(nil, content_type.suffix)
73
+
74
+ assert_equal({'charset' => 'utf-8'}, content_type.parameters)
75
+ assert_equal('utf-8', content_type.parameters['CharSet'])
76
+ end
77
+ describe('[invalid] quote in type') do
78
+ let(:content_type_str) { 'applic"ation/foo; foo=bar' }
79
+ it('gives up') do
80
+ assert_equal('applic', content_type.type)
81
+ assert_equal(nil, content_type.subtype)
82
+ end
83
+ end
84
+ describe('[invalid] backslash in type') do
85
+ let(:content_type_str) { 'applicati\on/foo; foo=bar' }
86
+ it('parses') do
87
+ assert_equal('applicati\\on', content_type.type)
88
+ assert_equal('foo', content_type.subtype)
89
+ end
90
+ end
91
+ describe('[invalid] quote in subtype') do
92
+ let(:content_type_str) { 'application/f"oo; foo=bar' }
93
+ it('gives up') do
94
+ assert_equal('application', content_type.type)
95
+ assert_equal('f', content_type.subtype)
96
+ end
97
+ end
98
+ describe('[invalid] backslash in subtype') do
99
+ let(:content_type_str) { 'application/fo\\o; foo=bar' }
100
+ it('parses') do
101
+ assert_equal('application', content_type.type)
102
+ assert_equal('fo\\o', content_type.subtype)
103
+ end
104
+ end
105
+ end
106
+ describe 'parameters' do
107
+ describe 'basic usage' do
108
+ let(:content_type_str) { 'application/foo; charset="utf-8"; foo=bar' }
109
+ it('parses') do
110
+ assert_equal({'charset' => 'utf-8', 'foo' => 'bar'}, content_type.parameters)
111
+ end
112
+ end
113
+ describe 'params with capitalization' do
114
+ let(:content_type_str) { 'application/foo; Charset="utf-8"; FOO=bar' }
115
+ it('parses') do
116
+ assert_equal({'charset' => 'utf-8', 'foo' => 'bar'}, content_type.parameters)
117
+ assert_equal('utf-8', content_type.parameters['CharSet'])
118
+ assert_equal('utf-8', content_type.parameters['Charset'])
119
+ assert_equal('bar', content_type.parameters['foo'])
120
+ assert_equal('bar', content_type.parameters['FOO'])
121
+ end
122
+ end
123
+ describe 'repeated params' do
124
+ let(:content_type_str) { 'application/foo; foo="first"; foo=second' }
125
+ it('will just overwrite') do
126
+ assert_equal({'foo' => 'second'}, content_type.parameters)
127
+ end
128
+ end
129
+ describe 'repeated params, different capitalization' do
130
+ let(:content_type_str) { 'application/foo; FOO=first; Foo=second' }
131
+ it('will just overwrite') do
132
+ assert_equal({'foo' => 'second'}, content_type.parameters)
133
+ end
134
+ end
135
+ describe 'empty strings' do
136
+ let(:content_type_str) { 'application/foo; empty1=; empty2=""' }
137
+ it('parses') do
138
+ assert_equal({'empty1' => '', 'empty2' => ''}, content_type.parameters)
139
+ end
140
+ end
141
+ describe 'empty strings with whitespace' do
142
+ let(:content_type_str) { 'application/foo; empty1= ; empty2="" ' }
143
+ it('parses') do
144
+ assert_equal({'empty1' => '', 'empty2' => ''}, content_type.parameters)
145
+ end
146
+ end
147
+ describe('[invalid] opening quote only') do
148
+ let(:content_type_str) { 'application/foo; foo=1; bar="' }
149
+ it('parses') do
150
+ assert_equal({'foo' => '1', 'bar' => ''}, content_type.parameters)
151
+ end
152
+ end
153
+ describe('[invalid] backlash with no character') do
154
+ let(:content_type_str) { 'application/foo; foo=1; bar="\\' }
155
+ it('parses') do
156
+ assert_equal({'foo' => '1', 'bar' => ''}, content_type.parameters)
157
+ end
158
+ end
159
+ describe('[invalid] extra following quoted string') do
160
+ let(:content_type_str) { 'application/foo; foo="1" 2; bar=3' }
161
+ it('sorta parses') do
162
+ assert_equal({'foo' => '1 2', 'bar' => '3'}, content_type.parameters)
163
+ end
164
+ end
165
+ describe('[invalid] quotes silliness') do
166
+ let(:content_type_str) { 'application/foo; foo="1" 2 "3 4" "5 " ; bar=3' }
167
+ it('sorta parses') do
168
+ assert_equal({'foo' => '1 2 3 4 5 ', 'bar' => '3'}, content_type.parameters)
169
+ end
170
+ end
171
+ describe('[invalid] backlash quote') do
172
+ let(:content_type_str) { 'application/foo; foo=1; bar="\\"' }
173
+ it('parses') do
174
+ assert_equal({'foo' => '1', 'bar' => '"'}, content_type.parameters)
175
+ end
176
+ end
177
+ describe('[invalid] trailing ;') do
178
+ let(:content_type_str) { 'application/foo; foo=bar;' }
179
+ it('parses') do
180
+ assert_equal({'foo' => 'bar'}, content_type.parameters)
181
+ end
182
+ end
183
+ describe('[invalid] extra ; inline') do
184
+ let(:content_type_str) { 'application/foo; ; ; foo=bar' }
185
+ it('parses') do
186
+ assert_equal({'foo' => 'bar'}, content_type.parameters)
187
+ end
188
+ end
189
+ describe('[invalid] whitespace around the =') do
190
+ let(:content_type_str) { 'application/foo; foo = bar; baz = qux' }
191
+ it('parses') do
192
+ assert_equal({'foo ' => ' bar', 'baz ' => ' qux'}, content_type.parameters)
193
+ end
194
+ end
195
+ describe('whitespace before the ;') do
196
+ let(:content_type_str) { 'application/foo; foo=bar ; baz=qux' }
197
+ it('parses') do
198
+ assert_equal({'foo' => 'bar', 'baz' => 'qux'}, content_type.parameters)
199
+ end
200
+ end
201
+ describe('no media_type') do
202
+ let(:content_type_str) { '; foo=bar' }
203
+ it('parses') do
204
+ assert_equal({'foo' => 'bar'}, content_type.parameters)
205
+ end
206
+ end
207
+ describe('[invalid] quote in parameter name') do
208
+ let(:content_type_str) { 'application/foo; fo"o=bar' }
209
+ it('gives up') do
210
+ assert_equal({}, content_type.parameters)
211
+ end
212
+ end
213
+ describe('[invalid] backslash in parameter name') do
214
+ let(:content_type_str) { 'application/foo; fo\\o=bar' }
215
+ it('parses') do
216
+ assert_equal({'fo\\o' => 'bar'}, content_type.parameters)
217
+ end
218
+ end
219
+ end
220
+ describe 'binary?' do
221
+ binary_content_type_strs = [
222
+ 'audio/ogg',
223
+ 'Audio/OGG; foo=bar',
224
+ 'VIDEO/foo+bar',
225
+ ]
226
+ binary_content_type_strs.each do |binary_content_type_str|
227
+ describe(binary_content_type_str) do
228
+ let(:content_type_str) { binary_content_type_str }
229
+ it 'is binary' do
230
+ assert(content_type.binary?(unknown: false))
231
+ end
232
+ end
233
+ end
234
+ not_binary_content_type_strs = [
235
+ 'text/anything',
236
+ 'TEXT/plain; charset=foo',
237
+ 'application/JSON',
238
+ 'media/foo+Json; foo="bar"',
239
+ ]
240
+ not_binary_content_type_strs.each do |not_binary_content_type_str|
241
+ describe(not_binary_content_type_str) do
242
+ let(:content_type_str) { not_binary_content_type_str }
243
+ it 'is not binary' do
244
+ assert(!content_type.binary?(unknown: true))
245
+ end
246
+ end
247
+ end
248
+ unknown_content_type_strs = [
249
+ 'foo',
250
+ 'foo/bar; note="not application/json"',
251
+ 'application/jsonisnotthis',
252
+ 'application/octet-stream',
253
+ ]
254
+ unknown_content_type_strs.each do |unknown_content_type_str|
255
+ describe(unknown_content_type_str) do
256
+ let(:content_type_str) { unknown_content_type_str }
257
+ it 'is unknown' do
258
+ assert(content_type.binary?(unknown: true))
259
+ assert(!content_type.binary?(unknown: false))
260
+ end
261
+ end
262
+ end
263
+ end
264
+ describe 'json?' do
265
+ json_content_type_strs = [
266
+ 'application/json',
267
+ 'Application/Json; charset=EBCDIC',
268
+ 'Text/json',
269
+ 'MEDIA/JSON',
270
+ 'media/foo.bar+json',
271
+ 'media/foo.bar+json; foo=',
272
+ ]
273
+ json_content_type_strs.each do |json_content_type_str|
274
+ describe(json_content_type_str) do
275
+ let(:content_type_str) { json_content_type_str }
276
+ it 'is json' do
277
+ assert(content_type.json?)
278
+ end
279
+ end
280
+ end
281
+ not_json_content_type_strs = [
282
+ 'json',
283
+ 'foo/bar; note="not application/json"',
284
+ 'application/jsonisnotthis',
285
+ 'text/json+xml', # I don't even know what I'm trying for here
286
+ ]
287
+ not_json_content_type_strs.each do |not_json_content_type_str|
288
+ describe(not_json_content_type_str) do
289
+ let(:content_type_str) { not_json_content_type_str }
290
+ it 'is not json' do
291
+ assert(!content_type.json?)
292
+ end
293
+ end
294
+ end
295
+ end
296
+ describe 'xml?' do
297
+ xml_content_type_strs = [
298
+ 'application/xml',
299
+ 'Application/Xml; charset=EBCDIC',
300
+ 'Text/xml',
301
+ 'MEDIA/XML',
302
+ 'media/foo.bar+xml',
303
+ 'media/foo.bar+xml; foo=',
304
+ ]
305
+ xml_content_type_strs.each do |xml_content_type_str|
306
+ describe(xml_content_type_str) do
307
+ let(:content_type_str) { xml_content_type_str }
308
+ it 'is xml' do
309
+ assert(content_type.xml?)
310
+ end
311
+ end
312
+ end
313
+ not_xml_content_type_strs = [
314
+ 'xml',
315
+ 'foo/bar; note="not application/xml"',
316
+ 'application/xmlisnotthis',
317
+ 'text/xml+json', # I don't even know what I'm trying for here
318
+ ]
319
+ not_xml_content_type_strs.each do |not_xml_content_type_str|
320
+ describe(not_xml_content_type_str) do
321
+ let(:content_type_str) { not_xml_content_type_str }
322
+ it 'is not xml' do
323
+ assert(!content_type.xml?)
324
+ end
325
+ end
326
+ end
327
+ end
328
+ describe 'form_urlencoded?' do
329
+ describe('application/x-www-form-urlencoded') do
330
+ let(:content_type_str) { 'application/x-www-form-urlencoded' }
331
+ it 'is form_urlencoded' do
332
+ assert(content_type.form_urlencoded?)
333
+ end
334
+ end
335
+ describe('application/foo') do
336
+ let(:content_type_str) { 'application/foo' }
337
+ it 'is not form_urlencoded' do
338
+ assert(!content_type.form_urlencoded?)
339
+ end
340
+ end
341
+ end
342
+ end