accept_headers 0.0.7 → 0.0.8

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: 1c59b8c10eea7c406d76ca08d6daf1eb25a92f41
4
- data.tar.gz: 76b2194684f98c8812f71e32daf84af838a38faa
3
+ metadata.gz: b55af3993793d079e27b4b85fd02344b7dbc878b
4
+ data.tar.gz: 30264067d3cbd349e13169a8592df750c81d06ba
5
5
  SHA512:
6
- metadata.gz: a3da1d44c7090dcd155f4b6f03e6bb6f40b9292d7bdbd9fee8f77cf7d440a1e8b9e3497f7ee0c42ac229b4377fb0c0b1932afa085f3dc0ca23fa452a82c98361
7
- data.tar.gz: af9ead68fe19c681981c4a522b4c73cc166fd2e0e1709677232dd5d2f755b5c61d0bce036da997a218818b42297e2d899fdc1dc5324a30049f740ae4141b80e0
6
+ metadata.gz: b45c6294fdba63025d7a1fe4d9c860078427d3011b98aa99c8a8b84429fd4454ed18aef2eefdb22d841a32284df4df475449da2787d485b41349b7664b5ca7e1
7
+ data.tar.gz: 32cbb89fbe0f5b85824a8f8ae9cac9bfa095a5f02de33625511fe7e8c1b8cd5f55de8e6231787dd062f10cfe55e0b409226e2146f7861454ed07952eb8c4dc3f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## HEAD
2
2
 
3
+ ## 0.0.8 / November 23, 2014
4
+
5
+ * Change `#negotiate` to return the type instead of a hash. In the case of `MediaType` it'll also populate `extensions` from the matching accept header media type.
6
+
3
7
  ## 0.0.7 / November 19, 2014
4
8
 
5
9
  * Rename `MediaType` `params` to `extensions`, since params technically includes the `q` value.
data/README.md CHANGED
@@ -65,7 +65,7 @@ media_types.list
65
65
  ]
66
66
  ```
67
67
 
68
- `#negotiate` takes an array of media range strings supported (by your API or route/controller) and returns a hash where the `supported` key contains the array element matched and the `matched` key containing a `MediaType` that was matched in the `Negotiator`s internal list.
68
+ `#negotiate` takes an array of media range strings supported (by your API or route/controller) and returns the best supported `MediaType` and the `extensions` params from the matching internal media type.
69
69
 
70
70
  This will first check the available list for any matching media types with a `q` of 0 and skip any matches. It does this because the RFC specifies that if the `q` value is 0, then content with this parameter is `not acceptable`. Then it'll look to the highest `q` values and look for matches in descending `q` value order and return the first match (accounting for wildcards). Finally, if there are no matches, it returns `nil`.
71
71
 
@@ -73,12 +73,9 @@ This will first check the available list for any matching media types with a `q`
73
73
  # The same media_types variable as above
74
74
  media_types.negotiate(['text/html', 'text/plain'])
75
75
 
76
- # Returns:
76
+ # Returns this equivalent:
77
77
 
78
- {
79
- supported: 'text/html',
80
- matched: AcceptHeaders::MediaType.new('text', 'html', q: 1, extensions: { 'level' => '1' })
81
- }
78
+ AcceptHeader::MediaType.new('text', 'html', extensions: { 'level' => '1' })
82
79
  ```
83
80
 
84
81
  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.
@@ -88,7 +85,7 @@ versions_header = 'Accept: application/json;version=2,application/json;version=1
88
85
  media_types = AcceptHeaders::MediaType::Negotiator.new(versions_header)
89
86
 
90
87
  m = media_types.negotiate('application/json')
91
- puts m[:match].extensions['version'] # returns '2'
88
+ puts m.extensions['version'] # returns '2'
92
89
  ```
93
90
 
94
91
  `#accept?`:
@@ -121,12 +118,9 @@ encodings.list
121
118
  ```ruby
122
119
  encodings.negotiate(['gzip', 'compress'])
123
120
 
124
- # Returns:
121
+ # Returns this equivalent:
125
122
 
126
- {
127
- supported: 'gzip',
128
- matched: AcceptHeaders::Encoding.new('gzip'))
129
- }
123
+ AcceptHeader::Encoding.new('gzip')
130
124
  ```
