Class: Praxis::ActionDefinition
- Inherits:
-
Object
- Object
- Praxis::ActionDefinition
show all
- Defined in:
- lib/praxis.rb,
lib/praxis/action_definition.rb,
lib/praxis/action_definition/headers_dsl_compiler.rb
Defined Under Namespace
Classes: HeadersDSLCompiler
Class Attribute Summary collapse
Instance Attribute Summary collapse
-
#api_definition ⇒ Object
readonly
Returns the value of attribute api_definition.
-
#endpoint_definition ⇒ Object
readonly
Returns the value of attribute endpoint_definition.
-
#metadata ⇒ Object
readonly
opaque hash of user-defined medata, used to decorate the definition, and also available in the generated JSON documents.
-
#name ⇒ Object
Setter/reader for a possible ‘sister’ action that is defined as post, and has the payload with the same structure as this GET action (with the exception of the params in the path attributes).
-
#responses ⇒ Object
readonly
Returns the value of attribute responses.
-
#route ⇒ Object
readonly
Returns the value of attribute route.
-
#sister_get_action ⇒ Object
Setter/reader for a possible ‘sister’ action that is defined as post, and has the payload with the same structure as this GET action (with the exception of the params in the path attributes).
-
#sister_post_action ⇒ Object
Setter/reader for a possible ‘sister’ action that is defined as post, and has the payload with the same structure as this GET action (with the exception of the params in the path attributes).
-
#traits ⇒ Object
readonly
Returns the value of attribute traits.
Class Method Summary
collapse
Instance Method Summary
collapse
-
#_internal_set(**args) ⇒ Object
-
#clone_action_as(name:) ⇒ Object
-
#clone_action_as_post(at:) ⇒ Object
-
#create_attribute(type = Attributor::Struct, **opts, &block) ⇒ Object
-
#derive_content_type(example, handler_name) ⇒ Object
Determine the content_type to report for a given example, using handler_name if possible.
-
#describe(context: nil) ⇒ Object
-
#description(text = nil) ⇒ Object
-
#enable_large_params_proxy_action(at: true) ⇒ Object
-
#headers(type = nil, **opts, &block) ⇒ Object
-
#headers_description(example:) ⇒ Object
-
#initialize(name, endpoint_definition, **_opts, &block) ⇒ ActionDefinition
constructor
A new instance of ActionDefinition.
-
#nodoc! ⇒ Object
-
#params(type = Attributor::Struct, **opts, &block) ⇒ Object
-
#params_description(example:) ⇒ Object
-
#payload(type = Attributor::Struct, **opts, &block) ⇒ Object
-
#payload_description(example:) ⇒ Object
-
#precomputed_header_keys_for_rack ⇒ Object
Good optimization to avoid creating lots of strings and comparisons on a per-request basis.
-
#resource_definition ⇒ Object
-
#response(name, type = nil, **args, &block) ⇒ Object
-
#routing(&block) ⇒ Object
-
#trait(trait_name) ⇒ Object
-
#update_attribute(attribute, options, block) ⇒ Object
Constructor Details
#initialize(name, endpoint_definition, **_opts, &block) ⇒ ActionDefinition
Returns a new instance of ActionDefinition.
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
# File 'lib/praxis/action_definition.rb', line 34
def initialize(name, endpoint_definition, **_opts, &block)
@name = name
@endpoint_definition = endpoint_definition
@responses = {}
@metadata = {}
@route = nil
@traits = []
if (media_type = endpoint_definition.media_type) && (media_type.is_a?(Class) && media_type < Praxis::Types::MediaTypeCommon)
@reference_media_type = media_type
end
version = endpoint_definition.version
api_info = ApiDefinition.instance.info(endpoint_definition.version)
route_base = "#{api_info.base_path}#{endpoint_definition.version_prefix}"
prefix = Array(endpoint_definition.routing_prefix)
@routing_config = RoutingConfig.new(version: version, base: route_base, prefix: prefix)
endpoint_definition.traits.each do |trait|
self.trait(trait)
end
endpoint_definition.action_defaults.apply!(self)
instance_eval(&block) if block_given?
end
|
Class Attribute Details
.doc_decorations ⇒ Object
Returns the value of attribute doc_decorations.
25
26
27
|
# File 'lib/praxis/action_definition.rb', line 25
def doc_decorations
@doc_decorations
end
|
Instance Attribute Details
#api_definition ⇒ Object
Returns the value of attribute api_definition.
14
15
16
|
# File 'lib/praxis/action_definition.rb', line 14
def api_definition
@api_definition
end
|
#endpoint_definition ⇒ Object
Returns the value of attribute endpoint_definition.
14
15
16
|
# File 'lib/praxis/action_definition.rb', line 14
def endpoint_definition
@endpoint_definition
end
|
opaque hash of user-defined medata, used to decorate the definition, and also available in the generated JSON documents
18
19
20
|
# File 'lib/praxis/action_definition.rb', line 18
def metadata
@metadata
end
|
#name ⇒ Object
Setter/reader for a possible ‘sister’ action that is defined as post, and has the payload with the same structure as this GET action (with the exception of the params in the path attributes)
22
23
24
|
# File 'lib/praxis/action_definition.rb', line 22
def name
@name
end
|
#responses ⇒ Object
Returns the value of attribute responses.
14
15
16
|
# File 'lib/praxis/action_definition.rb', line 14
def responses
@responses
end
|
#route ⇒ Object
Returns the value of attribute route.
14
15
16
|
# File 'lib/praxis/action_definition.rb', line 14
def route
@route
end
|
#sister_get_action ⇒ Object
Setter/reader for a possible ‘sister’ action that is defined as post, and has the payload with the same structure as this GET action (with the exception of the params in the path attributes)
22
23
24
|
# File 'lib/praxis/action_definition.rb', line 22
def sister_get_action
@sister_get_action
end
|
#sister_post_action ⇒ Object
Setter/reader for a possible ‘sister’ action that is defined as post, and has the payload with the same structure as this GET action (with the exception of the params in the path attributes)
22
23
24
|
# File 'lib/praxis/action_definition.rb', line 22
def sister_post_action
@sister_post_action
end
|
#traits ⇒ Object
Returns the value of attribute traits.
14
15
16
|
# File 'lib/praxis/action_definition.rb', line 14
def traits
@traits
end
|
Class Method Details
.decorate_docs(&callback) ⇒ Object
30
31
32
|
# File 'lib/praxis/action_definition.rb', line 30
def self.decorate_docs(&callback)
doc_decorations << callback
end
|
.url_description(route:, params_example:, params:) ⇒ Object
171
172
173
174
175
176
177
178
179
180
181
182
183
|
# File 'lib/praxis/action_definition.rb', line 171
def self.url_description(route:, params_example:, params:)
route_description = route.describe
example_hash = params_example ? params_example.dump : {}
hash = route.example(example_hash: example_hash, params: params)
query_string = URI.encode_www_form(hash[:query_params])
url = hash[:url]
url = [url, query_string].join('?') unless query_string.empty?
route_description[:example] = url
route_description
end
|
Instance Method Details
#_internal_set(**args) ⇒ Object
381
382
383
384
|
# File 'lib/praxis/action_definition.rb', line 381
def _internal_set(**args)
@payload = args[:payload] if args.key?(:payload)
@params = args[:params] if args.key?(:params)
end
|
#clone_action_as(name:) ⇒ Object
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
|
# File 'lib/praxis/action_definition.rb', line 365
def clone_action_as(name:)
cloned = clone
cloned.instance_eval do
@name = name.to_sym
@description = @description.clone
@metadata = @metadata.clone
@params = @params.clone
@responses = @responses.clone
@route = @route.clone
@routing_config = @routing_config.clone
@sister_post_action = @sister_post_action.clone
@traits = @traits.clone
end
cloned
end
|
#clone_action_as_post(at:) ⇒ Object
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
|
# File 'lib/praxis/action_definition.rb', line 328
def clone_action_as_post(at:)
action_name = name
cloned = clone_action_as(name: "#{action_name}_with_post")
raise "Only GET actions support the 'enable_large_params_proxy_action' DSL. Action #{action_name} is a #{rt.verb}" unless route.verb == 'GET'
cloned.instance_eval do
routing do
post "/#{at}"
end
end
raise "Using enable_large_params_proxy_action for an action requires the GET payload to be empty. Action #{name} has a payload defined" unless payload.nil?
route_params = route.path.named_captures.keys.collect(&:to_sym)
params_in_route = []
params_in_query = []
cloned.params.type.attributes.each do |k, _val|
if route_params.include? k
params_in_route.push k
else
params_in_query.push k
end
end
cloned._internal_set(
payload: cloned.params.duplicate(type: params.type.clone.slice!(*params_in_query)),
params: cloned.params.duplicate(type: params.type.clone.slice!(*params_in_route))
)
cloned.sister_get_action = self
self.sister_post_action = cloned
cloned
end
|
#create_attribute(type = Attributor::Struct, **opts, &block) ⇒ Object
89
90
91
92
93
|
# File 'lib/praxis/action_definition.rb', line 89
def create_attribute(type = Attributor::Struct, **opts, &block)
opts[:reference] = @reference_media_type if !opts.key?(:reference) && (@reference_media_type && block)
Attributor::Attribute.new(type, opts, &block)
end
|
#derive_content_type(example, handler_name) ⇒ Object
Determine the content_type to report for a given example, using handler_name if possible.
Considers any pre-defined set of values on the content_type attributge of the headers.
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
|
# File 'lib/praxis/action_definition.rb', line 259
def derive_content_type(example, handler_name)
return MediaTypeIdentifier.load(example.content_type) if example.is_a? Praxis::Types::MultipartArray
_, content_type_attribute = &.attributes&.find { |k, _v| k.to_s =~ /^content[-_]{1}type$/i }
if content_type_attribute&.options&.key?(:values)
content_type_attribute.options[:values].each do |ct|
mti = MediaTypeIdentifier.load(ct)
return mti if mti.handler_name == handler_name
end
pick = MediaTypeIdentifier.load(content_type_attribute.options[:values].first)
return pick if Praxis::Application.instance.handlers.include?(pick.handler_name)
return pick + handler_name
end
MediaTypeIdentifier.load("application/#{handler_name}")
end
|
#describe(context: nil) ⇒ Object
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
|
# File 'lib/praxis/action_definition.rb', line 185
def describe(context: nil)
{}.tap do |hash|
hash[:description] = description
hash[:name] = name
hash[:metadata] = metadata
if
= .example(context)
hash[:headers] = (example: )
end
if params
params_example = params.example(context)
hash[:params] = params_description(example: params_example)
end
if payload
payload_example = payload.example(context)
hash[:payload] = payload_description(example: payload_example)
end
hash[:responses] = responses.each_with_object({}) do |(_response_name, response), memo|
memo[response.name] = response.describe(context: context)
end
hash[:traits] = traits if traits.any?
hash[:urls] = [ActionDefinition.url_description(route: route, params: params, params_example: params_example)]
self.class.doc_decorations.each do |callback|
callback.call(self, hash)
end
end
end
|
#description(text = nil) ⇒ Object
166
167
168
169
|
# File 'lib/praxis/action_definition.rb', line 166
def description(text = nil)
@description = text if text
@description
end
|
#enable_large_params_proxy_action(at: true) ⇒ Object
319
320
321
|
# File 'lib/praxis/action_definition.rb', line 319
def enable_large_params_proxy_action(at: true)
self.sister_post_action = at end
|
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
|
# File 'lib/praxis/action_definition.rb', line 129
def (type = nil, **opts, &block)
return @headers unless block
unless opts.key? :required
opts[:required] = true end
if @headers
update_attribute(@headers, opts, block)
else
type ||= Attributor::Hash.of(key: String)
@headers = create_attribute(type,
dsl_compiler: HeadersDSLCompiler, case_insensitive_load: true,
**opts, &block)
@headers
end
@precomputed_header_keys_for_rack = nil end
|
217
218
219
220
221
222
223
224
|
# File 'lib/praxis/action_definition.rb', line 217
def (example:)
output = .describe(example: example)
= .attributes.select { |_k, attr| attr.options && attr.options[:required] == true }
output[:example] = .each_with_object({}) do |(name, _attr), hash|
hash[name] = example[name].to_s end
output
end
|
#nodoc! ⇒ Object
315
316
317
|
# File 'lib/praxis/action_definition.rb', line 315
def nodoc!
metadata[:doc_visibility] = :none
end
|
#params(type = Attributor::Struct, **opts, &block) ⇒ Object
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
|
# File 'lib/praxis/action_definition.rb', line 95
def params(type = Attributor::Struct, **opts, &block)
return @params if !block && (opts.nil? || opts.empty?) && type == Attributor::Struct
unless opts.key? :required
opts[:required] = true end
if @params
raise Exceptions::InvalidConfiguration, "Invalid type received for extending params: #{type.name}" unless type == Attributor::Struct && @params.type < Attributor::Struct
update_attribute(@params, opts, block)
else
@params = create_attribute(type, **opts, &block)
end
@params
end
|
#params_description(example:) ⇒ Object
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
|
# File 'lib/praxis/action_definition.rb', line 226
def params_description(example:)
route_params = []
if route.nil?
warn "Warning: No route defined for #{endpoint_definition.name}##{name}."
else
route_params = route.path
.named_captures
.keys
.collect(&:to_sym)
end
desc = params.describe(example: example)
desc[:type][:attributes].each_key do |k|
source = if route_params.include? k
'url'
else
'query'
end
desc[:type][:attributes][k][:source] = source
end
required_params = desc[:type][:attributes].select { |_k, v| v[:source] == 'query' && v[:required] == true }.keys
phash = required_params.each_with_object({}) do |name, hash|
hash[name] = example[name]
end
desc[:example] = URI.encode_www_form(phash)
desc
end
|
#payload(type = Attributor::Struct, **opts, &block) ⇒ Object
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
# File 'lib/praxis/action_definition.rb', line 113
def payload(type = Attributor::Struct, **opts, &block)
return @payload if !block && (opts.nil? || opts.empty?) && type == Attributor::Struct
unless opts.key?(:required)
opts = { required: true, null: false }.merge(opts) end
if @payload
raise Exceptions::InvalidConfiguration, "Invalid type received for extending params: #{type.name}" unless type == Attributor::Struct && @payload.type < Attributor::Struct
update_attribute(@payload, opts, block)
else
@payload = create_attribute(type, **opts, &block)
end
end
|
#payload_description(example:) ⇒ Object
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
|
# File 'lib/praxis/action_definition.rb', line 286
def payload_description(example:)
hash = payload.describe(example: example)
hash[:examples] = {}
default_handlers = ApiDefinition.instance.info.consumes
default_handlers.each do |default_handler|
dumped_payload = payload.dump(example, default_format: default_handler)
content_type = derive_content_type(example, default_handler)
handler = Praxis::Application.instance.handlers[content_type.handler_name]
generated_payload = if handler.nil?
dumped_payload
else
handler.generate(dumped_payload)
end
hash[:examples][default_handler] = {
content_type: content_type.to_s,
body: generated_payload
}
end
hash
end
|
Good optimization to avoid creating lots of strings and comparisons on a per-request basis. However, this is hacky, as it is rack-specific, and does not really belong here
152
153
154
155
156
157
158
|
# File 'lib/praxis/action_definition.rb', line 152
def
@precomputed_header_keys_for_rack ||= @headers.attributes.keys.each_with_object({}) do |key, hash|
name = key.to_s
name = "HTTP_#{name.gsub('-', '_').upcase}" unless %w[CONTENT_TYPE CONTENT_LENGTH].include?(name)
hash[name] = key
end
end
|
#resource_definition ⇒ Object
324
325
326
|
# File 'lib/praxis/action_definition.rb', line 324
def resource_definition
raise 'Praxis::ActionDefinition does not use `resource_definition` any longer. Use `endpoint_definition` instead.'
end
|
#response(name, type = nil, **args, &block) ⇒ Object
76
77
78
79
80
81
82
83
84
85
86
87
|
# File 'lib/praxis/action_definition.rb', line 76
def response(name, type = nil, **args, &block)
if type
type = type.construct(block) if block_given?
args[:media_type] = type
end
template = ApiDefinition.instance.response(name)
@responses[name] = template.compile(self, **args)
end
|
#routing(&block) ⇒ Object
160
161
162
163
164
|
# File 'lib/praxis/action_definition.rb', line 160
def routing(&block)
@routing_config.instance_eval(&block)
@route = @routing_config.route
end
|
#trait(trait_name) ⇒ Object
63
64
65
66
67
68
69
|
# File 'lib/praxis/action_definition.rb', line 63
def trait(trait_name)
raise Exceptions::InvalidTrait, "Trait #{trait_name} not found in the system" unless ApiDefinition.instance.traits.key? trait_name
trait = ApiDefinition.instance.traits.fetch(trait_name)
trait.apply!(self)
traits << trait_name
end
|
#update_attribute(attribute, options, block) ⇒ Object
71
72
73
74
|
# File 'lib/praxis/action_definition.rb', line 71
def update_attribute(attribute, options, block)
attribute.options.merge!(options)
attribute.type.attributes(**options, &block)
end
|