praxis 0.17.1 → 0.18.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/CHANGELOG.md +18 -0
- data/lib/api_browser/package.json +1 -1
- data/lib/praxis/action_definition.rb +119 -14
- data/lib/praxis/api_general_info.rb +21 -3
- data/lib/praxis/application.rb +1 -0
- data/lib/praxis/dispatcher.rb +18 -15
- data/lib/praxis/docs/generator.rb +208 -0
- data/lib/praxis/handlers/www_form.rb +2 -1
- data/lib/praxis/media_type.rb +1 -1
- data/lib/praxis/multipart/part.rb +58 -6
- data/lib/praxis/request_stages/action.rb +10 -8
- data/lib/praxis/request_stages/request_stage.rb +2 -2
- data/lib/praxis/resource_definition.rb +11 -3
- data/lib/praxis/response_definition.rb +49 -25
- data/lib/praxis/restful_doc_generator.rb +9 -1
- data/lib/praxis/route.rb +16 -0
- data/lib/praxis/router.rb +1 -1
- data/lib/praxis/simple_media_type.rb +2 -1
- data/lib/praxis/tasks/api_docs.rb +10 -1
- data/lib/praxis/tasks/console.rb +10 -3
- data/lib/praxis/types/media_type_common.rb +8 -1
- data/lib/praxis/types/multipart.rb +5 -0
- data/lib/praxis/types/multipart_array.rb +12 -4
- data/lib/praxis/version.rb +1 -1
- data/lib/praxis.rb +3 -0
- data/praxis.gemspec +3 -3
- data/spec/functional_spec.rb +3 -3
- data/spec/praxis/action_definition_spec.rb +40 -0
- data/spec/praxis/api_general_info_spec.rb +10 -3
- data/spec/praxis/media_type_collection_spec.rb +11 -4
- data/spec/praxis/media_type_spec.rb +3 -1
- data/spec/praxis/request_stages/action_spec.rb +13 -6
- data/spec/praxis/resource_definition_spec.rb +1 -1
- data/spec/praxis/response_definition_spec.rb +29 -3
- data/spec/praxis/router_spec.rb +1 -1
- data/spec/praxis/types/multipart_array_spec.rb +92 -0
- data/spec/praxis/types/multipart_spec.rb +5 -0
- data/spec/spec_app/design/api.rb +1 -0
- data/spec/spec_app/design/media_types/volume.rb +1 -3
- data/spec/spec_app/design/resources/instances.rb +1 -1
- data/spec/support/spec_media_types.rb +5 -4
- metadata +10 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5c55f5d3363aed4bc5157e0064e35b399e1bcf20
|
4
|
+
data.tar.gz: 2bf66b28b0d80ea8e57de43b69dc5c76e7e504ea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 32fdd9a038fa46b129d1da10a9edee94d032c2ff7ce14889cf45f162b57e3d2aaf4d3ecc4f5827ddb84a2bc33490259293a4b84a3905cd2dd269f1fb0a2a29c2
|
7
|
+
data.tar.gz: 04b7c664a294d7766e65640855112fb80455f6ba3fe7c840bb5b70e5de4d8bcf13b58f8cefaf151ea3787644185c4e20070f5f5cb2edcf2c58d78822ca2fef63
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,23 @@
|
|
2
2
|
|
3
3
|
## next
|
4
4
|
|
5
|
+
## 0.18.0
|
6
|
+
|
7
|
+
* Added `display_name` DSL to `ResourceDefinition` and `MediaType`
|
8
|
+
* It is a purely informational field, mostly to be used by consumers of the generated docs
|
9
|
+
* It defaults to the class name (stripping any of the prefix modules)
|
10
|
+
* Revamped document generation to output a more compact format:
|
11
|
+
* 1 file per api version: including info, resources, schemas and traits.
|
12
|
+
* 1 single index file with global info plus just a list of version names
|
13
|
+
* new task currently usable through `bundle exec rake praxis:docs:generate_beta`
|
14
|
+
* NOTE: leaves the current doc generation tasks and code intact (until the doc browser is switched to use this)
|
15
|
+
* Specialized `Multipart`’s family in its description to be ‘multipart’ instead of ‘hash’.
|
16
|
+
* Added `Praxis::Handlers::FormData` for 'multipart/form-data'. Currently returns the input unchanged in `parse` and `generate`.
|
17
|
+
* Added `Praxis::Handlers::WWWForm` for form-encoded data.
|
18
|
+
* Added `Docs::Generator`, experimental new documentation generator. Use the `praxis:docs:experiments` rake task to generate. *Note*: not currently compatible with the documentation browser.
|
19
|
+
* Added 'praxis.request_stage.execute' `ActiveSupport::Notifications` instrumentation to contorller action method execution in `RequestStages::Action#execute`.
|
20
|
+
* Make action headers, params and payloads be required by default as it is probably what most people expect from it. To make any of those three definitions non-required, simply add the `:required` option as used in any other attribute definition. For example: `payload required: false do ...`
|
21
|
+
|
5
22
|
## 0.17.1
|
6
23
|
|
7
24
|
* Fixes an issue that would make exported documentation broken.
|
@@ -47,6 +64,7 @@ config option, and uses the `media_type` defined for the response definition.
|
|
47
64
|
* Adds hierarchival navigation to the doc browser.
|
48
65
|
* Adds a ConfigurationProvider allowing for easy doc customization.
|
49
66
|
|
67
|
+
|
50
68
|
## 0.16.1
|
51
69
|
|
52
70
|
* Fixed a bug where documentation generation would fail if an application had headers in a Trait using the simplified `header` DSL.
|
@@ -67,7 +67,7 @@ module Praxis
|
|
67
67
|
unless ApiDefinition.instance.traits.has_key? trait_name
|
68
68
|
raise Exceptions::InvalidTrait.new("Trait #{trait_name} not found in the system")
|
69
69
|
end
|
70
|
-
|
70
|
+
|
71
71
|
trait = ApiDefinition.instance.traits.fetch(trait_name)
|
72
72
|
trait.apply!(self)
|
73
73
|
traits << trait_name
|
@@ -82,7 +82,7 @@ module Praxis
|
|
82
82
|
def response(name, type=nil, **args, &block)
|
83
83
|
if type
|
84
84
|
# should verify type is a media type
|
85
|
-
|
85
|
+
|
86
86
|
if block_given?
|
87
87
|
type = type.construct(block)
|
88
88
|
end
|
@@ -103,7 +103,11 @@ module Praxis
|
|
103
103
|
end
|
104
104
|
|
105
105
|
def params(type=Attributor::Struct, **opts, &block)
|
106
|
-
return @params if !block && type == Attributor::Struct
|
106
|
+
return @params if !block && ( opts.nil? || opts.empty? ) && type == Attributor::Struct
|
107
|
+
|
108
|
+
unless( opts.key? :required )
|
109
|
+
opts[:required] = true # Make the payload required by default
|
110
|
+
end
|
107
111
|
|
108
112
|
if @params
|
109
113
|
unless type == Attributor::Struct && @params.type < Attributor::Struct
|
@@ -128,7 +132,11 @@ module Praxis
|
|
128
132
|
end
|
129
133
|
|
130
134
|
def payload(type=Attributor::Struct, **opts, &block)
|
131
|
-
return @payload if !block && type == Attributor::Struct
|
135
|
+
return @payload if !block && ( opts.nil? || opts.empty? ) && type == Attributor::Struct
|
136
|
+
|
137
|
+
unless( opts.key? :required )
|
138
|
+
opts[:required] = true # Make the payload required by default
|
139
|
+
end
|
132
140
|
|
133
141
|
if @payload
|
134
142
|
unless type == Attributor::Struct && @payload.type < Attributor::Struct
|
@@ -144,6 +152,11 @@ module Praxis
|
|
144
152
|
|
145
153
|
def headers(type=nil, **opts, &block)
|
146
154
|
return @headers unless block
|
155
|
+
|
156
|
+
unless( opts.key? :required )
|
157
|
+
opts[:required] = true # Make the payload required by default
|
158
|
+
end
|
159
|
+
|
147
160
|
if @headers
|
148
161
|
update_attribute(@headers, opts, block)
|
149
162
|
else
|
@@ -188,32 +201,56 @@ module Praxis
|
|
188
201
|
@description
|
189
202
|
end
|
190
203
|
|
204
|
+
def self.url_description(route:, params_example:, params:)
|
205
|
+
route_description = route.describe
|
206
|
+
|
207
|
+
example_hash = params_example ? params_example.dump : {}
|
208
|
+
hash = route.example(example_hash: example_hash, params: params)
|
209
|
+
|
210
|
+
query_string = URI.encode_www_form(hash[:query_params])
|
211
|
+
url = hash[:url]
|
212
|
+
url = [url,query_string].join('?') unless query_string.empty?
|
213
|
+
|
214
|
+
route_description[:example] = url
|
215
|
+
route_description
|
216
|
+
end
|
191
217
|
|
192
|
-
def describe
|
218
|
+
def describe(context: nil)
|
193
219
|
{}.tap do |hash|
|
194
220
|
hash[:description] = description
|
195
221
|
hash[:name] = name
|
196
222
|
hash[:metadata] = metadata
|
197
|
-
|
198
|
-
|
199
|
-
|
223
|
+
if headers
|
224
|
+
headers_example = headers.example(context)
|
225
|
+
hash[:headers] = headers.describe(example: headers_example)
|
226
|
+
end
|
200
227
|
if params
|
201
|
-
|
228
|
+
params_example = params.example(context)
|
229
|
+
hash[:params] = params_description(example: params_example)
|
202
230
|
end
|
203
|
-
|
231
|
+
if payload
|
232
|
+
payload_example = payload.example(context)
|
233
|
+
|
234
|
+
hash[:payload] = payload_description(example: payload_example)
|
235
|
+
end
|
236
|
+
|
204
237
|
hash[:responses] = responses.inject({}) do |memo, (response_name, response)|
|
205
|
-
memo[response.name] = response.describe
|
238
|
+
memo[response.name] = response.describe(context: context)
|
206
239
|
memo
|
207
240
|
end
|
208
241
|
hash[:traits] = traits if traits.any?
|
209
|
-
|
242
|
+
# FIXME: change to :routes along with api browser
|
243
|
+
hash[:urls] = routes.collect do |route|
|
244
|
+
ActionDefinition.url_description(route: route, params: self.params, params_example: params_example)
|
245
|
+
end.compact
|
210
246
|
self.class.doc_decorations.each do |callback|
|
211
247
|
callback.call(self, hash)
|
212
248
|
end
|
213
249
|
end
|
214
250
|
end
|
215
251
|
|
216
|
-
|
252
|
+
|
253
|
+
def params_description(example:)
|
217
254
|
route_params = []
|
218
255
|
if primary_route.nil?
|
219
256
|
warn "Warning: No routes defined for #{resource_definition.name}##{name}."
|
@@ -224,7 +261,7 @@ module Praxis
|
|
224
261
|
collect(&:to_sym)
|
225
262
|
end
|
226
263
|
|
227
|
-
desc = params.describe
|
264
|
+
desc = params.describe(example: example)
|
228
265
|
desc[:type][:attributes].keys.each do |k|
|
229
266
|
source = if route_params.include? k
|
230
267
|
'url'
|
@@ -236,6 +273,74 @@ module Praxis
|
|
236
273
|
desc
|
237
274
|
end
|
238
275
|
|
276
|
+
# Determine the content_type to report for a given example,
|
277
|
+
# using handler_name if possible.
|
278
|
+
#
|
279
|
+
# Considers any pre-defined set of values on the content_type attributge
|
280
|
+
# of the headers.
|
281
|
+
def derive_content_type(example, handler_name)
|
282
|
+
# MultipartArrays *must* use the provided content_type
|
283
|
+
if example.kind_of? Praxis::Types::MultipartArray
|
284
|
+
return MediaTypeIdentifier.load(example.content_type)
|
285
|
+
end
|
286
|
+
|
287
|
+
_, content_type_attribute = self.headers && self.headers.attributes.find { |k,v| k.to_s =~ /^content[-_]{1}type$/i }
|
288
|
+
if content_type_attribute && content_type_attribute.options.key?(:values)
|
289
|
+
|
290
|
+
# if any defined value match the preferred handler_name, return it
|
291
|
+
content_type_attribute.options[:values].each do |ct|
|
292
|
+
mti = MediaTypeIdentifier.load(ct)
|
293
|
+
return mti if mti.handler_name == handler_name
|
294
|
+
end
|
295
|
+
|
296
|
+
# otherwise, pick the first
|
297
|
+
pick = MediaTypeIdentifier.load(content_type_attribute.options[:values].first)
|
298
|
+
|
299
|
+
# and return that one if it already corresponds to a registered handler
|
300
|
+
# otherwise, add the encoding
|
301
|
+
if Praxis::Application.instance.handlers.include?(pick.handler_name)
|
302
|
+
return pick
|
303
|
+
else
|
304
|
+
return pick + handler_name
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
# generic default encoding
|
309
|
+
MediaTypeIdentifier.load("application/#{handler_name}")
|
310
|
+
end
|
311
|
+
|
312
|
+
|
313
|
+
def payload_description(example:)
|
314
|
+
hash = payload.describe(example: example)
|
315
|
+
|
316
|
+
hash[:examples] = {}
|
317
|
+
|
318
|
+
default_handlers = ApiDefinition.instance.info.consumes
|
319
|
+
|
320
|
+
default_handlers.each do |default_handler|
|
321
|
+
dumped_payload = payload.dump(example, default_format: default_handler)
|
322
|
+
|
323
|
+
content_type = derive_content_type(example, default_handler)
|
324
|
+
handler = Praxis::Application.instance.handlers[content_type.handler_name]
|
325
|
+
|
326
|
+
# in case handler is nil, use dumped_payload as-is.
|
327
|
+
generated_payload = if handler.nil?
|
328
|
+
dumped_payload
|
329
|
+
else
|
330
|
+
handler.generate(dumped_payload)
|
331
|
+
end
|
332
|
+
|
333
|
+
|
334
|
+
hash[:examples][default_handler] = {
|
335
|
+
content_type: content_type.to_s,
|
336
|
+
body: generated_payload
|
337
|
+
}
|
338
|
+
end
|
339
|
+
|
340
|
+
hash
|
341
|
+
end
|
342
|
+
|
343
|
+
|
239
344
|
def nodoc!
|
240
345
|
metadata[:doc_visibility] = :none
|
241
346
|
end
|
@@ -10,8 +10,9 @@ module Praxis
|
|
10
10
|
|
11
11
|
if @global_info.nil? # this *is* the global info
|
12
12
|
version_with [:header, :params]
|
13
|
+
consumes 'json', 'x-www-form-urlencoded'
|
14
|
+
produces 'json'
|
13
15
|
end
|
14
|
-
|
15
16
|
end
|
16
17
|
|
17
18
|
def get(k)
|
@@ -97,6 +98,24 @@ module Praxis
|
|
97
98
|
end
|
98
99
|
end
|
99
100
|
|
101
|
+
def consumes(*vals)
|
102
|
+
if vals.empty?
|
103
|
+
return get(:consumes)
|
104
|
+
else
|
105
|
+
return set(:consumes, vals)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
def produces(*vals)
|
111
|
+
if vals.empty?
|
112
|
+
return get(:produces)
|
113
|
+
else
|
114
|
+
return set(:produces, vals)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
|
100
119
|
def base_params(type=Attributor::Struct, **opts, &block)
|
101
120
|
if !block && type == Attributor::Struct
|
102
121
|
get(:base_params)
|
@@ -105,10 +124,9 @@ module Praxis
|
|
105
124
|
end
|
106
125
|
end
|
107
126
|
|
108
|
-
|
109
127
|
def describe
|
110
128
|
hash = { schema_version: "1.0".freeze }
|
111
|
-
[:name, :title, :description, :base_path, :version_with, :endpoint].each do |attr|
|
129
|
+
[:name, :title, :description, :base_path, :version_with, :endpoint, :consumes, :produces].each do |attr|
|
112
130
|
val = self.__send__(attr)
|
113
131
|
hash[attr] = val unless val.nil?
|
114
132
|
end
|
data/lib/praxis/application.rb
CHANGED
data/lib/praxis/dispatcher.rb
CHANGED
@@ -79,25 +79,27 @@ module Praxis
|
|
79
79
|
payload = {request: request, response: nil}
|
80
80
|
|
81
81
|
Notifications.instrument 'praxis.request.all'.freeze, payload do
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
82
|
+
begin
|
83
|
+
# the response stage must be the final stage in the list
|
84
|
+
*stages, response_stage = @stages
|
85
|
+
|
86
|
+
stages.each do |stage|
|
87
|
+
result = stage.run
|
88
|
+
case result
|
89
|
+
when Response
|
90
|
+
controller.response = result
|
91
|
+
break
|
92
|
+
end
|
91
93
|
end
|
92
|
-
end
|
93
94
|
|
94
|
-
|
95
|
+
response_stage.run
|
95
96
|
|
96
|
-
|
97
|
-
|
97
|
+
payload[:response] = controller.response
|
98
|
+
controller.response.finish
|
99
|
+
rescue => e
|
100
|
+
@application.error_handler.handle!(request, e)
|
101
|
+
end
|
98
102
|
end
|
99
|
-
rescue => e
|
100
|
-
@application.error_handler.handle!(request, e)
|
101
103
|
ensure
|
102
104
|
@controller = nil
|
103
105
|
@action = nil
|
@@ -105,6 +107,7 @@ module Praxis
|
|
105
107
|
end
|
106
108
|
|
107
109
|
|
110
|
+
# TODO: fix for multithreaded environments
|
108
111
|
def reset_cache!
|
109
112
|
return unless Praxis::Blueprint.caching_enabled?
|
110
113
|
|
@@ -0,0 +1,208 @@
|
|
1
|
+
module Praxis
|
2
|
+
module Docs
|
3
|
+
|
4
|
+
class Generator
|
5
|
+
API_DOCS_DIRNAME = 'docs/api'
|
6
|
+
|
7
|
+
attr_reader :resources_by_version, :types_by_id, :infos_by_version
|
8
|
+
attr_reader :doc_root_dir
|
9
|
+
|
10
|
+
EXCLUDED_TYPES_FROM_OUTPUT = Set.new([
|
11
|
+
Attributor::Boolean,
|
12
|
+
Attributor::CSV,
|
13
|
+
Attributor::DateTime,
|
14
|
+
Attributor::Float,
|
15
|
+
Attributor::Hash,
|
16
|
+
Attributor::Ids,
|
17
|
+
Attributor::Integer,
|
18
|
+
Attributor::Object,
|
19
|
+
Attributor::String,
|
20
|
+
Attributor::Symbol
|
21
|
+
]).freeze
|
22
|
+
|
23
|
+
|
24
|
+
def initialize(root)
|
25
|
+
require 'yaml'
|
26
|
+
@resources_by_version = Hash.new do |h,k|
|
27
|
+
h[k] = Set.new
|
28
|
+
end
|
29
|
+
initialize_directories(root)
|
30
|
+
|
31
|
+
Attributor::AttributeResolver.current = Attributor::AttributeResolver.new
|
32
|
+
collect_infos
|
33
|
+
collect_resources
|
34
|
+
collect_types
|
35
|
+
end
|
36
|
+
|
37
|
+
def save!
|
38
|
+
write_index_file
|
39
|
+
resources_by_version.keys.each do |version|
|
40
|
+
write_version_file(version)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def initialize_directories(root)
|
47
|
+
@doc_root_dir = File.join(root, API_DOCS_DIRNAME)
|
48
|
+
|
49
|
+
# remove previous data (and reset the directory)
|
50
|
+
FileUtils.rm_rf @doc_root_dir if File.exists?(@doc_root_dir)
|
51
|
+
FileUtils.mkdir_p @doc_root_dir unless File.exists? @doc_root_dir
|
52
|
+
end
|
53
|
+
|
54
|
+
def collect_resources
|
55
|
+
# load all resource definitions registered with Praxis
|
56
|
+
Praxis::Application.instance.resource_definitions.map do |resource|
|
57
|
+
# skip resources with doc_visibility of :none
|
58
|
+
next if resource.metadata[:doc_visibility] == :none
|
59
|
+
version = resource.version
|
60
|
+
# TODO: it seems that we shouldn't hardcode n/a in Praxis
|
61
|
+
# version = "unversioned" if version == "n/a"
|
62
|
+
@resources_by_version[version] << resource
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def collect_types
|
67
|
+
@types_by_id = ObjectSpace.each_object( Class ).select do |obj|
|
68
|
+
obj < Attributor::Type
|
69
|
+
end.index_by(&:id)
|
70
|
+
end
|
71
|
+
|
72
|
+
def collect_infos
|
73
|
+
# All infos. Including keys for `:global`, "n/a", and any string version
|
74
|
+
@infos_by_version = ApiDefinition.instance.describe
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
# Recursively inspect the structure in data and collect any
|
79
|
+
# newly discovered types into the `reachable_types` in/out parameter
|
80
|
+
def collect_reachable_types( data, reachable_types )
|
81
|
+
case data
|
82
|
+
when Array
|
83
|
+
data.collect{|item| collect_reachable_types( item , reachable_types) }
|
84
|
+
when Hash
|
85
|
+
if data.key?(:type) && data[:type].kind_of?(Hash) && ( [:id,:name,:family] - data[:type].keys ).empty?
|
86
|
+
type_id = data[:type][:id]
|
87
|
+
unless type_id.nil?
|
88
|
+
unless types_by_id[type_id]
|
89
|
+
raise "Error! We have detected a reference to a 'Type' which is not derived from Attributor::Type" +
|
90
|
+
" Document generation cannot proceed."
|
91
|
+
end
|
92
|
+
reachable_types << types_by_id[type_id]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
data.values.map{|item| collect_reachable_types( item , reachable_types)}
|
96
|
+
end
|
97
|
+
reachable_types
|
98
|
+
end
|
99
|
+
|
100
|
+
def write_index_file
|
101
|
+
# Gather the versions
|
102
|
+
versions = infos_by_version.keys.reject{|v| v == :global || v == :traits }.map do |version|
|
103
|
+
version == "n/a" ? "unversioned" : version
|
104
|
+
end
|
105
|
+
data = {
|
106
|
+
info: infos_by_version[:global][:info],
|
107
|
+
versions: versions
|
108
|
+
# Note, I don't think we need to report the global traits (but rather the ones in the version)
|
109
|
+
}
|
110
|
+
filename = File.join(doc_root_dir, "index-new.json")
|
111
|
+
puts "Generating Index file: #{filename}"
|
112
|
+
File.open(filename, 'w') {|f| f.write(JSON.pretty_generate(data))}
|
113
|
+
end
|
114
|
+
|
115
|
+
def write_version_file( version )
|
116
|
+
version_info = infos_by_version[version]
|
117
|
+
# Hack, let's "inherit/copy" all traits of a version from the global definition
|
118
|
+
# Eventually traits should be defined for a version (and inheritable from global) so we'll emulate that here
|
119
|
+
version_info[:traits] = infos_by_version[:traits]
|
120
|
+
dumped_resources = dump_resources( resources_by_version[version] )
|
121
|
+
found_media_types = resources_by_version[version].collect {|r| r.media_type.describe }
|
122
|
+
|
123
|
+
collected_types = Set.new
|
124
|
+
collect_reachable_types( dumped_resources, collected_types );
|
125
|
+
collect_reachable_types( found_media_types , collected_types );
|
126
|
+
|
127
|
+
dumped_schemas = dump_schemas( collected_types )
|
128
|
+
|
129
|
+
full_data = {
|
130
|
+
info: version_info[:info],
|
131
|
+
resources: dumped_resources,
|
132
|
+
schemas: nil,#dumped_schemas,
|
133
|
+
traits: version_info[:traits] || []
|
134
|
+
}
|
135
|
+
# Write the file
|
136
|
+
version_file = ( version == "n/a" ? "unversioned" : version )
|
137
|
+
filename = File.join(doc_root_dir, version_file)
|
138
|
+
|
139
|
+
puts "Generating API file: #{filename} (in json and yaml)"
|
140
|
+
File.open(filename+".json", 'w') {|f| f.write(JSON.pretty_generate(full_data))}
|
141
|
+
File.open(filename+".yml", 'w') {|f| f.write(YAML.dump(full_data))}
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
def dump_resources( resources )
|
146
|
+
resources.each_with_object({}) do |r, hash|
|
147
|
+
# Do not report undocumentable resources
|
148
|
+
next if r.metadata[:doc_visibility] == :none
|
149
|
+
context = [r.id]
|
150
|
+
resource_description = r.describe(context: context)
|
151
|
+
|
152
|
+
# strip actions with doc_visibility of :none
|
153
|
+
resource_description[:actions].reject! { |a| a[:metadata][:doc_visibility] == :none }
|
154
|
+
|
155
|
+
# Go through the params/payload of each action and augment them by
|
156
|
+
# adding a generated example (then stick it into the description hash)
|
157
|
+
r.actions.each do |action_name, action|
|
158
|
+
# skip actions with doc_visibility of :none
|
159
|
+
next if action.metadata[:doc_visibility] == :none
|
160
|
+
|
161
|
+
action_description = resource_description[:actions].find {|a| a[:name] == action_name }
|
162
|
+
end
|
163
|
+
|
164
|
+
hash[r.id] = resource_description
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
def dump_schemas(types)
|
170
|
+
reportable_types = types - EXCLUDED_TYPES_FROM_OUTPUT
|
171
|
+
reportable_types.each_with_object({}) do |type, array|
|
172
|
+
context = [type.id]
|
173
|
+
example_data = type.example(context)
|
174
|
+
type_output = type.describe(false, example: example_data)
|
175
|
+
unless type_output[:display_name]
|
176
|
+
# For non MediaTypes or pure types or anonymous types fallback to their name, and worst case to their id
|
177
|
+
type_output[:display_name] = type_output[:name] || type_output[:id]
|
178
|
+
end
|
179
|
+
if type_output[:views]
|
180
|
+
type_output[:views].delete(:master)
|
181
|
+
type_output[:views].each do |view_name, view_info|
|
182
|
+
view_info[:example] = example_data.render(view: view_name)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
type_output[:example] = if example_data.respond_to? :render
|
186
|
+
example_data.render(view: :master)
|
187
|
+
else
|
188
|
+
type.dump(example_data)
|
189
|
+
end
|
190
|
+
array[type.id] = type_output
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
|
195
|
+
def dump_example_for(context_name, object)
|
196
|
+
example = object.example(Array(context_name))
|
197
|
+
if object.is_a? Praxis::Blueprint
|
198
|
+
example.render(view: :master)
|
199
|
+
elsif object.is_a? Attributor::Attribute
|
200
|
+
object.dump(example)
|
201
|
+
else
|
202
|
+
raise "Do not know how to dump this object (it is not a Blueprint or an Attribute): #{object}"
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
@@ -13,7 +13,8 @@ module Praxis
|
|
13
13
|
# Generate a URL-encoded WWW form from structured data. Not implemented since this format
|
14
14
|
# is not very useful for a response body.
|
15
15
|
def generate(structured_data)
|
16
|
-
|
16
|
+
return nil if structured_data.nil?
|
17
|
+
URI.encode_www_form(structured_data)
|
17
18
|
end
|
18
19
|
end
|
19
20
|
end
|