131
125
 
132
126
  `#accept?`:
@@ -163,12 +157,9 @@ languages.list
163
157
  ```ruby
164
158
  languages.negotiate(['en-us', 'zh-Hant'])
165
159
 
166
- # Returns:
160
+ # Returns this equivalent:
167
161
 
168
- {
169
- supported: 'en-us',
170
- matched: AcceptHeaders::Language.new('en', 'us'))
171
- }
162
+ AcceptHeaders::Language.new('en', 'us')
172
163
  ```
173
164
 
174
165
  `#accept?`:
@@ -7,8 +7,6 @@ module AcceptHeaders
7
7
 
8
8
  attr_reader :encoding
9
9
 
10
- ENCODING_PATTERN = /^\s*(?<encoding>[\w!#$%^&*\-\+{}\\|'.`~]+)\s*$/
11
-
12
10
  def initialize(encoding = '*', q: 1.0)
13
11
  self.encoding = encoding
14
12
  self.q = q
@@ -35,7 +33,7 @@ module AcceptHeaders
35
33
  end
36
34
 
37
35
  def match(encoding_string)
38
- match_data = ENCODING_PATTERN.match(encoding_string)
36
+ match_data = Negotiator::ENCODING_PATTERN.match(encoding_string)
39
37
  if !match_data
40
38
  false
41
39
  elsif encoding == match_data[:encoding]
@@ -6,21 +6,32 @@ module AcceptHeaders
6
6
  class Negotiator
7
7
  include Negotiatable
8
8
 
9
+ ENCODING_PATTERN = /^\s*(?<encoding>[\w!#$%^&*\-\+{}\\|'.`~]+)\s*$/
10
+ HEADER_PREFIX = 'Accept-Encoding:'
11
+
12
+ def negotiate(supported)
13
+ support, match = super(supported)
14
+ return nil if support.nil? && match.nil?
15
+ begin
16
+ return parse(support).first
17
+ rescue Encoding::Error
18
+ return nil
19
+ end
20
+ end
21
+
9
22
  private
10
- def parse(original_header)
11
- header = original_header.dup
12
- header.sub!(/\AAccept-Encoding:\s*/, '')
23
+ def no_header
24
+ [Encoding.new]
25
+ end
26
+
27
+ def parse_item(header)
28
+ return nil if header.nil?
13
29
  header.strip!
14
- return [Encoding.new] if header.empty?
15
- encodings = []
16
- header.split(',').each do |entry|
17
- encoding_arr = entry.split(';', 2)
18
- next if encoding_arr[0].nil?
19
- encoding = Encoding::ENCODING_PATTERN.match(encoding_arr[0])
20
- next if encoding.nil?
21
- encodings << Encoding.new(encoding[:encoding], q: parse_q(encoding_arr[1]))
22
- end
23
- encodings.sort! { |x,y| y <=> x }
30
+ encoding_string, q_string = header.split(';', 2)
31
+ raise Error if encoding_string.nil?
32
+ encoding = ENCODING_PATTERN.match(encoding_string)
33
+ raise Error if encoding.nil?
34
+ Encoding.new(encoding[:encoding], q: parse_q(q_string))
24
35
  end
25
36
  end
26
37
  end
@@ -7,8 +7,6 @@ module AcceptHeaders
7
7
 
8
8
  attr_reader :primary_tag, :subtag
9
9
 
10
- LANGUAGE_TAG_PATTERN = /^\s*(?<primary_tag>[\w]{1,8}|\*)(?:\s*\-\s*(?<subtag>[\w]{1,8}|\*))?\s*$/
11
-
12
10
  def initialize(primary_tag = '*', subtag = nil, q: 1.0)
13
11
  self.primary_tag = primary_tag
14
12
  self.subtag = subtag
@@ -63,7 +61,7 @@ module AcceptHeaders
63
61
  end
64
62
 
65
63
  def match(language_tag_string)
66
- match_data = LANGUAGE_TAG_PATTERN.match(language_tag_string)
64
+ match_data = Negotiator::LANGUAGE_TAG_PATTERN.match(language_tag_string)
67
65
  if !match_data
68
66
  false
69
67
  elsif primary_tag == match_data[:primary_tag] && subtag == match_data[:subtag]
@@ -6,29 +6,36 @@ module AcceptHeaders
6
6
  class Negotiator
7
7
  include Negotiatable
8
8
 
9
+ LANGUAGE_TAG_PATTERN = /^\s*(?<primary_tag>[\w]{1,8}|\*)(?:\s*\-\s*(?<subtag>[\w]{1,8}|\*))?\s*$/
10
+ HEADER_PREFIX = 'Accept-Language:'
11
+
12
+ def negotiate(supported)
13
+ support, match = super(supported)
14
+ return nil if support.nil? && match.nil?
15
+ begin
16
+ return parse(support).first
17
+ rescue Language::Error
18
+ return nil
19
+ end
20
+ end
21
+
9
22
  private
10
- def parse(original_header)
11
- header = original_header.dup
12
- header.sub!(/\AAccept-Language:\s*/, '')
23
+ def no_header
24
+ [Language.new]
25
+ end
26
+
27
+ def parse_item(header)
28
+ return nil if header.nil?
13
29
  header.strip!
14
- return [Language.new] if header.empty?
15
- languages = []
16
- header.split(',').each do |entry|
17
- language_arr = entry.split(';', 2)
18
- next if language_arr[0].nil?
19
- language_range = Language::LANGUAGE_TAG_PATTERN.match(language_arr[0])
20
- next if language_range.nil?
21
- begin
22
- languages << Language.new(
23
- language_range[:primary_tag],
24
- language_range[:subtag],
25
- q: parse_q(language_arr[1])
26
- )
27
- rescue Error
28
- next
29
- end
30
- end
31
- languages.sort! { |x,y| y <=> x }
30
+ language_string, q_string = header.split(';', 2)
31
+ raise Error if language_string.nil?
32
+ language_range = LANGUAGE_TAG_PATTERN.match(language_string)
33
+ raise Error if language_range.nil?
34
+ Language.new(
35
+ language_range[:primary_tag],
36
+ language_range[:subtag],
37
+ q: parse_q(q_string)
38
+ )
32
39
  end
33
40
  end
34
41
  end
@@ -7,8 +7,6 @@ module AcceptHeaders
7
7
 
8
8
  attr_reader :type, :subtype, :extensions
9
9
 
10
- MEDIA_TYPE_PATTERN = /^\s*(?<type>[\w!#$%^&*\-\+{}\\|'.`~]+)(?:\s*\/\s*(?<subtype>[\w!#$%^&*\-\+{}\\|'.`~]+))?\s*$/
11
-
12
10
  def initialize(type = '*', subtype = '*', q: 1.0, extensions: {})
13
11
  self.type = type
14
12
  self.subtype = subtype
@@ -77,7 +75,7 @@ module AcceptHeaders
77
75
  end
78
76
 
79
77
  def match(media_range_string)
80
- match_data = MEDIA_TYPE_PATTERN.match(media_range_string)
78
+ match_data = Negotiator::MEDIA_TYPE_PATTERN.match(media_range_string)
81
79
  if !match_data
82
80
  false
83
81
  elsif type == match_data[:type] && subtype == match_data[:subtype]
@@ -6,32 +6,40 @@ module AcceptHeaders
6
6
  class Negotiator
7
7
  include Negotiatable
8
8
 
9
+ MEDIA_TYPE_PATTERN = /^\s*(?<type>[\w!#$%^&*\-\+{}\\|'.`~]+)(?:\s*\/\s*(?<subtype>[\w!#$%^&*\-\+{}\\|'.`~]+))?\s*$/
9
10
  PARAMS_PATTERN = /(?<attribute>[\w!#$%^&*\-\+{}\\|'.`~]+)\s*\=\s*(?:\"(?<value>[^"]*)\"|\'(?<value>[^']*)\'|(?<value>[\w!#$%^&*\-\+{}\\|\'.`~]*))/
11
+ HEADER_PREFIX = 'Accept:'
12
+
13
+ def negotiate(supported)
14
+ support, match = super(supported)
15
+ return nil if support.nil? && match.nil?
16
+ begin
17
+ media_type = parse(support).first
18
+ media_type.extensions = match.extensions
19
+ return media_type
20
+ rescue MediaType::Error
21
+ return nil
22
+ end
23
+ end
10
24
 
11
25
  private
12
- def parse(original_header)
13
- header = original_header.dup
14
- header.sub!(/\AAccept:\s*/, '')
26
+ def no_header
27
+ [MediaType.new]
28
+ end
29
+
30
+ def parse_item(header)
31
+ return nil if header.nil?
15
32
  header.strip!
16
- return [MediaType.new] if header.empty?
17
- media_types = []
18
- header.split(',').each do |entry|
19
- accept_media_range, accept_extensions = entry.split(';', 2)
20
- next if accept_media_range.nil?
21
- media_range = MediaType::MEDIA_TYPE_PATTERN.match(accept_media_range)
22
- next if media_range.nil?
23
- begin
24
- media_types << MediaType.new(
25
- media_range[:type],
26
- media_range[:subtype],
27
- q: parse_q(accept_extensions),
28
- extensions: parse_extensions(accept_extensions)
29
- )
30
- rescue Error
31
- next
32
- end
33
- end
34
- media_types.sort! { |x,y| y <=> x }
33
+ accept_media_range, accept_extensions = header.split(';', 2)
34
+ raise Error if accept_media_range.nil?
35
+ media_range = MEDIA_TYPE_PATTERN.match(accept_media_range)
36
+ raise Error if media_range.nil?
37
+ MediaType.new(
38
+ media_range[:type],
39
+ media_range[:subtype],
40
+ q: parse_q(accept_extensions),
41
+ extensions: parse_extensions(accept_extensions)
42
+ )
35
43
  end
36
44
 
37
45
  def parse_extensions(extensions_string)
@@ -3,10 +3,10 @@ module AcceptHeaders
3
3
  class Error < StandardError; end
4
4
  class ParseError < Error; end
5
5
 
6
- Q_PATTERN = /(?:\A|;)\s*(?<exists>qs*\=)\s*(?:(?<q>0\.\d{1,3}|[01])|(?:[^;]*))\s*(?:\z|;)/
7
-
8
6
  attr_reader :list
9
7
 
8
+ Q_PATTERN = /(?:\A|;)\s*(?<exists>qs*\=)\s*(?:(?<q>0\.\d{1,3}|[01])|(?:[^;]*))\s*(?:\z|;)/
9
+
10
10
  def initialize(header)
11
11
  @list = parse(header)
12
12
  end
@@ -22,7 +22,7 @@ module AcceptHeaders
22
22
  if part.q == 0.0
23
23
  next
24
24
  else
25
- return { supported: support, matched: part }
25
+ return [support, part]
26
26
  end
27
27
  end
28
28
  end
@@ -35,8 +35,30 @@ module AcceptHeaders
35
35
  end
36
36
 
37
37
  private
38
- def parse(header)
39
- raise NotImplementedError.new("#parse(header) is not implemented")
38
+ def no_header
39
+ raise NotImplementedError.new("#no_header is not implemented")
40
+ end
41
+
42
+ def parse_item(entry)
43
+ raise NotImplementedError.new("#parse_item(entry) is not implemented")
44
+ end
45
+
46
+ def parse(header, &block)
47
+ header = header.dup
48
+ header.sub!(/\A#{self.class::HEADER_PREFIX}\s*/, '')
49
+ header.strip!
50
+ return no_header if header.empty?
51
+ list = []
52
+ header.split(',').each do |entry|
53
+ begin
54
+ item = parse_item(entry)
55
+ next if item.nil?
56
+ list << item
57
+ rescue Error
58
+ next
59
+ end
60
+ end
61
+ list.sort! { |x,y| y <=> x }
40
62
  end
41
63
 
42
64
  def parse_q(header)
@@ -1,3 +1,3 @@
1
1
  module AcceptHeaders
2
- VERSION = "0.0.7"
2
+ VERSION = "0.0.8"
3
3
  end
@@ -80,10 +80,7 @@ describe AcceptHeaders::Encoding::Negotiator do
80
80
  describe "negotiate supported encodings" do
81
81
  it "returns a best matching encoding" do
82
82
  n = subject.new('deflate; q=0.5, gzip, compress; q=0.8, identity')
83
- n.negotiate(['identity', 'deflate']).must_equal({
84
- supported: 'identity',
85
- matched: encoding.new('identity')
86
- })
83
+ n.negotiate(['identity', 'deflate']).must_equal encoding.new('identity')
87
84
  end
88
85
  end
89
86
  end
@@ -73,10 +73,7 @@ describe AcceptHeaders::Language::Negotiator do
73
73
  it "returns a best matching language" do
74
74
  match = language.new('en', 'us')
75
75
  n = subject.new('en-*, en-us, *;q=0.8')
76
- n.negotiate('en-us').must_equal({
77
- supported: 'en-us',
78
- matched: match
79
- })
76
+ n.negotiate('en-us').must_equal language.new('en', 'us')
80
77
  end
81
78
  end
82
79
  end
@@ -111,54 +111,57 @@ describe AcceptHeaders::MediaType::Negotiator do
111
111
  all_browsers.each do |browser|
112
112
  browser = subject.new(browser[:accept])
113
113
 
114
- browser.negotiate('text/html').must_equal({
115
- supported: 'text/html',
116
- matched: media_type.new('text', 'html')
117
- })
118
-
119
- browser.negotiate(['text/html', 'application/xhtml+xml']).must_equal({
120
- supported: 'text/html',
121
- matched: media_type.new('text', 'html')
122
- })
123
-
124
- browser.negotiate(['application/xhtml+xml', 'application/json']).must_equal({
125
- supported: 'application/xhtml+xml',
126
- matched: media_type.new('application', 'xhtml+xml')
127
- })
114
+ browser.negotiate('text/html').must_equal media_type.new(
115
+ 'text',
116
+ 'html'
117
+ )
118
+
119
+ browser.negotiate(['text/html', 'application/xhtml+xml']).must_equal media_type.new(
120
+ 'text',
121
+ 'html'
122
+ )
123
+
124
+ browser.negotiate(['application/xhtml+xml', 'application/json']).must_equal media_type.new(
125
+ 'application',
126
+ 'xhtml+xml'
127
+ )
128
128
  end
129
129
 
130
130
  [chrome, firefox, safari].each do| browser|
131
131
  browser = subject.new(browser[:accept])
132
132
 
133
- browser.negotiate(['application/xml']).must_equal({
134
- supported: 'application/xml',
135
- matched: media_type.new('application', 'xml', q: 0.9)
136
- })
133
+ browser.negotiate(['application/xml']).must_equal media_type.new(
134
+ 'application',
135
+ 'xml'
136
+ )
137
137
 
138
- browser.negotiate(['application/json']).must_equal({
139
- supported: 'application/json',
140
- matched: media_type.new('*', '*', q: 0.8)
141
- })
138
+ browser.negotiate(['application/json']).must_equal media_type.new(
139
+ 'application',
140
+ 'json'
141
+ )
142
142
  end
143
143
 
144
144
  api = subject.new('application/json;q=1;version=2,application/json;q=0.9;version=1')
145
145
  api.negotiate('text/html').must_be_nil
146
146
  api.negotiate('application/xml').must_be_nil
147
- api.negotiate(['application/xml', 'application/json']).must_equal({
148
- supported: 'application/json',
149
- matched: media_type.new('application', 'json', extensions: { 'version' => '2' })
150
- })
147
+ api.negotiate(['application/xml', 'application/json']).must_equal media_type.new(
148
+ 'application',
149
+ 'json',
150
+ extensions: {
151
+ 'version' => '1'
152
+ }
153
+ )
151
154
 
152
155
  q0 = subject.new('application/json,application/xml;q=0')
153
- q0.negotiate('application/json').must_equal({
154
- supported: 'application/json',
155
- matched: media_type.new('application', 'json')
156
- })
156
+ q0.negotiate('application/json').must_equal media_type.new(
157
+ 'application',
158
+ 'json'
159
+ )
157
160
  q0.negotiate('application/xml').must_be_nil
158
- q0.negotiate(['application/json', 'application/xml']).must_equal({
159
- supported: 'application/json',
160
- matched: media_type.new('application', 'json')
161
- })
161
+ q0.negotiate(['application/json', 'application/xml']).must_equal media_type.new(
162
+ 'application',
163
+ 'json'
164
+ )
162
165
  end
163
166
 
164
167
  it "rejects matching q=0 even if it matches media ranges where q > 0" do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: accept_headers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jack Chu
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-11-19 00:00:00.000000000 Z
11
+ date: 2014-11-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler