accept_headers 0.0.6 → 0.0.7

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: f095a96c5f335db2b23047bac10e48fff764b819
4
- data.tar.gz: da3121b83be60d9a41dd7bfcf98ca8925e0d80e8
3
+ metadata.gz: 1c59b8c10eea7c406d76ca08d6daf1eb25a92f41
4
+ data.tar.gz: 76b2194684f98c8812f71e32daf84af838a38faa
5
5
  SHA512:
6
- metadata.gz: 9655d4af6bcb6aab678a5bd5e50f4b2ae312a69e08e2ca7ea8e3a0ddaebd28adef3934f22fe4726f9e815dba0dce1400028fa67b9921ac32c2ecdc546b67e6de
7
- data.tar.gz: 082959990b7b5d8bea5d0222fba0aa3e23e72b11e42c27812ff1f940178bded01171de8c8246ef6350b17320e19586b275e0b8061d3e625a76556b629ac2825b
6
+ metadata.gz: a3da1d44c7090dcd155f4b6f03e6bb6f40b9292d7bdbd9fee8f77cf7d440a1e8b9e3497f7ee0c42ac229b4377fb0c0b1932afa085f3dc0ca23fa452a82c98361
7
+ data.tar.gz: af9ead68fe19c681981c4a522b4c73cc166fd2e0e1709677232dd5d2f755b5c61d0bce036da997a218818b42297e2d899fdc1dc5324a30049f740ae4141b80e0
data/.travis.yml CHANGED
@@ -10,7 +10,6 @@ matrix:
10
10
  allow_failures:
11
11
  - rvm: ruby-head
12
12
  - rvm: jruby-head
13
- - rvm: rbx
14
13
 
15
14
  addons:
16
15
  code_climate:
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  ## HEAD
2
2
 
3
+ ## 0.0.7 / November 19, 2014
4
+
5
+ * Rename `MediaType` `params` to `extensions`, since params technically includes the `q` value.
6
+ * Support rbx invalid `Float` exception message.
7
+ * Only strip accept param keys, values can contain white space if quoted.
8
+
3
9
  ## 0.0.6 / November 17, 2014
4
10
 
5
11
  * Support parsing params with quoted values.
data/README.md CHANGED
@@ -12,7 +12,7 @@ Some features of the library are:
12
12
  * Full support for the [Accept][rfc-sec14-1], [Accept-Encoding][rfc-sec14-3],
13
13
  and [Accept-Language][rfc-sec14-4] HTTP request headers
14
14
  * `Accept-Charset` is not supported because it's [obsolete](https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation#The_Accept-Charset.3A_header)
15
- * Parser tested against all IANA registered [media types][iana-media-types]
15
+ * Parser tested against all IANA registered [media types][iana-media-types] and [encodings][iana-encodings]
16
16
  * A comprehensive [spec suite][spec] that covers many edge cases
17
17
 
18
18
  This library is optimistic when parsing headers. If a specific media type, encoding, or language can't be parsed, is in an invalid format, or contains invalid characters, it will skip that specific entry when constructing the sorted list. If a `q` value can't be read or is in the wrong format (more than 3 decimal places), it will default it to `0.001` so it still has a chance to match. Lack of an explicit `q` value of course defaults to 1.
@@ -23,6 +23,7 @@ This library is optimistic when parsing headers. If a specific media type, encod
23
23
  [rfc-sec14-3]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
24
24
  [rfc-sec14-4]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
25
25
  [iana-media-types]: https://www.iana.org/assignments/media-types/media-types.xhtml
26
+ [iana-encodings]: https://www.iana.org/assignments/http-parameters/http-parameters.xml#content-coding
26
27
  [spec]: http://github.com/kamui/accept_headers/tree/master/spec/
27
28
 
28
29
  ## Installation
@@ -45,20 +46,21 @@ Or install it yourself as:
45
46
 
46
47
  ### Accept
47
48
 
