http_range 1.0.0 → 1.1.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.
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