haveapi 0.3.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +12 -0
- data/Gemfile +10 -10
- data/README.md +19 -12
- data/Rakefile +23 -0
- data/doc/hooks.erb +35 -0
- data/doc/index.md +1 -0
- data/doc/json-schema.erb +369 -0
- data/doc/protocol.md +178 -38
- data/doc/protocol.plantuml +220 -0
- data/haveapi.gemspec +6 -10
- data/lib/haveapi/action.rb +35 -6
- data/lib/haveapi/api.rb +22 -5
- data/lib/haveapi/common.rb +7 -0
- data/lib/haveapi/exceptions.rb +7 -0
- data/lib/haveapi/hooks.rb +19 -8
- data/lib/haveapi/model_adapters/active_record.rb +58 -19
- data/lib/haveapi/output_formatter.rb +8 -5
- data/lib/haveapi/output_formatters/json.rb +6 -1
- data/lib/haveapi/params/param.rb +33 -39
- data/lib/haveapi/params/resource.rb +20 -0
- data/lib/haveapi/params.rb +26 -4
- data/lib/haveapi/public/doc/protocol.png +0 -0
- data/lib/haveapi/resource.rb +2 -7
- data/lib/haveapi/server.rb +87 -26
- data/lib/haveapi/tasks/hooks.rb +3 -0
- data/lib/haveapi/tasks/yard.rb +12 -0
- data/lib/haveapi/validator.rb +134 -0
- data/lib/haveapi/validator_chain.rb +99 -0
- data/lib/haveapi/validators/acceptance.rb +38 -0
- data/lib/haveapi/validators/confirmation.rb +46 -0
- data/lib/haveapi/validators/custom.rb +21 -0
- data/lib/haveapi/validators/exclusion.rb +38 -0
- data/lib/haveapi/validators/format.rb +42 -0
- data/lib/haveapi/validators/inclusion.rb +42 -0
- data/lib/haveapi/validators/length.rb +71 -0
- data/lib/haveapi/validators/numericality.rb +104 -0
- data/lib/haveapi/validators/presence.rb +40 -0
- data/lib/haveapi/version.rb +2 -1
- data/lib/haveapi/views/doc_sidebars/json-schema.erb +7 -0
- data/lib/haveapi/views/main_layout.erb +11 -0
- data/lib/haveapi/views/version_page.erb +26 -3
- data/lib/haveapi.rb +7 -4
- metadata +45 -66
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 97a0780e86ee6d9273d743c2c6b7c87ba053b19d
|
4
|
+
data.tar.gz: aa2f1bda2b113b1ea0452da5940dabc1617bf68e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f665c8857e439f36280aae5634f3ff98cce618d645f6b4104a6d00c7a82727bd787edffed0ca7ed2f3daf60646711105e2d408b58f2557b40e773916a665afac
|
7
|
+
data.tar.gz: 29bcfa9af9295e10bde9ead47df70bca5d185ea79ea656660f80a26b4ed54cf3f35ea6e142b88f2f7ff6480f92080c4172d8e8986f912700bd6d57f0f1fcd34c
|
data/CHANGELOG
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
* Wed Jan 20 2016 - version 0.4.0
|
2
|
+
- Introduced protocol version, currently 1.0
|
3
|
+
- Renamed certain API configuration methods
|
4
|
+
- Document defined hooks in yardoc
|
5
|
+
- Implicit API version
|
6
|
+
- Include input parameter validators in the protocol
|
7
|
+
- Present validator from ActiveRecord is not imported to controller
|
8
|
+
- Clean-up dependencies
|
9
|
+
- Cross-link resources in online API documentation
|
10
|
+
- JSON schema of the documentation protocol
|
11
|
+
- UML diagram representing the documentation protocol
|
12
|
+
- Improved error reporting in action definition
|
data/Gemfile
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
|
+
gemspec
|
2
3
|
|
3
|
-
|
4
|
-
gem '
|
5
|
-
gem '
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
gem '
|
10
|
-
gem '
|
11
|
-
|
12
|
-
gem 'railties'
|
4
|
+
group :test do
|
5
|
+
gem 'rspec'
|
6
|
+
gem 'rack-test'
|
7
|
+
end
|
8
|
+
|
9
|
+
group :activerecord do
|
10
|
+
gem 'activerecord', '~> 4.1.14'
|
11
|
+
gem 'sinatra-activerecord', '~> 2.0.9'
|
12
|
+
end
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@ HaveAPI
|
|
2
2
|
=======
|
3
3
|
A framework for creating self-describing APIs in Ruby.
|
4
4
|
|
5
|
-
Note: HaveAPI is
|
5
|
+
Note: HaveAPI is in heavy development. It is not stable, its interface may change.
|
6
6
|
|
7
7
|
## What is a self-describing API?
|
8
8
|
A self-describing API responds to HTTP method `OPTIONS` and returns description
|
@@ -14,29 +14,33 @@ Clients use the self-description to learn how to communicate with the API,
|
|
14
14
|
which they otherwise know nothing about.
|
15
15
|
|
16
16
|
## Main features
|
17
|
-
- Creates RESTful APIs
|
17
|
+
- Creates RESTful APIs usable even with simple HTTP client, should it be needed
|
18
18
|
- Handles network communication, input/output formats and parameters
|
19
19
|
on both server and client, you need only to define resources and actions
|
20
|
-
- By writing the code you get the documentation
|
20
|
+
- By writing the code you get the documentation for free, it is available to all clients
|
21
21
|
- Auto-generated online HTML documentation
|
22
22
|
- Generic interface for clients - one client can be used to access all APIs
|
23
23
|
using this framework
|
24
24
|
- Ruby, PHP and JavaScript clients already available
|
25
25
|
- A change in the API is immediately reflected in all clients
|
26
26
|
- Supports API versioning
|
27
|
-
-
|
28
|
-
|
27
|
+
- ORM integration
|
28
|
+
- ActiveRecord
|
29
|
+
- integrated with controller
|
30
|
+
- loads and documents validators from models
|
31
|
+
- eager loading
|
32
|
+
- easy to support other ORM frameworks
|
29
33
|
|
30
34
|
## Usage
|
31
35
|
This text might not be complete or up-to-date, as things still often change.
|
32
36
|
Full use of HaveAPI may be seen
|
33
|
-
in [
|
34
|
-
as an example of how
|
37
|
+
in [vpsadmin-api](https://github.com/vpsfreecz/vpsadmin-api)
|
38
|
+
([online doc](https://api.vpsfree.cz)), which may serve as an example of how
|
39
|
+
are things meant to be used.
|
35
40
|
|
36
41
|
All resources and actions are represented by classes. They all must be stored
|
37
|
-
in a module, whose name is later given to HaveAPI.
|
38
|
-
|
39
|
-
HaveAPI then searches all classes in that module and constructs your API.
|
42
|
+
in a module, whose name is later given to HaveAPI. HaveAPI then searches all
|
43
|
+
classes in this module and builds your API.
|
40
44
|
|
41
45
|
For the purposes of this document, all resources will be in module `MyAPI`.
|
42
46
|
|
@@ -136,7 +140,7 @@ module MyAPI
|
|
136
140
|
|
137
141
|
# Helper method returning a query for all users
|
138
142
|
def query
|
139
|
-
|
143
|
+
::User.all
|
140
144
|
end
|
141
145
|
|
142
146
|
# This method is called if the request has meta[:count] = true
|
@@ -215,7 +219,6 @@ class BasicAuth < HaveAPI::Authentication::Basic::Provider
|
|
215
219
|
end
|
216
220
|
|
217
221
|
api.use_version(:all)
|
218
|
-
api.set_default_version(1)
|
219
222
|
api.auth_chain << BasicAuth
|
220
223
|
api.mount('/')
|
221
224
|
|
@@ -254,6 +257,10 @@ is not self-described.
|
|
254
257
|
If the user is authenticated when requesting self-description, only allowed
|
255
258
|
resources, actions and parameters will be returned.
|
256
259
|
|
260
|
+
## Validation of input data
|
261
|
+
Because validators are a part of the API's documentation, the clients can
|
262
|
+
perform client-side validation before the data is sent to the API server.
|
263
|
+
|
257
264
|
## Available clients
|
258
265
|
These clients completely rely on the API description and can be used for all
|
259
266
|
APIs that are using HaveAPI.
|
data/Rakefile
CHANGED
@@ -1,7 +1,30 @@
|
|
1
1
|
require 'bundler/gem_tasks'
|
2
2
|
require 'rspec/core'
|
3
3
|
require 'rspec/core/rake_task'
|
4
|
+
require 'active_support/core_ext/string/inflections'
|
5
|
+
require 'haveapi'
|
6
|
+
require 'haveapi/tasks/yard'
|
4
7
|
|
5
8
|
RSpec::Core::RakeTask.new(:spec) do |spec|
|
6
9
|
spec.pattern = FileList['spec/**/*_spec.rb']
|
7
10
|
end
|
11
|
+
|
12
|
+
begin
|
13
|
+
require 'yard'
|
14
|
+
|
15
|
+
YARD::Rake::YardocTask.new do |t|
|
16
|
+
t.files = ['lib/**/*.rb']
|
17
|
+
t.options = [
|
18
|
+
'--protected',
|
19
|
+
'--output-dir=html_doc',
|
20
|
+
'--files=doc/*.md',
|
21
|
+
'--files=doc/*.html'
|
22
|
+
]
|
23
|
+
t.before = Proc.new do
|
24
|
+
document_hooks.call
|
25
|
+
render_doc_file('doc/json-schema.erb', 'doc/JSON-Schema.html').call
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
rescue LoadError
|
30
|
+
end
|
data/doc/hooks.erb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
<%
|
2
|
+
def render_hash(hash)
|
3
|
+
return 'none' unless hash
|
4
|
+
'<dl>' + hash.map { |k,v| "<dt>#{k}</dt><dd>#{v}</dd>" }.join('') + '</dl>'
|
5
|
+
end
|
6
|
+
%>
|
7
|
+
# Hooks
|
8
|
+
<% HaveAPI::Hooks.hooks.each do |klass, hooks| %>
|
9
|
+
##<%= klass %>
|
10
|
+
<% hooks.each do |name, hook| %>
|
11
|
+
### <%= name %>
|
12
|
+
<table>
|
13
|
+
<tr>
|
14
|
+
<td style="vertical-align: top;">Description:</td>
|
15
|
+
<td><%= hook[:desc] %></td>
|
16
|
+
</tr>
|
17
|
+
<tr>
|
18
|
+
<td style="vertical-align: top;">Context:</td>
|
19
|
+
<td><%= hook[:context] || 'current' %></td>
|
20
|
+
</tr>
|
21
|
+
<tr>
|
22
|
+
<td style="vertical-align: top;">Arguments:</td>
|
23
|
+
<td><%= render_hash(hook[:args]) %></td>
|
24
|
+
</tr>
|
25
|
+
<tr>
|
26
|
+
<td style="vertical-align: top;">Initial value:</td>
|
27
|
+
<td><%= render_hash(hook[:initial]) %></td>
|
28
|
+
</tr>
|
29
|
+
<tr>
|
30
|
+
<td style="vertical-align: top;">Return value:</td>
|
31
|
+
<td><%= render_hash(hook[:ret]) %></td>
|
32
|
+
</tr>
|
33
|
+
</table>
|
34
|
+
<% end %>
|
35
|
+
<% end %>
|
data/doc/index.md
CHANGED
data/doc/json-schema.erb
ADDED
@@ -0,0 +1,369 @@
|
|
1
|
+
<%
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
DEFINITIONS = {
|
5
|
+
version: {
|
6
|
+
type: :object,
|
7
|
+
properties: {
|
8
|
+
authentication: {
|
9
|
+
type: :object,
|
10
|
+
properties: {
|
11
|
+
basic: { '$ref' => '#/definitions/auth_basic' },
|
12
|
+
token: { '$ref' => '#/definitions/auth_token' },
|
13
|
+
}
|
14
|
+
},
|
15
|
+
resources: {
|
16
|
+
type: :object,
|
17
|
+
'$ref' => '#/definitions/resources'
|
18
|
+
},
|
19
|
+
meta: {
|
20
|
+
type: :object,
|
21
|
+
properties: {
|
22
|
+
namespace: {
|
23
|
+
type: :string,
|
24
|
+
default: '_meta'
|
25
|
+
}
|
26
|
+
}
|
27
|
+
},
|
28
|
+
help: { type: :string }
|
29
|
+
}
|
30
|
+
},
|
31
|
+
|
32
|
+
auth_basic: {
|
33
|
+
type: :object,
|
34
|
+
},
|
35
|
+
|
36
|
+
auth_token: {
|
37
|
+
type: :object,
|
38
|
+
properties: {
|
39
|
+
http_header: {
|
40
|
+
type: :string,
|
41
|
+
default: 'X-HaveAPI-Auth-Token'
|
42
|
+
},
|
43
|
+
query_parameter: {
|
44
|
+
type: :string,
|
45
|
+
default: '_auth_token'
|
46
|
+
},
|
47
|
+
resources: {
|
48
|
+
type: :object,
|
49
|
+
'$ref' => '#/definitions/resources'
|
50
|
+
}
|
51
|
+
}
|
52
|
+
},
|
53
|
+
|
54
|
+
resources: {
|
55
|
+
type: :object,
|
56
|
+
patternProperties: {
|
57
|
+
'^[a-z_]+$' => {
|
58
|
+
type: :object,
|
59
|
+
properties: {
|
60
|
+
description: { type: :string },
|
61
|
+
actions: {
|
62
|
+
'$ref' => '#/definitions/actions'
|
63
|
+
},
|
64
|
+
resources: {
|
65
|
+
'$ref' => '#/definitions/resources'
|
66
|
+
}
|
67
|
+
}
|
68
|
+
}
|
69
|
+
}
|
70
|
+
},
|
71
|
+
|
72
|
+
actions: {
|
73
|
+
type: :object,
|
74
|
+
patternProperties: {
|
75
|
+
'^[a-z_]+$' => {
|
76
|
+
type: :object,
|
77
|
+
properties: {
|
78
|
+
auth: { type: :boolean },
|
79
|
+
description: { type: :string },
|
80
|
+
aliases: {
|
81
|
+
type: :array,
|
82
|
+
items: { type: :string }
|
83
|
+
},
|
84
|
+
input: { '$ref' => '#/definitions/input_parameters' },
|
85
|
+
output: { '$ref' => '#/definitions/output_parameters' },
|
86
|
+
meta: { '$ref' => '#/definitions/action_meta' },
|
87
|
+
examples: {
|
88
|
+
type: :object,
|
89
|
+
properties: {
|
90
|
+
title: { type: :string },
|
91
|
+
request: { type: :object },
|
92
|
+
response: { type: :object },
|
93
|
+
comment: { type: :string },
|
94
|
+
}
|
95
|
+
},
|
96
|
+
url: { type: :string },
|
97
|
+
method: { type: :string },
|
98
|
+
help: { type: :string }
|
99
|
+
}
|
100
|
+
}
|
101
|
+
}
|
102
|
+
|
103
|
+
},
|
104
|
+
|
105
|
+
input_parameters: {
|
106
|
+
type: :object,
|
107
|
+
properties: {
|
108
|
+
parameters: {
|
109
|
+
type: :object,
|
110
|
+
patternProperties: {
|
111
|
+
'^[a-z_]+$' => {
|
112
|
+
type: :object,
|
113
|
+
oneOf: [
|
114
|
+
{
|
115
|
+
title: 'Data type',
|
116
|
+
type: :object,
|
117
|
+
properties: {
|
118
|
+
required: { type: :boolean },
|
119
|
+
label: { type: :string },
|
120
|
+
description: { type: :string },
|
121
|
+
type: {
|
122
|
+
type: :string,
|
123
|
+
enum: %w(String Text Integer Float Datetime Boolean)
|
124
|
+
},
|
125
|
+
validators: { '$ref' => '#/definitions/input_validators' },
|
126
|
+
default: {}
|
127
|
+
}
|
128
|
+
},
|
129
|
+
{
|
130
|
+
title: 'Resource',
|
131
|
+
type: :object,
|
132
|
+
properties: {
|
133
|
+
required: { type: :boolean },
|
134
|
+
label: { type: :string },
|
135
|
+
description: { type: :string },
|
136
|
+
type: {
|
137
|
+
type: :string,
|
138
|
+
enum: %w(Resource)
|
139
|
+
},
|
140
|
+
resource: { type: :array },
|
141
|
+
value_id: { type: :string },
|
142
|
+
value_label: { type: :string },
|
143
|
+
value: {
|
144
|
+
type: :object,
|
145
|
+
properties: {
|
146
|
+
url: { type: :string },
|
147
|
+
method: { type: :string },
|
148
|
+
help: { type: :string },
|
149
|
+
}
|
150
|
+
},
|
151
|
+
choices: {
|
152
|
+
type: :object,
|
153
|
+
properties: {
|
154
|
+
url: { type: :string },
|
155
|
+
method: { type: :string },
|
156
|
+
help: { type: :string },
|
157
|
+
}
|
158
|
+
}
|
159
|
+
}
|
160
|
+
}
|
161
|
+
]
|
162
|
+
}
|
163
|
+
}
|
164
|
+
},
|
165
|
+
layout: {},
|
166
|
+
namespace: {}
|
167
|
+
}
|
168
|
+
},
|
169
|
+
|
170
|
+
input_validators: {
|
171
|
+
type: :object,
|
172
|
+
properties: {
|
173
|
+
accept: {
|
174
|
+
type: :object,
|
175
|
+
properties: {
|
176
|
+
value: {},
|
177
|
+
message: { type: :string }
|
178
|
+
}
|
179
|
+
},
|
180
|
+
confirm: {
|
181
|
+
type: :object,
|
182
|
+
properties: {
|
183
|
+
equal: { type: :boolean },
|
184
|
+
parameter: { type: :string },
|
185
|
+
message: { type: :string }
|
186
|
+
}
|
187
|
+
},
|
188
|
+
custom: { type: :string },
|
189
|
+
exclude: {
|
190
|
+
type: :object,
|
191
|
+
properties: {
|
192
|
+
values: { type: :array },
|
193
|
+
message: { type: :string }
|
194
|
+
}
|
195
|
+
},
|
196
|
+
format: {
|
197
|
+
type: :object,
|
198
|
+
properties: {
|
199
|
+
rx: { type: :string },
|
200
|
+
match: { type: :boolean },
|
201
|
+
description: { type: :string },
|
202
|
+
message: { type: :string }
|
203
|
+
}
|
204
|
+
},
|
205
|
+
include: {
|
206
|
+
type: :object,
|
207
|
+
properties: {
|
208
|
+
values: {
|
209
|
+
oneOf: [
|
210
|
+
{
|
211
|
+
title: 'Array of allowed values',
|
212
|
+
type: :array
|
213
|
+
},
|
214
|
+
{
|
215
|
+
title: 'Hash of allowed values',
|
216
|
+
type: :object
|
217
|
+
}
|
218
|
+
|
219
|
+
]
|
220
|
+
},
|
221
|
+
message: { type: :string }
|
222
|
+
}
|
223
|
+
},
|
224
|
+
length: {
|
225
|
+
oneOf: [
|
226
|
+
{
|
227
|
+
title: 'Equality',
|
228
|
+
type: :object,
|
229
|
+
properties: {
|
230
|
+
equals: { type: :integer },
|
231
|
+
message: { type: :string },
|
232
|
+
}
|
233
|
+
},
|
234
|
+
{
|
235
|
+
title: 'Interval',
|
236
|
+
type: :object,
|
237
|
+
properties: {
|
238
|
+
min: { type: :integer },
|
239
|
+
max: { type: :integer },
|
240
|
+
message: { type: :string },
|
241
|
+
}
|
242
|
+
}
|
243
|
+
]
|
244
|
+
},
|
245
|
+
number: {
|
246
|
+
type: :object,
|
247
|
+
properties: {
|
248
|
+
min: { type: :number },
|
249
|
+
max: { type: :number },
|
250
|
+
step: { type: :number },
|
251
|
+
mod: { type: :integer },
|
252
|
+
odd: { type: :boolean },
|
253
|
+
even: { type: :boolean },
|
254
|
+
message: { type: :string },
|
255
|
+
}
|
256
|
+
},
|
257
|
+
present: {
|
258
|
+
type: :object,
|
259
|
+
properties: {
|
260
|
+
empty: { type: :boolean },
|
261
|
+
message: { type: :string },
|
262
|
+
}
|
263
|
+
}
|
264
|
+
}
|
265
|
+
},
|
266
|
+
|
267
|
+
output_parameters: {
|
268
|
+
type: :object,
|
269
|
+
properties: {
|
270
|
+
parameters: {},
|
271
|
+
layout: {},
|
272
|
+
namespace: {}
|
273
|
+
}
|
274
|
+
},
|
275
|
+
|
276
|
+
action_meta: {
|
277
|
+
type: :object,
|
278
|
+
properties: {
|
279
|
+
object: {
|
280
|
+
input: { '$ref' => '#/definitions/input_parameters' },
|
281
|
+
output: { '$ref' => '#/definitions/output_parameters' },
|
282
|
+
},
|
283
|
+
global: {
|
284
|
+
input: { '$ref' => '#/definitions/input_parameters' },
|
285
|
+
output: { '$ref' => '#/definitions/output_parameters' },
|
286
|
+
}
|
287
|
+
}
|
288
|
+
}
|
289
|
+
}
|
290
|
+
|
291
|
+
ROOTS = {
|
292
|
+
all: {
|
293
|
+
title: 'Describe all API versions',
|
294
|
+
type: :object,
|
295
|
+
properties: {
|
296
|
+
default_version: {},
|
297
|
+
versions: {
|
298
|
+
type: :object,
|
299
|
+
patternProperties: {
|
300
|
+
'^.+$' => { '$ref' => '#/definitions/version' }
|
301
|
+
},
|
302
|
+
properties: {
|
303
|
+
default: { '$ref' => '#/definitions/version' }
|
304
|
+
},
|
305
|
+
}
|
306
|
+
},
|
307
|
+
required: %i(default_version versions)
|
308
|
+
},
|
309
|
+
|
310
|
+
versions: {
|
311
|
+
title: 'Show available API versions',
|
312
|
+
type: :object,
|
313
|
+
properties: {
|
314
|
+
versions: { type: :array },
|
315
|
+
default: {}
|
316
|
+
},
|
317
|
+
required: %i(versions default)
|
318
|
+
},
|
319
|
+
|
320
|
+
default: {
|
321
|
+
title: 'Describe only the default version of the API',
|
322
|
+
'$ref' => '#/definitions/version'
|
323
|
+
},
|
324
|
+
|
325
|
+
envelope: {
|
326
|
+
title: 'All response are wrapped in this envelope',
|
327
|
+
type: :object,
|
328
|
+
properties: {
|
329
|
+
version: {},
|
330
|
+
status: { type: :boolean },
|
331
|
+
response: { type: :object },
|
332
|
+
message: { type: :string },
|
333
|
+
errors: {
|
334
|
+
type: :object,
|
335
|
+
patternProperties: {
|
336
|
+
'^.+$' => { type: :array }
|
337
|
+
},
|
338
|
+
},
|
339
|
+
},
|
340
|
+
required: ['status'],
|
341
|
+
}
|
342
|
+
}
|
343
|
+
|
344
|
+
urls = {
|
345
|
+
'/' => {
|
346
|
+
root: :all,
|
347
|
+
definitions: true
|
348
|
+
},
|
349
|
+
'/?describe=versions' => {
|
350
|
+
root: :versions
|
351
|
+
},
|
352
|
+
'/?describe=default' => {
|
353
|
+
root: :default,
|
354
|
+
definitions: true
|
355
|
+
},
|
356
|
+
}
|
357
|
+
%>
|
358
|
+
|
359
|
+
<h1 id="envelope">Envelope</h1>
|
360
|
+
<pre><code><%= JSON.pretty_generate(ROOTS[:envelope]) %></code></pre>
|
361
|
+
|
362
|
+
<%
|
363
|
+
urls.each do |url, opts|
|
364
|
+
hash = ROOTS[opts[:root]]
|
365
|
+
hash = hash.merge(DEFINITIONS) if opts[:definitions]
|
366
|
+
%>
|
367
|
+
<h1 id="<%= opts[:root] %>">OPTIONS <%= url %></h1>
|
368
|
+
<pre><code><%= JSON.pretty_generate(hash) %></code></pre>
|
369
|
+
<% end %>
|