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
@@ -9,6 +9,7 @@ module Praxis
|
|
9
9
|
attr_accessor :payload_attribute
|
10
10
|
attr_accessor :headers_attribute
|
11
11
|
attr_accessor :filename_attribute
|
12
|
+
attr_accessor :default_handler
|
12
13
|
|
13
14
|
def self.check_option!(name, definition)
|
14
15
|
case name
|
@@ -76,6 +77,7 @@ module Praxis
|
|
76
77
|
@name = name
|
77
78
|
@body = body
|
78
79
|
@headers = headers
|
80
|
+
@default_handler = Praxis::Application.instance.handlers['json']
|
79
81
|
|
80
82
|
if content_type.nil?
|
81
83
|
self.content_type = 'text/plain'
|
@@ -211,24 +213,74 @@ module Praxis
|
|
211
213
|
|
212
214
|
def handler
|
213
215
|
handlers = Praxis::Application.instance.handlers
|
214
|
-
(content_type && handlers[content_type.handler_name]) ||
|
216
|
+
(content_type && handlers[content_type.handler_name]) || @default_handler
|
215
217
|
end
|
216
218
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
219
|
+
# Determine an appropriate default content_type for this part given
|
220
|
+
# the preferred handler_name, if possible.
|
221
|
+
#
|
222
|
+
# Considers any pre-defined set of values on the content_type attributge
|
223
|
+
# of the headers.
|
224
|
+
def derive_content_type(handler_name)
|
225
|
+
possible_values = if self.content_type.match 'text/plain'
|
226
|
+
_, content_type_attribute = self.headers_attribute && self.headers_attribute.attributes.find { |k,v| k.to_s =~ /^content[-_]{1}type$/i }
|
227
|
+
if content_type_attribute && content_type_attribute.options.key?(:values)
|
228
|
+
content_type_attribute.options[:values]
|
229
|
+
else
|
230
|
+
[]
|
231
|
+
end
|
232
|
+
else
|
233
|
+
[self.content_type]
|
234
|
+
end
|
235
|
+
|
236
|
+
# generic default encoding is the best we can do
|
237
|
+
if possible_values.empty?
|
238
|
+
return MediaTypeIdentifier.load("application/#{handler_name}")
|
239
|
+
end
|
240
|
+
|
241
|
+
# if any defined value match the preferred handler_name, return it
|
242
|
+
possible_values.each do |ct|
|
243
|
+
mti = MediaTypeIdentifier.load(ct)
|
244
|
+
return mti if mti.handler_name == handler_name
|
245
|
+
end
|
246
|
+
|
247
|
+
# otherwise, pick the first
|
248
|
+
pick = MediaTypeIdentifier.load(possible_values.first)
|
249
|
+
|
250
|
+
# and return that one if it already corresponds to a registered handler
|
251
|
+
# otherwise, add the encoding
|
252
|
+
if Praxis::Application.instance.handlers.include?(pick.handler_name)
|
253
|
+
return pick
|
254
|
+
else
|
255
|
+
return pick + handler_name
|
256
|
+
end
|
257
|
+
|
258
|
+
end
|
259
|
+
|
260
|
+
def dump(default_format: nil, **opts)
|
261
|
+
original_content_type = self.content_type
|
221
262
|
|
222
263
|
body = self.payload_attribute.dump(self.payload, **opts)
|
223
264
|
|
224
265
|
body_string = case body
|
225
266
|
when Hash, Array
|
226
|
-
|
267
|
+
if default_format
|
268
|
+
self.content_type = derive_content_type(default_format)
|
269
|
+
end
|
270
|
+
|
271
|
+
self.handler.generate(body)
|
227
272
|
else
|
228
273
|
body
|
229
274
|
end
|
230
275
|
|
276
|
+
header_string = self.headers.collect do |name, value|
|
277
|
+
"#{name}: #{value}"
|
278
|
+
end.join("\r\n")
|
279
|
+
|
280
|
+
|
231
281
|
"#{header_string}\r\n\r\n#{body_string}"
|
282
|
+
ensure
|
283
|
+
self.content_type = original_content_type
|
232
284
|
end
|
233
285
|
|
234
286
|
end
|
@@ -1,13 +1,15 @@
|
|
1
1
|
module Praxis
|
2
2
|
module RequestStages
|
3
3
|
|
4
|
-
class Action < RequestStage
|
5
|
-
|
4
|
+
class Action < RequestStage
|
5
|
+
|
6
6
|
def execute
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
response = Notifications.instrument 'praxis.request_stage.execute'.freeze, controller: controller do
|
8
|
+
if controller.method(action.name).arity == 0
|
9
|
+
controller.__send__(action.name)
|
10
|
+
else
|
11
|
+
controller.__send__(action.name, **request.params_hash)
|
12
|
+
end
|
11
13
|
end
|
12
14
|
|
13
15
|
case response
|
@@ -21,8 +23,8 @@ module Praxis
|
|
21
23
|
controller.response.request = request
|
22
24
|
nil # Action cannot return its OK request, as it would indicate the end of the stage chain
|
23
25
|
end
|
24
|
-
|
26
|
+
|
25
27
|
end
|
26
28
|
|
27
29
|
end
|
28
|
-
end
|
30
|
+
end
|
@@ -40,7 +40,7 @@ module Praxis
|
|
40
40
|
def action
|
41
41
|
@context.action
|
42
42
|
end
|
43
|
-
|
43
|
+
|
44
44
|
def request
|
45
45
|
@context.request
|
46
46
|
end
|
@@ -111,7 +111,7 @@ module Praxis
|
|
111
111
|
shortcut = stage.run
|
112
112
|
if shortcut && shortcut.kind_of?(Praxis::Response)
|
113
113
|
controller.response = shortcut
|
114
|
-
return shortcut
|
114
|
+
return shortcut
|
115
115
|
end
|
116
116
|
end
|
117
117
|
nil
|
@@ -60,6 +60,13 @@ module Praxis
|
|
60
60
|
|
61
61
|
attr_accessor :controller
|
62
62
|
|
63
|
+
def display_name( string=nil )
|
64
|
+
unless string
|
65
|
+
return @display_name ||= self.name.split("::").last # Best guess at a display name?
|
66
|
+
end
|
67
|
+
@display_name = string
|
68
|
+
end
|
69
|
+
|
63
70
|
def on_finalize
|
64
71
|
if block_given?
|
65
72
|
@on_finalize << Proc.new
|
@@ -268,13 +275,14 @@ module Praxis
|
|
268
275
|
self.name.gsub('::'.freeze,'-'.freeze)
|
269
276
|
end
|
270
277
|
|
271
|
-
def describe
|
278
|
+
def describe(context: nil)
|
272
279
|
{}.tap do |hash|
|
273
280
|
hash[:description] = description
|
274
|
-
hash[:media_type] = media_type.
|
275
|
-
hash[:actions] = actions.values.
|
281
|
+
hash[:media_type] = media_type.describe(true) if media_type
|
282
|
+
hash[:actions] = actions.values.collect{|action| action.describe(context: context)}
|
276
283
|
hash[:name] = self.name
|
277
284
|
hash[:parent] = self.parent.id if self.parent
|
285
|
+
hash[:display_name] = self.display_name
|
278
286
|
hash[:metadata] = metadata
|
279
287
|
hash[:traits] = self.traits
|
280
288
|
end
|
@@ -55,15 +55,6 @@ module Praxis
|
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
-
def example(context=nil)
|
59
|
-
return nil if self.media_type.nil?
|
60
|
-
return nil if self.media_type.kind_of?(SimpleMediaType)
|
61
|
-
if context.nil?
|
62
|
-
context = "#{self.media_type.name}-#{self.name}"
|
63
|
-
end
|
64
|
-
self.media_type.example(context)
|
65
|
-
end
|
66
|
-
|
67
58
|
def location(loc=nil)
|
68
59
|
return @spec[:location] if loc.nil?
|
69
60
|
unless ( loc.is_a?(Regexp) || loc.is_a?(String) )
|
@@ -112,7 +103,19 @@ module Praxis
|
|
112
103
|
end
|
113
104
|
end
|
114
105
|
|
115
|
-
def
|
106
|
+
def example(context=nil)
|
107
|
+
return nil if self.media_type.nil?
|
108
|
+
return nil if self.media_type.kind_of?(SimpleMediaType)
|
109
|
+
|
110
|
+
if context.nil?
|
111
|
+
context = "#{self.media_type.name}-#{self.name}"
|
112
|
+
end
|
113
|
+
|
114
|
+
self.media_type.example(context)
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
def describe(context: nil)
|
116
119
|
location_type = location.is_a?(Regexp) ? :regexp : :string
|
117
120
|
location_value = location.is_a?(Regexp) ? location.inspect : location
|
118
121
|
content = {
|
@@ -121,27 +124,48 @@ module Praxis
|
|
121
124
|
:headers => {}
|
122
125
|
}
|
123
126
|
content[:location] = _describe_header(location) unless location == nil
|
124
|
-
# TODO: Change the mime_type key to media_type!!
|
125
|
-
if media_type
|
126
|
-
content[:media_type] = if media_type.is_a? Symbol
|
127
|
-
media_type
|
128
|
-
else
|
129
|
-
media_type.describe(true) # TODO: is a shallow describe what we want? or just the name?
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
# Generate an appropriate response example.
|
134
|
-
# A rendered response will need to contain not just the body of the associated media_type...
|
135
|
-
# but also headers, url...etc...
|
136
|
-
#if (media_type_example = self.example)
|
137
|
-
# content[:example] = self.media_type.dump( media_type_example )
|
138
|
-
#end
|
139
127
|
|
140
128
|
unless headers == nil
|
141
129
|
headers.each do |name, value|
|
142
130
|
content[:headers][name] = _describe_header(value)
|
143
131
|
end
|
144
132
|
end
|
133
|
+
|
134
|
+
if self.media_type
|
135
|
+
payload = media_type.describe(true)
|
136
|
+
|
137
|
+
if (example_payload = self.example(context))
|
138
|
+
payload[:examples] = {}
|
139
|
+
rendered_payload = example_payload.dump
|
140
|
+
|
141
|
+
# FIXME: remove load when when MediaTypeCommon.identifier returns a MediaTypeIdentifier
|
142
|
+
identifier = MediaTypeIdentifier.load(self.media_type.identifier)
|
143
|
+
|
144
|
+
default_handlers = ApiDefinition.instance.info.produces
|
145
|
+
|
146
|
+
handlers = Praxis::Application.instance.handlers.select do |k,v|
|
147
|
+
default_handlers.include?(k)
|
148
|
+
end
|
149
|
+
|
150
|
+
if (handler = handlers[identifier.handler_name])
|
151
|
+
payload[:examples][identifier.handler_name] = {
|
152
|
+
content_type: identifier.to_s,
|
153
|
+
body: handler.generate(rendered_payload)
|
154
|
+
}
|
155
|
+
else
|
156
|
+
handlers.each do |name, handler|
|
157
|
+
content_type = identifier + name
|
158
|
+
payload[:examples][name] = {
|
159
|
+
content_type: content_type.to_s,
|
160
|
+
body: handler.generate(rendered_payload)
|
161
|
+
}
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
content[:payload] = payload
|
167
|
+
end
|
168
|
+
|
145
169
|
unless parts == nil
|
146
170
|
content[:parts_like] = parts.describe
|
147
171
|
end
|
@@ -29,7 +29,13 @@ module Praxis
|
|
29
29
|
the_type = the_type.type if the_type.is_a? Attributor::Attribute
|
30
30
|
|
31
31
|
# Collection types are special since they wrap a member type, so let's reach in and grab it
|
32
|
-
|
32
|
+
if the_type < Attributor::Collection
|
33
|
+
if the_type.member_attribute.nil?
|
34
|
+
the_type = the_type.member_type
|
35
|
+
else
|
36
|
+
the_type = the_type.member_attribute.type
|
37
|
+
end
|
38
|
+
end
|
33
39
|
|
34
40
|
if @inspected_types.include? the_type
|
35
41
|
# We're done if we've already inspected it
|
@@ -134,6 +140,8 @@ module Praxis
|
|
134
140
|
@doc_root_dir = File.join(@root_dir, API_DOCS_DIRNAME)
|
135
141
|
@resources = []
|
136
142
|
|
143
|
+
Attributor::AttributeResolver.current = Attributor::AttributeResolver.new
|
144
|
+
|
137
145
|
remove_previous_doc_data
|
138
146
|
load_resources
|
139
147
|
|
data/lib/praxis/route.rb
CHANGED
@@ -12,6 +12,22 @@ module Praxis
|
|
12
12
|
@prefixed_path = prefixed_path
|
13
13
|
end
|
14
14
|
|
15
|
+
def example(example_hash:{}, params:)
|
16
|
+
path_param_keys = self.path.named_captures.keys.collect(&:to_sym)
|
17
|
+
|
18
|
+
param_attributes = params ? params.attributes : {}
|
19
|
+
query_param_keys = param_attributes.keys - path_param_keys
|
20
|
+
required_query_param_keys = query_param_keys.each_with_object([]) do |p, array|
|
21
|
+
array << p if params.attributes[p].options[:required]
|
22
|
+
end
|
23
|
+
|
24
|
+
path_params = example_hash.select{|k,v| path_param_keys.include? k }
|
25
|
+
# Let's generate the example only using required params, to avoid mixing incompatible parameters
|
26
|
+
query_params = example_hash.select{|k,v| required_query_param_keys.include? k }
|
27
|
+
example = { verb: self.verb, url: self.path.expand(path_params), query_params: query_params }
|
28
|
+
|
29
|
+
end
|
30
|
+
|
15
31
|
def describe
|
16
32
|
result = {
|
17
33
|
verb: verb,
|
data/lib/praxis/router.rb
CHANGED
@@ -95,7 +95,7 @@ module Praxis
|
|
95
95
|
body += if version == 'n/a'
|
96
96
|
". Your request did not specify an API version.".freeze
|
97
97
|
else
|
98
|
-
". Your request
|
98
|
+
". Your request specified API version = \"#{version}\"."
|
99
99
|
end
|
100
100
|
pretty_versions = attempted_versions.collect(&:inspect).join(', ')
|
101
101
|
body += " Available versions = #{pretty_versions}."
|
@@ -27,7 +27,7 @@ namespace :praxis do
|
|
27
27
|
end
|
28
28
|
|
29
29
|
desc "Run API Documentation Browser"
|
30
|
-
task :preview, [:port] => [:install, :generate] do |t, args|
|
30
|
+
task :preview, [:port] => [:install, :generate] do |t, args|
|
31
31
|
doc_port = args[:port] || '9090'
|
32
32
|
exec({'USER_DOCS_PATH' => File.join(Dir.pwd, 'docs'), 'DOC_PORT' => doc_port}, "#{path}/node_modules/.bin/grunt serve --gruntfile '#{path}/Gruntfile.js'")
|
33
33
|
end
|
@@ -45,6 +45,15 @@ namespace :praxis do
|
|
45
45
|
generator = Praxis::RestfulDocGenerator.new(Dir.pwd)
|
46
46
|
end
|
47
47
|
|
48
|
+
desc "Generate BETA API docs (JSON definitions) for a Praxis App"
|
49
|
+
task :generate_beta => [:environment] do |t, args|
|
50
|
+
require 'fileutils'
|
51
|
+
|
52
|
+
Praxis::Blueprint.caching_enabled = false
|
53
|
+
generator = Praxis::Docs::Generator.new(Dir.pwd)
|
54
|
+
generator.save!
|
55
|
+
end
|
56
|
+
|
48
57
|
end
|
49
58
|
|
50
59
|
desc "Generate API docs (JSON definitions) for a Praxis App"
|
data/lib/praxis/tasks/console.rb
CHANGED
@@ -11,7 +11,6 @@ namespace :praxis do
|
|
11
11
|
rescue LoadError
|
12
12
|
# Fall back on irb
|
13
13
|
require 'irb'
|
14
|
-
require 'irb/ext/multi-irb'
|
15
14
|
end
|
16
15
|
|
17
16
|
Rake::Task['praxis:environment'].invoke
|
@@ -19,10 +18,18 @@ namespace :praxis do
|
|
19
18
|
if have_pry
|
20
19
|
Praxis::Application.instance.pry
|
21
20
|
else
|
22
|
-
#
|
23
|
-
|
21
|
+
# Keep IRB.setup from complaining about bad ARGV options
|
22
|
+
old_argv = ARGV.dup
|
23
|
+
ARGV.clear
|
24
24
|
IRB.setup nil
|
25
|
+
ARGV.concat(old_argv)
|
26
|
+
|
27
|
+
# Allow reentrant IRB
|
25
28
|
IRB.conf[:MAIN_CONTEXT] = IRB::Irb.new.context
|
29
|
+
require 'irb/ext/multi-irb'
|
30
|
+
|
31
|
+
# Use some special initialization magic to ensure that 'self' in the
|
32
|
+
# IRB session refers to Praxis::Application.instance.
|
26
33
|
IRB.irb(nil, Praxis::Application.instance)
|
27
34
|
end
|
28
35
|
end
|
@@ -9,7 +9,7 @@ module Praxis
|
|
9
9
|
def describe(shallow = false, **opts)
|
10
10
|
hash = super
|
11
11
|
unless shallow
|
12
|
-
hash.merge!(identifier: @identifier.to_s, description: @description)
|
12
|
+
hash.merge!(identifier: @identifier.to_s, description: @description, display_name: self.display_name)
|
13
13
|
end
|
14
14
|
hash
|
15
15
|
end
|
@@ -19,6 +19,13 @@ module Praxis
|
|
19
19
|
@description
|
20
20
|
end
|
21
21
|
|
22
|
+
def display_name( string=nil )
|
23
|
+
unless string
|
24
|
+
return @display_name ||= self.name.split("::").last # Best guess at a display name?
|
25
|
+
end
|
26
|
+
@display_name = string
|
27
|
+
end
|
28
|
+
|
22
29
|
# Get or set the identifier of this media type.
|
23
30
|
#
|
24
31
|
# @deprecated this method is not deprecated, but its return type will change to MediaTypeIdentifier in Praxis 1.0
|
@@ -52,8 +52,13 @@ module Praxis
|
|
52
52
|
end
|
53
53
|
|
54
54
|
def self.payload_type(type=nil, **opts, &block)
|
55
|
-
|
56
|
-
|
55
|
+
if type.nil?
|
56
|
+
if block_given?
|
57
|
+
type = Attributor::Struct
|
58
|
+
else
|
59
|
+
return @payload_type
|
60
|
+
end
|
61
|
+
end
|
57
62
|
@payload_type = Attributor.resolve_type(type)
|
58
63
|
@payload_attribute = Attributor::Attribute.new(@payload_type, **opts, &block)
|
59
64
|
@part_attribute = nil
|
@@ -188,7 +193,7 @@ module Praxis
|
|
188
193
|
end
|
189
194
|
|
190
195
|
sub_hash = part_attribute.describe(shallow, example: sub_example)
|
191
|
-
|
196
|
+
|
192
197
|
|
193
198
|
if (options = sub_hash.delete(:options))
|
194
199
|
sub_hash[:options] = {}
|
@@ -204,7 +209,7 @@ module Praxis
|
|
204
209
|
end
|
205
210
|
|
206
211
|
sub_hash[:type] = MultipartPart.describe(shallow, example: sub_example, options: part_attribute.options)
|
207
|
-
|
212
|
+
|
208
213
|
|
209
214
|
parts[part_name] = sub_hash
|
210
215
|
end
|
@@ -302,6 +307,9 @@ module Praxis
|
|
302
307
|
errors
|
303
308
|
end
|
304
309
|
|
310
|
+
def self.dump(value, **opts)
|
311
|
+
value.dump(**opts)
|
312
|
+
end
|
305
313
|
|
306
314
|
def dump(**opts)
|
307
315
|
boundary = content_type.parameters.get 'boundary'
|
data/lib/praxis/version.rb
CHANGED
data/lib/praxis.rb
CHANGED
data/praxis.gemspec
CHANGED
@@ -24,9 +24,9 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.add_dependency 'mustermann', '~> 0'
|
25
25
|
spec.add_dependency 'activesupport', '>= 3'
|
26
26
|
spec.add_dependency 'mime', '~> 0'
|
27
|
-
spec.add_dependency 'praxis-mapper', '>= 4.
|
28
|
-
spec.add_dependency 'praxis-blueprints', '>= 2.
|
29
|
-
spec.add_dependency 'attributor', '>= 4.0.
|
27
|
+
spec.add_dependency 'praxis-mapper', '>= 4.1'
|
28
|
+
spec.add_dependency 'praxis-blueprints', '>= 2.2'
|
29
|
+
spec.add_dependency 'attributor', '>= 4.0.1'
|
30
30
|
spec.add_dependency 'thor', '~> 0.18'
|
31
31
|
spec.add_dependency 'terminal-table', '~> 1.4'
|
32
32
|
spec.add_dependency 'harness', '~> 2'
|
data/spec/functional_spec.rb
CHANGED
@@ -268,7 +268,7 @@ describe 'Functional specs' do
|
|
268
268
|
|
269
269
|
|
270
270
|
context 'not found and API versions' do
|
271
|
-
context 'when no version is
|
271
|
+
context 'when no version is specified' do
|
272
272
|
it 'it tells you which available api versions would match' do
|
273
273
|
get '/api/clouds/1/instances/2?junk=foo',nil, 'global_session' => session
|
274
274
|
|
@@ -285,13 +285,13 @@ describe 'Functional specs' do
|
|
285
285
|
end
|
286
286
|
end
|
287
287
|
|
288
|
-
context 'when some version is
|
288
|
+
context 'when some version is specified, but wrong' do
|
289
289
|
it 'it tells you which possible correcte api versions exist' do
|
290
290
|
get '/api/clouds/1/instances/2?junk=foo&api_version=50.0', nil, 'global_session' => session
|
291
291
|
|
292
292
|
expect(last_response.status).to eq(404)
|
293
293
|
expect(last_response.headers["Content-Type"]).to eq("text/plain")
|
294
|
-
expect(last_response.body).to eq("NotFound. Your request
|
294
|
+
expect(last_response.body).to eq("NotFound. Your request specified API version = \"50.0\". Available versions = \"1.0\".")
|
295
295
|
end
|
296
296
|
end
|
297
297
|
|
@@ -100,6 +100,10 @@ describe Praxis::ActionDefinition do
|
|
100
100
|
end
|
101
101
|
|
102
102
|
describe '#params' do
|
103
|
+
it 'defaults to being required if omitted' do
|
104
|
+
expect(subject.params.options[:required]).to be(true)
|
105
|
+
end
|
106
|
+
|
103
107
|
it 'merges in more params' do
|
104
108
|
subject.params do
|
105
109
|
attribute :more, Attributor::Integer
|
@@ -108,9 +112,22 @@ describe Praxis::ActionDefinition do
|
|
108
112
|
attributes = subject.params.attributes.keys
|
109
113
|
expect(attributes).to match_array([:one, :inherited, :more])
|
110
114
|
end
|
115
|
+
|
116
|
+
it 'merges options (which allows overriding)' do
|
117
|
+
expect(subject.params.options[:required]).to be(true)
|
118
|
+
|
119
|
+
subject.params required: false
|
120
|
+
|
121
|
+
expect(subject.params.options[:required]).to be(false)
|
122
|
+
end
|
111
123
|
end
|
112
124
|
|
113
125
|
describe '#payload' do
|
126
|
+
it 'defaults to being required if omitted' do
|
127
|
+
expect(subject.payload.options[:required]).to be(true)
|
128
|
+
end
|
129
|
+
|
130
|
+
|
114
131
|
it 'merges in more payload' do
|
115
132
|
subject.payload do
|
116
133
|
attribute :more, Attributor::Integer
|
@@ -120,6 +137,14 @@ describe Praxis::ActionDefinition do
|
|
120
137
|
:two, :inherited, :more
|
121
138
|
])
|
122
139
|
end
|
140
|
+
|
141
|
+
it 'merges options (which allows overriding)' do
|
142
|
+
expect(subject.payload.options[:required]).to be(true)
|
143
|
+
|
144
|
+
subject.payload required: false
|
145
|
+
|
146
|
+
expect(subject.payload.options[:required]).to be(false)
|
147
|
+
end
|
123
148
|
end
|
124
149
|
|
125
150
|
describe '#headers' do
|
@@ -131,6 +156,10 @@ describe Praxis::ActionDefinition do
|
|
131
156
|
expect(subject.headers.type.options[:case_insensitive_load]).to be(true)
|
132
157
|
end
|
133
158
|
|
159
|
+
it 'defaults to being required if omitted' do
|
160
|
+
expect(subject.headers.options[:required]).to be(true)
|
161
|
+
end
|
162
|
+
|
134
163
|
it 'merges in more headers' do
|
135
164
|
subject.headers do
|
136
165
|
header "more"
|
@@ -139,6 +168,16 @@ describe Praxis::ActionDefinition do
|
|
139
168
|
expected_array = ["X_REQUESTED_WITH", "Inherited", "more"]
|
140
169
|
expect(subject.headers.attributes.keys).to match_array(expected_array)
|
141
170
|
end
|
171
|
+
|
172
|
+
it 'merges options (which allows overriding)' do
|
173
|
+
expect(subject.headers.options[:required]).to be(true)
|
174
|
+
|
175
|
+
subject.headers required: false do
|
176
|
+
header "even_more"
|
177
|
+
end
|
178
|
+
|
179
|
+
expect(subject.headers.options[:required]).to be(false)
|
180
|
+
end
|
142
181
|
end
|
143
182
|
|
144
183
|
context '#routing' do
|
@@ -187,6 +226,7 @@ describe Praxis::ActionDefinition do
|
|
187
226
|
expect(attributes[:one][:source]).to eq('url')
|
188
227
|
end
|
189
228
|
end
|
229
|
+
|
190
230
|
end
|
191
231
|
|
192
232
|
context 'href generation' do
|
@@ -16,6 +16,10 @@ describe Praxis::ApiGeneralInfo do
|
|
16
16
|
description "Description"
|
17
17
|
endpoint 'api.example.com'
|
18
18
|
base_path "/base"
|
19
|
+
|
20
|
+
consumes 'xml', 'x-www-form-urlencoded'
|
21
|
+
produces 'json', 'x-www-form-urlencoded'
|
22
|
+
|
19
23
|
base_params do
|
20
24
|
attribute :name, String
|
21
25
|
end
|
@@ -36,8 +40,10 @@ describe Praxis::ApiGeneralInfo do
|
|
36
40
|
end
|
37
41
|
|
38
42
|
its(:name) { should eq 'Name' }
|
39
|
-
|
43
|
+
its(:consumes) { should eq ['xml', 'x-www-form-urlencoded']}
|
44
|
+
its(:produces) { should eq ['json', 'x-www-form-urlencoded']}
|
40
45
|
end
|
46
|
+
|
41
47
|
context '.describe' do
|
42
48
|
before do
|
43
49
|
info.instance_exec &info_block
|
@@ -53,6 +59,8 @@ describe Praxis::ApiGeneralInfo do
|
|
53
59
|
its([:base_params, :name, :type, :name]) { should eq 'String' }
|
54
60
|
its([:version_with]) { should eq([:header, :params]) }
|
55
61
|
its([:endpoint]) { should eq 'api.example.com' }
|
62
|
+
its([:consumes]) { should eq ['xml', 'x-www-form-urlencoded'] }
|
63
|
+
its([:produces]) { should eq ['json', 'x-www-form-urlencoded'] }
|
56
64
|
end
|
57
65
|
|
58
66
|
context 'base_path with versioning' do
|
@@ -66,8 +74,7 @@ describe Praxis::ApiGeneralInfo do
|
|
66
74
|
|
67
75
|
global_info.version_with :path
|
68
76
|
global_info.base_path '/api/v:api_version'
|
69
|
-
end
|
70
|
-
|
77
|
+
end
|
71
78
|
|
72
79
|
its(:base_path) { should eq '/api/v1.0'}
|
73
80
|
end
|