lotus-router 0.4.3 → 0.5.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 +4 -4
- data/CHANGELOG.md +15 -0
- data/LICENSE.md +1 -1
- data/README.md +93 -5
- data/lib/lotus/router.rb +203 -0
- data/lib/lotus/router/version.rb +1 -1
- data/lib/lotus/routing/endpoint.rb +2 -1
- data/lib/lotus/routing/error.rb +7 -0
- data/lib/lotus/routing/force_ssl.rb +1 -1
- data/lib/lotus/routing/http_router.rb +28 -8
- data/lib/lotus/routing/parsing/json_parser.rb +12 -1
- data/lib/lotus/routing/parsing/parser.rb +16 -1
- data/lib/lotus/routing/recognized_route.rb +153 -0
- data/lib/lotus/routing/routes_inspector.rb +40 -7
- data/lotus-router.gemspec +2 -1
- metadata +21 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 385192d4a396ff6a0e91dabff73a8351c55848ed
|
4
|
+
data.tar.gz: efd06ad2632579b10f7cf6229aba7b3d760fbc08
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a0c1078e04043023b44f1a72189e6890dd8e1ccfa53f966bb6624527f2444fa5825ede6944e17c0b171b2eecf393cb1f39573e65073a74b512813114c37a00e0
|
7
|
+
data.tar.gz: 662945b950952171f02220b318e808b87c44ad44b54034defff58772fd30f92d3ab9036737433888ae06c2b8be07ccbb52d6e4f1f8d739c2f33dffb5e1281d01
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,21 @@
|
|
1
1
|
# Lotus::Router
|
2
2
|
Rack compatible HTTP router for Ruby
|
3
3
|
|
4
|
+
## v0.5.0 - 2016-01-12
|
5
|
+
### Added
|
6
|
+
- [Luca Guidi] Added `Lotus::Router#recognize` as a testing facility. Example `router.recognize('/') # => associated route`
|
7
|
+
- [Luca Guidi] Added `Lotus::Router.define` in order to wrap routes definitions in `config/routes.rb` when `Lotus::Router` is used outside of Lotus projects
|
8
|
+
- [David Strauß] Make `Lotus::Routing::Parsing::JsonParser` compatible with `application/vnd.api+json` MIME Type
|
9
|
+
- [Alfonso Uceda Pompa] Improved exception messages for `Lotus::Router#path` and `#url`
|
10
|
+
|
11
|
+
### Fixed
|
12
|
+
- [Alfonso Uceda Pompa] Ensure `Lotus::Router#path` and `#url` to generate correct URL for mounted applications
|
13
|
+
- [Vladislav Zarakovsky] Ensure Force SSL mode to respect Rack SPEC
|
14
|
+
|
15
|
+
### Changed
|
16
|
+
- [Alfonso Uceda Pompa] A failure for body parsers raises a `Lotus::Routing::Parsing::BodyParsingError` exception
|
17
|
+
- [Karim Tarek] Introduced `Lotus::Router::Error` and let all the framework exceptions to inherit from it.
|
18
|
+
|
4
19
|
## v0.4.3 - 2015-09-30
|
5
20
|
### Added
|
6
21
|
- [Luca Guidi] Official support for JRuby 9k+
|
data/LICENSE.md
CHANGED
data/README.md
CHANGED
@@ -568,18 +568,106 @@ router.path(:new_user_favorites, user_id: 1) # => /users/1/favorites/new
|
|
568
568
|
router.path(:edit_user_favorites, user_id: 1, id: 2) # => /users/1/favorites/2/edit
|
569
569
|
```
|
570
570
|
|
571
|
+
### Body Parsers
|
572
|
+
|
573
|
+
Rack ignores request bodies unless they come from a form submission.
|
574
|
+
If we have a JSON endpoint, the payload isn't available in the params hash:
|
575
|
+
|
576
|
+
```ruby
|
577
|
+
Rack::Request.new(env).params # => {}
|
578
|
+
```
|
579
|
+
|
580
|
+
This feature enables body parsing for specific MIME Types.
|
581
|
+
It comes with a built-in JSON parser and allows to pass custom parsers.
|
582
|
+
|
583
|
+
#### JSON Parsing
|
584
|
+
|
585
|
+
```ruby
|
586
|
+
require 'lotus/router'
|
587
|
+
|
588
|
+
endpoint = ->(env) { [200, {},[env['router.params'].inspect]] }
|
589
|
+
|
590
|
+
router = Lotus::Router.new(parsers: [:json]) do
|
591
|
+
patch '/books/:id', to: endpoint
|
592
|
+
end
|
593
|
+
```
|
594
|
+
|
595
|
+
```shell
|
596
|
+
curl http://localhost:2300/books/1 \
|
597
|
+
-H "Content-Type: application/json" \
|
598
|
+
-H "Accept: application/json" \
|
599
|
+
-d '{"published":"true"}' \
|
600
|
+
-X PATCH
|
601
|
+
|
602
|
+
# => [200, {}, ["{:published=>\"true\",:id=>\"1\"}"]]
|
603
|
+
```
|
604
|
+
|
605
|
+
If the json can't be parsed an exception is raised:
|
606
|
+
|
607
|
+
```ruby
|
608
|
+
Lotus::Routing::Parsing::BodyParsingError
|
609
|
+
```
|
610
|
+
|
611
|
+
#### Custom Parsers
|
612
|
+
|
613
|
+
```ruby
|
614
|
+
require 'lotus/router'
|
615
|
+
|
616
|
+
# See Lotus::Routing::Parsing::Parser
|
617
|
+
class XmlParser
|
618
|
+
def mime_types
|
619
|
+
['application/xml', 'text/xml']
|
620
|
+
end
|
621
|
+
|
622
|
+
# Parse body and return a Hash
|
623
|
+
def parse(body)
|
624
|
+
# parse xml
|
625
|
+
rescue SomeXmlParsingError => e
|
626
|
+
raise Lotus::Routing::Parsing::BodyParsingError.new(e)
|
627
|
+
end
|
628
|
+
end
|
629
|
+
|
630
|
+
endpoint = ->(env) { [200, {},[env['router.params'].inspect]] }
|
631
|
+
|
632
|
+
router = Lotus::Router.new(parsers: [XmlParser.new]) do
|
633
|
+
patch '/authors/:id', to: endpoint
|
634
|
+
end
|
635
|
+
```
|
636
|
+
|
637
|
+
```shell
|
638
|
+
curl http://localhost:2300/authors/1 \
|
639
|
+
-H "Content-Type: application/xml" \
|
640
|
+
-H "Accept: application/xml" \
|
641
|
+
-d '<name>LG</name>' \
|
642
|
+
-X PATCH
|
643
|
+
|
644
|
+
# => [200, {}, ["{:name=>\"LG\",:id=>\"1\"}"]]
|
645
|
+
```
|
646
|
+
|
571
647
|
## Testing
|
572
648
|
|
573
649
|
```ruby
|
574
650
|
require 'lotus/router'
|
575
|
-
require 'rack/request'
|
576
651
|
|
577
652
|
router = Lotus::Router.new do
|
578
|
-
get '/', to:
|
653
|
+
get '/books/:id', to: 'books#show', as: :book
|
579
654
|
end
|
580
655
|
|
581
|
-
|
582
|
-
|
656
|
+
route = router.recognize('/books/23')
|
657
|
+
route.verb # "GET"
|
658
|
+
route.action # => "books#show"
|
659
|
+
route.params # => {:id=>"23"}
|
660
|
+
route.routable? # => true
|
661
|
+
|
662
|
+
route = router.recognize(:book, id: 23)
|
663
|
+
route.verb # "GET"
|
664
|
+
route.action # => "books#show"
|
665
|
+
route.params # => {:id=>"23"}
|
666
|
+
route.routable? # => true
|
667
|
+
|
668
|
+
route = router.recognize('/books/23', method: :post)
|
669
|
+
route.verb # "POST"
|
670
|
+
route.routable? # => false
|
583
671
|
```
|
584
672
|
|
585
673
|
## Versioning
|
@@ -601,4 +689,4 @@ Thanks to Joshua Hull ([@joshbuddy](https://github.com/joshbuddy)) for his
|
|
601
689
|
|
602
690
|
## Copyright
|
603
691
|
|
604
|
-
Copyright © 2014-
|
692
|
+
Copyright © 2014-2016 Luca Guidi – Released under MIT License
|
data/lib/lotus/router.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
+
require 'rack/request'
|
1
2
|
require 'lotus/routing/http_router'
|
2
3
|
require 'lotus/routing/namespace'
|
3
4
|
require 'lotus/routing/resource'
|
4
5
|
require 'lotus/routing/resources'
|
6
|
+
require 'lotus/routing/error'
|
5
7
|
|
6
8
|
module Lotus
|
7
9
|
# Rack compatible, lightweight and fast HTTP Router.
|
@@ -71,6 +73,57 @@ module Lotus
|
|
71
73
|
#
|
72
74
|
# # All the requests starting with "/api" will be forwarded to Api::App
|
73
75
|
class Router
|
76
|
+
# This error is raised when <tt>#call</tt> is invoked on a non-routable
|
77
|
+
# recognized route.
|
78
|
+
#
|
79
|
+
# @since 0.5.0
|
80
|
+
#
|
81
|
+
# @see Lotus::Router#recognize
|
82
|
+
# @see Lotus::Routing::RecognizedRoute
|
83
|
+
# @see Lotus::Routing::RecognizedRoute#call
|
84
|
+
# @see Lotus::Routing::RecognizedRoute#routable?
|
85
|
+
class NotRoutableEndpointError < Lotus::Routing::Error
|
86
|
+
REQUEST_METHOD = 'REQUEST_METHOD'.freeze
|
87
|
+
PATH_INFO = 'PATH_INFO'.freeze
|
88
|
+
|
89
|
+
def initialize(env)
|
90
|
+
super %(Cannot find routable endpoint for #{ env[REQUEST_METHOD] } "#{ env[PATH_INFO] }")
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns the given block as it is.
|
95
|
+
#
|
96
|
+
# When Lotus::Router is used as a standalone gem and the routes are defined
|
97
|
+
# into a configuration file, some systems could raise an exception.
|
98
|
+
#
|
99
|
+
# Imagine the following file into a Ruby on Rails application:
|
100
|
+
#
|
101
|
+
# get '/', to: 'api#index'
|
102
|
+
#
|
103
|
+
# Because Ruby on Rails in production mode use to eager load code and the
|
104
|
+
# routes file uses top level method calls, it crashes the application.
|
105
|
+
#
|
106
|
+
# If we wrap these routes with <tt>Lotus::Router.define</tt>, the block
|
107
|
+
# doesn't get yielded but just returned to the caller as it is.
|
108
|
+
#
|
109
|
+
# Usually the receiver of this block is <tt>Lotus::Router#initialize</tt>,
|
110
|
+
# which finally evaluates the block.
|
111
|
+
#
|
112
|
+
# @param blk [Proc] a set of route definitions
|
113
|
+
#
|
114
|
+
# @return [Proc] the given block
|
115
|
+
#
|
116
|
+
# @since 0.5.0
|
117
|
+
#
|
118
|
+
# @example
|
119
|
+
# # apps/web/config/routes.rb
|
120
|
+
# Lotus::Router.define do
|
121
|
+
# get '/', to: 'home#index'
|
122
|
+
# end
|
123
|
+
def self.define(&blk)
|
124
|
+
blk
|
125
|
+
end
|
126
|
+
|
74
127
|
# Initialize the router.
|
75
128
|
#
|
76
129
|
# @param options [Hash] the options to initialize the router
|
@@ -878,6 +931,125 @@ module Lotus
|
|
878
931
|
@router.call(env)
|
879
932
|
end
|
880
933
|
|
934
|
+
# Recognize the given env, path, or name and return a route for testing
|
935
|
+
# inspection.
|
936
|
+
#
|
937
|
+
# If the route cannot be recognized, it still returns an object for testing
|
938
|
+
# inspection.
|
939
|
+
#
|
940
|
+
# @param env [Hash, String, Symbol] Rack env, path or route name
|
941
|
+
# @param options [Hash] a set of options for Rack env or route params
|
942
|
+
# @param params [Hash] a set of params
|
943
|
+
#
|
944
|
+
# @return [Lotus::Routing::RecognizedRoute] the recognized route
|
945
|
+
#
|
946
|
+
# @since 0.5.0
|
947
|
+
#
|
948
|
+
# @see Lotus::Router#env_for
|
949
|
+
# @see Lotus::Routing::RecognizedRoute
|
950
|
+
#
|
951
|
+
# @example Successful Path Recognition
|
952
|
+
# require 'lotus/router'
|
953
|
+
#
|
954
|
+
# router = Lotus::Router.new do
|
955
|
+
# get '/books/:id', to: 'books#show', as: :book
|
956
|
+
# end
|
957
|
+
#
|
958
|
+
# route = router.recognize('/books/23')
|
959
|
+
# route.verb # => "GET" (default)
|
960
|
+
# route.routable? # => true
|
961
|
+
# route.params # => {:id=>"23"}
|
962
|
+
#
|
963
|
+
# @example Successful Rack Env Recognition
|
964
|
+
# require 'lotus/router'
|
965
|
+
#
|
966
|
+
# router = Lotus::Router.new do
|
967
|
+
# get '/books/:id', to: 'books#show', as: :book
|
968
|
+
# end
|
969
|
+
#
|
970
|
+
# route = router.recognize(Rack::MockRequest.env_for('/books/23'))
|
971
|
+
# route.verb # => "GET" (default)
|
972
|
+
# route.routable? # => true
|
973
|
+
# route.params # => {:id=>"23"}
|
974
|
+
#
|
975
|
+
# @example Successful Named Route Recognition
|
976
|
+
# require 'lotus/router'
|
977
|
+
#
|
978
|
+
# router = Lotus::Router.new do
|
979
|
+
# get '/books/:id', to: 'books#show', as: :book
|
980
|
+
# end
|
981
|
+
#
|
982
|
+
# route = router.recognize(:book, id: 23)
|
983
|
+
# route.verb # => "GET" (default)
|
984
|
+
# route.routable? # => true
|
985
|
+
# route.params # => {:id=>"23"}
|
986
|
+
#
|
987
|
+
# @example Failing Recognition For Unknown Path
|
988
|
+
# require 'lotus/router'
|
989
|
+
#
|
990
|
+
# router = Lotus::Router.new do
|
991
|
+
# get '/books/:id', to: 'books#show', as: :book
|
992
|
+
# end
|
993
|
+
#
|
994
|
+
# route = router.recognize('/books')
|
995
|
+
# route.verb # => "GET" (default)
|
996
|
+
# route.routable? # => false
|
997
|
+
#
|
998
|
+
# @example Failing Recognition For Path With Wrong HTTP Verb
|
999
|
+
# require 'lotus/router'
|
1000
|
+
#
|
1001
|
+
# router = Lotus::Router.new do
|
1002
|
+
# get '/books/:id', to: 'books#show', as: :book
|
1003
|
+
# end
|
1004
|
+
#
|
1005
|
+
# route = router.recognize('/books/23', method: :post)
|
1006
|
+
# route.verb # => "POST"
|
1007
|
+
# route.routable? # => false
|
1008
|
+
#
|
1009
|
+
# @example Failing Recognition For Rack Env With Wrong HTTP Verb
|
1010
|
+
# require 'lotus/router'
|
1011
|
+
#
|
1012
|
+
# router = Lotus::Router.new do
|
1013
|
+
# get '/books/:id', to: 'books#show', as: :book
|
1014
|
+
# end
|
1015
|
+
#
|
1016
|
+
# route = router.recognize(Rack::MockRequest.env_for('/books/23', method: :post))
|
1017
|
+
# route.verb # => "POST"
|
1018
|
+
# route.routable? # => false
|
1019
|
+
#
|
1020
|
+
# @example Failing Recognition Named Route With Wrong Params
|
1021
|
+
# require 'lotus/router'
|
1022
|
+
#
|
1023
|
+
# router = Lotus::Router.new do
|
1024
|
+
# get '/books/:id', to: 'books#show', as: :book
|
1025
|
+
# end
|
1026
|
+
#
|
1027
|
+
# route = router.recognize(:book)
|
1028
|
+
# route.verb # => "GET" (default)
|
1029
|
+
# route.routable? # => false
|
1030
|
+
#
|
1031
|
+
# @example Failing Recognition Named Route With Wrong HTTP Verb
|
1032
|
+
# require 'lotus/router'
|
1033
|
+
#
|
1034
|
+
# router = Lotus::Router.new do
|
1035
|
+
# get '/books/:id', to: 'books#show', as: :book
|
1036
|
+
# end
|
1037
|
+
#
|
1038
|
+
# route = router.recognize(:book, {method: :post}, {id: 1})
|
1039
|
+
# route.verb # => "POST"
|
1040
|
+
# route.routable? # => false
|
1041
|
+
# route.params # => {:id=>"1"}
|
1042
|
+
def recognize(env, options = {}, params = nil)
|
1043
|
+
require 'lotus/routing/recognized_route'
|
1044
|
+
|
1045
|
+
env = env_for(env, options, params)
|
1046
|
+
responses, _ = *@router.recognize(env)
|
1047
|
+
|
1048
|
+
Routing::RecognizedRoute.new(
|
1049
|
+
responses.nil? ? responses : responses.first,
|
1050
|
+
env, @router)
|
1051
|
+
end
|
1052
|
+
|
881
1053
|
# Generate an relative URL for a specified named route.
|
882
1054
|
# The additional arguments will be used to compose the relative URL - in
|
883
1055
|
# case it has tokens to match - and for compose the query string.
|
@@ -957,5 +1129,36 @@ module Lotus
|
|
957
1129
|
require 'lotus/routing/routes_inspector'
|
958
1130
|
Routing::RoutesInspector.new(@router.routes)
|
959
1131
|
end
|
1132
|
+
|
1133
|
+
protected
|
1134
|
+
|
1135
|
+
# Fabricate Rack env for the given Rack env, path or named route
|
1136
|
+
#
|
1137
|
+
# @param env [Hash, String, Symbol] Rack env, path or route name
|
1138
|
+
# @param options [Hash] a set of options for Rack env or route params
|
1139
|
+
# @param params [Hash] a set of params
|
1140
|
+
#
|
1141
|
+
# @return [Hash] Rack env
|
1142
|
+
#
|
1143
|
+
# @since 0.5.0
|
1144
|
+
# @api private
|
1145
|
+
#
|
1146
|
+
# @see Lotus::Router#recognize
|
1147
|
+
# @see http://www.rubydoc.info/github/rack/rack/Rack%2FMockRequest.env_for
|
1148
|
+
def env_for(env, options = {}, params = nil)
|
1149
|
+
env = case env
|
1150
|
+
when String
|
1151
|
+
Rack::MockRequest.env_for(env, options)
|
1152
|
+
when Symbol
|
1153
|
+
begin
|
1154
|
+
url = path(env, params || options)
|
1155
|
+
return env_for(url, options)
|
1156
|
+
rescue Lotus::Routing::InvalidRouteException
|
1157
|
+
{}
|
1158
|
+
end
|
1159
|
+
else
|
1160
|
+
env
|
1161
|
+
end
|
1162
|
+
end
|
960
1163
|
end
|
961
1164
|
end
|
data/lib/lotus/router/version.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'delegate'
|
2
|
+
require 'lotus/routing/error'
|
2
3
|
require 'lotus/utils/class'
|
3
4
|
|
4
5
|
module Lotus
|
@@ -7,7 +8,7 @@ module Lotus
|
|
7
8
|
# This is raised when the router fails to load an endpoint at the runtime.
|
8
9
|
#
|
9
10
|
# @since 0.1.0
|
10
|
-
class EndpointNotFound < ::
|
11
|
+
class EndpointNotFound < Lotus::Routing::Error
|
11
12
|
end
|
12
13
|
|
13
14
|
# Routing endpoint
|
@@ -4,6 +4,7 @@ require 'lotus/routing/endpoint_resolver'
|
|
4
4
|
require 'lotus/routing/route'
|
5
5
|
require 'lotus/routing/parsers'
|
6
6
|
require 'lotus/routing/force_ssl'
|
7
|
+
require 'lotus/routing/error'
|
7
8
|
require 'lotus/utils/path_prefix'
|
8
9
|
|
9
10
|
Lotus::Utils::IO.silence_warnings do
|
@@ -17,7 +18,7 @@ module Lotus
|
|
17
18
|
# given arguments.
|
18
19
|
#
|
19
20
|
# @since 0.1.0
|
20
|
-
class InvalidRouteException < ::
|
21
|
+
class InvalidRouteException < Lotus::Routing::Error
|
21
22
|
end
|
22
23
|
|
23
24
|
# HTTP router
|
@@ -30,6 +31,16 @@ module Lotus
|
|
30
31
|
# @since 0.1.0
|
31
32
|
# @api private
|
32
33
|
class HttpRouter < ::HttpRouter
|
34
|
+
# Script name - rack enviroment variable
|
35
|
+
#
|
36
|
+
# @since 0.5.0
|
37
|
+
# @api private
|
38
|
+
SCRIPT_NAME = 'SCRIPT_NAME'.freeze
|
39
|
+
|
40
|
+
# @since 0.5.0
|
41
|
+
# @api private
|
42
|
+
attr_reader :namespace
|
43
|
+
|
33
44
|
# Initialize the router.
|
34
45
|
#
|
35
46
|
# @see Lotus::Router#initialize
|
@@ -39,9 +50,10 @@ module Lotus
|
|
39
50
|
def initialize(options = {}, &blk)
|
40
51
|
super(options, &nil)
|
41
52
|
|
42
|
-
@
|
43
|
-
@
|
44
|
-
@
|
53
|
+
@namespace = options[:namespace] if options[:namespace]
|
54
|
+
@default_scheme = options[:scheme] if options[:scheme]
|
55
|
+
@default_host = options[:host] if options[:host]
|
56
|
+
@default_port = options[:port] if options[:port]
|
45
57
|
@route_class = options[:route] || Routing::Route
|
46
58
|
@resolver = options[:resolver] || Routing::EndpointResolver.new(options)
|
47
59
|
@parsers = Routing::Parsers.new(options[:parsers])
|
@@ -145,16 +157,24 @@ module Lotus
|
|
145
157
|
end
|
146
158
|
end
|
147
159
|
|
148
|
-
private
|
149
|
-
|
150
|
-
|
160
|
+
# @api private
|
161
|
+
# @since 0.5.0
|
162
|
+
def rewrite_path_info(env, request)
|
163
|
+
super
|
164
|
+
env[SCRIPT_NAME] = @prefix + env[SCRIPT_NAME]
|
151
165
|
end
|
152
166
|
|
167
|
+
private
|
168
|
+
|
153
169
|
def _rescue_url_recognition
|
154
170
|
yield
|
155
171
|
rescue ::HttpRouter::InvalidRouteException,
|
156
172
|
::HttpRouter::TooManyParametersException => e
|
157
|
-
raise Routing::InvalidRouteException.new(e.message)
|
173
|
+
raise Routing::InvalidRouteException.new("#{ e.message } - please check given arguments")
|
174
|
+
end
|
175
|
+
|
176
|
+
def add_with_request_method(path, method, opts = {}, &app)
|
177
|
+
super.generate(@resolver, opts, &app)
|
158
178
|
end
|
159
179
|
|
160
180
|
def _custom_path(uri_string)
|
@@ -5,11 +5,22 @@ module Lotus
|
|
5
5
|
module Parsing
|
6
6
|
class JsonParser < Parser
|
7
7
|
def mime_types
|
8
|
-
['application/json']
|
8
|
+
['application/json', 'application/vnd.api+json']
|
9
9
|
end
|
10
10
|
|
11
|
+
# Parse a json string
|
12
|
+
#
|
13
|
+
# @param body [String] a json string
|
14
|
+
#
|
15
|
+
# @return [Hash] the parsed json
|
16
|
+
#
|
17
|
+
# @raise [Lotus::Routing::Parsing::BodyParsingError] when the body can't be parsed.
|
18
|
+
#
|
19
|
+
# @since 0.2.0
|
11
20
|
def parse(body)
|
12
21
|
JSON.parse(body)
|
22
|
+
rescue JSON::ParserError => e
|
23
|
+
raise BodyParsingError.new(e.message)
|
13
24
|
end
|
14
25
|
end
|
15
26
|
end
|
@@ -1,16 +1,27 @@
|
|
1
1
|
require 'lotus/utils/class'
|
2
2
|
require 'lotus/utils/string'
|
3
|
+
require 'lotus/routing/error'
|
3
4
|
|
4
5
|
module Lotus
|
5
6
|
module Routing
|
6
7
|
module Parsing
|
7
|
-
|
8
|
+
# Body parsing error
|
9
|
+
# This is raised when parser fails to parse the body
|
10
|
+
#
|
11
|
+
# @since 0.5.0
|
12
|
+
class BodyParsingError < Lotus::Routing::Error
|
13
|
+
end
|
14
|
+
|
15
|
+
# @since 0.2.0
|
16
|
+
class UnknownParserError < Lotus::Routing::Error
|
8
17
|
def initialize(parser)
|
9
18
|
super("Unknown Parser: `#{ parser }'")
|
10
19
|
end
|
11
20
|
end
|
12
21
|
|
22
|
+
# @since 0.2.0
|
13
23
|
class Parser
|
24
|
+
# @since 0.2.0
|
14
25
|
def self.for(parser)
|
15
26
|
case parser
|
16
27
|
when String, Symbol
|
@@ -20,15 +31,19 @@ module Lotus
|
|
20
31
|
end
|
21
32
|
end
|
22
33
|
|
34
|
+
# @since 0.2.0
|
23
35
|
def mime_types
|
24
36
|
raise NotImplementedError
|
25
37
|
end
|
26
38
|
|
39
|
+
# @since 0.2.0
|
27
40
|
def parse(body)
|
28
41
|
Hash.new
|
29
42
|
end
|
30
43
|
|
31
44
|
private
|
45
|
+
# @since 0.2.0
|
46
|
+
# @api private
|
32
47
|
def self.require_parser(parser)
|
33
48
|
require "lotus/routing/parsing/#{ parser }_parser"
|
34
49
|
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'lotus/utils/string'
|
2
|
+
|
3
|
+
module Lotus
|
4
|
+
module Routing
|
5
|
+
# Represents a result of router path recognition.
|
6
|
+
#
|
7
|
+
# @since 0.5.0
|
8
|
+
#
|
9
|
+
# @see Lotus::Router#recognize
|
10
|
+
class RecognizedRoute
|
11
|
+
# @since 0.5.0
|
12
|
+
# @api private
|
13
|
+
REQUEST_METHOD = 'REQUEST_METHOD'.freeze
|
14
|
+
|
15
|
+
# @since 0.5.0
|
16
|
+
# @api private
|
17
|
+
NAMESPACE = '%s::'.freeze
|
18
|
+
|
19
|
+
# @since 0.5.0
|
20
|
+
# @api private
|
21
|
+
NAMESPACE_REPLACEMENT = ''.freeze
|
22
|
+
|
23
|
+
# @since 0.5.0
|
24
|
+
# @api private
|
25
|
+
ACTION_PATH_SEPARATOR = '/'.freeze
|
26
|
+
|
27
|
+
# @since 0.5.0
|
28
|
+
# @api public
|
29
|
+
attr_reader :params
|
30
|
+
|
31
|
+
# Creates a new instance
|
32
|
+
#
|
33
|
+
# @param response [HttpRouter::Response] raw response of recognition
|
34
|
+
# @param env [Hash] Rack env
|
35
|
+
# @param router [Lotus::Routing::HttpRouter] low level router
|
36
|
+
#
|
37
|
+
# @return [Lotus::Routing::RecognizedRoute]
|
38
|
+
#
|
39
|
+
# @since 0.5.0
|
40
|
+
# @api private
|
41
|
+
def initialize(response, env, router)
|
42
|
+
@env = env
|
43
|
+
|
44
|
+
unless response.nil?
|
45
|
+
@endpoint = response.route.dest
|
46
|
+
@params = response.params
|
47
|
+
end
|
48
|
+
|
49
|
+
@namespace = router.namespace
|
50
|
+
@action_separator = router.action_separator
|
51
|
+
end
|
52
|
+
|
53
|
+
# Rack protocol compatibility
|
54
|
+
#
|
55
|
+
# @param env [Hash] Rack env
|
56
|
+
#
|
57
|
+
# @return [Array] serialized Rack response
|
58
|
+
#
|
59
|
+
# @raise [Lotus::Router::NotRoutableEndpointError] if not routable
|
60
|
+
#
|
61
|
+
# @since 0.5.0
|
62
|
+
# @api public
|
63
|
+
#
|
64
|
+
# @see Lotus::Routing::RecognizedRoute#routable?
|
65
|
+
# @see Lotus::Router::NotRoutableEndpointError
|
66
|
+
def call(env)
|
67
|
+
if routable?
|
68
|
+
@endpoint.call(env)
|
69
|
+
else
|
70
|
+
raise Lotus::Router::NotRoutableEndpointError.new(@env)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# HTTP verb (aka method)
|
75
|
+
#
|
76
|
+
# @return [String]
|
77
|
+
#
|
78
|
+
# @since 0.5.0
|
79
|
+
# @api public
|
80
|
+
def verb
|
81
|
+
@env[REQUEST_METHOD]
|
82
|
+
end
|
83
|
+
|
84
|
+
# Action name
|
85
|
+
#
|
86
|
+
# @return [String]
|
87
|
+
#
|
88
|
+
# @since 0.5.0
|
89
|
+
# @api public
|
90
|
+
#
|
91
|
+
# @see Lotus::Router#recognize
|
92
|
+
#
|
93
|
+
# @example
|
94
|
+
# require 'lotus/router'
|
95
|
+
#
|
96
|
+
# router = Lotus::Router.new do
|
97
|
+
# get '/books/:id', to: 'books#show'
|
98
|
+
# end
|
99
|
+
#
|
100
|
+
# puts router.recognize('/books/23').action # => "books#show"
|
101
|
+
def action
|
102
|
+
namespace = NAMESPACE % @namespace
|
103
|
+
|
104
|
+
if destination.match(namespace)
|
105
|
+
Lotus::Utils::String.new(
|
106
|
+
destination.sub(namespace, NAMESPACE_REPLACEMENT)
|
107
|
+
).underscore.rsub(ACTION_PATH_SEPARATOR, @action_separator)
|
108
|
+
else
|
109
|
+
destination
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Check if routable
|
114
|
+
#
|
115
|
+
# @return [TrueClass,FalseClass]
|
116
|
+
#
|
117
|
+
# @since 0.5.0
|
118
|
+
# @api public
|
119
|
+
#
|
120
|
+
# @see Lotus::Router#recognize
|
121
|
+
#
|
122
|
+
# @example
|
123
|
+
# require 'lotus/router'
|
124
|
+
#
|
125
|
+
# router = Lotus::Router.new do
|
126
|
+
# get '/', to: 'home#index'
|
127
|
+
# end
|
128
|
+
#
|
129
|
+
# puts router.recognize('/').routable? # => true
|
130
|
+
# puts router.recognize('/foo').routable? # => false
|
131
|
+
def routable?
|
132
|
+
!!@endpoint
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
# @since 0.5.0
|
138
|
+
# @api private
|
139
|
+
#
|
140
|
+
# @see Lotus::Routing::Endpoint
|
141
|
+
def destination
|
142
|
+
@destination ||= begin
|
143
|
+
case k = @endpoint.__getobj__
|
144
|
+
when Class
|
145
|
+
k
|
146
|
+
else
|
147
|
+
k.class
|
148
|
+
end.name
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -18,6 +18,29 @@ module Lotus
|
|
18
18
|
# @api private
|
19
19
|
HTTP_METHODS_SEPARATOR = ', '.freeze
|
20
20
|
|
21
|
+
# Default inspector header hash values
|
22
|
+
#
|
23
|
+
# @since 0.5.0
|
24
|
+
# @api private
|
25
|
+
INSPECTOR_HEADER_HASH = Hash[
|
26
|
+
name: 'Name',
|
27
|
+
methods: 'Method',
|
28
|
+
path: 'Path',
|
29
|
+
endpoint: 'Action'
|
30
|
+
].freeze
|
31
|
+
|
32
|
+
# Default inspector header name values
|
33
|
+
#
|
34
|
+
# @since 0.5.0
|
35
|
+
# @api private
|
36
|
+
INSPECTOR_HEADER_NAME = 'Name'.freeze
|
37
|
+
|
38
|
+
# Empty line string
|
39
|
+
#
|
40
|
+
# @since 0.5.0
|
41
|
+
# @api private
|
42
|
+
EMPTY_LINE = "\n".freeze
|
43
|
+
|
21
44
|
# Instantiate a new inspector
|
22
45
|
#
|
23
46
|
# @return [Lotus::Routing::RoutesInspector] the new instance
|
@@ -50,8 +73,10 @@ module Lotus
|
|
50
73
|
# end
|
51
74
|
#
|
52
75
|
# puts router.inspector.to_s
|
53
|
-
# # =>
|
54
|
-
#
|
76
|
+
# # => Name Method Path Action
|
77
|
+
#
|
78
|
+
# GET, HEAD / Home::Index
|
79
|
+
# login GET, HEAD /login Sessions::New
|
55
80
|
# POST /login Sessions::Create
|
56
81
|
# logout GET, HEAD /logout Sessions::Destroy
|
57
82
|
#
|
@@ -68,7 +93,9 @@ module Lotus
|
|
68
93
|
# formatter = "| %{methods} | %{name} | %{path} | %{endpoint} |\n"
|
69
94
|
#
|
70
95
|
# puts router.inspector.to_s(formatter)
|
71
|
-
# # => |
|
96
|
+
# # => | Method | Name | Path | Action |
|
97
|
+
#
|
98
|
+
# | GET, HEAD | | / | Home::Index |
|
72
99
|
# | GET, HEAD | login | /login | Sessions::New |
|
73
100
|
# | POST | | /login | Sessions::Create |
|
74
101
|
# | GET, HEAD | logout | /logout | Sessions::Destroy |
|
@@ -100,10 +127,12 @@ module Lotus
|
|
100
127
|
# formatter = "| %{methods} | %{name} | %{path} | %{endpoint} |\n"
|
101
128
|
#
|
102
129
|
# puts router.inspector.to_s(formatter)
|
103
|
-
# # => |
|
104
|
-
#
|
105
|
-
# | GET, HEAD |
|
106
|
-
# | GET, HEAD |
|
130
|
+
# # => | Method | Name | Path | Action |
|
131
|
+
#
|
132
|
+
# | GET, HEAD | | /fakeroute | Fake::Index |
|
133
|
+
# | GET, HEAD | | /admin/home | Home::Index |
|
134
|
+
# | GET, HEAD | | /api/posts | Posts::Index |
|
135
|
+
# | GET, HEAD | | /api/second_mount/comments | Comments::Index |
|
107
136
|
def to_s(formatter = FORMATTER, base_path = nil)
|
108
137
|
base_path = Utils::PathPrefix.new(base_path)
|
109
138
|
result = ''
|
@@ -119,6 +148,10 @@ module Lotus
|
|
119
148
|
end
|
120
149
|
end
|
121
150
|
|
151
|
+
unless result.include?(INSPECTOR_HEADER_NAME)
|
152
|
+
result.insert(0, formatter % INSPECTOR_HEADER_HASH + EMPTY_LINE)
|
153
|
+
end
|
154
|
+
|
122
155
|
result
|
123
156
|
end
|
124
157
|
|
data/lotus-router.gemspec
CHANGED
@@ -20,9 +20,10 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.required_ruby_version = '>= 2.0.0'
|
21
21
|
|
22
22
|
spec.add_dependency 'http_router', '~> 0.11'
|
23
|
-
spec.add_dependency 'lotus-utils', '~> 0.
|
23
|
+
spec.add_dependency 'lotus-utils', '~> 0.6'
|
24
24
|
|
25
25
|
spec.add_development_dependency 'bundler', '~> 1.5'
|
26
26
|
spec.add_development_dependency 'minitest', '~> 5'
|
27
27
|
spec.add_development_dependency 'rake', '~> 10'
|
28
|
+
spec.add_development_dependency 'rack-test', '~> 0.6'
|
28
29
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lotus-router
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Luca Guidi
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2016-01-12 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: http_router
|
@@ -32,20 +32,14 @@ dependencies:
|
|
32
32
|
requirements:
|
33
33
|
- - "~>"
|
34
34
|
- !ruby/object:Gem::Version
|
35
|
-
version: '0.
|
36
|
-
- - ">="
|
37
|
-
- !ruby/object:Gem::Version
|
38
|
-
version: 0.5.1
|
35
|
+
version: '0.6'
|
39
36
|
type: :runtime
|
40
37
|
prerelease: false
|
41
38
|
version_requirements: !ruby/object:Gem::Requirement
|
42
39
|
requirements:
|
43
40
|
- - "~>"
|
44
41
|
- !ruby/object:Gem::Version
|
45
|
-
version: '0.
|
46
|
-
- - ">="
|
47
|
-
- !ruby/object:Gem::Version
|
48
|
-
version: 0.5.1
|
42
|
+
version: '0.6'
|
49
43
|
- !ruby/object:Gem::Dependency
|
50
44
|
name: bundler
|
51
45
|
requirement: !ruby/object:Gem::Requirement
|
@@ -88,6 +82,20 @@ dependencies:
|
|
88
82
|
- - "~>"
|
89
83
|
- !ruby/object:Gem::Version
|
90
84
|
version: '10'
|
85
|
+
- !ruby/object:Gem::Dependency
|
86
|
+
name: rack-test
|
87
|
+
requirement: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - "~>"
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0.6'
|
92
|
+
type: :development
|
93
|
+
prerelease: false
|
94
|
+
version_requirements: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - "~>"
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0.6'
|
91
99
|
description: Rack compatible HTTP router for Ruby
|
92
100
|
email:
|
93
101
|
- me@lucaguidi.com
|
@@ -105,12 +113,14 @@ files:
|
|
105
113
|
- lib/lotus/router/version.rb
|
106
114
|
- lib/lotus/routing/endpoint.rb
|
107
115
|
- lib/lotus/routing/endpoint_resolver.rb
|
116
|
+
- lib/lotus/routing/error.rb
|
108
117
|
- lib/lotus/routing/force_ssl.rb
|
109
118
|
- lib/lotus/routing/http_router.rb
|
110
119
|
- lib/lotus/routing/namespace.rb
|
111
120
|
- lib/lotus/routing/parsers.rb
|
112
121
|
- lib/lotus/routing/parsing/json_parser.rb
|
113
122
|
- lib/lotus/routing/parsing/parser.rb
|
123
|
+
- lib/lotus/routing/recognized_route.rb
|
114
124
|
- lib/lotus/routing/resource.rb
|
115
125
|
- lib/lotus/routing/resource/action.rb
|
116
126
|
- lib/lotus/routing/resource/nested.rb
|
@@ -140,7 +150,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
140
150
|
version: '0'
|
141
151
|
requirements: []
|
142
152
|
rubyforge_project:
|
143
|
-
rubygems_version: 2.
|
153
|
+
rubygems_version: 2.5.1
|
144
154
|
signing_key:
|
145
155
|
specification_version: 4
|
146
156
|
summary: Rack compatible HTTP router for Ruby and Lotus
|