respect-rails 0.1.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.
- data/FAQ.md +98 -0
- data/MIT-LICENSE +20 -0
- data/README.md +291 -0
- data/RELATED_WORK.md +47 -0
- data/RELEASE_NOTES.md +20 -0
- data/Rakefile +32 -0
- data/app/assets/javascripts/respect/rails/schemas.js +32 -0
- data/app/assets/stylesheets/respect/rails/schemas.css +160 -0
- data/app/controllers/respect/rails/schemas_controller.rb +36 -0
- data/app/helpers/respect/rails/schemas_helper.rb +78 -0
- data/app/views/layouts/respect/rails/schemas.html.erb +14 -0
- data/app/views/respect/rails/request_validation_exception.html.erb +38 -0
- data/app/views/respect/rails/schemas/doc.html.erb +158 -0
- data/app/views/respect/rails/schemas/index.html.erb +4 -0
- data/config/routes.rb +4 -0
- data/lib/respect/rails/action_def.rb +37 -0
- data/lib/respect/rails/action_schema.rb +41 -0
- data/lib/respect/rails/application_info.rb +26 -0
- data/lib/respect/rails/controller_helper.rb +107 -0
- data/lib/respect/rails/engine.rb +63 -0
- data/lib/respect/rails/engine_info.rb +27 -0
- data/lib/respect/rails/headers_helper.rb +11 -0
- data/lib/respect/rails/headers_simplifier.rb +31 -0
- data/lib/respect/rails/info.rb +72 -0
- data/lib/respect/rails/request_def.rb +38 -0
- data/lib/respect/rails/request_helper.rb +101 -0
- data/lib/respect/rails/request_schema.rb +102 -0
- data/lib/respect/rails/response_def.rb +43 -0
- data/lib/respect/rails/response_helper.rb +67 -0
- data/lib/respect/rails/response_schema.rb +84 -0
- data/lib/respect/rails/response_schema_set.rb +60 -0
- data/lib/respect/rails/route_info.rb +101 -0
- data/lib/respect/rails/version.rb +5 -0
- data/lib/respect/rails.rb +74 -0
- data/lib/tasks/respect_tasks.rake +4 -0
- data/test/dummy/README.rdoc +261 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/assets/javascripts/application.js +15 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/controllers/automatic_validation_controller.rb +300 -0
- data/test/dummy/app/controllers/caught_exception_controller.rb +58 -0
- data/test/dummy/app/controllers/disabled_controller.rb +37 -0
- data/test/dummy/app/controllers/manual_validation_controller.rb +63 -0
- data/test/dummy/app/controllers/no_schema_controller.rb +17 -0
- data/test/dummy/app/controllers/skipped_automatic_validation_controller.rb +35 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/helpers/respect/application_macros.rb +10 -0
- data/test/dummy/app/helpers/respect/circle_schema.rb +16 -0
- data/test/dummy/app/helpers/respect/point_schema.rb +19 -0
- data/test/dummy/app/helpers/respect/rgba_schema.rb +18 -0
- data/test/dummy/app/views/automatic_validation/request_format.html.erb +1 -0
- data/test/dummy/app/views/automatic_validation/request_format.pdf.erb +1 -0
- data/test/dummy/app/views/caught_exception/response_validator.html.erb +1 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config/application.rb +58 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +37 -0
- data/test/dummy/config/environments/production.rb +67 -0
- data/test/dummy/config/environments/test.rb +37 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +15 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/respect.rb +9 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +38 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/db/development.sqlite3 +0 -0
- data/test/dummy/db/schema.rb +16 -0
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/lib/exts/Rgba.rb +11 -0
- data/test/dummy/lib/exts/circle.rb +11 -0
- data/test/dummy/lib/exts/point.rb +11 -0
- data/test/dummy/log/development.log +6 -0
- data/test/dummy/log/test.log +851 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +25 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/functional/automatic_validation_controller_test.rb +131 -0
- data/test/functional/caught_exception_controller_test.rb +50 -0
- data/test/functional/disabled_controller_test.rb +16 -0
- data/test/functional/manual_validation_controller_test.rb +24 -0
- data/test/functional/no_schema_controller_test.rb +12 -0
- data/test/functional/respect/rails/schemas_controller_test.rb +18 -0
- data/test/functional/skipped_automatic_validation_controller_test.rb +12 -0
- data/test/headers_can_dumped_in_json.sh +33 -0
- data/test/integration/navigation_test.rb +38 -0
- data/test/request_headers_validation_in_dev_mode.sh +33 -0
- data/test/test_helper.rb +17 -0
- data/test/unit/action_schema_test.rb +21 -0
- data/test/unit/application_info_test.rb +11 -0
- data/test/unit/controller_helper_test.rb +4 -0
- data/test/unit/engine_info_test.rb +11 -0
- data/test/unit/engine_test.rb +7 -0
- data/test/unit/info_test.rb +42 -0
- data/test/unit/request_def_test.rb +22 -0
- data/test/unit/request_helper_test.rb +67 -0
- data/test/unit/request_schema_test.rb +164 -0
- data/test/unit/response_def_test.rb +9 -0
- data/test/unit/response_helper_test.rb +73 -0
- data/test/unit/response_schema_set_test.rb +18 -0
- data/test/unit/response_schema_test.rb +147 -0
- 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
|