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.
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: []