haveapi 0.3.2 → 0.4.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/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 %>
|