soar_sc-rack-router 0.1.2 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d498fa3bf594aa2b22ca48aa31b8cb8097796c5d
4
- data.tar.gz: 8c925e0ddf11b97bb5818aedabba4e0b955b756b
3
+ metadata.gz: 06db72006f7280dbcdfd881a108d084e9fb67b79
4
+ data.tar.gz: 4ffcd46d095e341fce59740dad5a6faaa3ed8552
5
5
  SHA512:
6
- metadata.gz: 3642dec09f7242a7a0475790c2c92262efab5790be0361714c8a42d117a90b9e85ba78d140a3aa729da5812b602de625ee478747ae05b485476e019156a8a1ae
7
- data.tar.gz: 8e407af01156f52e78b79144c80cb225e471234c5d0ad608b298dbe389e26e627b37049faa91983a887dc6f4f6e3b60b0bf3e5ad546afae85672c0707ce3fd27
6
+ metadata.gz: 1962096dc371cc19ce1d25bde13a0724f9d934006bcf0a0c36036f7bc3b762a2154025effb39aa52aa8f90c2b5a024ddf502e22d8683c3f27376e5bfb38122f2
7
+ data.tar.gz: c7b0b28b6ec325022474b76bff8194de423beb484b185eba8516d7c9549eaaea0a60c16bbbda5a20793ad883663ca2201af1d38116313a3d877426dd30f7bc50
data/README.md CHANGED
@@ -14,6 +14,11 @@ Because it supports parameterized URLs, it is familiar to users of several Ruby
14
14
  into WADL and other service descriptions. The parameter support is compatible with Rack::Request, without pushing a dependency on Rack::Request
15
15
  down into the router action API; the only requirement for router actions is that they are rack apps.
16
16
 
