respect-rails 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. data/FAQ.md +98 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +291 -0
  4. data/RELATED_WORK.md +47 -0
  5. data/RELEASE_NOTES.md +20 -0
  6. data/Rakefile +32 -0
  7. data/app/assets/javascripts/respect/rails/schemas.js +32 -0
  8. data/app/assets/stylesheets/respect/rails/schemas.css +160 -0
  9. data/app/controllers/respect/rails/schemas_controller.rb +36 -0
  10. data/app/helpers/respect/rails/schemas_helper.rb +78 -0
  11. data/app/views/layouts/respect/rails/schemas.html.erb +14 -0
  12. data/app/views/respect/rails/request_validation_exception.html.erb +38 -0
  13. data/app/views/respect/rails/schemas/doc.html.erb +158 -0
  14. data/app/views/respect/rails/schemas/index.html.erb +4 -0
  15. data/config/routes.rb +4 -0
  16. data/lib/respect/rails/action_def.rb +37 -0
  17. data/lib/respect/rails/action_schema.rb +41 -0
  18. data/lib/respect/rails/application_info.rb +26 -0
  19. data/lib/respect/rails/controller_helper.rb +107 -0
  20. data/lib/respect/rails/engine.rb +63 -0
  21. data/lib/respect/rails/engine_info.rb +27 -0
  22. data/lib/respect/rails/headers_helper.rb +11 -0
  23. data/lib/respect/rails/headers_simplifier.rb +31 -0
  24. data/lib/respect/rails/info.rb +72 -0
  25. data/lib/respect/rails/request_def.rb +38 -0
  26. data/lib/respect/rails/request_helper.rb +101 -0
  27. data/lib/respect/rails/request_schema.rb +102 -0
  28. data/lib/respect/rails/response_def.rb +43 -0
  29. data/lib/respect/rails/response_helper.rb +67 -0
  30. data/lib/respect/rails/response_schema.rb +84 -0
  31. data/lib/respect/rails/response_schema_set.rb +60 -0
  32. data/lib/respect/rails/route_info.rb +101 -0
  33. data/lib/respect/rails/version.rb +5 -0
  34. data/lib/respect/rails.rb +74 -0
  35. data/lib/tasks/respect_tasks.rake +4 -0
  36. data/test/dummy/README.rdoc +261 -0
  37. data/test/dummy/Rakefile +7 -0
  38. data/test/dummy/app/assets/javascripts/application.js +15 -0
  39. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  40. data/test/dummy/app/controllers/application_controller.rb +3 -0
  41. data/test/dummy/app/controllers/automatic_validation_controller.rb +300 -0
  42. data/test/dummy/app/controllers/caught_exception_controller.rb +58 -0
  43. data/test/dummy/app/controllers/disabled_controller.rb +37 -0
  44. data/test/dummy/app/controllers/manual_validation_controller.rb +63 -0
  45. data/test/dummy/app/controllers/no_schema_controller.rb +17 -0
  46. data/test/dummy/app/controllers/skipped_automatic_validation_controller.rb +35 -0
  47. data/test/dummy/app/helpers/application_helper.rb +2 -0
  48. data/test/dummy/app/helpers/respect/application_macros.rb +10 -0
  49. data/test/dummy/app/helpers/respect/circle_schema.rb +16 -0
  50. data/test/dummy/app/helpers/respect/point_schema.rb +19 -0
  51. data/test/dummy/app/helpers/respect/rgba_schema.rb +18 -0
  52. data/test/dummy/app/views/automatic_validation/request_format.html.erb +1 -0
  53. data/test/dummy/app/views/automatic_validation/request_format.pdf.erb +1 -0
  54. data/test/dummy/app/views/caught_exception/response_validator.html.erb +1 -0
  55. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  56. data/test/dummy/config/application.rb +58 -0
  57. data/test/dummy/config/boot.rb +10 -0
  58. data/test/dummy/config/database.yml +25 -0
  59. data/test/dummy/config/environment.rb +5 -0
  60. data/test/dummy/config/environments/development.rb +37 -0
  61. data/test/dummy/config/environments/production.rb +67 -0
  62. data/test/dummy/config/environments/test.rb +37 -0
  63. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  64. data/test/dummy/config/initializers/inflections.rb +15 -0
  65. data/test/dummy/config/initializers/mime_types.rb +5 -0
  66. data/test/dummy/config/initializers/respect.rb +9 -0
  67. data/test/dummy/config/initializers/secret_token.rb +7 -0
  68. data/test/dummy/config/initializers/session_store.rb +8 -0
  69. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  70. data/test/dummy/config/locales/en.yml +5 -0
  71. data/test/dummy/config/routes.rb +38 -0
  72. data/test/dummy/config.ru +4 -0
  73. data/test/dummy/db/development.sqlite3 +0 -0
  74. data/test/dummy/db/schema.rb +16 -0
  75. data/test/dummy/db/test.sqlite3 +0 -0
  76. data/test/dummy/lib/exts/Rgba.rb +11 -0
  77. data/test/dummy/lib/exts/circle.rb +11 -0
  78. data/test/dummy/lib/exts/point.rb +11 -0
  79. data/test/dummy/log/development.log +6 -0
  80. data/test/dummy/log/test.log +851 -0
  81. data/test/dummy/public/404.html +26 -0
  82. data/test/dummy/public/422.html +26 -0
  83. data/test/dummy/public/500.html +25 -0
  84. data/test/dummy/public/favicon.ico +0 -0
  85. data/test/dummy/script/rails +6 -0
  86. data/test/functional/automatic_validation_controller_test.rb +131 -0
  87. data/test/functional/caught_exception_controller_test.rb +50 -0
  88. data/test/functional/disabled_controller_test.rb +16 -0
  89. data/test/functional/manual_validation_controller_test.rb +24 -0
  90. data/test/functional/no_schema_controller_test.rb +12 -0
  91. data/test/functional/respect/rails/schemas_controller_test.rb +18 -0
  92. data/test/functional/skipped_automatic_validation_controller_test.rb +12 -0
  93. data/test/headers_can_dumped_in_json.sh +33 -0
  94. data/test/integration/navigation_test.rb +38 -0
  95. data/test/request_headers_validation_in_dev_mode.sh +33 -0
  96. data/test/test_helper.rb +17 -0
  97. data/test/unit/action_schema_test.rb +21 -0
  98. data/test/unit/application_info_test.rb +11 -0
  99. data/test/unit/controller_helper_test.rb +4 -0
  100. data/test/unit/engine_info_test.rb +11 -0
  101. data/test/unit/engine_test.rb +7 -0
  102. data/test/unit/info_test.rb +42 -0
  103. data/test/unit/request_def_test.rb +22 -0
  104. data/test/unit/request_helper_test.rb +67 -0
  105. data/test/unit/request_schema_test.rb +164 -0
  106. data/test/unit/response_def_test.rb +9 -0
  107. data/test/unit/response_helper_test.rb +73 -0
  108. data/test/unit/response_schema_set_test.rb +18 -0
  109. data/test/unit/response_schema_test.rb +147 -0
  110. metadata +334 -0