48
- `AcceptHeaders::MediaType::Negotiator` is a class that is initialized with an `Accept` header string and will internally store an array of `MediaType`s in descending order according to the spec, which takes into account `q` value, `type`/`subtype` and `params` specificity.
49
+ `AcceptHeaders::MediaType::Negotiator` is a class that is initialized with an `Accept` header string and will internally store an array of `MediaType`s in descending order according to the spec, which takes into account `q` value, `type`/`subtype` and `extensions` specificity.
49
50
 
50
51
  ```ruby
51
- media_types = AcceptHeaders::MediaType::Negotiator.new("text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5")
52
+ accept_header = 'Accept: text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5'
53
+ media_types = AcceptHeaders::MediaType::Negotiator.new(accept_header)
52
54
 
53
55
  media_types.list
54
56
 
55
57
  # Returns:
56
58
 
57
59
  [
58
- AcceptHeaders::MediaType.new('text', 'html', params: { 'level' => '1' }),
60
+ AcceptHeaders::MediaType.new('text', 'html', extensions: { 'level' => '1' }),
59
61
  AcceptHeaders::MediaType.new('text', 'html', q: 0.7),
60
62
  AcceptHeaders::MediaType.new('*', '*', q: 0.5),
61
- AcceptHeaders::MediaType.new('text', 'html', q: 0.4, params: { 'level' => '2' }),
63
+ AcceptHeaders::MediaType.new('text', 'html', q: 0.4, extensions: { 'level' => '2' }),
62
64
  AcceptHeaders::MediaType.new('text', '*', q: 0.3)
63
65
  ]
64
66
  ```
@@ -75,10 +77,20 @@ media_types.negotiate(['text/html', 'text/plain'])
75
77
 
76
78
  {
77
79
  supported: 'text/html',
78
- matched: AcceptHeaders::MediaType.new('text', 'html', q: 1, params: { 'level' => '1' })
80
+ matched: AcceptHeaders::MediaType.new('text', 'html', q: 1, extensions: { 'level' => '1' })
79
81
  }
80
82
  ```
81
83
 
84
+ It returns the matching `MediaType`, so you can see which one matched and also access the `extensions` params. For example, if you wanted to put your API version in the extensions, you could then retrieve the value.
85
+
86
+ ```ruby
87
+ versions_header = 'Accept: application/json;version=2,application/json;version=1;q=0.8'
88
+ media_types = AcceptHeaders::MediaType::Negotiator.new(versions_header)
89
+
90
+ m = media_types.negotiate('application/json')
91
+ puts m[:match].extensions['version'] # returns '2'
92
+ ```
93
+
82
94
  `#accept?`:
83
95
 
84
96
  ```ruby
@@ -90,7 +102,8 @@ media_types.accept?('text/html') # true
90
102
  `AcceptHeader::Encoding::Encoding`:
91
103
 
92
104
  ```ruby
93
- encodings = AcceptHeaders::Encoding::Negotiator.new("deflate; q=0.5, gzip, compress; q=0.8, identity")
105
+ accept_encoding = 'Accept-Encoding: deflate; q=0.5, gzip, compress; q=0.8, identity'
106
+ encodings = AcceptHeaders::Encoding::Negotiator.new(accept_encoding)
94
107
 
95
108
  encodings.list
96
109
 
@@ -131,7 +144,8 @@ encodings.accept?('identity') # true
131
144
  `Accept::Language::Negotiator`:
132
145
 
