haveapi 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.editorconfig +15 -0
- data/CHANGELOG +15 -0
- data/README.md +66 -47
- data/doc/create-client.md +14 -5
- data/doc/json-schema.erb +16 -2
- data/doc/protocol.md +25 -3
- data/doc/protocol.plantuml +14 -8
- data/haveapi.gemspec +4 -2
- data/lib/haveapi.rb +5 -3
- data/lib/haveapi/action.rb +34 -6
- data/lib/haveapi/action_state.rb +92 -0
- data/lib/haveapi/authentication/basic/provider.rb +7 -0
- data/lib/haveapi/authentication/token/provider.rb +5 -0
- data/lib/haveapi/client_example.rb +83 -0
- data/lib/haveapi/client_examples/curl.rb +86 -0
- data/lib/haveapi/client_examples/fs_client.rb +116 -0
- data/lib/haveapi/client_examples/http.rb +91 -0
- data/lib/haveapi/client_examples/js_client.rb +149 -0
- data/lib/haveapi/client_examples/php_client.rb +122 -0
- data/lib/haveapi/client_examples/ruby_cli.rb +117 -0
- data/lib/haveapi/client_examples/ruby_client.rb +106 -0
- data/lib/haveapi/context.rb +3 -2
- data/lib/haveapi/example.rb +29 -2
- data/lib/haveapi/extensions/action_exceptions.rb +2 -2
- data/lib/haveapi/extensions/base.rb +1 -1
- data/lib/haveapi/extensions/exception_mailer.rb +339 -0
- data/lib/haveapi/hooks.rb +1 -1
- data/lib/haveapi/parameters/typed.rb +5 -3
- data/lib/haveapi/public/css/highlight.css +99 -0
- data/lib/haveapi/public/doc/protocol.png +0 -0
- data/lib/haveapi/public/js/highlight.pack.js +2 -0
- data/lib/haveapi/public/js/highlighter.js +9 -0
- data/lib/haveapi/public/js/main.js +32 -0
- data/lib/haveapi/public/js/nojs-tabs.js +196 -0
- data/lib/haveapi/resources/action_state.rb +196 -0
- data/lib/haveapi/server.rb +96 -27
- data/lib/haveapi/version.rb +2 -2
- data/lib/haveapi/views/main_layout.erb +14 -0
- data/lib/haveapi/views/version_page.erb +187 -13
- data/lib/haveapi/views/version_sidebar.erb +37 -3
- metadata +49 -5
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'pp'
|
2
|
+
|
3
|
+
module HaveAPI::ClientExamples
|
4
|
+
class RubyClient < HaveAPI::ClientExample
|
5
|
+
label 'Ruby'
|
6
|
+
code :ruby
|
7
|
+
order 0
|
8
|
+
|
9
|
+
def init
|
10
|
+
<<END
|
11
|
+
require 'haveapi-client'
|
12
|
+
|
13
|
+
client = HaveAPI::Client.new("#{base_url}", version: "#{version}")
|
14
|
+
END
|
15
|
+
end
|
16
|
+
|
17
|
+
def auth(method, desc)
|
18
|
+
case method
|
19
|
+
when :basic
|
20
|
+
<<END
|
21
|
+
#{init}
|
22
|
+
|
23
|
+
client.authenticate(:basic, username: "user", password: "secret")
|
24
|
+
END
|
25
|
+
|
26
|
+
when :token
|
27
|
+
<<END
|
28
|
+
#{init}
|
29
|
+
|
30
|
+
# Get token using username and password
|
31
|
+
client.authenticate(:token, username: "user", password: "secret")
|
32
|
+
|
33
|
+
puts "Token = \#{client.auth.token}"
|
34
|
+
|
35
|
+
# Next time, the client can authenticate using the token directly
|
36
|
+
client.authenticate(:token, token: saved_token)
|
37
|
+
END
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def example(sample)
|
42
|
+
args = []
|
43
|
+
|
44
|
+
args.concat(sample[:url_params]) if sample[:url_params]
|
45
|
+
|
46
|
+
if sample[:request] && !sample[:request].empty?
|
47
|
+
args << PP.pp(sample[:request], '').strip
|
48
|
+
end
|
49
|
+
|
50
|
+
out = "#{init}\n"
|
51
|
+
out << "reply = client.#{resource_path.join('.')}.#{action_name}"
|
52
|
+
out << "(#{args.join(', ')})" unless args.empty?
|
53
|
+
|
54
|
+
return (out << response(sample)) if sample[:status]
|
55
|
+
|
56
|
+
out << "\n"
|
57
|
+
out << "# Raises exception HaveAPI::Client::ActionFailed"
|
58
|
+
out
|
59
|
+
end
|
60
|
+
|
61
|
+
def response(sample)
|
62
|
+
out = "\n\n"
|
63
|
+
|
64
|
+
case action[:output][:layout]
|
65
|
+
when :hash
|
66
|
+
out << "# reply is an instance of HaveAPI::Client::Response\n"
|
67
|
+
out << "# reply.response() returns a hash of output parameters:\n"
|
68
|
+
out << PP.pp(sample[:response] || {}, '').split("\n").map { |v| "# #{v}" }.join("\n")
|
69
|
+
|
70
|
+
when :hash_list
|
71
|
+
out << "# reply is an instance of HaveAPI::Client::Response\n"
|
72
|
+
out << "# reply.response() returns an array of hashes:\n"
|
73
|
+
out << PP.pp(sample[:response] || [], '').split("\n").map { |v| "# #{v}" }.join("\n")
|
74
|
+
|
75
|
+
when :object
|
76
|
+
out << "# reply is an instance of HaveAPI::Client::ResourceInstance\n"
|
77
|
+
|
78
|
+
(sample[:response] || {}).each do |k, v|
|
79
|
+
param = action[:output][:parameters][k]
|
80
|
+
|
81
|
+
if param[:type] == 'Resource'
|
82
|
+
out << "# reply.#{k} = HaveAPI::Client::ResourceInstance("
|
83
|
+
out << "resource: #{param[:resource].join('.')}, "
|
84
|
+
|
85
|
+
if v.is_a?(::Hash)
|
86
|
+
out << v.map { |k,v| "#{k}: #{PP.pp(v, '').strip}" }.join(', ')
|
87
|
+
else
|
88
|
+
out << "id: #{v}"
|
89
|
+
end
|
90
|
+
|
91
|
+
out << ")\n"
|
92
|
+
|
93
|
+
else
|
94
|
+
out << "# reply.#{k} = #{PP.pp(v, '')}"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
when :object_list
|
99
|
+
out << "# reply is an instance of HaveAPI::Client::ResourceInstanceList,\n"
|
100
|
+
out << "# which is a subclass of Array"
|
101
|
+
end
|
102
|
+
|
103
|
+
out
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
data/lib/haveapi/context.rb
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
module HaveAPI
|
2
2
|
class Context
|
3
|
-
attr_accessor :server, :version, :resource, :action, :url, :args,
|
3
|
+
attr_accessor :server, :version, :request, :resource, :action, :url, :args,
|
4
4
|
:params, :current_user, :authorization, :endpoint,
|
5
5
|
:action_instance, :action_prepare, :layout
|
6
6
|
|
7
|
-
def initialize(server, version: nil, resource: [], action: nil,
|
7
|
+
def initialize(server, version: nil, request: nil, resource: [], action: nil,
|
8
8
|
url: nil, args: nil, params: nil, user: nil,
|
9
9
|
authorization: nil, endpoint: nil)
|
10
10
|
@server = server
|
11
11
|
@version = version
|
12
|
+
@request = request
|
12
13
|
@resource = resource
|
13
14
|
@action = action
|
14
15
|
@url = url
|
data/lib/haveapi/example.rb
CHANGED
@@ -4,6 +4,10 @@ module HaveAPI
|
|
4
4
|
@title = title
|
5
5
|
end
|
6
6
|
|
7
|
+
def url_params(*params)
|
8
|
+
@url_params = params
|
9
|
+
end
|
10
|
+
|
7
11
|
def request(f)
|
8
12
|
@request = f
|
9
13
|
end
|
@@ -12,21 +16,44 @@ module HaveAPI
|
|
12
16
|
@response = f
|
13
17
|
end
|
14
18
|
|
19
|
+
def status(status)
|
20
|
+
@status = status
|
21
|
+
end
|
22
|
+
|
23
|
+
def message(msg)
|
24
|
+
@message = msg
|
25
|
+
end
|
26
|
+
|
27
|
+
def errors(errs)
|
28
|
+
@errors = errs
|
29
|
+
end
|
30
|
+
|
31
|
+
def http_status(code)
|
32
|
+
@http_status = code
|
33
|
+
end
|
34
|
+
|
15
35
|
def comment(str)
|
16
36
|
@comment = str
|
17
37
|
end
|
18
38
|
|
19
39
|
def provided?
|
20
|
-
|
40
|
+
instance_variables.detect do |v|
|
41
|
+
instance_variable_get(v)
|
42
|
+
end ? true : false
|
21
43
|
end
|
22
44
|
|
23
45
|
def describe
|
24
46
|
if provided?
|
25
47
|
{
|
26
48
|
title: @title,
|
49
|
+
comment: @comment,
|
50
|
+
url_params: @url_params,
|
27
51
|
request: @request,
|
28
52
|
response: @response,
|
29
|
-
|
53
|
+
status: @status.nil? ? true : @status,
|
54
|
+
message: @message,
|
55
|
+
errors: @errors,
|
56
|
+
http_status: @http_status || 200,
|
30
57
|
}
|
31
58
|
else
|
32
59
|
{}
|
@@ -1,8 +1,8 @@
|
|
1
1
|
module HaveAPI::Extensions
|
2
2
|
class ActionExceptions < Base
|
3
3
|
class << self
|
4
|
-
def enabled
|
5
|
-
HaveAPI::Action.connect_hook(:exec_exception) do |ret,
|
4
|
+
def enabled(server)
|
5
|
+
HaveAPI::Action.connect_hook(:exec_exception) do |ret, context, e|
|
6
6
|
break(ret) unless @exceptions
|
7
7
|
|
8
8
|
@exceptions.each do |handler|
|
@@ -0,0 +1,339 @@
|
|
1
|
+
require 'net/smtp'
|
2
|
+
require 'mail'
|
3
|
+
|
4
|
+
module HaveAPI::Extensions
|
5
|
+
# This extension mails exceptions raised during action execution and description
|
6
|
+
# construction to specified e-mail address.
|
7
|
+
#
|
8
|
+
# The template is based on {Sinatra::ShowExceptions::TEMPLATE}, but the JavaScript
|
9
|
+
# functions are removed, since e-mail doesn't support it. HaveAPI-specific content
|
10
|
+
# is added. Some helper methods are taken either from Sinatra or Rack.
|
11
|
+
class ExceptionMailer < Base
|
12
|
+
# @param opts [Hash] options
|
13
|
+
# @option opts to [String] recipient address
|
14
|
+
# @option opts from [String] sender address
|
15
|
+
# @option opts subject [String] '%s' is replaced by the error message
|
16
|
+
# @option opts smtp [Hash, falsy] smtp options, sendmail is used if not provided
|
17
|
+
def initialize(opts)
|
18
|
+
@opts = opts
|
19
|
+
end
|
20
|
+
|
21
|
+
def enabled(server)
|
22
|
+
HaveAPI::Action.connect_hook(:exec_exception) do |ret, context, e|
|
23
|
+
log(context, e)
|
24
|
+
ret
|
25
|
+
end
|
26
|
+
|
27
|
+
server.connect_hook(:description_exception) do |ret, context, e|
|
28
|
+
log(context, e)
|
29
|
+
ret
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def log(context, exception)
|
34
|
+
req = context.request.request
|
35
|
+
path = (req.script_name + req.path_info).squeeze("/")
|
36
|
+
|
37
|
+
frames = exception.backtrace.map { |line|
|
38
|
+
frame = OpenStruct.new
|
39
|
+
|
40
|
+
if line =~ /(.*?):(\d+)(:in `(.*)')?/
|
41
|
+
frame.filename = $1
|
42
|
+
frame.lineno = $2.to_i
|
43
|
+
frame.function = $4
|
44
|
+
|
45
|
+
begin
|
46
|
+
lineno = frame.lineno-1
|
47
|
+
lines = ::File.readlines(frame.filename)
|
48
|
+
frame.context_line = lines[lineno].chomp
|
49
|
+
rescue
|
50
|
+
end
|
51
|
+
|
52
|
+
frame
|
53
|
+
else
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
}.compact
|
57
|
+
|
58
|
+
env = context.request.env
|
59
|
+
|
60
|
+
mail(context, exception, TEMPLATE.result(binding))
|
61
|
+
end
|
62
|
+
|
63
|
+
def mail(context, exception, body)
|
64
|
+
mail = ::Mail.new({
|
65
|
+
from: @opts[:from],
|
66
|
+
to: @opts[:to],
|
67
|
+
subject: @opts[:subject] % [exception.to_s],
|
68
|
+
body: body,
|
69
|
+
content_type: 'text/html; charset=UTF-8',
|
70
|
+
})
|
71
|
+
|
72
|
+
if @opts[:smtp]
|
73
|
+
mail.delivery_method(:smtp, @opts[:smtp])
|
74
|
+
|
75
|
+
else
|
76
|
+
mail.delivery_method(:sendmail)
|
77
|
+
end
|
78
|
+
|
79
|
+
mail.deliver!
|
80
|
+
mail
|
81
|
+
end
|
82
|
+
|
83
|
+
protected
|
84
|
+
# From {Sinatra::ShowExceptions}
|
85
|
+
def frame_class(frame)
|
86
|
+
if frame.filename =~ /lib\/sinatra.*\.rb/
|
87
|
+
"framework"
|
88
|
+
elsif (defined?(Gem) && frame.filename.include?(Gem.dir)) ||
|
89
|
+
frame.filename =~ /\/bin\/(\w+)$/
|
90
|
+
"system"
|
91
|
+
else
|
92
|
+
"app"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# From {Rack::ShowExceptions}
|
97
|
+
def h(obj)
|
98
|
+
case obj
|
99
|
+
when String
|
100
|
+
Rack::Utils.escape_html(obj)
|
101
|
+
else
|
102
|
+
Rack::Utils.escape_html(obj.inspect)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
TEMPLATE = ERB.new(<<END
|
107
|
+
<!DOCTYPE html>
|
108
|
+
<html>
|
109
|
+
<head>
|
110
|
+
<meta charset="utf-8">
|
111
|
+
<title><%=h exception.class %> at <%=h path %></title>
|
112
|
+
<style type="text/css">
|
113
|
+
* {margin: 0; padding: 0; border: 0; outline: 0;}
|
114
|
+
div.clear {clear: both;}
|
115
|
+
body {background: #EEEEEE; margin: 0; padding: 0;
|
116
|
+
font-family: 'Lucida Grande', 'Lucida Sans Unicode',
|
117
|
+
'Garuda';}
|
118
|
+
code {font-family: 'Lucida Console', monospace;
|
119
|
+
font-size: 12px;}
|
120
|
+
li {height: 18px;}
|
121
|
+
ul {list-style: none; margin: 0; padding: 0;}
|
122
|
+
ol:hover {cursor: pointer;}
|
123
|
+
ol li {white-space: pre;}
|
124
|
+
#explanation {font-size: 12px; color: #666666;
|
125
|
+
margin: 20px 0 0 100px;}
|
126
|
+
/* WRAP */
|
127
|
+
#wrap {width: 1000px; background: #FFFFFF; margin: 0 auto;
|
128
|
+
padding: 30px 5px 20px 5px;
|
129
|
+
border-left: 1px solid #DDDDDD;
|
130
|
+
border-right: 1px solid #DDDDDD;}
|
131
|
+
/* HEADER */
|
132
|
+
#header {margin: 0 auto 25px auto;}
|
133
|
+
#header #summary {margin: 12px 0 0 20px;
|
134
|
+
font-family: 'Lucida Grande', 'Lucida Sans Unicode';}
|
135
|
+
h1 {margin: 0; font-size: 36px; color: #981919;}
|
136
|
+
h2 {margin: 0; font-size: 22px; color: #333333;}
|
137
|
+
#header ul {margin: 0; font-size: 12px; color: #666666;}
|
138
|
+
#header ul li strong{color: #444444;}
|
139
|
+
#header ul li {display: inline; padding: 0 10px;}
|
140
|
+
#header ul li.first {padding-left: 0;}
|
141
|
+
#header ul li.last {border: 0; padding-right: 0;}
|
142
|
+
/* BODY */
|
143
|
+
#backtrace,
|
144
|
+
#get,
|
145
|
+
#post,
|
146
|
+
#cookies,
|
147
|
+
#rack, #context {width: 980px; margin: 0 auto 10px auto;}
|
148
|
+
p#nav {float: right; font-size: 14px;}
|
149
|
+
/* BACKTRACE */
|
150
|
+
h3 {float: left; width: 100px; margin-bottom: 10px;
|
151
|
+
color: #981919; font-size: 14px; font-weight: bold;}
|
152
|
+
#nav a {color: #666666; text-decoration: none; padding: 0 5px;}
|
153
|
+
#backtrace li.frame-info {background: #f7f7f7; padding-left: 10px;
|
154
|
+
font-size: 12px; color: #333333;}
|
155
|
+
#backtrace ul {list-style-position: outside; border: 1px solid #E9E9E9;
|
156
|
+
border-bottom: 0;}
|
157
|
+
#backtrace ol {width: 920px; margin-left: 50px;
|
158
|
+
font: 10px 'Lucida Console', monospace; color: #666666;}
|
159
|
+
#backtrace ol li {border: 0; border-left: 1px solid #E9E9E9;
|
160
|
+
padding: 2px 0;}
|
161
|
+
#backtrace ol code {font-size: 10px; color: #555555; padding-left: 5px;}
|
162
|
+
#backtrace-ul li {border-bottom: 1px solid #E9E9E9; height: auto;
|
163
|
+
padding: 3px 0;}
|
164
|
+
#backtrace-ul .code {padding: 6px 0 4px 0;}
|
165
|
+
/* REQUEST DATA */
|
166
|
+
p.no-data {padding-top: 2px; font-size: 12px; color: #666666;}
|
167
|
+
table.req {width: 980px; text-align: left; font-size: 12px;
|
168
|
+
color: #666666; padding: 0; border-spacing: 0;
|
169
|
+
border: 1px solid #EEEEEE; border-bottom: 0;
|
170
|
+
border-left: 0;
|
171
|
+
clear:both}
|
172
|
+
table.req tr th {padding: 2px 10px; font-weight: bold;
|
173
|
+
background: #F7F7F7; border-bottom: 1px solid #EEEEEE;
|
174
|
+
border-left: 1px solid #EEEEEE;}
|
175
|
+
table.req tr td {padding: 2px 20px 2px 10px;
|
176
|
+
border-bottom: 1px solid #EEEEEE;
|
177
|
+
border-left: 1px solid #EEEEEE;}
|
178
|
+
/* HIDE PRE/POST CODE AT START */
|
179
|
+
.pre-context,
|
180
|
+
.post-context {display: none;}
|
181
|
+
table td.code {width:750px}
|
182
|
+
table td.code div {width:750px;overflow:hidden}
|
183
|
+
</style>
|
184
|
+
</head>
|
185
|
+
<body>
|
186
|
+
<div id="wrap">
|
187
|
+
<div id="header">
|
188
|
+
<div id="summary">
|
189
|
+
<h1><strong><%=h exception.class %></strong> at <strong><%=h path %>
|
190
|
+
</strong></h1>
|
191
|
+
<h2><%=h exception.message %></h2>
|
192
|
+
<ul>
|
193
|
+
<li class="first"><strong>file:</strong> <code>
|
194
|
+
<%=h frames.first.filename.split("/").last %></code></li>
|
195
|
+
<li><strong>location:</strong> <code><%=h frames.first.function %>
|
196
|
+
</code></li>
|
197
|
+
<li class="last"><strong>line:
|
198
|
+
</strong> <%=h frames.first.lineno %></li>
|
199
|
+
</ul>
|
200
|
+
</div>
|
201
|
+
<div class="clear"></div>
|
202
|
+
</div>
|
203
|
+
<div id="context">
|
204
|
+
<h3>Context</h3>
|
205
|
+
<table class="req">
|
206
|
+
<tr>
|
207
|
+
<th>API version</th>
|
208
|
+
<td><%=h context.version %></td>
|
209
|
+
</tr>
|
210
|
+
<tr>
|
211
|
+
<th>Action</th>
|
212
|
+
<td><%= h(context.action && context.action.to_s) %></td>
|
213
|
+
</tr>
|
214
|
+
<tr>
|
215
|
+
<th>Arguments</th>
|
216
|
+
<td><%=h context.args %></td>
|
217
|
+
</tr>
|
218
|
+
<tr>
|
219
|
+
<th>Parameters</th>
|
220
|
+
<td><%=h context.params %></td>
|
221
|
+
</tr>
|
222
|
+
<tr>
|
223
|
+
<th>User</th>
|
224
|
+
<td><%=h context.request.current_user %></td>
|
225
|
+
</tr>
|
226
|
+
</table>
|
227
|
+
<div class="clear"></div>
|
228
|
+
</div>
|
229
|
+
<div id="backtrace">
|
230
|
+
<h3>BACKTRACE</h3>
|
231
|
+
<p id="nav"><strong>JUMP TO:</strong>
|
232
|
+
<a href="#get-info">GET</a>
|
233
|
+
<a href="#post-info">POST</a>
|
234
|
+
<a href="#cookie-info">COOKIES</a>
|
235
|
+
<a href="#env-info">ENV</a>
|
236
|
+
</p>
|
237
|
+
<div class="clear"></div>
|
238
|
+
<ul id="backtrace-ul">
|
239
|
+
<% frames.each do |frame| %>
|
240
|
+
<li class="frame-info <%= frame_class(frame) %>">
|
241
|
+
<code><%=h frame.filename %></code> in
|
242
|
+
<code><strong><%=h frame.function %></strong></code>
|
243
|
+
</li>
|
244
|
+
<li class="code <%= frame_class(frame) %>">
|
245
|
+
<ol start="<%= frame.lineno %>" class="context">
|
246
|
+
<li class="context-line">
|
247
|
+
<code><%=h frame.context_line %></code>
|
248
|
+
</li>
|
249
|
+
</ol>
|
250
|
+
<div class="clear"></div>
|
251
|
+
</li>
|
252
|
+
<% end %>
|
253
|
+
</ul>
|
254
|
+
</div> <!-- /BACKTRACE -->
|
255
|
+
<div id="get">
|
256
|
+
<h3 id="get-info">GET</h3>
|
257
|
+
<% if req.GET and not req.GET.empty? %>
|
258
|
+
<table class="req">
|
259
|
+
<tr>
|
260
|
+
<th>Variable</th>
|
261
|
+
<th>Value</th>
|
262
|
+
</tr>
|
263
|
+
<% req.GET.sort_by { |k, v| k.to_s }.each { |key, val| %>
|
264
|
+
<tr>
|
265
|
+
<td><%=h key %></td>
|
266
|
+
<td class="code"><div><%=h val.inspect %></div></td>
|
267
|
+
</tr>
|
268
|
+
<% } %>
|
269
|
+
</table>
|
270
|
+
<% else %>
|
271
|
+
<p class="no-data">No GET data.</p>
|
272
|
+
<% end %>
|
273
|
+
<div class="clear"></div>
|
274
|
+
</div> <!-- /GET -->
|
275
|
+
<div id="post">
|
276
|
+
<h3 id="post-info">POST</h3>
|
277
|
+
<% if req.POST and not req.POST.empty? %>
|
278
|
+
<table class="req">
|
279
|
+
<tr>
|
280
|
+
<th>Variable</th>
|
281
|
+
<th>Value</th>
|
282
|
+
</tr>
|
283
|
+
<% req.POST.sort_by { |k, v| k.to_s }.each { |key, val| %>
|
284
|
+
<tr>
|
285
|
+
<td><%=h key %></td>
|
286
|
+
<td class="code"><div><%=h val.inspect %></div></td>
|
287
|
+
</tr>
|
288
|
+
<% } %>
|
289
|
+
</table>
|
290
|
+
<% else %>
|
291
|
+
<p class="no-data">No POST data.</p>
|
292
|
+
<% end %>
|
293
|
+
<div class="clear"></div>
|
294
|
+
</div> <!-- /POST -->
|
295
|
+
<div id="cookies">
|
296
|
+
<h3 id="cookie-info">COOKIES</h3>
|
297
|
+
<% unless req.cookies.empty? %>
|
298
|
+
<table class="req">
|
299
|
+
<tr>
|
300
|
+
<th>Variable</th>
|
301
|
+
<th>Value</th>
|
302
|
+
</tr>
|
303
|
+
<% req.cookies.each { |key, val| %>
|
304
|
+
<tr>
|
305
|
+
<td><%=h key %></td>
|
306
|
+
<td class="code"><div><%=h val.inspect %></div></td>
|
307
|
+
</tr>
|
308
|
+
<% } %>
|
309
|
+
</table>
|
310
|
+
<% else %>
|
311
|
+
<p class="no-data">No cookie data.</p>
|
312
|
+
<% end %>
|
313
|
+
<div class="clear"></div>
|
314
|
+
</div> <!-- /COOKIES -->
|
315
|
+
<div id="rack">
|
316
|
+
<h3 id="env-info">Rack ENV</h3>
|
317
|
+
<table class="req">
|
318
|
+
<tr>
|
319
|
+
<th>Variable</th>
|
320
|
+
<th>Value</th>
|
321
|
+
</tr>
|
322
|
+
<% env.sort_by { |k, v| k.to_s }.each { |key, val| %>
|
323
|
+
<tr>
|
324
|
+
<td><%=h key %></td>
|
325
|
+
<td class="code"><div><%=h val %></div></td>
|
326
|
+
</tr>
|
327
|
+
<% } %>
|
328
|
+
</table>
|
329
|
+
<div class="clear"></div>
|
330
|
+
</div> <!-- /RACK ENV -->
|
331
|
+
<p id="explanation">You're seeing this error because you have
|
332
|
+
enabled HaveAPI Extension <code>HaveAPI::Extensions::ExceptionMailer</code>.</p>
|
333
|
+
</div> <!-- /WRAP -->
|
334
|
+
</body>
|
335
|
+
</html>
|
336
|
+
END
|
337
|
+
)
|
338
|
+
end
|
339
|
+
end
|