roda 2.15.0 → 2.16.0

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: 625daaffa474b5553308633c41d20b76fa3096d8
4
- data.tar.gz: 4bcfc8c8c1258f6860e8ac430ba59679b2c62c69
3
+ metadata.gz: c3051f95d0665c935a0aa391b7f5b3d4ec3905ec
4
+ data.tar.gz: c3a81d9c6384dc8708c941fcb8171295fcaff6bc
5
5
  SHA512:
6
- metadata.gz: 2331dd19bd5efb8c3187df90917c4a23f814d52ec205b553ac1131fb39a787fffc83930ce697011cda5ac6926ab7cf31a209033dfc5589342b9410ff09b6a743
7
- data.tar.gz: 0abd741f20b87dbe497eba823357ce323aa1d82f7a8048895afe4d5b47eefeb340e50fdf9c71daa8c9cb2c064a5e4f30cda800aa751a345f84f57d2424a6a617
6
+ metadata.gz: e6479fd6fffd9fdcefc3c09e3543142668b7cd15b3f52af8fda896d438a174b54c1a8350c5aea6a5745188f5eaec78b5fe3a11405365a1318ff38a0098acd307
7
+ data.tar.gz: 1a837206ad2a4a158e5c939c0a52107752f3e9c9108255e843db681c1f2a62265ee5ca54ba96071b441ca760de7ee2871cf58822ce9f12ed704fa283fcb786b2
data/CHANGELOG CHANGED
@@ -1,3 +1,11 @@
1
+ = 2.16.0 (2016-07-13)
2
+
3
+ * Add type_routing plugin, for routing based on path extensions and Accept headers (Papierkorb, jeremyevans) (#75)
4
+
5
+ * Add unescape_path plugin, for decoding URL-encoded PATH_INFO before routing (jeremyevans) (#74)
6
+
7
+ * Add request_headers plugin, for simpler access to request headers (celsworth) (#72)
8
+
1
9
  = 2.15.0 (2016-06-13)
2
10
 
3
11
  * Add public plugin for r.public method for serving all files in the public directory (jeremyevans)
@@ -0,0 +1,48 @@
1
+ = New Features
2
+
3
+ * A type_routing plugin has been added. This plugin allows routing
4
+ based on the requested type, which can be submitted either via a
5
+ file extension or Accept header:
6
+
7
+ plugin :type_routing
8
+
9
+ route do |r|
10
+ r.get 'a' do
11
+ r.html{ "<h1>This is the HTML response</h1>" }
12
+ r.json{ '{"json": "ok"}' }
13
+ r.xml{ "<root>This is the XML response</root>" }
14
+ end
15
+ end
16
+
17
+ # /a or /a.html => HTML response
18
+ # /a.json => JSON response
19
+ # /a.xml => XML response
20
+
21
+ The response content type is set appropriately when the r.html,
22
+ r.json, or r.xml block is yielded to. Using plugin options, you can
23
+ add support for custom types, and choose whether to use only file
24
+ extensions or only Accept headers for type matching.
25
+
26
+ * A request_headers plugin has been added. This allows easier access
27
+ to request headers. For example, to access a header called
28
+ X-My-Header, by default you would need to use the CGI mangled name:
29
+
30
+ r.env['HTTP_X_MY_HEADER']
31
+
32
+ The request_headers plugin allows the easier to use:
33
+
34
+ r.headers['X-My-Header']
35
+
36
+ * An unescape_path plugin has been added. By default, Roda does not
37
+ unescape a URL-encoded PATH_INFO before routing. This plugin allows
38
+ URL-encoded PATH_INFO to work, supporting %2f as well as / as path
39
+ separators, and having captures return unescaped values:
40
+
41
+ plugin :unescape_path
42
+
43
+ route do |r|
44
+ # Assume /b/a URL encoded at %2f%62%2f%61
45
+ r.on :x, /(.)/ do |*x|
46
+ # x => ['b', 'a']
47
+ end
48
+ end
data/lib/roda.rb CHANGED
@@ -350,7 +350,7 @@ class Roda
350
350
  def initialize(scope, env)
351
351
  @scope = scope
352
352
  @captures = []
353
- @remaining_path = env[PATH_INFO]
353
+ @remaining_path = _remaining_path(env)
354
354
  super(env)
355
355
  end
356
356
 
@@ -722,6 +722,11 @@ class Roda
722
722
  SEGMENT
723
723
  end
724
724
 
725
+ # The base remaining path to use.
726
+ def _remaining_path(env)
727
+ env[PATH_INFO]
728
+ end
729
+
725
730
  # Backbone of the verb method support, using a terminal match if
726
731
  # args is not empty, or a regular match if it is empty.
727
732
  def _verb(args, &block)
@@ -6,13 +6,9 @@ class Roda
6
6
  # The per_thread_caching plugin changes the default cache
7
7
  # from being a shared thread safe cache to a separate cache per
8
8
  # thread. This means getting or setting values no longer
9
- # needs a mutex on non-MRI ruby implementations, which may be
10
- # faster when using a thread pool. However, since the caches
11
- # are no longer shared, this will take up more memory.
12
- #
13
- # Note that it does not make sense to use this plugin on MRI,
14
- # since the default cache on MRI doesn't use a mutex as it
15
- # is already thread safe due to the GVL.
9
+ # needs a mutex, which may be faster when using a thread pool.
10
+ # However, since the caches are no longer shared, this will
11
+ # take up more memory.
16
12
  #
17
13
  # Using this plugin changes the matcher regexp cache to use
18
14
  # per-thread caches, and changes the default for future
@@ -0,0 +1,83 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'set'
4
+
5
+ class Roda
6
+ module RodaPlugins
7
+ # The request_headers plugin provides access to headers sent in the
8
+ # request in a more natural way than directly accessing the env hash.
9
+ #
10
+ # In practise this means you don't need to uppercase, convert dashes
11
+ # to underscores, or add a HTTP_ prefix.
12
+ #
13
+ # For example, to access a header called X-My-Header you
14
+ # would previously need to do:
15
+ #
16
+ # r.env['HTTP_X_MY_HEADER']
17
+ #
18
+ # But with this plugin you can now say:
19
+ #
20
+ # r.headers['X-My-Header']
21
+ #
22
+ # The name is actually case-insensitive so x-my-header will work as well.
23
+ #
24
+ #
25
+ # Example:
26
+ #
27
+ # plugin :request_headers
28
+ #
29
+ module RequestHeaders
30
+ module RequestMethods
31
+ # Provide access to the request headers while normalising indexes.
32
+ def headers
33
+ @request_headers ||= Headers.new(@env)
34
+ end
35
+ end
36
+
37
+ class Headers
38
+ # Set of environment variable names that don't need HTTP_ prepended to them.
39
+ CGI_VARIABLES = Set.new(%w'
40
+ AUTH_TYPE
41
+ CONTENT_LENGTH
42
+ CONTENT_TYPE
43
+ GATEWAY_INTERFACE
44
+ HTTPS
45
+ PATH_INFO
46
+ PATH_TRANSLATED
47
+ QUERY_STRING
48
+ REMOTE_ADDR
49
+ REMOTE_HOST
50
+ REMOTE_IDENT
51
+ REMOTE_USER
52
+ REQUEST_METHOD
53
+ SCRIPT_NAME
54
+ SERVER_NAME
55
+ SERVER_PORT
56
+ SERVER_PROTOCOL
57
+ SERVER_SOFTWARE
58
+ ').freeze
59
+
60
+ def initialize(env)
61
+ @env = env
62
+ end
63
+
64
+ # Returns the value for the given key mapped to @env
65
+ def [](key)
66
+ @env[env_name(key)]
67
+ end
68
+
69
+ private
70
+
71
+ # Convert a HTTP header name into an environment variable name
72
+ def env_name(key)
73
+ key = key.to_s.upcase
74
+ key.tr!('-', '_')
75
+ key = 'HTTP_' + key unless CGI_VARIABLES.include?(key)
76
+ key
77
+ end
78
+ end
79
+ end
80
+
81
+ register_plugin(:request_headers, RequestHeaders)
82
+ end
83
+ end
@@ -0,0 +1,198 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # Allows to respond to specific request data types. User agents can request
7
+ # specific data types by either supplying an appropriate +Accept+ header
8
+ # or by appending it as file extension to the path.
9
+ #
10
+ # Example:
11
+ #
12
+ # plugin :type_routing
13
+ #
14
+ # route do |r|
15
+ # r.get 'a' do
16
+ # r.html{ "<h1>This is the HTML response</h1>" }
17
+ # r.json{ '{"json": "ok"}' }
18
+ # r.xml{ "<root>This is the XML response</root>" }
19
+ # "Unsupported data type"
20
+ # end
21
+ # end
22
+ #
23
+ # This application will handle the following paths:
24
+ # /a.html :: HTML response
25
+ # /a.json :: JSON response
26
+ # /a.xml :: XML response
27
+ # /a :: HTML, JSON, or XML response, depending on the Accept header
28
+ #
29
+ # The response +Content-Type+ header will be set to a suitable value when
30
+ # the block is matched.
31
+ #
32
+ # Note that if no match is found, code will continue to execute, which can
33
+ # result in unexpected behaviour. This should only happen if you do not
34
+ # handle all supported/configured types. If you want to simplify handling,
35
+ # you can just place the html handling after the other types, without using
36
+ # a separate block:
37
+ #
38
+ # route do |r|
39
+ # r.get 'a' do
40
+ # r.json{ '{"json": "ok"}' }
41
+ # r.xml{ "<root>This is the XML response</root>" }
42
+ #
43
+ # "<h1>This is the HTML response</h1>"
44
+ # end
45
+ # end
46
+ #
47
+ # This works correctly because Roda assumes the html type by default.
48
+ #
49
+ # To match custom extensions, use the :types option:
50
+ #
51
+ # plugin :type_routing, :types => {
52
+ # :yaml => 'application/x-yaml',
53
+ # :js => 'application/javascript; charset=utf-8',
54
+ # }
55
+ #
56
+ # route do |r|
57
+ # r.get 'a' do
58
+ # r.yaml{ YAML.dump "YAML data" }
59
+ # r.js{ "JavaScript code" }
60
+ # # or:
61
+ # r.on_type(:js){ "JavaScript code" }
62
+ # "Unsupported data type"
63
+ # end
64
+ # end
65
+ #
66
+ # = Plugin options
67
+ #
68
+ # The following plugin options are supported:
69
+ #
70
+ # :default_type :: The default data type to assume if the client did not
71
+ # provide one. Defaults to +:html+.
72
+ # :exclude :: Exclude one or more types from the default set (default set
73
+ # is :html, :xml, :json).
74
+ # :types :: Mapping from a data type to its MIME-Type. Used both to match
75
+ # incoming requests and to provide +Content-Type+ values. If the
76
+ # value is +nil+, no +Content-Type+ will be set. The type may
77
+ # contain media type parameters, which will be sent to the client
78
+ # but ignored for request matching.
79
+ # :use_extension :: Whether to take the path extension into account.
80
+ # Default is +true+.
81
+ # :use_header :: Whether to take the +Accept+ header into account.
82
+ # Default is +true+.
83
+ module TypeRouting
84
+ ACCEPT_HEADER = 'HTTP_ACCEPT'.freeze
85
+ CONTENT_TYPE_HEADER = 'Content-Type'.freeze
86
+
87
+ CONFIGURATION = {
88
+ :mimes => {
89
+ 'text/json' => :json,
90
+ 'application/json' => :json,
91
+ 'text/xml' => :xml,
92
+ 'application/xml' => :xml,
93
+ 'text/html' => :html,
94
+ }.freeze,
95
+ :types => {
96
+ :json => 'application/json'.freeze,
97
+ :xml => 'application/xml'.freeze,
98
+ :html => 'text/html'.freeze,
99
+ }.freeze,
100
+ :use_extension => true,
101
+ :use_header => true,
102
+ :default_type => :html
103
+ }.freeze
104
+
105
+ def self.configure(app, opts = {})
106
+ config = (app.opts[:type_routing] || CONFIGURATION).dup
107
+ [:use_extension, :use_header, :default_type].each do |key|
108
+ config[key] = opts[key] if opts.has_key?(key)
109
+ end
110
+
111
+ types = config[:types] = config[:types].dup
112
+ mimes = config[:mimes] = config[:mimes].dup
113
+
114
+ Array(opts[:exclude]).each do |type|
115
+ types.delete(type)
116
+ mimes.reject!{|_, v| v == type}
117
+ end
118
+
119
+ if mapping = opts[:types]
120
+ types.merge!(mapping)
121
+
122
+ mapping.each do |k, v|
123
+ if v
124
+ mimes[v.split(';', 2).first] = k
125
+ end
126
+ end
127
+ end
128
+
129
+ types.freeze
130
+ mimes.freeze
131
+
132
+ type_keys = config[:types].keys
133
+ config[:extension_regexp] = /(.+)\.(#{Regexp.union(type_keys.map(&:to_s))})\z/
134
+
135
+ type_keys.each do |type|
136
+ app::RodaRequest.send(:define_method, type) do |&block|
137
+ on_type(type, &block)
138
+ end
139
+ end
140
+
141
+ app.opts[:type_routing] = config.freeze
142
+ end
143
+
144
+ module RequestMethods
145
+ # Yields if the given +type+ matches the requested data type and halts
146
+ # the request afterwards, returning the result of the block.
147
+ def on_type(type, &block)
148
+ return unless type == requested_type
149
+ response[CONTENT_TYPE_HEADER] ||= @scope.opts[:type_routing][:types][type]
150
+ always(&block)
151
+ end
152
+
153
+ # Returns the data type the client requests.
154
+ def requested_type
155
+ return @requested_type if defined?(@requested_type)
156
+
157
+ opts = @scope.opts[:type_routing]
158
+ @requested_type = accept_response_type if opts[:use_header]
159
+ @requested_type ||= opts[:default_type]
160
+ end
161
+
162
+ private
163
+
164
+ # Removes a trailing file extension from the path, and sets
165
+ # the requested type if so.
166
+ def _remaining_path(env)
167
+ opts = scope.opts[:type_routing]
168
+ path = super
169
+
170
+ if opts[:use_extension]
171
+ if m = path.match(opts[:extension_regexp])
172
+ @requested_type = m[2].to_sym
173
+ path = m[1]
174
+ end
175
+ end
176
+
177
+ path
178
+ end
179
+
180
+ # The response type indicated by the Accept request header.
181
+ def accept_response_type
182
+ mimes = @scope.opts[:type_routing][:mimes]
183
+
184
+ @env[ACCEPT_HEADER].to_s.split(/\s*,\s*/).map do |part|
185
+ mime, _= part.split(/\s*;\s*/, 2)
186
+ if sym = mimes[mime]
187
+ return sym
188
+ end
189
+ end
190
+
191
+ nil
192
+ end
193
+ end
194
+ end
195
+
196
+ register_plugin(:type_routing, TypeRouting)
197
+ end
198
+ end
@@ -0,0 +1,32 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The unescape_path plugin decodes a URL-encoded path
7
+ # before routing. This fixes routing when the slashes
8
+ # are URL-encoded as %2f and returns decoded parameters
9
+ # when matched by symbols or regexps.
10
+ #
11
+ # plugin :unescape_path
12
+ #
13
+ # route do |r|
14
+ # # Assume /b/a URL encoded at %2f%62%2f%61
15
+ # r.on :x, /(.)/ do |*x|
16
+ # # x => ['b', 'a']
17
+ # end
18
+ # end
19
+ module UnescapePath
20
+ module RequestMethods
21
+ private
22
+
23
+ # Unescape the path.
24
+ def _remaining_path(env)
25
+ Rack::Utils.unescape(super)
26
+ end
27
+ end
28
+ end
29
+
30
+ register_plugin(:unescape_path, UnescapePath)
31
+ end
32
+ end
data/lib/roda/version.rb CHANGED
@@ -4,7 +4,7 @@ class Roda
4
4
  RodaMajorVersion = 2
5
5
 
6
6
  # The minor version of Roda, updated for new feature releases of Roda.
7
- RodaMinorVersion = 15
7
+ RodaMinorVersion = 16
8
8
 
9
9
  # The patch version of Roda, updated only for bug fixes from the last
10
10
  # feature release.
@@ -0,0 +1,39 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ describe "request_headers plugin" do
4
+ def header_app(header_name)
5
+ app(:bare) do
6
+ plugin :request_headers
7
+ route do |r|
8
+ r.on do
9
+ # return the value of the request header in the response body,
10
+ # or the static string 'not found' if it hasn't been supplied.
11
+ r.headers[header_name] || 'not found'
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ it "must add HTTP_ prefix when appropriate" do
18
+ header_app('Foo')
19
+ body('/', {'HTTP_FOO' => 'a'}).must_equal 'a'
20
+ end
21
+
22
+ it "must ignore HTTP_ prefix when appropriate" do
23
+ header_app('Content-Type')
24
+ body('/', {'CONTENT_TYPE' => 'a'}).must_equal 'a'
25
+ end
26
+
27
+ it "must return nil for non-existant headers" do
28
+ header_app('X-Non-Existant')
29
+ body('/').must_equal 'not found'
30
+ end
31
+
32
+ it "must be case-insensitive" do
33
+ header_app('X-My-Header')
34
+ body('/', {'HTTP_X_MY_HEADER' => 'a'}).must_equal 'a'
35
+
36
+ header_app('x-my-header')
37
+ body('/', {'HTTP_X_MY_HEADER' => 'a'}).must_equal 'a'
38
+ end
39
+ end
@@ -0,0 +1,255 @@
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
4
+
5
+ describe "type_routing plugin" do
6
+ before do
7
+ app(:type_routing) do |r|
8
+ r.is 'a' do
9
+ r.html{ "HTML: #{r.requested_type}" }
10
+ r.json{ "JSON: #{r.requested_type}" }
11
+ r.xml{ "XML: #{r.requested_type}" }
12
+ "No match"
13
+ end
14
+ end
15
+ end
16
+
17
+ it "uses the file extension in the path" do
18
+ body('/a').must_equal 'HTML: html'
19
+ header('Content-Type', '/a').must_equal 'text/html'
20
+
21
+ body('/a.html').must_equal 'HTML: html'
22
+ header('Content-Type', '/a.html').must_equal 'text/html'
23
+
24
+ body('/a.json').must_equal 'JSON: json'
25
+ header('Content-Type', '/a.json').must_equal 'application/json'
26
+
27
+ body('/a.xml').must_equal 'XML: xml'
28
+ header('Content-Type', '/a.xml').must_equal 'application/xml'
29
+
30
+ status('/a.yadda').must_equal 404
31
+ end
32
+
33
+ it "uses the Accept header value" do
34
+ body('/a', 'HTTP_ACCEPT' => 'text/html').must_equal 'HTML: html'
35
+ header('Content-Type', '/a', 'HTTP_ACCEPT' => 'text/html').must_equal 'text/html'
36
+
37
+ body('/a', 'HTTP_ACCEPT' => 'application/json').must_equal 'JSON: json'
38
+ header('Content-Type', '/a', 'HTTP_ACCEPT' => 'application/json').must_equal 'application/json'
39
+
40
+ body('/a', 'HTTP_ACCEPT' => 'application/xml').must_equal 'XML: xml'
41
+ header('Content-Type', '/a', 'HTTP_ACCEPT' => 'application/xml').must_equal 'application/xml'
42
+
43
+ body('/a', 'HTTP_ACCEPT' => 'some/thing').must_equal 'HTML: html'
44
+ header('Content-Type', '/a', 'HTTP_ACCEPT' => 'some/thing').must_equal 'text/html'
45
+ end
46
+
47
+ it "favors the file extension over the Accept header" do
48
+ body('/a.json', 'HTTP_ACCEPT' => 'text/html').must_equal 'JSON: json'
49
+ body('/a.xml', 'HTTP_ACCEPT' => 'application/json').must_equal 'XML: xml'
50
+ body('/a.html', 'HTTP_ACCEPT' => 'application/xml').must_equal 'HTML: html'
51
+ end
52
+
53
+
54
+ it "uses the default if neither file extension nor Accept header are given" do
55
+ body('/a').must_equal 'HTML: html'
56
+ header('Content-Type', '/a').must_equal 'text/html'
57
+ end
58
+ end
59
+
60
+ describe "type_routing plugin" do
61
+ it "does not use the file extension if its disabled" do
62
+ app(:bare) do
63
+ plugin :type_routing, :use_extension => false
64
+
65
+ route do |r|
66
+ r.is 'a' do
67
+ r.html{ "HTML" }
68
+ r.json{ "JSON" }
69
+ end
70
+ end
71
+ end
72
+
73
+ status('/a.json').must_equal 404
74
+ status('/a.html').must_equal 404
75
+ body('/a', 'HTTP_ACCEPT' => 'text/html').must_equal 'HTML'
76
+ body('/a', 'HTTP_ACCEPT' => 'application/json').must_equal 'JSON'
77
+ end
78
+
79
+ it "does not use the Accept header if its disabled" do
80
+ app(:bare) do
81
+ plugin :type_routing, :use_header => false
82
+
83
+ route do |r|
84
+ r.is 'a' do
85
+ r.html{ "HTML" }
86
+ r.json{ "JSON" }
87
+ end
88
+ end
89
+ end
90
+
91
+ body('/a', 'HTTP_ACCEPT' => 'text/html').must_equal 'HTML'
92
+ body('/a', 'HTTP_ACCEPT' => 'application/json').must_equal 'HTML'
93
+ body('/a.html', 'HTTP_ACCEPT' => 'application/json').must_equal 'HTML'
94
+ body('/a.json', 'HTTP_ACCEPT' => 'text/html').must_equal 'JSON'
95
+ end
96
+
97
+ it "only eats known file extensions" do
98
+ app(:bare) do
99
+ plugin :type_routing
100
+
101
+ route do |r|
102
+ r.is 'a' do
103
+ r.html{ "HTML" }
104
+ r.json{ "JSON" }
105
+ r.xml{ "XML" }
106
+ raise "Mismatch!"
107
+ end
108
+
109
+ r.is 'a.jpg' do
110
+ "Okay"
111
+ end
112
+ end
113
+ end
114
+
115
+ body('/a.html').must_equal 'HTML'
116
+ body('/a.json').must_equal 'JSON'
117
+ body('/a.xml').must_equal 'XML'
118
+ body('/a.jpg').must_equal 'Okay'
119
+ end
120
+
121
+ it "uses custom data types" do
122
+ app(:bare) do
123
+ plugin :type_routing, :types => { :yaml => 'application/x-yaml' }
124
+
125
+ route do |r|
126
+ r.is 'a' do
127
+ r.html{ "HTML" }
128
+ r.yaml{ "YAML" }
129
+ raise "Mismatch!"
130
+ end
131
+ end
132
+ end
133
+
134
+ body('/a.html').must_equal 'HTML'
135
+ body('/a.yaml').must_equal 'YAML'
136
+ header('Content-Type', '/a.yaml').must_equal 'application/x-yaml'
137
+ end
138
+
139
+ it "handles response-specific type information when using custom types" do
140
+ app(:bare) do
141
+ plugin :type_routing, :exclude=>:html, :default_type=>:json, :types => { :html => 'text/html; charset=utf-8' }
142
+
143
+ route do |r|
144
+ r.is 'a' do
145
+ r.json{ "JSON" }
146
+ r.html{ "HTML" }
147
+ raise "Mismatch!"
148
+ end
149
+ end
150
+ end
151
+
152
+ body('/a').must_equal 'JSON'
153
+ body('/a.html').must_equal 'HTML'
154
+ header('Content-Type', '/a.html').must_equal 'text/html; charset=utf-8'
155
+ header('Content-Type', '/a', 'HTTP_ACCEPT' => 'text/html').must_equal 'text/html; charset=utf-8'
156
+ end
157
+
158
+ it "Handle nil content type when using custom types" do
159
+ app(:bare) do
160
+ plugin :type_routing, :exclude=>:html, :default_type=>:json, :types => { :html => nil}
161
+
162
+ route do |r|
163
+ r.is 'a' do
164
+ r.html{ "HTML" }
165
+ r.json{ "JSON" }
166
+ raise "Mismatch!"
167
+ end
168
+ end
169
+ end
170
+
171
+ body('/a').must_equal 'JSON'
172
+ body('/a.html').must_equal 'HTML'
173
+ header('Content-Type', '/a.html').must_equal 'text/html'
174
+ header('Content-Type', '/a', 'HTTP_ACCEPT' => 'text/html').must_equal 'application/json'
175
+ end
176
+
177
+ it "uses custom default type" do
178
+ app(:bare) do
179
+ plugin :type_routing, :default_type => :json
180
+
181
+ route do |r|
182
+ r.is 'a' do
183
+ r.html{ "HTML" }
184
+ r.json{ "JSON" }
185
+ raise "Mismatch!"
186
+ end
187
+ end
188
+ end
189
+
190
+ body('/a').must_equal 'JSON'
191
+ body('/a.html').must_equal 'HTML'
192
+ body('/a.json').must_equal 'JSON'
193
+ end
194
+
195
+ it "supports nil default type" do
196
+ app(:bare) do
197
+ plugin :type_routing, :default_type => nil
198
+
199
+ route do |r|
200
+ r.is 'a' do
201
+ r.html{ "HTML" }
202
+ r.json{ "JSON" }
203
+ "None"
204
+ end
205
+ end
206
+ end
207
+
208
+ body('/a').must_equal 'None'
209
+ body('/a.html').must_equal 'HTML'
210
+ body('/a.json').must_equal 'JSON'
211
+ end
212
+
213
+ it "excludes given types" do
214
+ app(:bare) do
215
+ plugin :type_routing, :exclude => [ :xml ]
216
+
217
+ route do |r|
218
+ r.is 'a' do
219
+ r.html{ "HTML" }
220
+ r.json{ "JSON" }
221
+ r.xml{ raise "Mismatch!" }
222
+ raise "Mismatch"
223
+ end
224
+ end
225
+ end
226
+
227
+ body('/a.html').must_equal 'HTML'
228
+ body('/a.json').must_equal 'JSON'
229
+ status('/a.xml').must_equal 404
230
+
231
+ body('/a', 'HTTP_ACCEPT' => 'text/xml').must_equal 'HTML'
232
+ body('/a', 'HTTP_ACCEPT' => 'application/json').must_equal 'JSON'
233
+ body('/a', 'HTTP_ACCEPT' => 'text/xml').must_equal 'HTML'
234
+ body('/a', 'HTTP_ACCEPT' => 'application/xml').must_equal 'HTML'
235
+ end
236
+
237
+ it "handles loading the plugin multiple times correctly" do
238
+ app(:bare) do
239
+ plugin :type_routing, :default_type => :json
240
+ plugin :type_routing
241
+
242
+ route do |r|
243
+ r.is 'a' do
244
+ r.html{ "HTML" }
245
+ r.json{ "JSON" }
246
+ raise "Mismatch!"
247
+ end
248
+ end
249
+ end
250
+
251
+ body('/a').must_equal 'JSON'
252
+ body('/a.html').must_equal 'HTML'
253
+ body('/a.json').must_equal 'JSON'
254
+ end
255
+ end
@@ -0,0 +1,22 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ describe "unescape_path_path plugin" do
4
+ it "decodes URL-encoded routing path" do
5
+ app(:unescape_path) do |r|
6
+ r.on 'b' do
7
+ r.get /(.)/ do |a|
8
+ "#{a}-b"
9
+ end
10
+ end
11
+
12
+ r.get :name do |name|
13
+ name
14
+ end
15
+ end
16
+
17
+ body('/a').must_equal 'a'
18
+ body('/%61').must_equal 'a'
19
+ body('%2f%61').must_equal 'a'
20
+ body('%2f%62%2f%61').must_equal 'a-b'
21
+ end
22
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roda
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.15.0
4
+ version: 2.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-06-13 00:00:00.000000000 Z
11
+ date: 2016-07-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -181,6 +181,7 @@ extra_rdoc_files:
181
181
  - doc/release_notes/2.13.0.txt
182
182
  - doc/release_notes/2.14.0.txt
183
183
  - doc/release_notes/2.15.0.txt
184
+ - doc/release_notes/2.16.0.txt
184
185
  files:
185
186
  - CHANGELOG
186
187
  - MIT-LICENSE
@@ -199,6 +200,7 @@ files:
199
200
  - doc/release_notes/2.13.0.txt
200
201
  - doc/release_notes/2.14.0.txt
201
202
  - doc/release_notes/2.15.0.txt
203
+ - doc/release_notes/2.16.0.txt
202
204
  - doc/release_notes/2.2.0.txt
203
205
  - doc/release_notes/2.3.0.txt
204
206
  - doc/release_notes/2.4.0.txt
@@ -264,6 +266,7 @@ files:
264
266
  - lib/roda/plugins/public.rb
265
267
  - lib/roda/plugins/render.rb
266
268
  - lib/roda/plugins/render_each.rb
269
+ - lib/roda/plugins/request_headers.rb
267
270
  - lib/roda/plugins/response_request.rb
268
271
  - lib/roda/plugins/run_handler.rb
269
272
  - lib/roda/plugins/shared_vars.rb
@@ -276,6 +279,8 @@ files:
276
279
  - lib/roda/plugins/symbol_matchers.rb
277
280
  - lib/roda/plugins/symbol_status.rb
278
281
  - lib/roda/plugins/symbol_views.rb
282
+ - lib/roda/plugins/type_routing.rb
283
+ - lib/roda/plugins/unescape_path.rb
279
284
  - lib/roda/plugins/view_options.rb
280
285
  - lib/roda/plugins/view_subdirs.rb
281
286
  - lib/roda/plugins/websockets.rb
@@ -346,6 +351,7 @@ files:
346
351
  - spec/plugin/public_spec.rb
347
352
  - spec/plugin/render_each_spec.rb
348
353
  - spec/plugin/render_spec.rb
354
+ - spec/plugin/request_headers_spec.rb
349
355
  - spec/plugin/response_request_spec.rb
350
356
  - spec/plugin/run_handler_spec.rb
351
357
  - spec/plugin/shared_vars_spec.rb
@@ -357,6 +363,8 @@ files:
357
363
  - spec/plugin/symbol_matchers_spec.rb
358
364
  - spec/plugin/symbol_status_spec.rb
359
365
  - spec/plugin/symbol_views_spec.rb
366
+ - spec/plugin/type_routing_spec.rb
367
+ - spec/plugin/unescape_path_spec.rb
360
368
  - spec/plugin/view_options_spec.rb
361
369
  - spec/plugin/websockets_spec.rb
362
370
  - spec/plugin_spec.rb