negotiator 0.1
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.
- data/Readme.md +43 -0
- data/lib/negotiator.rb +69 -0
- data/test/test_negotiator.rb +133 -0
- metadata +48 -0
data/Readme.md
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# Negotiator
|
2
|
+
|
3
|
+
This library does HTTP content negotiation.
|
4
|
+
|
5
|
+
This particular library intends to follow the HTTP
|
6
|
+
specification closely.
|
7
|
+
|
8
|
+
You give it an Accept header and a list of content types,
|
9
|
+
and it'll pick one. In case of a tie, it'll choose the one
|
10
|
+
that comes first in the provided Hash (as long as you're
|
11
|
+
using ruby >= 1.9). If none match, it'll return nil.
|
12
|
+
|
13
|
+
A missing (nil) header is treated the same as `*/*`, as
|
14
|
+
required by HTTP.
|
15
|
+
|
16
|
+
## Examples
|
17
|
+
|
18
|
+
> header = "text/xml, application/json, text/plain; q=0.8, */*; q=0.5"
|
19
|
+
> available = {"text/plain" => 1.0, "application/json" => 1.0, "text/xml" => 1.0}
|
20
|
+
> Negotiator.pick(header, available)
|
21
|
+
"application/json"
|
22
|
+
|
23
|
+
> header = "audio/*; q=0.5, audio/mp3; q=1.0"
|
24
|
+
> available = {"audio/flac" => 1.0, "audio/ogg" => 0.8, "audio/mp3" => 0.7}
|
25
|
+
> Negotiator.pick(header, available)
|
26
|
+
"audio/mp3"
|
27
|
+
|
28
|
+
## Advice
|
29
|
+
|
30
|
+
If `Negotiator.pick` returns `nil`, you should send the
|
31
|
+
HTTP 406 (not acceptable) response.
|
32
|
+
|
33
|
+
## Running Tests
|
34
|
+
|
35
|
+
$ turn test
|
36
|
+
|
37
|
+
## The End
|
38
|
+
|
39
|
+
Bug reports and pull requests are most welcome!
|
40
|
+
|
41
|
+
Thank you!
|
42
|
+
|
43
|
+
Have fun!
|
data/lib/negotiator.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
class Negotiator
|
2
|
+
VERSION = "0.1"
|
3
|
+
|
4
|
+
def self.pick(h, o)
|
5
|
+
r = parse(h)
|
6
|
+
cand = []
|
7
|
+
for s, q in o
|
8
|
+
st = s.split(/\//)[0]
|
9
|
+
case true
|
10
|
+
when r[0].include?(s)
|
11
|
+
q *= r[0][s]
|
12
|
+
when r[1].include?(st)
|
13
|
+
q *= r[1][st]
|
14
|
+
else
|
15
|
+
q *= r[2]
|
16
|
+
end
|
17
|
+
if q > 0
|
18
|
+
cand << [s, q]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
if cand.empty?
|
23
|
+
return nil
|
24
|
+
end
|
25
|
+
|
26
|
+
best = cand[0]
|
27
|
+
for s, q in cand
|
28
|
+
if q > best[1]
|
29
|
+
best = [s, q]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
return best[0]
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.parse(h)
|
36
|
+
if !h
|
37
|
+
h = "*/*"
|
38
|
+
end
|
39
|
+
|
40
|
+
qual = [{}, {}, 0.0]
|
41
|
+
p = h.split(/,\s*/)
|
42
|
+
for s in p
|
43
|
+
t, st, q = parse_media_range(s)
|
44
|
+
|
45
|
+
case true
|
46
|
+
when t == "*"
|
47
|
+
qual[2] = q
|
48
|
+
when st == "*"
|
49
|
+
qual[1][t] = q
|
50
|
+
else
|
51
|
+
qual[0]["#{t}/#{st}"] = q
|
52
|
+
end
|
53
|
+
end
|
54
|
+
qual
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.parse_media_range(s)
|
58
|
+
q = 1.0
|
59
|
+
if s[";"]
|
60
|
+
s, qs = s.split(/;\s*/)
|
61
|
+
if qs.index("q=") == 0
|
62
|
+
q = Float(qs[2,qs.length])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
t, st = s.split(/\//)
|
67
|
+
return t, st, q
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
$VERBOSE = true
|
2
|
+
require 'test/unit'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
require '../negotiator/lib/negotiator'
|
6
|
+
|
7
|
+
class TestNegotiator < Test::Unit::TestCase
|
8
|
+
def test_any
|
9
|
+
h = "*/*"
|
10
|
+
a = {"text/plain" => 1.0, "application/json" => 0.9999, "text/xml" => 0.9998}
|
11
|
+
t = Negotiator.pick(h, a)
|
12
|
+
assert_equal("text/plain", t)
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_subsume_any
|
16
|
+
h = "text/plain, */*"
|
17
|
+
a = {"text/plain" => 1.0, "application/json" => 0.9999, "text/xml" => 0.9998}
|
18
|
+
t = Negotiator.pick(h, a)
|
19
|
+
assert_equal("text/plain", t)
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_part
|
23
|
+
h = "text/*"
|
24
|
+
a = {"text/plain" => 1.0, "application/json" => 0.9999, "text/xml" => 0.9998}
|
25
|
+
t = Negotiator.pick(h, a)
|
26
|
+
assert_equal("text/plain", t)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_subsume_part
|
30
|
+
h = "text/plain, text/*"
|
31
|
+
a = {"text/plain" => 1.0, "application/json" => 0.9999, "text/xml" => 0.9998}
|
32
|
+
t = Negotiator.pick(h, a)
|
33
|
+
assert_equal("text/plain", t)
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_full_only
|
37
|
+
h = "application/xml"
|
38
|
+
a = {"application/json" => 1.0, "text/html" => 0.9999, "application/xml" => 0.9998}
|
39
|
+
t = Negotiator.pick(h, a)
|
40
|
+
assert_equal("application/xml", t)
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_no_match
|
44
|
+
h = "text/plain"
|
45
|
+
a = {"application/json" => 1.0, "text/html" => 0.9999, "application/xml" => 0.9998}
|
46
|
+
t = Negotiator.pick(h, a)
|
47
|
+
assert_equal(nil, t)
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_empty_header
|
51
|
+
h = ""
|
52
|
+
a = {"application/json" => 1.0, "text/html" => 0.9999, "application/xml" => 0.9998}
|
53
|
+
t = Negotiator.pick(h, a)
|
54
|
+
assert_equal(nil, t)
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_nil_header
|
58
|
+
h = nil
|
59
|
+
a = {"application/json" => 1.0, "text/html" => 0.9999, "application/xml" => 0.9998}
|
60
|
+
t = Negotiator.pick(h, a)
|
61
|
+
assert_equal("application/json", t)
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_3levels
|
65
|
+
h = "application/xml, application/json, text/plain; q=0.8, */*; q=0.5"
|
66
|
+
a = {"text/plain" => 1.0, "application/xml" => 0.9999}
|
67
|
+
t = Negotiator.pick(h, a)
|
68
|
+
assert_equal("application/xml", t)
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_browser
|
72
|
+
h = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
|
73
|
+
a = {"application/json" => 1.0, "text/html" => 0.9999, "application/xml" => 0.9998}
|
74
|
+
t = Negotiator.pick(h, a)
|
75
|
+
assert_equal("text/html", t)
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_general_markdown_preferred
|
79
|
+
h = "audio/*; q=0.2, audio/basic"
|
80
|
+
a = {"audio/mp3" => 1.0, "audio/basic" => 0.9999}
|
81
|
+
t = Negotiator.pick(h, a)
|
82
|
+
assert_equal("audio/basic", t)
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_general_markdown_nonpreferred
|
86
|
+
h = "audio/*; q=0.2, audio/basic"
|
87
|
+
a = {"audio/mp3" => 1.0, "audio/basic" => 0.1}
|
88
|
+
t = Negotiator.pick(h, a)
|
89
|
+
assert_equal("audio/mp3", t)
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_general_markup_preferred
|
93
|
+
h = "audio/*; q=1.2, audio/basic"
|
94
|
+
a = {"audio/mp3" => 1.0, "audio/basic" => 0.9999}
|
95
|
+
t = Negotiator.pick(h, a)
|
96
|
+
assert_equal("audio/mp3", t)
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_general_markup_nonpreferred
|
100
|
+
h = "audio/*; q=1.2, audio/basic"
|
101
|
+
a = {"audio/mp3" => 1.0, "audio/basic" => 1.3}
|
102
|
+
t = Negotiator.pick(h, a)
|
103
|
+
assert_equal("audio/basic", t)
|
104
|
+
end
|
105
|
+
|
106
|
+
def test_specific_markdown_preferred
|
107
|
+
h = "audio/*, audio/basic; q=0.2"
|
108
|
+
a = {"audio/mp3" => 1.0, "audio/basic" => 0.9999}
|
109
|
+
t = Negotiator.pick(h, a)
|
110
|
+
assert_equal("audio/mp3", t)
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_specific_markdown_nonpreferred
|
114
|
+
h = "audio/*, audio/basic; q=0.2"
|
115
|
+
a = {"audio/mp3" => 0.1, "audio/basic" => 1.0}
|
116
|
+
t = Negotiator.pick(h, a)
|
117
|
+
assert_equal("audio/basic", t)
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_specific_markup_preferred
|
121
|
+
h = "audio/*, audio/basic; q=1.2"
|
122
|
+
a = {"audio/mp3" => 1.0, "audio/basic" => 0.9999}
|
123
|
+
t = Negotiator.pick(h, a)
|
124
|
+
assert_equal("audio/basic", t)
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_specific_markup_nonpreferred
|
128
|
+
h = "audio/*, audio/basic; q=1.2"
|
129
|
+
a = {"audio/mp3" => 1.3, "audio/basic" => 1.0}
|
130
|
+
t = Negotiator.pick(h, a)
|
131
|
+
assert_equal("audio/mp3", t)
|
132
|
+
end
|
133
|
+
end
|
metadata
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: negotiator
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Keith Rarick
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-03-09 00:00:00.000000000Z
|
13
|
+
dependencies: []
|
14
|
+
description: Given an HTTP Accept header and a set of available content types, this
|
15
|
+
gem will tell you what to do.
|
16
|
+
email: kr@xph.us
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- Readme.md
|
22
|
+
- lib/negotiator.rb
|
23
|
+
- test/test_negotiator.rb
|
24
|
+
homepage: https://github.com/kr/negotiator
|
25
|
+
licenses: []
|
26
|
+
post_install_message:
|
27
|
+
rdoc_options: []
|
28
|
+
require_paths:
|
29
|
+
- lib
|
30
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - ! '>='
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '0'
|
36
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ! '>='
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
requirements: []
|
43
|
+
rubyforge_project:
|
44
|
+
rubygems_version: 1.8.10
|
45
|
+
signing_key:
|
46
|
+
specification_version: 3
|
47
|
+
summary: Correct HTTP Content Negotiation
|
48
|
+
test_files: []
|