ur 0.0.2 → 0.2.0

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