praxis 0.17.1 → 0.18.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/.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
|