17
+ Users of Ruby web frameworks like Rails and Sinatra may be surprised that double-slashes in request paths are not normalized.
18
+ In Sinatra, this is actually done by the `rack-protection` middleware, not `sinatra-router`.
19
+ Users of `SoarSc::Rack::Router` definitely should be using the [rack-protection](https://rubygems.org/gems/rack-protection) middleware
20
+ above the router in their stacks.
21
+
17
22
  ## Installation
18
23
 
19
24
  Add this line to your application's Gemfile:
@@ -74,10 +79,12 @@ stack = Rack::Builder.new do
74
79
  use Auditing
75
80
  run ->(...)
76
81
  end
77
-
78
- run ->(env) { [404, {'Content-Type' => 'text/plain'}, ['Object Not Found']] }
79
82
  end
83
+
84
+ run ->(env) { [404, {'Content-Type' => 'text/plain'}, ['Object Not Found']] }
80
85
  end
86
+
87
+ run stack
81
88
  ```
82
89
 
83
90
  An example of parameterized URL support:
@@ -100,6 +107,25 @@ end
100
107
  run stack
101
108
  ```
102
109
 
110
+ Note that, even though the router doesn't break the rack stack by demanding that routing actions receive a `Rack::Request`,
111
+ it's recommended that routing actions use `Rack::Request` internally to retrieve path parameters. The router adds them to rack's
112
+ `env` using `Rack::Request#update_param`, making `Rack::Request#params` the safest way to retrieve them.
113
+
114
+ Finally, here is a demonstration that doesn't use `Rack::Builder`, just to clarify that it's not a dependency:
115
+
116
+ ```ruby
117
+ require 'soar_sc/rack/router'
118
+
119
+ index = ->(env) { ... }
120
+ about = ->(env) { ... }
121
+ contact = ->(env) { ...]
122
+
123
+ run SoarSc::Rack::Router.new(index) do
124
+ get "/about", about
125
+ get "/contact", contact
126
+ end
127
+ ```
128
+
103
129
  ## Development
104
130
 
105
131
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -1,5 +1,6 @@
1
1
  require "soar_sc/rack/router/builder_syntax"
2
2
  require "soar_sc/rack/router/errors"
3
+ require "soar_sc/rack/router/http_method"
3
4
  require "soar_sc/rack/router/route"
4
5
  require "soar_sc/rack/router/version"
5
6
 
@@ -11,18 +12,17 @@ module SoarSc
11
12
 
12
13
  include BuilderSyntax
13
14
 
15
+ EMPTY_STRING = ""
16
+
14
17
  def initialize(app, routes = {}, &block)
15
18
  @app = app
16
- @static_routes = []
17
- @parameterized_routes = []
18
- routes.each { |(p, a)| add_route(Route.new('*', p, a)) }
19
+ @routes = []
20
+ routes.each { |(p, a)| add_route(Route.new(HttpMethod::ANY, p, a)) }
19
21
  instance_eval(&block) if block_given?
20
22
  end
21
23
 
22
24
  def call(env)
23
- if route = @static_routes.detect { |r| r.matches?(env) }
24
- route.action.call(env)
25
- elsif route = @parameterized_routes.detect { |r| r.matches?(env) }
25
+ if route = @routes.detect { |r| r.matches?(env) }
26
26
  update_path_parameters!(env, route.extract_path_parameters(env))
27
27
  route.action.call(env)
28
28
  else
@@ -34,18 +34,33 @@ module SoarSc
34
34
 
35
35
  # Called by BuilderSyntax methods
36
36
  def add_route(route)
37
- if (@static_routes + @parameterized_routes).detect { |r| r.path == route.path and (r.method == route.method or r.method = "*") }
37
+ if include?(route)
38
38
  raise DuplicateRouteError.for_route(route)
39
- elsif route.static?
40
- @static_routes << route
41
39
  else
42
- @parameterized_routes << route
40
+ @routes << route
41
+ end
42
+ end
43
+
44
+ def include?(route)
45
+ @routes.detect do |other|
46
+ route.matches?(env_for_route(other)) or other.matches?(env_for_route(route))
43
47
  end
44
48
  end
45
49
 
50
+ # Creates a minimal env that would be matched by the given route.
51
+ def env_for_route(route)
52
+ {
53
+ Route::REQUEST_METHOD => (route.method == HttpMethod::ANY ? HttpMethod::GET : route.method),
54
+ Route::SCRIPT_NAME => route.path,
55
+ Route::PATH_INFO => EMPTY_STRING
56
+ }
57
+ end
58
+
46
59
  def update_path_parameters!(env, params)
47
- req = ::Rack::Request.new(env)
48
- params.each { |p, v| req.update_param(p, v) }
60
+ unless params.empty?
61
+ req = ::Rack::Request.new(env)
62
+ params.each { |p, v| req.update_param(p, v) }
63
+ end
49
64
  end
50
65
 
51
66
  end
@@ -1,3 +1,5 @@
1
+ require 'soar_sc/rack/router/http_method'
2
+
1
3
  module SoarSc
2
4
  module Rack
3
5
  class Router
@@ -5,39 +7,39 @@ module SoarSc
5
7
  module BuilderSyntax
6
8
 
7
9
  def map(path, app)
8
- add_route(Route.new('*', path, app))
10
+ add_route(Route.new(HttpMethod::ANY, path, app))
9
11
  end
10
12
 
11
13
  def get(path, app)
12
- add_route(Route.new('GET', path, app))
14
+ add_route(Route.new(HttpMethod::GET, path, app))
13
15
  end
14
16
 
15
17
  def post(path, app)
16
- add_route(Route.new('POST', path, app))
18
+ add_route(Route.new(HttpMethod::POST, path, app))
17
19
  end
18
20
 
19
21
  def put(path, app)
20
- add_route(Route.new('PUT', path, app))
22
+ add_route(Route.new(HttpMethod::PUT, path, app))
21
23
  end
22
24
 
23
25
  def delete(path, app)
24
- add_route(Route.new('DELETE', path, app))
26
+ add_route(Route.new(HttpMethod::DELETE, path, app))
25
27
  end
26
28
 
27
29
  def options(path, app)
28
- add_route(Route.new('OPTIONS', path, app))
30
+ add_route(Route.new(HttpMethod::OPTIONS, path, app))
29
31
  end
30
32
 
31
33
  def head(path, app)
32
- add_route(Route.new('HEAD', path, app))
34
+ add_route(Route.new(HttpMethod::HEAD, path, app))
33
35
  end
34
36
 
35
37
  def trace(path, app)
36
- add_route(Route.new('TRACE', path, app))
38
+ add_route(Route.new(HttpMethod::TRACE, path, app))
37
39
  end
38
40
 
39
41
  def connect(path, app)
40
- add_route(Route.new('CONNECT', path, app))
42
+ add_route(Route.new(HttpMethod::CONNECT, path, app))
41
43
  end
42
44
 
43
45
  end
@@ -0,0 +1,19 @@
1
+ module SoarSc
2
+ module Rack
3
+ class Router
4
+ module HttpMethod
5
+
6
+ ANY = "*"
7
+ GET = "GET"
8
+ POST = "POST"
9
+ PUT = "PUT"
10
+ DELETE = "DELETE"
11
+ HEAD = "HEAD"
12
+ OPTIONS = "OPTIONS"
13
+ TRACE = "TRACE"
14
+ CONNECT = "CONNECT"
15
+
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,35 +1,50 @@
1
+ require 'soar_sc/rack/router/http_method'
2
+
3
+ # TODO Normalize paths (compress runs of slashes)
1
4
  module SoarSc
2
5
  module Rack
3
6
  class Router
4
7
 
5
8
  class Route
9
+ PARAMETER_PREFIX = ":"
10
+ PARAMETER_VALID_REGEXP = /:[a-z_][a-z0-9_]*/
11
+ REQUEST_METHOD = "REQUEST_METHOD"
12
+ SCRIPT_NAME = "SCRIPT_NAME"
13
+ PATH_INFO = "PATH_INFO"
14
+ EMPTY_HASH = {}.freeze
15
+
6
16
  attr_reader :method, :path, :action
7
17
 
8
18
  def initialize(method, path, action)
9
19
  validate_path(path)
10
20
  @method, @path, @action = method, path, action
11
- end
12
-
13
- def static?
14
- components(path).none? { |c| c.start_with?(":") }
21
+ @static = components(path).none? { |c| c.start_with?(PARAMETER_PREFIX) }
15
22
  end
16
23
 
17
24
  def matches?(env)
18
- return false unless (method == env["REQUEST_METHOD"] or method == "*")
25
+ return false unless method == env[REQUEST_METHOD] or method == HttpMethod::ANY
26
+
27
+ return request_path(env) == path if @static
19
28
 
29
+ route_components = components(path)
20
30
  req_components = components(request_path(env))
21
- components(path).each_with_index do |c, i|
22
- return false unless c.start_with?(":") or c == req_components[i]
31
+
32
+ return false unless route_components.size == req_components.size
33
+
34
+ route_components.each_with_index do |c, i|
35
+ return false unless c.start_with?(PARAMETER_PREFIX) or c == req_components[i]
23
36
  end
24
37
 
25
38
  return true
26
39
  end
27
40
 
28
41
  def extract_path_parameters(env)
42
+ return EMPTY_HASH if @static
43
+
29
44
  req_components = components(request_path(env))
30
45
  parameters = {}
31
46
  components(path).each_with_index do |c, i|
32
- parameters[c[1..-1]] = req_components[i] if c.start_with?(":")
47
+ parameters[c[1..-1]] = req_components[i] if c.start_with?(PARAMETER_PREFIX)
33
48
  end
34
49
  parameters
35
50
  end
@@ -37,7 +52,7 @@ module SoarSc
37
52
  private
38
53
 
39
54
  def request_path(env)
40
- env["SCRIPT_NAME"].to_s + env["PATH_INFO"].to_s
55
+ env[SCRIPT_NAME] + env[PATH_INFO]
41
56
  end
42
57
 
43
58
  def components(path)
@@ -46,7 +61,7 @@ module SoarSc
46
61
 
47
62
  def validate_path(path)
48
63
  components(path).each do |c|
49
- if c.start_with?(":") and c !~ /:[a-z_][a-z0-9_]*+/
64
+ if c.start_with?(PARAMETER_PREFIX) and c !~ PARAMETER_VALID_REGEXP
50
65
  raise InvalidParameterError, "Invalid parameter #{c} in path #{path}"
51
66
  end
52
67
  end
@@ -1,7 +1,7 @@
1
1
  module SoarSc
2
2
  module Rack
3
3
  class Router
4
- VERSION = "0.1.2"
4
+ VERSION = "1.0.0"
5
5
  end
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: soar_sc-rack-router
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sheldon Hearn
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-04-19 00:00:00.000000000 Z
11
+ date: 2016-04-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -86,6 +86,7 @@ files:
86
86
  - lib/soar_sc/rack/router.rb
87
87
  - lib/soar_sc/rack/router/builder_syntax.rb
88
88
  - lib/soar_sc/rack/router/errors.rb
89
+ - lib/soar_sc/rack/router/http_method.rb
89
90
  - lib/soar_sc/rack/router/route.rb
90
91
  - lib/soar_sc/rack/router/version.rb
91
92
  - soar_sc-rack-router.gemspec