haveapi 0.6.0 → 0.7.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.
- 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
|