sinatra-rest-addons 0.1.4 → 1.0.0
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/VERSION +1 -1
- data/lib/sinatra/rest/helpers.rb +93 -1
- data/lib/sinatra/rest/routes.rb +0 -85
- data/sinatra-rest-addons.gemspec +4 -2
- data/test/test_helpers.rb +182 -0
- data/test/test_routes.rb +0 -194
- metadata +4 -2
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
1.0.0
|
data/lib/sinatra/rest/helpers.rb
CHANGED
@@ -6,11 +6,103 @@ module Sinatra
|
|
6
6
|
# class App < Sinatra::Base
|
7
7
|
# helpers Sinatra::REST::Helpers
|
8
8
|
# end
|
9
|
-
module Helpers
|
9
|
+
module Helpers
|
10
|
+
|
11
|
+
INFINITY = 1/0.0
|
12
|
+
|
10
13
|
def compute_etag(*args)
|
11
14
|
raise ArgumentError, "You must provide at least one parameter for the ETag computation" if args.empty?
|
12
15
|
Digest::SHA1.hexdigest(args.join("."))
|
13
16
|
end
|
17
|
+
|
18
|
+
# e.g.
|
19
|
+
# get '/' do
|
20
|
+
# provides :xml, "text/html;level=5"
|
21
|
+
# "Hello"
|
22
|
+
# end
|
23
|
+
# will accept requests having an Accept header containing at least one of the following value:
|
24
|
+
# * application/xml
|
25
|
+
# * application/*
|
26
|
+
# * text/html
|
27
|
+
# * text/html;level=5
|
28
|
+
# * text/html;level=6
|
29
|
+
def provides(*formats)
|
30
|
+
generate_type_hash = Proc.new{ |header|
|
31
|
+
type, *params = header.split(/;\s*/)
|
32
|
+
Hash[*params.map{|p| p.split(/\s*=\s*/)}.flatten].merge("type" => type)
|
33
|
+
}
|
34
|
+
supported_formats = formats.map do |f|
|
35
|
+
# selects the correct mime type if a symbol is given
|
36
|
+
f.is_a?(Symbol) ? ::Rack::Mime::MIME_TYPES[".#{f.to_s}"] : f
|
37
|
+
end.compact.map do |f|
|
38
|
+
generate_type_hash.call(f)
|
39
|
+
end
|
40
|
+
# request.accept is an Array
|
41
|
+
accepted_formats = request.accept.map do |f|
|
42
|
+
generate_type_hash.call(f)
|
43
|
+
end
|
44
|
+
selected_format = nil
|
45
|
+
accepted_formats.each{ |accepted_format|
|
46
|
+
selected_format = supported_formats.detect{ |supported_format|
|
47
|
+
Regexp.new(Regexp.escape(accepted_format["type"]).gsub("\\*", ".*?"), Regexp::IGNORECASE) =~ supported_format["type"] &&
|
48
|
+
(accepted_format["level"] || INFINITY).to_f >= (supported_format["level"] || 0).to_f
|
49
|
+
}
|
50
|
+
break unless selected_format.nil?
|
51
|
+
}
|
52
|
+
if selected_format.nil?
|
53
|
+
content_type :txt
|
54
|
+
halt 406, supported_formats.map{|f|
|
55
|
+
output = f["type"]
|
56
|
+
output.concat(";level=#{f["level"]}") if f.has_key?("level")
|
57
|
+
output
|
58
|
+
}.join(",")
|
59
|
+
else
|
60
|
+
response.headers['Content-Type'] = "#{selected_format["type"]}#{selected_format["level"].nil? ? "" : ";level=#{selected_format["level"]}"}"
|
61
|
+
end
|
62
|
+
end # def provides
|
63
|
+
|
64
|
+
|
65
|
+
# Automatically decode the input data (coming from a POST or PUT request) based on the request's content-type.
|
66
|
+
# You must pass a Proc that will return the parser object to use to decode the payload.
|
67
|
+
# The parser object must respond to a <tt>parse</tt> method.
|
68
|
+
# You may also pass a hash of options:
|
69
|
+
# * <tt>:size_range</tt>: byte range specifying the minimum and maximum length (in bytes) of the payload [default=(1..1024**3)]
|
70
|
+
#
|
71
|
+
# e.g.
|
72
|
+
# post '/resource' do
|
73
|
+
# data = decode lambda{|content_type| if content_type =~ /^application\/.*json$/i then JSON}, :size_range => (1..2*1024**3)
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# The processing of the request will be halted in the following cases:
|
77
|
+
# * 400 if the payload is not within the specified size range.
|
78
|
+
# * 400 if the payload cannot be correctly parsed.
|
79
|
+
# * 415 if the payload's content type is not supported (i.e. the given Proc returns nil)
|
80
|
+
#
|
81
|
+
def decode(proc, config = {})
|
82
|
+
raise ArgumentError, "You must pass an object that responds to #call" unless proc.respond_to?(:call)
|
83
|
+
size_range = config.delete(:size_range) || (1..(1024**3))
|
84
|
+
case (mime_type = request.env['CONTENT_TYPE'])
|
85
|
+
when /^application\/x-www-form-urlencoded/i
|
86
|
+
request.env['sinatra.decoded_input'] = request.env['rack.request.form_hash']
|
87
|
+
else
|
88
|
+
content = ""
|
89
|
+
request.env['rack.input'].each do |block|
|
90
|
+
content.concat(block)
|
91
|
+
break if content.length > size_range.end
|
92
|
+
end
|
93
|
+
if not size_range.include?(content.length)
|
94
|
+
halt 400, "Input data size must be between #{size_range.begin} and #{size_range.end} bytes."
|
95
|
+
elsif parser = proc.call(mime_type)
|
96
|
+
begin
|
97
|
+
parser.parse(content)
|
98
|
+
rescue StandardError => e
|
99
|
+
halt 400, "#{e.class.name}: #{e.message}"
|
100
|
+
end
|
101
|
+
else
|
102
|
+
halt 415, "Format #{mime_type} not supported"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end # def decode
|
14
106
|
end # module Helpers
|
15
107
|
end # module REST
|
16
108
|
end # module Sinatra
|
data/lib/sinatra/rest/routes.rb
CHANGED
@@ -11,51 +11,6 @@ module Sinatra
|
|
11
11
|
|
12
12
|
# Allow the definition of OPTIONS routes
|
13
13
|
def options(path, opts={}, &bk); route 'OPTIONS', path, opts, &bk end
|
14
|
-
|
15
|
-
# e.g.
|
16
|
-
# get '/', :provides => [:xml, "text/html;level=5"] { "hello" }
|
17
|
-
# will accept requests with Accept header =
|
18
|
-
# * application/xml
|
19
|
-
# * application/*
|
20
|
-
# * text/html
|
21
|
-
# * text/html;level=5
|
22
|
-
# * text/html;level=6
|
23
|
-
def provides(*formats)
|
24
|
-
generate_type_hash = Proc.new{ |header|
|
25
|
-
type, *params = header.split(/;\s*/)
|
26
|
-
Hash[*params.map{|p| p.split(/\s*=\s*/)}.flatten].merge("type" => type)
|
27
|
-
}
|
28
|
-
condition {
|
29
|
-
supported_formats = formats.map do |f|
|
30
|
-
# selects the correct mime type if a symbol is given
|
31
|
-
f.is_a?(Symbol) ? ::Rack::Mime::MIME_TYPES[".#{f.to_s}"] : f
|
32
|
-
end.compact.map do |f|
|
33
|
-
generate_type_hash.call(f)
|
34
|
-
end
|
35
|
-
# request.accept is an Array
|
36
|
-
accepted_formats = request.accept.map do |f|
|
37
|
-
generate_type_hash.call(f)
|
38
|
-
end
|
39
|
-
selected_format = nil
|
40
|
-
accepted_formats.each{ |accepted_format|
|
41
|
-
selected_format = supported_formats.detect{ |supported_format|
|
42
|
-
Regexp.new(Regexp.escape(accepted_format["type"]).gsub("\\*", ".*?"), Regexp::IGNORECASE) =~ supported_format["type"] &&
|
43
|
-
(accepted_format["level"] || INFINITY).to_f >= (supported_format["level"] || 0).to_f
|
44
|
-
}
|
45
|
-
break unless selected_format.nil?
|
46
|
-
}
|
47
|
-
if selected_format.nil?
|
48
|
-
content_type :txt
|
49
|
-
halt 406, supported_formats.map{|f|
|
50
|
-
output = f["type"]
|
51
|
-
output.concat(";level=#{f["level"]}") if f.has_key?("level")
|
52
|
-
output
|
53
|
-
}.join(",")
|
54
|
-
else
|
55
|
-
response.headers['Content-Type'] = "#{selected_format["type"]}#{selected_format["level"].nil? ? "" : ";level=#{selected_format["level"]}"}"
|
56
|
-
end
|
57
|
-
}
|
58
|
-
end # def provides
|
59
14
|
|
60
15
|
# Allow access to the route based on the result of the given proc, whose argument is a <tt>credentials</tt> object.
|
61
16
|
# You MUST declare a helper function named <tt>credentials</tt> that will return an object (of your choice) containing the client's credentials,
|
@@ -76,46 +31,6 @@ module Sinatra
|
|
76
31
|
end
|
77
32
|
}
|
78
33
|
end # def allow
|
79
|
-
|
80
|
-
# Automatically decode the input data (coming from a POST or PUT request) based on the request's content-type.
|
81
|
-
# First argument must be a parsing procedure that will be used to parse the input data according to its content type.
|
82
|
-
# The decoded input will be available in request.env['sinatra.decoded_input']
|
83
|
-
# e.g.
|
84
|
-
# post '/',
|
85
|
-
# :decode => Proc.new{ |content_type, content|
|
86
|
-
# content_type =~ /^application\/.*json$/i ? JSON.parse(content) : throw(:halt, [400, "Don't know how to parse '#{content_type}' content."])
|
87
|
-
# } do
|
88
|
-
# "#{request.env['sinatra.decoded_input']}"
|
89
|
-
# end
|
90
|
-
def decode(*args)
|
91
|
-
args = [args] unless args.kind_of? Array
|
92
|
-
parsing_proc = args.shift
|
93
|
-
raise ArgumentError, "You must provide a proc to parse the input data" unless parsing_proc.kind_of?(Proc)
|
94
|
-
size_range = args.shift || (1..(1024**3))
|
95
|
-
condition {
|
96
|
-
begin
|
97
|
-
case (mime_type = request.env['CONTENT_TYPE'])
|
98
|
-
when /^application\/x-www-form-urlencoded/i
|
99
|
-
request.env['sinatra.decoded_input'] = request.env['rack.request.form_hash']
|
100
|
-
else
|
101
|
-
content = ""
|
102
|
-
request.env['rack.input'].each do |block|
|
103
|
-
content.concat(block)
|
104
|
-
break if content.length > size_range.end
|
105
|
-
end
|
106
|
-
if not size_range.include?(content.length)
|
107
|
-
content_type :txt
|
108
|
-
halt 400, "Input data size must be between #{size_range.begin} and #{size_range.end} bytes."
|
109
|
-
else
|
110
|
-
request.env['sinatra.decoded_input'] = parsing_proc.call(mime_type, content)
|
111
|
-
end
|
112
|
-
end
|
113
|
-
rescue StandardError => e
|
114
|
-
content_type :txt
|
115
|
-
halt 400, "#{e.class.name}: #{e.message}"
|
116
|
-
end
|
117
|
-
}
|
118
|
-
end # def decode
|
119
34
|
|
120
35
|
end # module Routes
|
121
36
|
end # module REST
|
data/sinatra-rest-addons.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{sinatra-rest-addons}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "1.0.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Cyril Rohr"]
|
12
|
-
s.date = %q{
|
12
|
+
s.date = %q{2010-01-07}
|
13
13
|
s.description = %q{A set of helpers and extensions for sinatra apps that expose REST resources.}
|
14
14
|
s.email = %q{cyril.rohr@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -26,6 +26,7 @@ Gem::Specification.new do |s|
|
|
26
26
|
"lib/sinatra/rest/routes.rb",
|
27
27
|
"sinatra-rest-addons.gemspec",
|
28
28
|
"test/helper.rb",
|
29
|
+
"test/test_helpers.rb",
|
29
30
|
"test/test_routes.rb"
|
30
31
|
]
|
31
32
|
s.homepage = %q{http://github.com/crohr/sinatra-rest-addons}
|
@@ -35,6 +36,7 @@ Gem::Specification.new do |s|
|
|
35
36
|
s.summary = %q{A set of helpers and extensions for sinatra apps that expose REST resources.}
|
36
37
|
s.test_files = [
|
37
38
|
"test/helper.rb",
|
39
|
+
"test/test_helpers.rb",
|
38
40
|
"test/test_routes.rb"
|
39
41
|
]
|
40
42
|
|
@@ -0,0 +1,182 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
class HelpersTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
context "helper :provides" do
|
7
|
+
|
8
|
+
test "should return 406 and the list of supported types, if the server does not support the types accepted by the client [simple matching]" do
|
9
|
+
mock_app {
|
10
|
+
helpers Sinatra::REST::Helpers
|
11
|
+
get '/' do
|
12
|
+
provides "application/xml", "application/vnd.x.y.z+xml"
|
13
|
+
request.env['HTTP_ACCEPT']
|
14
|
+
end
|
15
|
+
}
|
16
|
+
get '/', {}, { 'HTTP_ACCEPT' => 'application/json' }
|
17
|
+
assert_equal 406, last_response.status
|
18
|
+
assert_equal 'application/xml,application/vnd.x.y.z+xml', last_response.body
|
19
|
+
assert_equal 'text/plain', last_response.headers['Content-Type']
|
20
|
+
end
|
21
|
+
test "should return 406 if the accepted type has a level lower than what is supported" do
|
22
|
+
mock_app {
|
23
|
+
helpers Sinatra::REST::Helpers
|
24
|
+
get '/' do
|
25
|
+
provides "application/xml;level=5"
|
26
|
+
request.env['HTTP_ACCEPT']
|
27
|
+
end
|
28
|
+
}
|
29
|
+
get '/', {}, { 'HTTP_ACCEPT' => 'application/xml;level=4' }
|
30
|
+
assert_equal 406, last_response.status
|
31
|
+
assert_equal 'application/xml;level=5', last_response.body
|
32
|
+
assert_equal 'text/plain', last_response.headers['Content-Type']
|
33
|
+
end
|
34
|
+
test "should return the first matching type if the accepted type contains a *" do
|
35
|
+
mock_app {
|
36
|
+
helpers Sinatra::REST::Helpers
|
37
|
+
get '/' do
|
38
|
+
provides "application/xml", "application/vnd.x.y.z+xml"
|
39
|
+
request.env['HTTP_ACCEPT']
|
40
|
+
end
|
41
|
+
}
|
42
|
+
get '/', {}, { 'HTTP_ACCEPT' => 'application/*' }
|
43
|
+
assert_equal 200, last_response.status
|
44
|
+
assert_equal 'application/*', last_response.body
|
45
|
+
assert_equal 'application/xml', last_response.headers['Content-Type']
|
46
|
+
end
|
47
|
+
test "should respect the order in which the accepted formats are declared when looking for the format to select" do
|
48
|
+
mock_app {
|
49
|
+
helpers Sinatra::REST::Helpers
|
50
|
+
get '/' do
|
51
|
+
provides "application/json", "application/xml", "application/vnd.x.y.z+xml"
|
52
|
+
request.env['HTTP_ACCEPT']
|
53
|
+
end
|
54
|
+
}
|
55
|
+
get '/', {}, { 'HTTP_ACCEPT' => 'application/xml, */*' }
|
56
|
+
assert_equal 200, last_response.status
|
57
|
+
assert_equal 'application/xml, */*', last_response.body
|
58
|
+
assert_equal 'application/xml', last_response.headers['Content-Type']
|
59
|
+
end
|
60
|
+
test "should be successful if the accepted type does not require a specific level" do
|
61
|
+
mock_app {
|
62
|
+
helpers Sinatra::REST::Helpers
|
63
|
+
get '/' do
|
64
|
+
provides "application/xml;level=5"
|
65
|
+
request.env['HTTP_ACCEPT']
|
66
|
+
end
|
67
|
+
}
|
68
|
+
get '/', {}, { 'HTTP_ACCEPT' => 'application/xml' }
|
69
|
+
assert last_response.ok?
|
70
|
+
assert_equal 'application/xml', last_response.body
|
71
|
+
assert_equal 'application/xml;level=5', last_response.headers['Content-Type']
|
72
|
+
end
|
73
|
+
test "should be successful if the accepted type level is greater than what is supported" do
|
74
|
+
mock_app {
|
75
|
+
helpers Sinatra::REST::Helpers
|
76
|
+
get '/' do
|
77
|
+
provides "application/xml;level=5"
|
78
|
+
request.env['HTTP_ACCEPT']
|
79
|
+
end
|
80
|
+
}
|
81
|
+
get '/', {}, { 'HTTP_ACCEPT' => 'application/xml;level=6' }
|
82
|
+
assert last_response.ok?
|
83
|
+
assert_equal 'application/xml;level=6', last_response.body
|
84
|
+
assert_equal 'application/xml;level=5', last_response.headers['Content-Type']
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context "helper :decode" do
|
89
|
+
setup do
|
90
|
+
PARSING_PROC = Proc.new{|content_type| content_type =~ /^application\/.*json$/i ? JSON : nil} unless defined?(PARSING_PROC)
|
91
|
+
end
|
92
|
+
test "should return 400 if the input content is empty" do
|
93
|
+
mock_app {
|
94
|
+
helpers Sinatra::REST::Helpers
|
95
|
+
post '/' do
|
96
|
+
data = decode PARSING_PROC
|
97
|
+
end
|
98
|
+
error 400 do
|
99
|
+
content_type :txt
|
100
|
+
"#{response.body.to_s}"
|
101
|
+
end
|
102
|
+
}
|
103
|
+
post '/', "", {'CONTENT_TYPE' => "application/json"}
|
104
|
+
assert_equal 400, last_response.status
|
105
|
+
assert_equal 'Input data size must be between 1 and 1073741824 bytes.', last_response.body
|
106
|
+
assert_equal 'text/plain', last_response.headers['Content-Type']
|
107
|
+
end
|
108
|
+
test "should return 400 if the input content is too large" do
|
109
|
+
mock_app {
|
110
|
+
helpers Sinatra::REST::Helpers
|
111
|
+
post '/' do
|
112
|
+
decode PARSING_PROC, :size_range => 2...30
|
113
|
+
end
|
114
|
+
}
|
115
|
+
post '/', '{"key1": ["value1", "value2"]}', {'CONTENT_TYPE' => "application/json"}
|
116
|
+
assert_equal 400, last_response.status
|
117
|
+
assert_equal 'Input data size must be between 2 and 30 bytes.', last_response.body
|
118
|
+
end
|
119
|
+
test "should return 400 if the input content can be parsed but is malformed" do
|
120
|
+
mock_app {
|
121
|
+
helpers Sinatra::REST::Helpers
|
122
|
+
post '/'do
|
123
|
+
decode PARSING_PROC, :size_range => 2...30
|
124
|
+
end
|
125
|
+
}
|
126
|
+
post '/', '{"key1": ["value1", "value2"]', {'CONTENT_TYPE' => "application/json"}
|
127
|
+
assert_equal 400, last_response.status
|
128
|
+
assert_equal "JSON::ParserError: 618: unexpected token at '{\"key1\": [\"value1\", \"value2\"]'", last_response.body
|
129
|
+
end
|
130
|
+
test "should return 415 if the parsing proc does not return a parser" do
|
131
|
+
mock_app {
|
132
|
+
helpers Sinatra::REST::Helpers
|
133
|
+
post '/' do
|
134
|
+
decode PARSING_PROC
|
135
|
+
end
|
136
|
+
}
|
137
|
+
post '/', '<item></item>', {'CONTENT_TYPE' => "application/xml"}
|
138
|
+
assert_equal 415, last_response.status
|
139
|
+
assert_equal "Format application/xml not supported", last_response.body
|
140
|
+
end
|
141
|
+
test "should correctly parse the input content if a parser can be found for the specified content type, and the decoded input must be returned" do
|
142
|
+
input = {"key1" => ["value1", "value2"]}
|
143
|
+
mock_app {
|
144
|
+
helpers Sinatra::REST::Helpers
|
145
|
+
post '/' do
|
146
|
+
data = decode PARSING_PROC, :size_range => 2...30
|
147
|
+
content_type 'application/json'
|
148
|
+
JSON.dump data
|
149
|
+
end
|
150
|
+
}
|
151
|
+
post '/', JSON.dump(input), {'CONTENT_TYPE' => "application/json"}
|
152
|
+
assert_equal 200, last_response.status
|
153
|
+
assert_equal JSON.dump(input), last_response.body
|
154
|
+
assert_equal 'application/json', last_response.headers['Content-Type']
|
155
|
+
end
|
156
|
+
test "should set the decoded input to the params hash already decoded by Sinatra, if the request's content type is application/x-www-form-urlencoded" do
|
157
|
+
input = {"key1" => ["value1", "value2"]}
|
158
|
+
mock_app {
|
159
|
+
helpers Sinatra::REST::Helpers
|
160
|
+
post '/' do
|
161
|
+
data = decode PARSING_PROC, :size_range => 2...30
|
162
|
+
content_type 'application/json'
|
163
|
+
JSON.dump data
|
164
|
+
end
|
165
|
+
}
|
166
|
+
post '/', input
|
167
|
+
assert_equal 200, last_response.status
|
168
|
+
assert_equal JSON.dump(input), last_response.body
|
169
|
+
assert_equal 'application/json', last_response.headers['Content-Type']
|
170
|
+
end
|
171
|
+
test "should raise an error if the first argument is not a proc" do
|
172
|
+
mock_app {
|
173
|
+
helpers Sinatra::REST::Helpers
|
174
|
+
post '/' do
|
175
|
+
decode :whatever
|
176
|
+
end
|
177
|
+
}
|
178
|
+
assert_raise(ArgumentError){ post '/' }
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
data/test/test_routes.rb
CHANGED
@@ -19,200 +19,6 @@ class RoutesTest < Test::Unit::TestCase
|
|
19
19
|
assert_equal 'remove me', response.body
|
20
20
|
end
|
21
21
|
|
22
|
-
context "route option :provides" do
|
23
|
-
|
24
|
-
test "should return 406 and the list of supported types, if the server does not support the types accepted by the client [simple matching]" do
|
25
|
-
mock_app {
|
26
|
-
register Sinatra::REST::Routes
|
27
|
-
get '/', :provides => ["application/xml", "application/vnd.x.y.z+xml"] do
|
28
|
-
request.env['HTTP_ACCEPT']
|
29
|
-
end
|
30
|
-
}
|
31
|
-
get '/', {}, { 'HTTP_ACCEPT' => 'application/json' }
|
32
|
-
assert_equal 406, last_response.status
|
33
|
-
assert_equal 'application/xml,application/vnd.x.y.z+xml', last_response.body
|
34
|
-
assert_equal 'text/plain', last_response.headers['Content-Type']
|
35
|
-
end
|
36
|
-
test "should return 406 if the accepted type has a level lower than what is supported" do
|
37
|
-
mock_app {
|
38
|
-
register Sinatra::REST::Routes
|
39
|
-
get '/', :provides => ["application/xml;level=5"] do
|
40
|
-
request.env['HTTP_ACCEPT']
|
41
|
-
end
|
42
|
-
}
|
43
|
-
get '/', {}, { 'HTTP_ACCEPT' => 'application/xml;level=4' }
|
44
|
-
assert_equal 406, last_response.status
|
45
|
-
assert_equal 'application/xml;level=5', last_response.body
|
46
|
-
assert_equal 'text/plain', last_response.headers['Content-Type']
|
47
|
-
end
|
48
|
-
test "should return the first matching type if the accepted type contains a *" do
|
49
|
-
mock_app {
|
50
|
-
register Sinatra::REST::Routes
|
51
|
-
get '/', :provides => ["application/xml", "application/vnd.x.y.z+xml"] do
|
52
|
-
request.env['HTTP_ACCEPT']
|
53
|
-
end
|
54
|
-
}
|
55
|
-
get '/', {}, { 'HTTP_ACCEPT' => 'application/*' }
|
56
|
-
assert_equal 200, last_response.status
|
57
|
-
assert_equal 'application/*', last_response.body
|
58
|
-
assert_equal 'application/xml', last_response.headers['Content-Type']
|
59
|
-
end
|
60
|
-
test "should respect the order in which the accepted formats are declared when looking for the format to select" do
|
61
|
-
mock_app {
|
62
|
-
register Sinatra::REST::Routes
|
63
|
-
get '/', :provides => ["application/json", "application/xml", "application/vnd.x.y.z+xml"] do
|
64
|
-
request.env['HTTP_ACCEPT']
|
65
|
-
end
|
66
|
-
}
|
67
|
-
get '/', {}, { 'HTTP_ACCEPT' => 'application/xml, */*' }
|
68
|
-
assert_equal 200, last_response.status
|
69
|
-
assert_equal 'application/xml, */*', last_response.body
|
70
|
-
assert_equal 'application/xml', last_response.headers['Content-Type']
|
71
|
-
end
|
72
|
-
test "should be successful if the accepted type does not require a specific level" do
|
73
|
-
mock_app {
|
74
|
-
register Sinatra::REST::Routes
|
75
|
-
get '/', :provides => ["application/xml;level=5"] do
|
76
|
-
request.env['HTTP_ACCEPT']
|
77
|
-
end
|
78
|
-
}
|
79
|
-
get '/', {}, { 'HTTP_ACCEPT' => 'application/xml' }
|
80
|
-
assert last_response.ok?
|
81
|
-
assert_equal 'application/xml', last_response.body
|
82
|
-
assert_equal 'application/xml;level=5', last_response.headers['Content-Type']
|
83
|
-
end
|
84
|
-
test "should be successful if the accepted type level is greater than what is supported" do
|
85
|
-
mock_app {
|
86
|
-
register Sinatra::REST::Routes
|
87
|
-
get '/', :provides => ["application/xml;level=5"] do
|
88
|
-
request.env['HTTP_ACCEPT']
|
89
|
-
end
|
90
|
-
}
|
91
|
-
get '/', {}, { 'HTTP_ACCEPT' => 'application/xml;level=6' }
|
92
|
-
assert last_response.ok?
|
93
|
-
assert_equal 'application/xml;level=6', last_response.body
|
94
|
-
assert_equal 'application/xml;level=5', last_response.headers['Content-Type']
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
context "route option :decode" do
|
99
|
-
setup do
|
100
|
-
PARSING_PROC = Proc.new{|content_type, content| content_type =~ /^application\/.*json$/i ? JSON.parse(content) : throw(:halt, [400, "Cannot parse"])} unless defined? PARSING_PROC
|
101
|
-
end
|
102
|
-
test "should return 400 if the input content is empty" do
|
103
|
-
mock_app {
|
104
|
-
register Sinatra::REST::Routes
|
105
|
-
post '/', :decode => PARSING_PROC do
|
106
|
-
request.env['sinatra.decoded_input'].inspect
|
107
|
-
end
|
108
|
-
}
|
109
|
-
post '/', "", {'CONTENT_TYPE' => "application/json"}
|
110
|
-
assert_equal 400, last_response.status
|
111
|
-
assert_equal 'Input data size must be between 1 and 1073741824 bytes.', last_response.body
|
112
|
-
assert_equal 'text/plain', last_response.headers['Content-Type']
|
113
|
-
end
|
114
|
-
test "should return 400 if the input content is too large" do
|
115
|
-
mock_app {
|
116
|
-
register Sinatra::REST::Routes
|
117
|
-
post '/', :decode => [PARSING_PROC, 2...30] do
|
118
|
-
request.env['sinatra.decoded_input'].inspect
|
119
|
-
end
|
120
|
-
}
|
121
|
-
post '/', '{"key1": ["value1", "value2"]}', {'CONTENT_TYPE' => "application/json"}
|
122
|
-
assert_equal 400, last_response.status
|
123
|
-
assert_equal 'Input data size must be between 2 and 30 bytes.', last_response.body
|
124
|
-
assert_equal 'text/plain', last_response.headers['Content-Type']
|
125
|
-
end
|
126
|
-
test "should return 400 if the input content can be parsed but is malformed" do
|
127
|
-
mock_app {
|
128
|
-
register Sinatra::REST::Routes
|
129
|
-
post '/', :decode => [PARSING_PROC, 2..30] do
|
130
|
-
request.env['sinatra.decoded_input'].inspect
|
131
|
-
end
|
132
|
-
}
|
133
|
-
post '/', '{"key1": ["value1", "value2"]', {'CONTENT_TYPE' => "application/json"}
|
134
|
-
assert_equal 400, last_response.status
|
135
|
-
assert_equal "JSON::ParserError: 618: unexpected token at '{\"key1\": [\"value1\", \"value2\"]'", last_response.body
|
136
|
-
assert_equal 'text/plain', last_response.headers['Content-Type']
|
137
|
-
end
|
138
|
-
test "should allow the parsing proc to throw an exception if needed (e.g. no parser can be found for the requested content type)" do
|
139
|
-
mock_app {
|
140
|
-
register Sinatra::REST::Routes
|
141
|
-
post '/', :decode => [PARSING_PROC, 2..30] do
|
142
|
-
request.env['sinatra.decoded_input'].inspect
|
143
|
-
end
|
144
|
-
}
|
145
|
-
post '/', '<item></item>', {'CONTENT_TYPE' => "application/xml"}
|
146
|
-
assert_equal 400, last_response.status
|
147
|
-
assert_equal "Cannot parse", last_response.body
|
148
|
-
assert_equal 'text/html', last_response.headers['Content-Type']
|
149
|
-
end
|
150
|
-
test "should correctly parse the input content if a parser can be found for the specified content type, and the decoded input must be made available in env['sinatra.decoded_input']" do
|
151
|
-
input = {"key1" => ["value1", "value2"]}
|
152
|
-
mock_app {
|
153
|
-
register Sinatra::REST::Routes
|
154
|
-
post '/', :decode => [PARSING_PROC, 2...30] do
|
155
|
-
content_type 'application/json'
|
156
|
-
JSON.dump request.env['sinatra.decoded_input']
|
157
|
-
end
|
158
|
-
}
|
159
|
-
post '/', JSON.dump(input), {'CONTENT_TYPE' => "application/json"}
|
160
|
-
assert_equal 200, last_response.status
|
161
|
-
assert_equal JSON.dump(input), last_response.body
|
162
|
-
assert_equal 'application/json', last_response.headers['Content-Type']
|
163
|
-
end
|
164
|
-
test "should work" do
|
165
|
-
input = "{\"walltime\":3600,\"resources\":\"/nodes=1\",\"at\":1258552306,\"on_launch\":{\"in\":\"/home/crohr\",\"do\":\"id\"}}"
|
166
|
-
mock_app {
|
167
|
-
register Sinatra::REST::Routes
|
168
|
-
post '/', :decode => [PARSING_PROC, 2...3000] do
|
169
|
-
content_type 'application/json'
|
170
|
-
JSON.dump request.env['sinatra.decoded_input']
|
171
|
-
end
|
172
|
-
}
|
173
|
-
post '/', input, {'CONTENT_TYPE' => "application/json"}
|
174
|
-
p last_response.body
|
175
|
-
assert_equal 200, last_response.status
|
176
|
-
assert_equal JSON.parse(input), JSON.parse(last_response.body)
|
177
|
-
assert_equal 'application/json', last_response.headers['Content-Type']
|
178
|
-
end
|
179
|
-
test "should set the decoded input to the params hash already decoded by Sinatra, if the request's content type is application/x-www-form-urlencoded" do
|
180
|
-
input = {"key1" => ["value1", "value2"]}
|
181
|
-
mock_app {
|
182
|
-
register Sinatra::REST::Routes
|
183
|
-
post '/', :decode => [PARSING_PROC, 2...30] do
|
184
|
-
content_type 'application/json'
|
185
|
-
JSON.dump request.env['sinatra.decoded_input']
|
186
|
-
end
|
187
|
-
}
|
188
|
-
post '/', input
|
189
|
-
assert_equal 200, last_response.status
|
190
|
-
assert_equal JSON.dump(input), last_response.body
|
191
|
-
assert_equal 'application/json', last_response.headers['Content-Type']
|
192
|
-
end
|
193
|
-
test "should return 400 if the parsing proc raises a StandardError" do
|
194
|
-
mock_app {
|
195
|
-
register Sinatra::REST::Routes
|
196
|
-
post '/', :decode => Proc.new{|content_type, content| raise(StandardError, "error message") } do
|
197
|
-
request.env['sinatra.decoded_input'].inspect
|
198
|
-
end
|
199
|
-
}
|
200
|
-
post '/', 'whatever content', {'CONTENT_TYPE' => "whatever type"}
|
201
|
-
assert_equal 400, last_response.status
|
202
|
-
assert_equal "StandardError: error message", last_response.body
|
203
|
-
assert_equal 'text/plain', last_response.headers['Content-Type']
|
204
|
-
end
|
205
|
-
test "should raise an error if the first argument is not a proc" do
|
206
|
-
assert_raise(ArgumentError){ mock_app {
|
207
|
-
register Sinatra::REST::Routes
|
208
|
-
disable :raise_errors, :show_exceptions
|
209
|
-
post '/', :decode => :whatever do
|
210
|
-
JSON.dump request.env['sinatra.decoded_input']
|
211
|
-
end
|
212
|
-
} }
|
213
|
-
end
|
214
|
-
end
|
215
|
-
|
216
22
|
context "route option :allow" do
|
217
23
|
test "should return 403 if the proc returns false" do
|
218
24
|
mock_app {
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sinatra-rest-addons
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cyril Rohr
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2010-01-07 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -51,6 +51,7 @@ files:
|
|
51
51
|
- lib/sinatra/rest/routes.rb
|
52
52
|
- sinatra-rest-addons.gemspec
|
53
53
|
- test/helper.rb
|
54
|
+
- test/test_helpers.rb
|
54
55
|
- test/test_routes.rb
|
55
56
|
has_rdoc: true
|
56
57
|
homepage: http://github.com/crohr/sinatra-rest-addons
|
@@ -82,4 +83,5 @@ specification_version: 3
|
|
82
83
|
summary: A set of helpers and extensions for sinatra apps that expose REST resources.
|
83
84
|
test_files:
|
84
85
|
- test/helper.rb
|
86
|
+
- test/test_helpers.rb
|
85
87
|
- test/test_routes.rb
|