http_range 1.0.0 → 1.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
  SHA1:
3
- metadata.gz: 6f193e3a32d1e236790886073848643050da5d0b
4
- data.tar.gz: 99b3dc5188a26fb91d4bf6565cb253bf735757eb
3
+ metadata.gz: d22f33d1f2262be5ab34420858513ef205e2c7f8
4
+ data.tar.gz: 15d172574a2cc0addaad0fa99c595c9c24a34b78
5
5
  SHA512:
6
- metadata.gz: 06330411edb69fa887cb44cec4d9168951d0db998ad90e0db0887adb987f76d6bd45895870aa4219f4ea329b283a8e27061a4838d26af68cea7f3802ef1f5fc5
7
- data.tar.gz: 3ad46bb2d5c68534374b87efdb6735ed57cc7de81ef1225adc6e2ae868e6f4e973bac19116f328f0e795b0ed8a8d985573b9aa12fbc7997fcd69bb4bd99504b1
6
+ metadata.gz: ea84d9b2e7b591e7f953f93be6762a48238e4bcde1f372a905ac55d9ad06838e63646b231c82dfdb2dc644fcb6367330535cdb02c28dcb4e704b9ec35c67acef
7
+ data.tar.gz: 45a7a78e40e059a99118869a3f9815bf91ea1167927daeb7a9a515ad5333d0402f5c33005569dd30d13ccee841a862f12553693e4cf80e26bdd957942f8b70e8
data/README.md CHANGED
@@ -35,6 +35,37 @@ http_range.first_inclusive # => true
35
35
  http_range.last_inclusive # => false
