respect-rails 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,36 @@
|
|
1
|
+
module Respect
|
2
|
+
module Rails
|
3
|
+
# Controller to get all the information relative to the
|
4
|
+
# REST API of this application.
|
5
|
+
class SchemasController < ActionController::Base
|
6
|
+
def_action_schema :index do |s|
|
7
|
+
s.request do |r|
|
8
|
+
r.path_parameters do |s|
|
9
|
+
s.string "format", in: %w{html json}
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def index
|
15
|
+
respond_to do |format|
|
16
|
+
format.html # index.html.erb
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def_action_schema :doc do |s|
|
21
|
+
s.request do |r|
|
22
|
+
r.path_parameters do |s|
|
23
|
+
s.string "format", in: %w{html json}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def doc
|
29
|
+
@info = Respect::Rails::Info.new
|
30
|
+
respond_to do |format|
|
31
|
+
format.html # doc.html.erb
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end # module Rails
|
36
|
+
end # module Respect
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Respect
|
2
|
+
module Rails
|
3
|
+
module SchemasHelper
|
4
|
+
|
5
|
+
def highlight_json_schema(json_schema)
|
6
|
+
Respect::JSONSchemaHTMLFormatter.new(json_schema).dump.html_safe
|
7
|
+
end
|
8
|
+
|
9
|
+
# FIXME(Nicolas Despres): Test me.
|
10
|
+
def describe_option(name, value)
|
11
|
+
result = describe_option_internal(name, value)
|
12
|
+
result.html_safe if result
|
13
|
+
end
|
14
|
+
|
15
|
+
def toggler(text, &block)
|
16
|
+
@toggler_id ||= -1
|
17
|
+
@toggler_id += 1
|
18
|
+
toggler_key = "toggle_#@toggler_id"
|
19
|
+
content_tag :div do
|
20
|
+
result = content_tag(:div, class: "summary") do
|
21
|
+
content_tag :a, text, href: "#", onclick: "return toggle(#@toggler_id)", id: "#{toggler_key}_toggle"
|
22
|
+
end
|
23
|
+
result << content_tag(:div, capture(&block), id: toggler_key, style: "display:none")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# FIXME(Nicolas Despres): Make me more generic to all enumerable and test me.
|
28
|
+
def flat_each(hash, &block)
|
29
|
+
flat_each_rec([], hash, &block)
|
30
|
+
end
|
31
|
+
|
32
|
+
# FIXME(Nicolas Despres): Test me
|
33
|
+
def build_parameter_name(path)
|
34
|
+
path.first.to_s + path[1..-1].reduce(""){|r, x| r + "[#{x}]" }
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def describe_option_internal(name, value)
|
40
|
+
case name
|
41
|
+
when :doc, :required
|
42
|
+
nil
|
43
|
+
when :default
|
44
|
+
if value.nil?
|
45
|
+
nil
|
46
|
+
else
|
47
|
+
"default to #{value.inspect}"
|
48
|
+
end
|
49
|
+
when :equal_to
|
50
|
+
"Must be equal to #{value.inspect}"
|
51
|
+
when :allow_nil
|
52
|
+
if value
|
53
|
+
"May be null"
|
54
|
+
else
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
when :strict
|
58
|
+
if value
|
59
|
+
"Must contains exactly these parameters"
|
60
|
+
else
|
61
|
+
nil
|
62
|
+
end
|
63
|
+
else
|
64
|
+
"#{name}: #{value.inspect}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def flat_each_rec(path, hash, &block)
|
69
|
+
hash.each do |k, v|
|
70
|
+
block.call(path + [k], v)
|
71
|
+
if v.is_a?(Respect::HashSchema)
|
72
|
+
flat_each_rec(path + [k], v, &block)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<%= content_tag :title, "#{Respect::Rails::Engine.doc_app_name} API" %>
|
5
|
+
<%= stylesheet_link_tag "respect/rails/schemas", :media => "all" %>
|
6
|
+
<%= javascript_include_tag "respect/rails/schemas" %>
|
7
|
+
<%= csrf_meta_tags %>
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
|
11
|
+
<%= yield %>
|
12
|
+
|
13
|
+
</body>
|
14
|
+
</html>
|
@@ -0,0 +1,38 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8" />
|
5
|
+
<title>Request validation error caught</title>
|
6
|
+
<style>
|
7
|
+
body { background-color: #fff; color: #333; }
|
8
|
+
|
9
|
+
body, p, ol, ul, td {
|
10
|
+
font-family: helvetica, verdana, arial, sans-serif;
|
11
|
+
font-size: 13px;
|
12
|
+
line-height: 18px;
|
13
|
+
}
|
14
|
+
|
15
|
+
pre {
|
16
|
+
background-color: #eee;
|
17
|
+
padding: 10px;
|
18
|
+
font-size: 11px;
|
19
|
+
white-space: pre-wrap;
|
20
|
+
}
|
21
|
+
|
22
|
+
a { color: #000; }
|
23
|
+
a:visited { color: #666; }
|
24
|
+
a:hover { color: #fff; background-color:#000; }
|
25
|
+
</style>
|
26
|
+
</head>
|
27
|
+
<body>
|
28
|
+
<h1>Request validation error in <%= "#{controller_name.camelize}Controller##{action_name}" %></h1>
|
29
|
+
<p>
|
30
|
+
Invalid <%= @error.part %> parameters:
|
31
|
+
<pre><%= @error.context.join("\n") %></pre>
|
32
|
+
</p>
|
33
|
+
<p>
|
34
|
+
Processed object:
|
35
|
+
<%= Respect::JSONSchemaHTMLFormatter.new(@error.object).dump.html_safe %>
|
36
|
+
</p>
|
37
|
+
</body>
|
38
|
+
</html>
|
@@ -0,0 +1,158 @@
|
|
1
|
+
<h1><%= @info.app.name %> API Documentation</h1>
|
2
|
+
|
3
|
+
<%= content_tag :p, @info.app.title %>
|
4
|
+
<%= content_tag :p, @info.app.description %>
|
5
|
+
<p>
|
6
|
+
This document describes the <%= @info.app.name %> API interface. It is designed for developers
|
7
|
+
who wants to interoperate with <%= @info.app.name %> in their own application.
|
8
|
+
</p>
|
9
|
+
|
10
|
+
<div class="toc">
|
11
|
+
<h2 class="title">Table of content</h2>
|
12
|
+
|
13
|
+
<% @info.toc.keys.sort.each do |controller_name| %>
|
14
|
+
<%= content_tag :h3, controller_name.gsub(%r{/}, ' ').titleize %>
|
15
|
+
<table>
|
16
|
+
<% @info.toc[controller_name].keys.sort.each do |action_name| %>
|
17
|
+
<% route = @info.toc[controller_name][action_name] %>
|
18
|
+
<tr>
|
19
|
+
<%= content_tag :td do %>
|
20
|
+
<%= link_to route.spec, doc_path(anchor: route.anchor) %>
|
21
|
+
<% end %>
|
22
|
+
<%= content_tag :td, route.schema.title, width: "60%" %>
|
23
|
+
</tr>
|
24
|
+
<% end %>
|
25
|
+
</table>
|
26
|
+
<% end %>
|
27
|
+
</div>
|
28
|
+
|
29
|
+
<hr>
|
30
|
+
|
31
|
+
<div class="route">
|
32
|
+
<%
|
33
|
+
@info.toc.keys.sort.each do |controller_name|
|
34
|
+
@info.toc[controller_name].keys.sort.each do |action_name|
|
35
|
+
|
36
|
+
route = @info.toc[controller_name][action_name]
|
37
|
+
action_schema = route.schema
|
38
|
+
%>
|
39
|
+
<h2 class="title">
|
40
|
+
<%= content_tag :a, route.spec, id: route.anchor %>
|
41
|
+
</h2>
|
42
|
+
|
43
|
+
<%= content_tag :p, action_schema.title %>
|
44
|
+
<%= content_tag :p, action_schema.description %>
|
45
|
+
|
46
|
+
<%
|
47
|
+
if action_schema.request
|
48
|
+
request_schema = action_schema.request
|
49
|
+
%>
|
50
|
+
<h3>Request</h3>
|
51
|
+
<% unless request_schema.headers.documented_properties.empty? %>
|
52
|
+
<h4>Headers</h4>
|
53
|
+
|
54
|
+
<table>
|
55
|
+
<% request_schema.headers.each do |name, schema| %>
|
56
|
+
<tr>
|
57
|
+
<td width="30%">
|
58
|
+
<strong><%= name %></strong>
|
59
|
+
<br>
|
60
|
+
<span class="constraint">
|
61
|
+
<%= schema.optional? ? "optional" : "required" %>
|
62
|
+
<%= schema.class.statement_name %>
|
63
|
+
</span>
|
64
|
+
</td>
|
65
|
+
<td>
|
66
|
+
<% if schema.title %>
|
67
|
+
<%= schema.title %>
|
68
|
+
<br>
|
69
|
+
<% end %>
|
70
|
+
<% if schema.description %>
|
71
|
+
<%= schema.description %>
|
72
|
+
<br>
|
73
|
+
<% end %>
|
74
|
+
<ul>
|
75
|
+
<%
|
76
|
+
schema.options.each do |name, value|
|
77
|
+
desc = describe_option(name, value)
|
78
|
+
if desc %>
|
79
|
+
<li><span class="constraint"><%= desc %></span></li>
|
80
|
+
<% end %>
|
81
|
+
<% end %>
|
82
|
+
</ul>
|
83
|
+
</td>
|
84
|
+
</tr>
|
85
|
+
<% end %>
|
86
|
+
</table>
|
87
|
+
|
88
|
+
<%= toggler "Show headers schema" do %>
|
89
|
+
<%= highlight_json_schema request_schema.headers.to_h %>
|
90
|
+
<% end %>
|
91
|
+
<% end %>
|
92
|
+
|
93
|
+
<%
|
94
|
+
[ :path, :query, :body ].each do |name|
|
95
|
+
params_schema = request_schema.send("#{name}_parameters")
|
96
|
+
unless params_schema.documented_properties.empty? %>
|
97
|
+
<h4><%= name.capitalize %> parameters</h4>
|
98
|
+
|
99
|
+
<table>
|
100
|
+
<% flat_each(params_schema) do |path, schema| %>
|
101
|
+
<tr>
|
102
|
+
<td width="30%">
|
103
|
+
<strong><%= build_parameter_name path %></strong>
|
104
|
+
<br>
|
105
|
+
<span class="constraint">
|
106
|
+
<%= schema.optional? ? "optional" : "required" %>
|
107
|
+
<%= schema.class.statement_name %>
|
108
|
+
</span>
|
109
|
+
</td>
|
110
|
+
<td>
|
111
|
+
<% if schema.title %>
|
112
|
+
<%= schema.title %>
|
113
|
+
<br>
|
114
|
+
<% end %>
|
115
|
+
<% if schema.description %>
|
116
|
+
<%= schema.description %>
|
117
|
+
<br>
|
118
|
+
<% end %>
|
119
|
+
<ul>
|
120
|
+
<%
|
121
|
+
schema.options.each do |name, value|
|
122
|
+
desc = describe_option(name, value)
|
123
|
+
if desc %>
|
124
|
+
<li><span class="constraint"><%= desc %></span></li>
|
125
|
+
<% end %>
|
126
|
+
<% end %>
|
127
|
+
</ul>
|
128
|
+
</td>
|
129
|
+
</tr>
|
130
|
+
<% end %>
|
131
|
+
</table>
|
132
|
+
|
133
|
+
<%= toggler "Show #{name} parameters schema" do %>
|
134
|
+
<%= highlight_json_schema params_schema.to_h %>
|
135
|
+
<% end %>
|
136
|
+
<% end %>
|
137
|
+
<% end %>
|
138
|
+
<% end %>
|
139
|
+
|
140
|
+
<% unless action_schema.responses.empty? %>
|
141
|
+
<% action_schema.responses.each do |status, response_schema| %>
|
142
|
+
<h3>Response <%= response_schema.http_status %> [ <%= response_schema.status %> ]</h3>
|
143
|
+
<%= content_tag :p, response_schema.title %>
|
144
|
+
<%= content_tag :p, response_schema.description %>
|
145
|
+
<% unless response_schema.headers.documented_properties.empty? %>
|
146
|
+
<h4>Headers</h4>
|
147
|
+
<%= highlight_json_schema response_schema.headers.to_h %>
|
148
|
+
<% end %>
|
149
|
+
<% if response_schema.body %>
|
150
|
+
<h4>Body</h4>
|
151
|
+
<%= highlight_json_schema response_schema.body.to_h %>
|
152
|
+
<% end %>
|
153
|
+
<% end %>
|
154
|
+
<% end %>
|
155
|
+
|
156
|
+
<% end %>
|
157
|
+
<% end %>
|
158
|
+
</div>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module Respect
|
2
|
+
module Rails
|
3
|
+
class ActionDef
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def eval(*args, &block)
|
7
|
+
new(*args).eval(&block)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(*args)
|
12
|
+
@action_schema = ActionSchema.new(*args)
|
13
|
+
end
|
14
|
+
|
15
|
+
def eval(&block)
|
16
|
+
block.call(self)
|
17
|
+
@action_schema
|
18
|
+
end
|
19
|
+
|
20
|
+
def request(&block)
|
21
|
+
@action_schema.request = RequestSchema.define(@action_schema.controller_name,
|
22
|
+
@action_schema.action_name, &block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def response_for(&block)
|
26
|
+
block.call(@action_schema.responses)
|
27
|
+
@action_schema.responses
|
28
|
+
end
|
29
|
+
|
30
|
+
# Set the documentation to +text+.
|
31
|
+
def documentation(text)
|
32
|
+
@action_schema.documentation = text
|
33
|
+
end
|
34
|
+
|
35
|
+
end # class ActionDef
|
36
|
+
end # module Rails
|
37
|
+
end # module Respect
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Respect
|
2
|
+
module Rails
|
3
|
+
class ActionSchema
|
4
|
+
include Respect::DocHelper
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def from_controller(controller_name, action_name)
|
8
|
+
klass = "#{controller_name}_controller".classify.safe_constantize
|
9
|
+
@schema = klass.new.action_schema(action_name) if klass
|
10
|
+
end
|
11
|
+
|
12
|
+
def define(*args, &block)
|
13
|
+
ActionDef.eval(*args, &block)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(controller, action_name)
|
18
|
+
@controller = controller
|
19
|
+
@action_name = action_name
|
20
|
+
@responses = ResponseSchemaSet.new(controller_name, @action_name)
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :controller, :action_name
|
24
|
+
|
25
|
+
def controller_name
|
26
|
+
@controller.controller_name
|
27
|
+
end
|
28
|
+
|
29
|
+
attr_accessor :request
|
30
|
+
|
31
|
+
attr_reader :responses
|
32
|
+
|
33
|
+
# Whether there is at least a request schema or one response schema for this action.
|
34
|
+
def has_schema?
|
35
|
+
request || !responses.empty? || documentation
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_accessor :documentation
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Respect
|
2
|
+
module Rails
|
3
|
+
class ApplicationInfo < EngineInfo
|
4
|
+
include Respect::DocHelper
|
5
|
+
|
6
|
+
def initialize(app_class = ::Rails.application.class)
|
7
|
+
super(app_class)
|
8
|
+
unless app_class < ::Rails::Application
|
9
|
+
raise "'#{app_class}' must be an ancestor of ::Rails::Application."
|
10
|
+
end
|
11
|
+
@app_class = app_class
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :app_class
|
15
|
+
|
16
|
+
def name
|
17
|
+
Engine.doc_app_name
|
18
|
+
end
|
19
|
+
|
20
|
+
def documentation
|
21
|
+
Respect::Rails::Engine.app_documentation
|
22
|
+
end
|
23
|
+
|
24
|
+
end # class ApplicationInfo
|
25
|
+
end # module Rails
|
26
|
+
end # module Respect
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module Respect
|
2
|
+
module Rails
|
3
|
+
module ControllerHelper
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do |base|
|
7
|
+
around_filter :load_schemas!
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
# Install a handler for {Respect::Rails::RequestValidationError}. It provides a more specific
|
12
|
+
# error report than the default exception handler in development. For instance you get the
|
13
|
+
# context of where the error happened in the JSON document.
|
14
|
+
#
|
15
|
+
# Example:
|
16
|
+
# # In your ApplicationController class.
|
17
|
+
# rescue_from_request_valiation_error if Rails.env.development?
|
18
|
+
def rescue_from_request_validation_error
|
19
|
+
rescue_from Respect::Rails::RequestValidationError do |exception|
|
20
|
+
respond_to do |format|
|
21
|
+
format.html do
|
22
|
+
@error = exception
|
23
|
+
render template: "respect/rails/request_validation_exception", layout: false, status: :internal_server_error
|
24
|
+
end
|
25
|
+
format.json do
|
26
|
+
render json: exception.to_json, status: :internal_server_error
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# FIXME(Nicolas Despres): Document me.
|
33
|
+
def def_action_schema(action_name, &block)
|
34
|
+
define_method("#{action_name}_schema") do
|
35
|
+
Respect::Rails::ActionSchema.define(self, action_name, &block)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# FIXME(Nicolas Despres): Test me!!!
|
41
|
+
def action_schema(action = nil)
|
42
|
+
action ||= self.action_name
|
43
|
+
method_name = "#{action}_schema"
|
44
|
+
if self.respond_to?(method_name)
|
45
|
+
send(method_name)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
# This "around" filter calls +validation_request_schema+ and +validation_response_schema+
|
52
|
+
# respectively before and after the controller's action.
|
53
|
+
def validate_schemas!
|
54
|
+
validate_request_schema!
|
55
|
+
yield
|
56
|
+
validate_response_schema! if Respect::Rails::Engine.validate_response
|
57
|
+
end
|
58
|
+
|
59
|
+
# This "before" filter validates the request.
|
60
|
+
def validate_request_schema!
|
61
|
+
request.validate_schema
|
62
|
+
end
|
63
|
+
|
64
|
+
# This "after" filter validates the response with the schema associated to the
|
65
|
+
# response status if one is found.
|
66
|
+
def validate_response_schema!
|
67
|
+
load_response_schema!
|
68
|
+
response.validate_schema
|
69
|
+
end
|
70
|
+
|
71
|
+
# This "around" filter calls +load_request_schema+ and +load_response_schema+
|
72
|
+
# respectively before and after the controller's action. It is useful
|
73
|
+
# if you want to do the validation yourself. It only load the action schema
|
74
|
+
# and attach the request schema to the request object and the response schema to
|
75
|
+
# the response object.
|
76
|
+
def load_schemas!
|
77
|
+
load_request_schema!
|
78
|
+
yield
|
79
|
+
if Respect::Rails::Engine.validate_response
|
80
|
+
load_response_schema!
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# This "before" filter load and attach the action schema to the request object.
|
85
|
+
# It is safe to call this method several times.
|
86
|
+
def load_request_schema!
|
87
|
+
request.send(:action_schema=, action_schema) unless request.has_schema?
|
88
|
+
end
|
89
|
+
|
90
|
+
# This "after" filter attach the response schema to the response object.
|
91
|
+
# You can safely call this filter multiple times (i.e. from other after
|
92
|
+
# filters callbacks).
|
93
|
+
def load_response_schema!
|
94
|
+
response.send(:schema=, request.response_schema(response.status)) unless response.has_schema?
|
95
|
+
end
|
96
|
+
|
97
|
+
# Before filter which sanitize all request parameters: +params+,
|
98
|
+
# +query_parameters+, +path_parameters+ and +request_parameters+.
|
99
|
+
# The request is validated first if it has not been yet.
|
100
|
+
def sanitize_params!
|
101
|
+
request.sanitize_params!
|
102
|
+
end
|
103
|
+
end # module ControllerHelper
|
104
|
+
end # module Rails
|
105
|
+
end # module Respect
|
106
|
+
|
107
|
+
ActionController::Base.send :include, Respect::Rails::ControllerHelper
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'respect'
|
2
|
+
|
3
|
+
require 'respect/rails/headers_helper'
|
4
|
+
require 'respect/rails/request_helper'
|
5
|
+
require 'respect/rails/response_helper'
|
6
|
+
require 'respect/rails/controller_helper'
|
7
|
+
|
8
|
+
module Respect
|
9
|
+
module Rails
|
10
|
+
class Engine < ::Rails::Engine
|
11
|
+
isolate_namespace Respect::Rails
|
12
|
+
engine_name 'respect'
|
13
|
+
|
14
|
+
# Whether responses are validated by the +validate_schemas+ around filter.
|
15
|
+
# By default this is +true+ in development and test environment and +false+ otherwise.
|
16
|
+
mattr_accessor :validate_response
|
17
|
+
self.validate_response = (::Rails.env.development? || ::Rails.env.test?)
|
18
|
+
|
19
|
+
# Whether response validation error are caught. The exception will be rendered in JSON
|
20
|
+
# and the HTTP status will be set to 500.
|
21
|
+
# By default this is +true+ in development mode only.
|
22
|
+
mattr_accessor :catch_response_validation_error
|
23
|
+
self.catch_response_validation_error = ::Rails.env.development?
|
24
|
+
|
25
|
+
# Whether to validate request headers.
|
26
|
+
# This option is available because Rails 3 does not support headers well in test mode.
|
27
|
+
# It is +false+ by default in test mode to let you do non-headers related functional
|
28
|
+
# test anyway.
|
29
|
+
# See: https://github.com/rails/rails/issues/6513
|
30
|
+
mattr_accessor :disable_request_headers_validation
|
31
|
+
self.disable_request_headers_validation = ::Rails.env.test?
|
32
|
+
|
33
|
+
# Set the application name use in the documentation. By default it is the same
|
34
|
+
# as your application class.
|
35
|
+
mattr_writer :doc_app_name
|
36
|
+
def self.doc_app_name
|
37
|
+
@@doc_app_name ||= ::Rails.application.class.parent_name
|
38
|
+
end
|
39
|
+
|
40
|
+
# Default way to setup Respect for Rails.
|
41
|
+
def self.setup(&block)
|
42
|
+
block.call(self)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Set the documentation of your application. The title would be the first line
|
46
|
+
# if followed by an empty line and the description would be the rest.
|
47
|
+
# If +text+ is +nil+ the current documentation is returned.
|
48
|
+
def self.app_documentation(text = nil)
|
49
|
+
if text
|
50
|
+
@@app_documentation = text
|
51
|
+
else
|
52
|
+
@@app_documentation
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Include all +helper_modules+ in the DSL definition classes, so that you can use its
|
57
|
+
# methods for defining schema.
|
58
|
+
def self.helpers(*helper_modules)
|
59
|
+
helper_modules.each{|m| Respect.extend_dsl_with(m) }
|
60
|
+
end
|
61
|
+
end # class Engine
|
62
|
+
end # module Rails
|
63
|
+
end # module Respect
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Respect
|
2
|
+
module Rails
|
3
|
+
class EngineInfo
|
4
|
+
include Comparable
|
5
|
+
|
6
|
+
def initialize(engine_class)
|
7
|
+
unless engine_class < ::Rails::Engine
|
8
|
+
raise "'#{engine_class}' must be an ancestor of ::Rails::Engine."
|
9
|
+
end
|
10
|
+
@engine_class = engine_class
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :engine_class
|
14
|
+
|
15
|
+
def name
|
16
|
+
@engine_class.engine_name.underscore
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_accessor :routes
|
20
|
+
|
21
|
+
def <=>(other)
|
22
|
+
self.name <=> other.name
|
23
|
+
end
|
24
|
+
|
25
|
+
end # class EngineInfo
|
26
|
+
end # module Rails
|
27
|
+
end # module Respect
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Respect
|
2
|
+
module Rails
|
3
|
+
module HeadersSimplifier
|
4
|
+
# Copy the given +headers+ hash table but get rid of the
|
5
|
+
# _complex_ type at the same time.
|
6
|
+
#
|
7
|
+
# This is useful if you plan to serialize +headers+ and want to
|
8
|
+
# get rid of complex type which may raise errors when serializing.
|
9
|
+
def simplify_headers(headers)
|
10
|
+
case headers
|
11
|
+
when String, Numeric, TrueClass, FalseClass, Symbol, Mime::Type
|
12
|
+
headers
|
13
|
+
when Array
|
14
|
+
result = []
|
15
|
+
headers.each do |x|
|
16
|
+
v = simplify_headers(x)
|
17
|
+
result << v if v
|
18
|
+
end
|
19
|
+
result
|
20
|
+
when Hash
|
21
|
+
result = {}
|
22
|
+
headers.each do |k, v|
|
23
|
+
new_v = simplify_headers(v)
|
24
|
+
result[k] = new_v if new_v
|
25
|
+
end
|
26
|
+
result
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end # module HeadersSimplifier
|
30
|
+
end # module Rails
|
31
|
+
end # module Respect
|