133
146
  ```ruby
134
- languages = AcceptHeaders::Language::Negotiator.new("en-*, en-us, *;q=0.8")
147
+ accept_language = 'Accept-Language: en-*, en-us, *;q=0.8'
148
+ languages = AcceptHeaders::Language::Negotiator.new(accept_language)
135
149
 
136
150
  languages.list
137
151
 
@@ -5,7 +5,7 @@ module AcceptHeaders
5
5
  include Comparable
6
6
  include Acceptable
7
7
 
8
- attr_reader :primary_tag, :subtag, :params
8
+ attr_reader :primary_tag, :subtag
9
9
 
10
10
  LANGUAGE_TAG_PATTERN = /^\s*(?<primary_tag>[\w]{1,8}|\*)(?:\s*\-\s*(?<subtag>[\w]{1,8}|\*))?\s*$/
11
11
 
@@ -6,9 +6,9 @@ module AcceptHeaders
6
6
  class Negotiator
7
7
  include Negotiatable
8
8
 
9
- private
10
- PARAM_PATTERN = /(?<attribute>[\w!#$%^&*\-\+{}\\|'.`~]+)\s*\=\s*(?:\"(?<value>[^"]*)\"|\'(?<value>[^']*)\'|(?<value>[\w!#$%^&*\-\+{}\\|\'.`~]*))/
9
+ PARAMS_PATTERN = /(?<attribute>[\w!#$%^&*\-\+{}\\|'.`~]+)\s*\=\s*(?:\"(?<value>[^"]*)\"|\'(?<value>[^']*)\'|(?<value>[\w!#$%^&*\-\+{}\\|\'.`~]*))/
11
10
 
11
+ private
12
12
  def parse(original_header)
13
13
  header = original_header.dup
14
14
  header.sub!(/\AAccept:\s*/, '')
@@ -16,7 +16,7 @@ module AcceptHeaders
16
16
  return [MediaType.new] if header.empty?
17
17
  media_types = []
18
18
  header.split(',').each do |entry|
19
- accept_media_range, accept_params = entry.split(';', 2)
19
+ accept_media_range, accept_extensions = entry.split(';', 2)
20
20
  next if accept_media_range.nil?
21
21
  media_range = MediaType::MEDIA_TYPE_PATTERN.match(accept_media_range)
22
22
  next if media_range.nil?
@@ -24,8 +24,8 @@ module AcceptHeaders
24
24
  media_types << MediaType.new(
25
25
  media_range[:type],
26
26
  media_range[:subtype],
27
- q: parse_q(accept_params),
28
- params: parse_params(accept_params)
27
+ q: parse_q(accept_extensions),
28
+ extensions: parse_extensions(accept_extensions)
29
29
  )
30
30
  rescue Error
31
31
  next
@@ -34,19 +34,19 @@ module AcceptHeaders
34
34
  media_types.sort! { |x,y| y <=> x }
35
35
  end
36
36
 
37
- def parse_params(params_string)
38
- return {} if !params_string || params_string.empty?
39
- if params_string.match(/['"]/)
40
- params = params_string.scan(PARAM_PATTERN).map(&:compact).to_h
37
+ def parse_extensions(extensions_string)
38
+ return {} if !extensions_string || extensions_string.empty?
39
+ if extensions_string.match(/['"]/)
40
+ extensions = extensions_string.scan(PARAMS_PATTERN).map(&:compact).to_h
41
41
  else
42
- params = {}
43
- params_string.split(';').each do |part|
44
- param = PARAM_PATTERN.match(part)
45
- params[param[:attribute]] = param[:value] if param
42
+ extensions = {}
43
+ extensions_string.split(';').each do |part|
44
+ param = PARAMS_PATTERN.match(part)
45
+ extensions[param[:attribute]] = param[:value] if param
46
46
  end
47
47
  end
48
- params.delete('q')
49
- params
48
+ extensions.delete('q')
49
+ extensions
50
50
  end
51
51
  end
52
52
  end
@@ -5,15 +5,15 @@ module AcceptHeaders
5
5
  include Comparable
6
6
  include Acceptable
7
7
 
8
- attr_reader :type, :subtype, :params
8
+ attr_reader :type, :subtype, :extensions
9
9
 
10
10
  MEDIA_TYPE_PATTERN = /^\s*(?<type>[\w!#$%^&*\-\+{}\\|'.`~]+)(?:\s*\/\s*(?<subtype>[\w!#$%^&*\-\+{}\\|'.`~]+))?\s*$/
11
11
 
12
- def initialize(type = '*', subtype = '*', q: 1.0, params: {})
12
+ def initialize(type = '*', subtype = '*', q: 1.0, extensions: {})
13
13
  self.type = type
14
14
  self.subtype = subtype
15
15
  self.q = q
16
- self.params = params
16
+ self.extensions = extensions
17
17
  end
18
18
 
19
19
  def <=>(other)
@@ -25,9 +25,9 @@ module AcceptHeaders
25
25
  -1
26
26
  elsif (type != '*' && other.type == '*') || (subtype != '*' && other.subtype == '*')
27
27
  1
28
- elsif params.size < other.params.size
28
+ elsif extensions.size < other.extensions.size
29
29
  -1
30
- elsif params.size > other.params.size
30
+ elsif extensions.size > other.extensions.size
31
31
  1
32
32
  else
33
33
  0
@@ -46,12 +46,12 @@ module AcceptHeaders
46
46
  end
47
47
  end
48
48
 
49
- def params=(hash)
50
- @params = {}
49
+ def extensions=(hash)
50
+ @extensions = {}
51
51
  hash.each do |k,v|
52
- @params[k.strip] = v.strip
52
+ @extensions[k.strip] = v
53
53
  end
54
- @params
54
+ @extensions
55
55
  end
56
56
 
57
57
  def to_h
@@ -59,15 +59,15 @@ module AcceptHeaders
59
59
  type: type,
60
60
  subtype: subtype,
61
61
  q: q,
62
- params: params
62
+ extensions: extensions
63
63
  }
64
64
  end
65
65
 
66
66
  def to_s
67
67
  qvalue = (q == 0 || q == 1) ? q.to_i : q
68
68
  string = "#{media_range};q=#{qvalue}"
69
- if params.size > 0
70
- params.each { |k, v| string.concat(";#{k}=#{v}") }
69
+ if extensions.size > 0
70
+ extensions.each { |k, v| string.concat(";#{k}=#{v}") }
71
71
  end
72
72
  string
73
73
  end
@@ -1,3 +1,3 @@
1
1
  module AcceptHeaders
2
- VERSION = "0.0.6"
2
+ VERSION = "0.0.7"
3
3
  end
@@ -1,140 +1,146 @@
1
1
  require_relative "spec_helper"
2
2
 
3
- module AcceptHeaders
4
- describe Encoding do
5
- subject do
6
- AcceptHeaders::Encoding
7
- end
3
+ describe AcceptHeaders::Encoding do
4
+ subject do
5
+ AcceptHeaders::Encoding
6
+ end
8
7
 
9
- it "defaults encoding to *" do
10
- subject.new.encoding.must_equal '*'
11
- end
8
+ it "defaults encoding to *" do
9
+ subject.new.encoding.must_equal '*'
10
+ end
12
11
 
13
- it "strips and downcases the encoding" do
14
- subject.new("\t\nGZIP\s\r").encoding.must_equal "gzip"
15
- end
12
+ it "strips and downcases the encoding" do
13
+ subject.new("\t\nGZIP\s\r").encoding.must_equal "gzip"
14
+ end
16
15
 
17
- it "optionally supports a q argument" do
18
- subject.new('gzip', q: 0.8).q.must_equal 0.8
19
- end
16
+ it "optionally supports a q argument" do
17
+ subject.new('gzip', q: 0.8).q.must_equal 0.8
18
+ end
20
19
 
21
- it "compares on q value all other values remaining equal" do
22
- subject.new(q: 0.514).must_be :>, subject.new(q: 0.1)
23
- subject.new(q: 0).must_be :<, subject.new(q: 1)
24
- subject.new(q: 0.9).must_equal subject.new(q: 0.9)
25
- end
20
+ it "compares on q value all other values remaining equal" do
21
+ subject.new(q: 0.514).must_be :>, subject.new(q: 0.1)
22
+ subject.new(q: 0).must_be :<, subject.new(q: 1)
23
+ subject.new(q: 0.9).must_equal subject.new(q: 0.9)
24
+ end
26
25
 
27
- it "raises an InvalidQError if q can't be converted to a float" do
28
- e = -> do
29
- subject.new('gzip', q: 'a')
30
- end.must_raise Encoding::InvalidQError
26
+ it "raises an InvalidQError if q can't be converted to a float" do
27
+ e = -> do
28
+ subject.new('gzip', q: 'a')
29
+ end.must_raise AcceptHeaders::Encoding::InvalidQError
30
+
31
+ e.message.must_match INVALID_FLOAT_PATTERN
32
+
33
+ subject.new('gzip', q: '1')
34
+ end
31
35
 
32
- e.message.must_equal 'invalid value for Float(): "a"'
36
+ it "raises an InvalidQError unless q value is between 0 and 1" do
37
+ [-1.0, -0.1, 1.1].each do |q|
38
+ e = -> do
39
+ subject.new('gzip', q: q)
40
+ end.must_raise AcceptHeaders::Encoding::InvalidQError
33
41
 
34
- subject.new('gzip', q: '1')
42
+ e.message.must_equal "q must be between 0 and 1"
35
43
  end
36
44
 
37
- it "raises an InvalidQError unless q value is between 0 and 1" do
38
- [-1.0, -0.1, 1.1].each do |q|
39
- e = -> do
40
- subject.new('gzip', q: q)
41
- end.must_raise Encoding::InvalidQError
45
+ subject.new('gzip', q: 1)
46
+ subject.new('compress', q: 0)
47
+ end
42
48
 
43
- e.message.must_equal "q must be between 0 and 1"
44
- end
49
+ it "raises an InvalidQError if q has more than a precision of 3" do
50
+ e = -> do
51
+ subject.new('gzip', q: 0.1234)
52
+ end.must_raise AcceptHeaders::Encoding::InvalidQError
45
53
 
46
- subject.new('gzip', q: 1)
47
- subject.new('compress', q: 0)
48
- end
54
+ e.message.must_equal "q must be at most 3 decimal places"
49
55
 
50
- it "raises an InvalidQError if q has more than a precision of 3" do
51
- e = -> do
52
- subject.new('gzip', q: 0.1234)
53
- end.must_raise Encoding::InvalidQError
56
+ subject.new('gzip', q: 0.123)
57
+ end
58
+
59
+ it "converts to hash" do
60
+ subject.new('gzip').to_h.must_equal({
61
+ encoding: 'gzip',
62
+ q: 1.0
63
+ })
64
+ end
54
65
 
55
- e.message.must_equal "q must be at most 3 decimal places"
66
+ it "converts to string" do
67
+ s = subject.new('gzip', q: 0.9).to_s
68
+ s.must_equal "gzip;q=0.9"
69
+ end
56
70
 
57
- subject.new('gzip', q: 0.123)
71
+ describe "#accept?" do
72
+ it "accepted if the encoding is the same" do
73
+ a = subject.new('gzip')
74
+ a.accept?('gzip').must_equal true
75
+ b = subject.new('gzip', q: 0.001)
76
+ b.accept?('gzip').must_equal true
58
77
  end
59
78
 
60
- it "converts to hash" do
61
- subject.new('gzip').to_h.must_equal({
62
- encoding: 'gzip',
63
- q: 1.0
64
- })
79
+ it "accepted if the encoding is *" do
80
+ a = subject.new('*')
81
+ a.accept?('gzip').must_equal true
82
+ b = subject.new('*', q: 0.1)
83
+ b.accept?('gzip').must_equal true
65
84
  end
66
85
 
67
- it "converts to string" do
68
- s = subject.new('gzip', q: 0.9).to_s
69
- s.must_equal "gzip;q=0.9"
86
+ it "not accepted if the encoding doesn't match" do
87
+ a = subject.new('gzip')
88
+ a.accept?('compress').must_equal false
89
+ b = subject.new('gzip', q: 0.4)
90
+ b.accept?('compress').must_equal false
70
91
  end
71
92
 
72
- describe "#accept?" do
73
- it "accepted if the encoding is the same" do
74
- a = subject.new('gzip')
75
- a.accept?('gzip').must_equal true
76
- b = subject.new('gzip', q: 0.001)
77
- b.accept?('gzip').must_equal true
78
- end
93
+ it "not accepted if q is 0" do
94
+ a = subject.new('gzip', q: 0)
95
+ a.accept?('gzip').must_equal false
96
+ b = subject.new('*', q: 0)
97
+ b.accept?('gzip').must_equal false
98
+ end
79
99
 
80
- it "accepted if the encoding is *" do
81
- a = subject.new('*')
82
- a.accept?('gzip').must_equal true
83
- b = subject.new('*', q: 0.1)
84
- b.accept?('gzip').must_equal true
100
+ it "not accepted compared against nil" do
101
+ subject.new('gzip').accept?(nil).must_equal false
102
+ end
103
+
104
+ # TODO: test *
105
+ # it "not accepted if..." do
106
+ # a = subject.new('gzip')
107
+ # a.accept?('*').must_equal true
108
+ # end
109
+ end
110
+
111
+ describe "#reject?" do
112
+ describe "given q is 0" do
113
+ it "rejected if the encoding is the same" do
114
+ a = subject.new('gzip', q: 0)
115
+ a.reject?('gzip').must_equal true
85
116
  end
86
117
 
87
- it "not accepted if the encoding doesn't match" do
88
- a = subject.new('gzip')
89
- a.accept?('compress').must_equal false
90
- b = subject.new('gzip', q: 0.4)
91
- b.accept?('compress').must_equal false
118
+ it "rejected if the encoding is *" do
119
+ a = subject.new('*', q: 0)
120
+ a.reject?('gzip').must_equal true
92
121
  end
93
122
 
94
- it "not accepted if q is 0" do
123
+ it "not rejected if the encoding doesn't match" do
95
124
  a = subject.new('gzip', q: 0)
96
- a.accept?('gzip').must_equal false
97
- b = subject.new('*', q: 0)
98
- b.accept?('gzip').must_equal false
125
+ a.reject?('compress').must_equal false
99
126
  end
100
127
 
101
128
  # TODO: test *
102
- # it "not accepted if..." do
103
- # a = subject.new('gzip')
104
- # a.accept?('*').must_equal true
129
+ # it "not rejected if..." do
130
+ # a = subject.new('gzip', q: 0)
131
+ # a.reject?('*').must_equal true
105
132
  # end
106
133
  end
107
134
 
108
- describe "#reject?" do
109
- describe "given q is 0" do
110
- it "rejected if the encoding is the same" do
111
- a = subject.new('gzip', q: 0)
112
- a.reject?('gzip').must_equal true
113
- end
114
-
115
- it "rejected if the encoding is *" do
116
- a = subject.new('*', q: 0)
117
- a.reject?('gzip').must_equal true
118
- end
119
-
120
- it "not rejected if the encoding doesn't match" do
121
- a = subject.new('gzip', q: 0)
122
- a.reject?('compress').must_equal false
123
- end
124
-
125
- # TODO: test *
126
- # it "not rejected if..." do
127
- # a = subject.new('gzip', q: 0)
128
- # a.reject?('*').must_equal true
129
- # end
130
- end
135
+ it "not rejected if q > 0" do
136
+ a = subject.new('gzip', q: 0.001)
137
+ a.reject?('gzip').must_equal false
138
+ b = subject.new('*', q: 0.9)
139
+ b.reject?('gzip').must_equal false
140
+ end
131
141
 
132
- it "not rejected if q > 0" do
133
- a = subject.new('gzip', q: 0.001)
134
- a.reject?('gzip').must_equal false
135
- b = subject.new('*', q: 0.9)
136
- b.reject?('gzip').must_equal false
137
- end
142
+ it "not rejected compared against nil" do
143
+ subject.new('gzip').reject?(nil).must_equal false
138
144
  end
139
145
  end
140
146
  end