36
36
  ```
37
37
 
38
+ Or, in a Rack app:
39
+
40
+ ```ruby
41
+ # Range: id a..z[; max=100
42
+ # found in env['HTTP_RANGE']
43
+ app = Rack::Builder.app do
44
+ use HTTPRange::Middleware::AcceptRanges
45
+
46
+ run lambda do |env|
47
+
48
+ env['rack.range.attribute'] # => 'id'
49
+ env['rack.range.first'] # => 'a'
50
+ env['rack.range.last'] # => 'z'
51
+ env['rack.range.first_inclusive'] # => true
52
+ env['rack.range.last_inclusive'] # => false
53
+ env['rack.range.order'] # => nil
54
+ env['rack.range.max'] # => '100'
55
+
56
+ [200, {}, "Body"]
57
+ end
58
+ end
59
+ ```
60
+
61
+ ## Inspiration
62
+
63
+ Inspired by Heroku's [Interagent HTTP API Design][2] as well as brandur's
64
+ [HTTPAccept][3] library.
65
+
66
+ [2]: https://github.com/interagent/http-api-design
67
+ [3]: https://github.com/brandur/http_accept
68
+
38
69
  ## Contributing
39
70
 
40
71
  1. Fork it ( https://github.com/[my-github-username]/http_range/fork )
data/SPEC.md ADDED
@@ -0,0 +1,25 @@
1
+ # Range HTTP Header Spec for HTTP APIs
2
+
3
+ Largely from [Heroku's Range header documentation][1].
4
+
5
+ [1]: https://devcenter.heroku.com/articles/platform-api-reference#ranges
6
+
7
+ ```
8
+ Range: <attr> [<]>]<first>..<last>[<[>][; order=<asc|desc>][; max=<int>]
9
+ ```
10
+
11
+ The `[` and `]` exclusion operators are [described thusly][2], assuming a range
12
+ from `a` to `z`:
13
+
14
+ [2]: https://github.com/interagent/http-api-design/issues/36#issuecomment-48226357
15
+
16
+ * `a..z` to get everything
17
+ * `a..` to get the default size worth of results greater than or equal to a
18
+ * `..z` to get the default size worth of results less than or equal to z
19
+ * `]a..` results greater than a (not greater than or equal as above)
20
+ * `..z[` results less than z (not less than or equal as above)
21
+
22
+ Intentional deviations from Heroku's design:
23
+
24
+ * Separate `order` and `max` into separate parameters delimited by `;` to avoid
25
+ two needless rounds of parsing (`;` and `,`)
data/http_range.gemspec CHANGED
@@ -18,7 +18,9 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ['lib']
20
20
 
21
- spec.add_development_dependency 'bundler', '~> 1.7'
22
- spec.add_development_dependency 'rake', '~> 10.0'
23
- spec.add_development_dependency 'rspec', '~> 3.1'
21
+
22
+ spec.add_development_dependency 'bundler', '~> 1.7'
23
+ spec.add_development_dependency 'rack-test', '~> 0.6.2'
24
+ spec.add_development_dependency 'rake', '~> 10.0'
25
+ spec.add_development_dependency 'rspec', '~> 3.1'
24
26
  end
@@ -0,0 +1,80 @@
1
+ require 'json'
2
+
3
+ class HTTPRange
4
+ module Middleware
5
+
6
+ # Rack Middleware for parsing `Range` headers. See [SPEC][1] for
7
+ # more details.
8
+ #
9
+ # [1]: https://github.com/h3h/http_range/blob/master/SPEC.md
10
+ #
11
+ # Request Header Format:
12
+ #
13
+ # Range: <attr> <first>..<last>[; order=<asc|desc>][; max=<int>]
14
+ #
15
+ # Examples:
16
+ #
17
+ # Range: id 29f99177-36e9-466c-baef-f855e1ab731e..6c714be1-d901-4c40-adb3-22c9a8cda950; max=100
18
+ # Range: created_at 2014-09-18T16:30:38Z..2014-09-18T17:30:33Z; order=desc
19
+ #
20
+ # Output:
21
+ #
22
+ # `env` keys exposed from the parsed content of the requested Range header:
23
+ #
24
+ # - env['rack.range.attribute']: attribute name
25
+ # - env['rack.range.first']: first value in the range
26
+ # - env['rack.range.last']: last value in the range
27
+ # - env['rack.range.first_inclusive']: whether the first value is inclusive
28
+ # - env['rack.range.last_inclusive']: whether the last value is inclusive
29
+ # - env['rack.range.order']: order param
30
+ # - env['rack.range.max']: max param
31
+ #
32
+ # Usage:
33
+ #
34
+ # use HTTPRange::Middleware::AcceptRanges
35
+ #
36
+ class AcceptRanges
37
+ def initialize(app)
38
+ @app = app
39
+ end
40
+
41
+ def call(env)
42
+ range_header = env['HTTP_RANGE']
43
+ if range_header && range_header.length > 0
44
+ http_range = HTTPRange.parse(range_header)
45
+
46
+ set_env_fields(
47
+ env: env,
48
+ attribute: http_range.attribute,
49
+ first: http_range.first,
50
+ last: http_range.last,
51
+ first_inclusive: http_range.first_inclusive,
52
+ last_inclusive: http_range.last_inclusive,
53
+ order: http_range.order,
54
+ max: http_range.max,
55
+ )
56
+ end
57
+
58
+ @app.call(env)
59
+ rescue HTTPRange::MalformedRangeHeaderError => ex
60
+ [400, {'Content-Type' => 'application/json'}, [{errors: [ex.message]}.to_json]]
61
+ end
62
+
63
+ private
64
+
65
+ # Expose fields in the given env hash for the rest of the application.
66
+ #
67
+ # @param env [Hash]
68
+ # @param **fields [Hash]
69
+ #
70
+ # @return [nil]
71
+ #
72
+ def set_env_fields(env:, **fields)
73
+ fields.each do |field, value|
74
+ env["rack.range.#{field}"] = value
75
+ end
76
+ end
77
+ end
78
+
79
+ end
80
+ end
@@ -3,6 +3,8 @@ class HTTPRange
3
3
  # Accepts the HTTP Range header string to parse.
4
4
  #
5
5
  def initialize(header_string)
6
+ raise MalformedRangeHeaderError, "Missing Range header value." if blank?(header_string)
7
+
6
8
  @header_string = header_string.dup
7
9
  end
8
10
 
@@ -1,3 +1,3 @@
1
1
  class HTTPRange
2
- VERSION = "1.0.0"
2
+ VERSION = '1.1.0'
3
3
  end
data/lib/http_range.rb CHANGED
@@ -22,3 +22,4 @@ class HTTPRange
22
22
  end
23
23
 
24
24
  require 'http_range/parser'
25
+ require 'http_range/middleware/accept_ranges'
@@ -6,7 +6,7 @@ RSpec.describe HTTPRange do
6
6
  subject { described_class.parse(header) }
7
7
 
8
8
  context "for a correctly formatted Range header" do
9
- let(:header) { "Range: id 29f99177-36e9-466c-baef-f855e1ab731e..6c714be1-d901-4c40-adb3-22c9a8cda950[; max=100" }
9
+ let(:header) { 'Range: id 29f99177-36e9-466c-baef-f855e1ab731e..6c714be1-d901-4c40-adb3-22c9a8cda950[; max=100' }
10
10
 
11
11
  it "returns an instance of the class" do
12
12
  expect(subject).to be_an(HTTPRange)
@@ -62,5 +62,11 @@ RSpec.describe HTTPRange do
62
62
 
63
63
  it { expect { subject }.to raise_error(HTTPRange::MalformedRangeHeaderError) }
64
64
  end
65
+
66
+ context "for a nil Range header" do
67
+ let(:header) { nil }
68
+
69
+ it { expect { subject }.to raise_error(HTTPRange::MalformedRangeHeaderError) }
70
+ end
65
71
  end
66
72
  end
@@ -0,0 +1,41 @@
1
+ require 'http_range'
2
+ require 'spec_helper'
3
+
4
+ RSpec.describe HTTPRange::Middleware::AcceptRanges do
5
+ let(:app) { ->(env) { [200, env, 'body'] } }
6
+ let(:middleware) { HTTPRange::Middleware::AcceptRanges.new(app) }
7
+
8
+ context "without a Range header" do
9
+ let(:headers) { {} }
10
+
11
+ before { @response = call_with_headers(middleware, headers) }
12
+
13
+ it { expect(@response.status).to eq 200 }
14
+ it { expect(@response['rack.range.attribute']).to be_nil }
15
+ it { expect(@response['rack.range.first']).to be_nil }
16
+ it { expect(@response['rack.range.last']).to be_nil }
17
+ it { expect(@response['rack.range.first_inclusive']).to be_nil }
18
+ it { expect(@response['rack.range.last_inclusive']).to be_nil }
19
+ it { expect(@response['rack.range.order']).to be_nil }
20
+ it { expect(@response['rack.range.max']).to be_nil }
21
+ end
22
+
23
+ context "with a valid Range header" do
24
+ let(:headers) { {'HTTP_RANGE' => 'Range: id a..z[; max=100'} }
25
+
26
+ before { @response = call_with_headers(middleware, headers) }
27
+
28
+ it { expect(@response.status).to eq 200 }
29
+ it { expect(@response['rack.range.attribute']).to eq('id') }
30
+ it { expect(@response['rack.range.first']).to eq('a') }
31
+ it { expect(@response['rack.range.last']).to eq('z') }
32
+ it { expect(@response['rack.range.first_inclusive']).to eq(true) }
33
+ it { expect(@response['rack.range.last_inclusive']).to eq(false) }
34
+ it { expect(@response['rack.range.order']).to be_nil }
35
+ it { expect(@response['rack.range.max']).to eq('100') }
36
+ end
37
+
38
+ def call_with_headers(middleware, headers)
39
+ Rack::MockRequest.new(middleware).get('/', headers)
40
+ end
41
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'rack/test'
1
2
  require 'rspec/core'
2
3
 
3
4
  RSpec.configure do |config|
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: http_range
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brad Fults
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-18 00:00:00.000000000 Z
11
+ date: 2014-09-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rack-test
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.6.2
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.6.2
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rake
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -64,11 +78,14 @@ files:
64
78
  - LICENSE.txt
65
79
  - README.md
66
80
  - Rakefile
81
+ - SPEC.md
67
82
  - http_range.gemspec
68
83
  - lib/http_range.rb
84
+ - lib/http_range/middleware/accept_ranges.rb
69
85
  - lib/http_range/parser.rb
70
86
  - lib/http_range/version.rb
71
87
  - spec/http_range_spec.rb
88
+ - spec/middleware/accept_ranges_spec.rb
72
89
  - spec/spec_helper.rb
73
90
  homepage: ''
74
91
  licenses:
@@ -96,4 +113,5 @@ specification_version: 4
96
113
  summary: Assists in parsing HTTP Range headers.
97
114
  test_files:
98
115
  - spec/http_range_spec.rb
116
+ - spec/middleware/accept_ranges_spec.rb
99
117
  - spec/spec_helper.rb