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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 53b681f4148b22f7bbcd6521eeb8b241673d9253
4
- data.tar.gz: 94af70e6ee5f0d7a8e4ebf84c5ddc14a3bdba63d
3
+ metadata.gz: 385192d4a396ff6a0e91dabff73a8351c55848ed
4
+ data.tar.gz: efd06ad2632579b10f7cf6229aba7b3d760fbc08
5
5
  SHA512:
6
- metadata.gz: 98ae99df5474dbde90fc2c9fc8f27f09877cb1327a00a01ed7e3a897d37ce4501af4dce06a45bc78270e3a908fb6ac833d7784d24ea63be56aefa3c79b5a6599
7
- data.tar.gz: 6200a6ba3498a91a123161f8f23e0e79e92a2bc903647fe43ac644733564148de73571da35d49f04fada7a34ac278f7dd38ff3b833fdc136e2aebca5c9d66838
6
+ metadata.gz: a0c1078e04043023b44f1a72189e6890dd8e1ccfa53f966bb6624527f2444fa5825ede6944e17c0b171b2eecf393cb1f39573e65073a74b512813114c37a00e0
7
+ data.tar.gz: 662945b950952171f02220b318e808b87c44ad44b54034defff58772fd30f92d3ab9036737433888ae06c2b8be07ccbb52d6e4f1f8d739c2f33dffb5e1281d01
@@ -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
@@ -1,4 +1,4 @@
1
- Copyright © 2014-2015 Luca Guidi
1
+ Copyright © 2014-2016 Luca Guidi
2
2
 
3
3
  MIT License
4
4
 
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: ->(env) { [200, {}, ['Hi!']] }
653
+ get '/books/:id', to: 'books#show', as: :book
579
654
  end
580
655
 
581
- app = Rack::MockRequest.new(router)
582
- app.get('/') # => #<Rack::MockResponse:0x007fc4540dc238 ...>
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-2015 Luca Guidi – Released under MIT License
692
+ Copyright © 2014-2016 Luca Guidi – Released under MIT License
@@ -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
@@ -1,6 +1,6 @@
1
1
  module Lotus
2
2
  class Router
3
3
  # @since 0.1.0
4
- VERSION = '0.4.3'.freeze
4
+ VERSION = '0.5.0'.freeze
5
5
  end
6
6
  end
@@ -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 < ::StandardError
11
+ class EndpointNotFound < Lotus::Routing::Error
11
12
  end
12
13
 
13
14
  # Routing endpoint
@@ -0,0 +1,7 @@
1
+ module Lotus
2
+ module Routing
3
+ # @since 0.5.0
4
+ class Error < ::StandardError
5
+ end
6
+ end
7
+ end
@@ -81,7 +81,7 @@ module Lotus
81
81
  # @api private
82
82
  IDEMPOTENT_METHODS = ['GET', 'HEAD'].freeze
83
83
 
84
- EMPTY_BODY = ''.freeze
84
+ EMPTY_BODY = [].freeze
85
85
 
86
86
  # Initialize ForceSsl.
87
87
  #
@@ -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 < ::StandardError
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
- @default_scheme = options[:scheme] if options[:scheme]
43
- @default_host = options[:host] if options[:host]
44
- @default_port = options[:port] if options[:port]
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
- def add_with_request_method(path, method, opts = {}, &app)
150
- super.generate(@resolver, opts, &app)
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
- class UnknownParserError < ::StandardError
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
- # # => GET, HEAD / Home::Index
54
- # login GET, HEAD /login Sessions::New
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
- # # => | GET, HEAD | | / | Home::Index |
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
- # # => | GET, HEAD | | /fakeroute | Fake::Index |
104
- # | GET, HEAD | | /admin/home | Home::Index |
105
- # | GET, HEAD | | /api/posts | Posts::Index |
106
- # | GET, HEAD | | /api/second_mount/comments | Comments::Index |
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
 
@@ -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.5', '>= 0.5.1'
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.3
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: 2015-09-30 00:00:00.000000000 Z
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.5'
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.5'
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.4.5.1
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