accept_headers 0.0.6 → 0.0.7

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: 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