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,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
|