negotiator 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/Readme.md +43 -0
  2. data/lib/negotiator.rb +69 -0
  3. data/test/test_negotiator.rb +133 -0
  4. metadata +48 -0
@@ -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!
@@ -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: []