@@ -0,0 +1,72 @@
1
+ module Respect
2
+ module Rails
3
+ # The implementation is strongly inspired from
4
+ # ActionDispatch::Routing::RoutesInspector.
5
+ class Info
6
+
7
+ def initialize
8
+ @engines = {}
9
+ @app = ApplicationInfo.new
10
+ @app.routes = collect_routes(::Rails.application.routes.routes)
11
+ @engines[@app.name] = @app
12
+ @toc = build_toc(self.routes)
13
+ end
14
+
15
+ attr_reader :engines
16
+ attr_reader :app
17
+ attr_reader :toc
18
+
19
+ def routes
20
+ @app.routes
21
+ end
22
+
23
+ private
24
+
25
+ def collect_routes(routes, mounted_point = nil)
26
+ result = []
27
+ routes.each do |route|
28
+ route = RouteInfo.new(route, mounted_point)
29
+ next if route.internal?
30
+ if route.engine?
31
+ result += collect_engine_routes(route)
32
+ else
33
+ if route.has_schema?
34
+ result << route
35
+ end
36
+ end
37
+ end
38
+ result
39
+ end
40
+
41
+ def collect_engine_routes(route)
42
+ return unless route.engine?
43
+ engine_info = EngineInfo.new(route.endpoint)
44
+ return if @engines[engine_info.name]
45
+
46
+ routes = route.rack_app.routes
47
+ if routes.is_a?(ActionDispatch::Routing::RouteSet)
48
+ engine_info.routes = collect_routes(routes.routes, route)
49
+ @engines[engine_info.name] = engine_info
50
+ engine_info.routes
51
+ else
52
+ []
53
+ end
54
+ end
55
+
56
+ def build_toc(routes)
57
+ toc = {}
58
+ routes.each do |route|
59
+ next if route.engine?
60
+ if route.has_schema?
61
+ controller = route.controller_name
62
+ action = route.action_name
63
+ toc[controller] ||= {}
64
+ toc[controller][action] = route
65
+ end
66
+ end
67
+ toc
68
+ end
69
+
70
+ end # class Info
71
+ end # module Rails
72
+ end # module Respect
@@ -0,0 +1,38 @@
1
+ module Respect
2
+ module Rails
3
+ class RequestDef
4
+
5
+ class << self
6
+ def eval(*args, &block)
7
+ new(*args).eval(&block)
8
+ end
9
+ end
10
+
11
+ def initialize(*args)
12
+ @request_schema = RequestSchema.new(*args)
13
+ end
14
+
15
+ def eval(&block)
16
+ block.call(self)
17
+ @request_schema
18
+ end
19
+
20
+ def path_parameters(&block)
21
+ @request_schema.path_parameters = HashSchema.define(&block)
22
+ end
23
+
24
+ def body_parameters(&block)
25
+ @request_schema.body_parameters = HashSchema.define(&block)
26
+ end
27
+
28
+ def query_parameters(&block)
29
+ @request_schema.query_parameters = HashSchema.define(&block)
30
+ end
31
+
32
+ def headers(&block)
33
+ @request_schema.headers = HashSchema.define(&block)
34
+ end
35
+
36
+ end # class RequestDef
37
+ end
38
+ end # module Respect
@@ -0,0 +1,101 @@
1
+ module Respect
2
+ module Rails
3
+ module RequestHelper
4
+ extend ActiveSupport::Concern
5
+
6
+ # Return whether this request validates the schema.
7
+ # You can get the validation error via {#last_validation_error}.
8
+ def validate_schema?
9
+ begin
10
+ validate_schema
11
+ rescue Respect::Rails::RequestValidationError
12
+ false
13
+ end
14
+ end
15
+
16
+ # Raise a {Respect::Rails::RequestValidationError} exception if this
17
+ # request does not validate the schema.
18
+ def validate_schema
19
+ log_msg = " Request validation: "
20
+ @validated = nil
21
+ measure = Benchmark.realtime do
22
+ @validated = request_schema.validate?(self) unless request_schema.nil?
23
+ end
24
+ if @validated.nil?
25
+ log_msg += "none"
26
+ else
27
+ if @validated == true
28
+ log_msg += "success"
29
+ else
30
+ log_msg += "failure"
31
+ end
32
+ end
33
+ log_msg += " (%.1fms)" % [ measure * 1000 ]
34
+ ::Rails.logger.info log_msg
35
+ if @validated == false
36
+ last_validation_error.context.each do |msg|
37
+ ::Rails.logger.info " #{msg}"
38
+ end
39
+ raise last_validation_error
40
+ end
41
+ true
42
+ end
43
+
44
+ def response_schema(http_status)
45
+ unless action_schema.nil? || action_schema.responses.nil?
46
+ action_schema.responses[http_status]
47
+ end
48
+ end
49
+
50
+ def request_schema
51
+ action_schema.request if action_schema
52
+ end
53
+
54
+ attr_reader :action_schema
55
+ attr_writer :action_schema
56
+ private :action_schema=
57
+
58
+ def has_schema?
59
+ !!action_schema
60
+ end
61
+
62
+ def last_validation_error
63
+ request_schema.last_error
64
+ end
65
+
66
+ # Returns the sanitized parameters if the schema validation has succeed.
67
+ def sane_params
68
+ request_schema.sanitized_params if request_schema
69
+ end
70
+
71
+ # Returns +nil+ if never validated, +true+ if validated successfully and
72
+ # +false+ if validation failed.
73
+ def validated
74
+ @validated
75
+ end
76
+
77
+ # Returns +true+ if the validation has been done and succeed and +false+
78
+ # otherwise.
79
+ def validated?
80
+ !!validated
81
+ end
82
+
83
+ # Sanitize all the request's parameters (path, query and body) *in-place*.
84
+ # if the schema validation has succeed. Validate it first if it has not
85
+ # been yet.
86
+ def sanitize_params!
87
+ if request_schema
88
+ if validated.nil?
89
+ validate_schema
90
+ end
91
+ if validated?
92
+ request_schema.sanitize!(self)
93
+ end
94
+ end
95
+ end
96
+
97
+ end # module RequestHelper
98
+ end # module Rails
99
+ end # module Respect
100
+
101
+ ActionDispatch::Request.send :include, Respect::Rails::RequestHelper
@@ -0,0 +1,102 @@
1
+ module Respect
2
+ module Rails
3
+ class RequestSchema
4
+ include HeadersSimplifier
5
+
6
+ class << self
7
+ def define(*args, &block)
8
+ RequestDef.eval(*args, &block)
9
+ end
10
+ end
11
+
12
+ def initialize(controller, action)
13
+ @controller = controller
14
+ @action = action
15
+ @default_path_parameters = HashSchema.define do |s|
16
+ s.string "controller", equal_to: @controller.to_s, doc: false
17
+ s.string "action", equal_to: @action.to_s, doc: false
18
+ end
19
+ @path_parameters = @default_path_parameters.dup
20
+ @body_parameters = HashSchema.new
21
+ @query_parameters = HashSchema.new
22
+ @headers = HashSchema.new
23
+ end
24
+
25
+ attr_reader :controller, :action
26
+
27
+ attr_accessor :headers
28
+
29
+ attr_accessor :body_parameters, :query_parameters
30
+
31
+ attr_reader :path_parameters, :default_path_parameters
32
+
33
+ # Merge +path_parameters+ with {#default_path_parameters} and store it.
34
+ def path_parameters=(path_parameters)
35
+ @path_parameters = @default_path_parameters.merge(path_parameters)
36
+ end
37
+
38
+ attr_reader :sanitized_params
39
+
40
+ # Validate the given +request+.
41
+ # Raise a {RequestValidationError} if an error occur.
42
+ # Returns +true+ on success.
43
+ def validate(request)
44
+ # Validate requests.
45
+ unless Respect::Rails::Engine.disable_request_headers_validation
46
+ begin
47
+ headers.validate(request.headers)
48
+ rescue Respect::ValidationError => e
49
+ raise RequestValidationError.new(e, :headers, simplify_headers(request.headers))
50
+ end
51
+ end
52
+ [ :path, :query, :body ].each do |name|
53
+ begin
54
+ send("#{name}_parameters").validate(request.params)
55
+ rescue Respect::ValidationError => e
56
+ raise RequestValidationError.new(e, name, request.params)
57
+ end
58
+ end
59
+ # Build sane parameters.
60
+ @sanitized_params = {}
61
+ [ :path, :query, :body ].each do |name|
62
+ @sanitized_params.merge!(send("#{name}_parameters").sanitized_object)
63
+ end
64
+ true
65
+ end
66
+
67
+ def validate?(request)
68
+ begin
69
+ validate(request)
70
+ true
71
+ rescue RequestValidationError => e
72
+ @last_error = e
73
+ false
74
+ end
75
+ end
76
+
77
+ # Return the last validation error that happens during the
78
+ # validation process. (set by {#validate?})
79
+ # Reset each time {#validate?} is called.
80
+ attr_reader :last_error
81
+
82
+ # Validate the request and sanitize its parameters if the validation succeed.
83
+ # You can disable the sanitization with
84
+ # {Respect::Rails::Engine.sanitize_request_parameters}.
85
+ def validate!(request)
86
+ valid = validate?(request)
87
+ if valid
88
+ sanitize!(request)
89
+ end
90
+ valid
91
+ end
92
+
93
+ # Sanitize all the request's parameters (path, query and body) *in-place*.
94
+ def sanitize!(request)
95
+ [ :path, :query, :body ].each do |name|
96
+ send("#{name}_parameters").sanitize_object!(request.params)
97
+ end
98
+ end
99
+
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,43 @@
1
+ module Respect
2
+ module Rails
3
+ class ResponseDef
4
+
5
+ class << self
6
+ def eval(*args, &block)
7
+ new(*args).eval(&block)
8
+ end
9
+ end
10
+
11
+ def initialize(*args)
12
+ @response_schema = ResponseSchema.new(*args)
13
+ end
14
+
15
+ def eval(&block)
16
+ block.call(self)
17
+ @response_schema
18
+ end
19
+
20
+ # Define the schema of the response body.
21
+ # @option options [Boolean] hash (true) whether the body is hash schema.
22
+ def body(options = {}, &block)
23
+ @response_schema.body = (
24
+ if options.fetch(:hash, true)
25
+ HashSchema.define(&block)
26
+ else
27
+ Schema.define(&block)
28
+ end
29
+ )
30
+ end
31
+
32
+ def headers(options = {}, &block)
33
+ @response_schema.headers = HashSchema.define(options, &block)
34
+ end
35
+
36
+ # Set the documentation to +text+.
37
+ def documentation(text)
38
+ @response_schema.documentation = text
39
+ end
40
+
41
+ end # class ResponseDef
42
+ end # module Rails
43
+ end # module Respect
@@ -0,0 +1,67 @@
1
+ module Respect
2
+ module Rails
3
+ module ResponseHelper
4
+ extend ActiveSupport::Concern
5
+
6
+ attr_reader :schema
7
+ attr_writer :schema
8
+ private :schema=
9
+
10
+ def has_schema?
11
+ !!@schema
12
+ end
13
+
14
+ # Return whether this response validates the schema.
15
+ # You can get the validation error via {#last_validation_error}.
16
+ def validate_schema?
17
+ begin
18
+ validate_schema
19
+ rescue Respect::Rails::ResponseValidationError => e
20
+ false
21
+ end
22
+ end
23
+
24
+ # Raise a {Respect::Rails::ResponseValidationError} exception if this
25
+ # response does not validate the schema.
26
+ def validate_schema
27
+ log_msg = " Response validation: "
28
+ valid = nil
29
+ measure = Benchmark.realtime do
30
+ if schema && content_type == Mime::JSON
31
+ valid = schema.validate?(self)
32
+ end
33
+ end
34
+ if valid.nil?
35
+ log_msg += "none"
36
+ else
37
+ if valid
38
+ log_msg += "success"
39
+ else
40
+ log_msg += "failure"
41
+ end
42
+ end
43
+ log_msg += " (%.1fms)" % [ measure * 1000 ]
44
+ ::Rails.logger.info log_msg
45
+ if valid == false
46
+ last_validation_error.context.each do |msg|
47
+ ::Rails.logger.info " #{msg}"
48
+ end
49
+ if Respect::Rails::Engine.catch_response_validation_error
50
+ self.body = last_validation_error.to_json
51
+ self.status = :internal_server_error
52
+ else
53
+ raise last_validation_error
54
+ end
55
+ end
56
+ true
57
+ end
58
+
59
+ def last_validation_error
60
+ schema.last_error
61
+ end
62
+
63
+ end # module ResponseHelper
64
+ end # module Rails
65
+ end # module Respect
66
+
67
+ ActionDispatch::Response.send :include, Respect::Rails::ResponseHelper
@@ -0,0 +1,84 @@
1
+ module Respect
2
+ module Rails
3
+ class ResponseSchema
4
+ include HeadersSimplifier
5
+ include Respect::DocHelper
6
+
7
+ class << self
8
+
9
+ def http_status(status)
10
+ Rack::Utils.status_code(status)
11
+ end
12
+
13
+ # FIXME(Nicolas Despres): Move me to another module/class.
14
+ def symbolize_http_status(http_status)
15
+ h = Rack::Utils::HTTP_STATUS_CODES
16
+ (h[http_status] || h[500]).downcase.gsub(/\s|-/, '_').to_sym
17
+ end
18
+
19
+ def define(*args, &block)
20
+ ResponseDef.eval(*args, &block)
21
+ end
22
+
23
+ # Read the response schema definition from the given +filename+ and
24
+ # evaluate it as a block passed to {ResponseSchema.define}
25
+ def from_file(status, filename)
26
+ define(status) do |r|
27
+ r.instance_eval(File.read(filename), filename)
28
+ end
29
+ end
30
+ end
31
+
32
+ def initialize(status = :ok)
33
+ @status = status
34
+ @headers = HashSchema.new
35
+ end
36
+
37
+ attr_reader :status
38
+
39
+ def http_status
40
+ self.class.http_status(@status)
41
+ end
42
+
43
+ attr_accessor :body
44
+
45
+ def ==(other)
46
+ @status == other.status && @body == other.body
47
+ end
48
+
49
+ def validate(response)
50
+ if headers
51
+ begin
52
+ headers.validate(response.headers)
53
+ rescue Respect::ValidationError => e
54
+ raise Respect::Rails::ResponseValidationError.new(e, :headers, simplify_headers(response.headers))
55
+ end
56
+ end
57
+ if body
58
+ decoded_body = ActiveSupport::JSON.decode(response.body)
59
+ begin
60
+ body.validate(decoded_body)
61
+ rescue Respect::ValidationError => e
62
+ raise Respect::Rails::ResponseValidationError.new(e, :body, decoded_body)
63
+ end
64
+ end
65
+ true
66
+ end
67
+
68
+ def validate?(response)
69
+ begin
70
+ validate(response)
71
+ true
72
+ rescue Respect::Rails::ResponseValidationError => e
73
+ @last_error = e
74
+ false
75
+ end
76
+ end
77
+
78
+ attr_reader :last_error
79
+
80
+ attr_accessor :headers
81
+ attr_accessor :documentation
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,60 @@
1
+ module Respect
2
+ module Rails
3
+ class ResponseSchemaSet
4
+
5
+ class << self
6
+ # FIXME(Nicolas Despres): Move me to another module/class.
7
+ def symbolize_status(status)
8
+ case status
9
+ when Symbol
10
+ status
11
+ when String
12
+ if status =~ /^\d+$/
13
+ symbolize_status(status.to_i)
14
+ else
15
+ status.to_sym
16
+ end
17
+ when Numeric
18
+ ResponseSchema.symbolize_http_status(status.to_i)
19
+ else
20
+ raise ArgumentError, "cannot normalize status '#{status}:#{status.class}'"
21
+ end
22
+ end
23
+ end
24
+
25
+ # Initialize a new ResponseSchemaSet object for the given controller's
26
+ # action and collect response' schema from their respective file.
27
+ def initialize(controller_name, action_name)
28
+ @controller_name = controller_name
29
+ @action_name = action_name
30
+ @set = {}
31
+ end
32
+
33
+ attr_reader :controller_name, :action_name
34
+
35
+ def method_missing(method_name, *arguments, &block)
36
+ define_response(method_name, *arguments, &block)
37
+ end
38
+
39
+ def [](http_status)
40
+ @set[http_status]
41
+ end
42
+
43
+ delegate :each, :empty?, to: :@set
44
+
45
+ def <<(response_schema)
46
+ @set[response_schema.http_status] = response_schema
47
+ end
48
+
49
+ def is(status, *arguments, &block)
50
+ define_response(status, *arguments, &block)
51
+ end
52
+
53
+ def define_response(status, *arguments, &block)
54
+ status = self.class.symbolize_status(status)
55
+ self << ResponseSchema.define(status, *arguments, &block)
56
+ end
57
+
58
+ end
59
+ end # module Rails
60
+ end # module Respect
@@ -0,0 +1,101 @@
1
+ module Respect
2
+ module Rails
3
+ # A wrapper around ActionDispatch::Routing::Route. It provides only
4
+ # access to the information we need when generating doc.
5
+ # It is very strongly inspired from ActionDispatch::Routing::RouteWrapper class
6
+ # which is internal and designed to be used with
7
+ # ActionDispoatch::Routing::RouteInspector. These classes are not meant
8
+ # to be used by external user as far as I can read in rails code base.
9
+ # So, we made up our own so that we can easily adjust it to our need.
10
+ class RouteInfo
11
+ include Comparable
12
+
13
+ def initialize(route, mount_point)
14
+ @route = route
15
+ if !mount_point.is_a?(self.class) && !mount_point.nil? && !mount_point.engine?
16
+ raise "'#{mount_point.inspect}' must be a route to an engine if set"
17
+ end
18
+ @mount_point = mount_point
19
+ end
20
+
21
+ attr_reader :route, :mount_point
22
+
23
+ def path
24
+ path = @route.path.spec.to_s
25
+ if @mount_point
26
+ path = @mount_point.path + path
27
+ end
28
+ if path.length > 1 && path =~ %r{/$}
29
+ path.chop!
30
+ end
31
+ path
32
+ end
33
+
34
+ def internal?
35
+ path =~ %r{\A#{::Rails.application.config.assets.prefix}} \
36
+ || controller_name =~ %r{\Arails/(info|welcome)}
37
+ end
38
+
39
+ def <=>(other)
40
+ self.path <=> other.path
41
+ end
42
+
43
+ def verb
44
+ @route.verb.source.gsub(/[$^]/, '')
45
+ end
46
+
47
+ def controller_name
48
+ @route.requirements[:controller] || ':controller'
49
+ end
50
+
51
+ def action_name
52
+ @route.requirements[:action] || ':action'
53
+ end
54
+
55
+ def schema
56
+ @schema = ActionSchema.from_controller(controller_name, action_name)
57
+ end
58
+
59
+ def has_schema?
60
+ schema && schema.has_schema?
61
+ end
62
+
63
+ def spec
64
+ "#{verb} #{path}".strip
65
+ end
66
+
67
+ def url
68
+ if schema
69
+ options = schema_set.default_url_options
70
+ else
71
+ options = @route.defaults.merge({ format: 'json' })
72
+ end
73
+ ::Rails.application.routes.url_for(options)
74
+ end
75
+
76
+ def endpoint
77
+ rack_app ? rack_app : nil
78
+ end
79
+
80
+ def rack_app(app = @route.app)
81
+ @rack_app ||= begin
82
+ class_name = app.class.name.to_s
83
+ if class_name == "ActionDispatch::Routing::Mapper::Constraints"
84
+ rack_app(app.app)
85
+ elsif ActionDispatch::Routing::Redirect === app || class_name !~ /^ActionDispatch::Routing/
86
+ app
87
+ end
88
+ end
89
+ end
90
+
91
+ def engine?
92
+ rack_app && rack_app.respond_to?(:routes)
93
+ end
94
+
95
+ def anchor
96
+ "#{controller_name}_#{action_name}"
97
+ end
98
+
99
+ end # class RouteInfo
100
+ end # module Rails
101
+ end # module Respect
@@ -0,0 +1,5 @@
1
+ module Respect
2
+ module Rails
3
+ VERSION = "0.1.0"
4
+ end
5
+ end