praxis 0.17.1 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/CHANGELOG.md +18 -0
  4. data/lib/api_browser/package.json +1 -1
  5. data/lib/praxis/action_definition.rb +119 -14
  6. data/lib/praxis/api_general_info.rb +21 -3
  7. data/lib/praxis/application.rb +1 -0
  8. data/lib/praxis/dispatcher.rb +18 -15
  9. data/lib/praxis/docs/generator.rb +208 -0
  10. data/lib/praxis/handlers/www_form.rb +2 -1
  11. data/lib/praxis/media_type.rb +1 -1
  12. data/lib/praxis/multipart/part.rb +58 -6
  13. data/lib/praxis/request_stages/action.rb +10 -8
  14. data/lib/praxis/request_stages/request_stage.rb +2 -2
  15. data/lib/praxis/resource_definition.rb +11 -3
  16. data/lib/praxis/response_definition.rb +49 -25
  17. data/lib/praxis/restful_doc_generator.rb +9 -1
  18. data/lib/praxis/route.rb +16 -0
  19. data/lib/praxis/router.rb +1 -1
  20. data/lib/praxis/simple_media_type.rb +2 -1
  21. data/lib/praxis/tasks/api_docs.rb +10 -1
  22. data/lib/praxis/tasks/console.rb +10 -3
  23. data/lib/praxis/types/media_type_common.rb +8 -1
  24. data/lib/praxis/types/multipart.rb +5 -0
  25. data/lib/praxis/types/multipart_array.rb +12 -4
  26. data/lib/praxis/version.rb +1 -1
  27. data/lib/praxis.rb +3 -0
  28. data/praxis.gemspec +3 -3
  29. data/spec/functional_spec.rb +3 -3
  30. data/spec/praxis/action_definition_spec.rb +40 -0
  31. data/spec/praxis/api_general_info_spec.rb +10 -3
  32. data/spec/praxis/media_type_collection_spec.rb +11 -4
  33. data/spec/praxis/media_type_spec.rb +3 -1
  34. data/spec/praxis/request_stages/action_spec.rb +13 -6
  35. data/spec/praxis/resource_definition_spec.rb +1 -1
  36. data/spec/praxis/response_definition_spec.rb +29 -3
  37. data/spec/praxis/router_spec.rb +1 -1
  38. data/spec/praxis/types/multipart_array_spec.rb +92 -0
  39. data/spec/praxis/types/multipart_spec.rb +5 -0
  40. data/spec/spec_app/design/api.rb +1 -0
  41. data/spec/spec_app/design/media_types/volume.rb +1 -3
  42. data/spec/spec_app/design/resources/instances.rb +1 -1
  43. data/spec/support/spec_media_types.rb +5 -4
  44. 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]) || handlers['json']
216
+ (content_type && handlers[content_type.handler_name]) || @default_handler
215
217
  end
216
218
 
217
- def dump(**opts)
218
- header_string = self.headers.collect do |name, value|
219
- "#{name}: #{value}"
220
- end.join("\r\n")
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
- handler.generate(body)
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
- if controller.method(action.name).arity == 0
8
- response = controller.__send__(action.name)
9
- else
10
- response = controller.__send__(action.name, **request.params_hash)
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.id if media_type
275
- hash[:actions] = actions.values.map(&:describe)
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 describe
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
- the_type = the_type.member_attribute.type if the_type < Attributor::Collection
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 speficied API version = \"#{version}\"."
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}."
@@ -9,7 +9,7 @@ module Praxis
9
9
  def name
10
10
  self.class.name
11
11
  end
12
-
12
+
13
13
  def id
14
14
  self.class.name.gsub("::",'-')
15
15
  end
@@ -17,6 +17,7 @@ module Praxis
17
17
  def describe(shallow=true)
18
18
  {identifier: identifier}
19
19
  end
20
+
20
21
  end
21
22
 
22
23
  end
@@ -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"
@@ -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
- # Use some special initialization magic to ensure that 'self' in the
23
- # IRB session refers to Praxis::Application.instance.
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
@@ -98,6 +98,11 @@ module Praxis
98
98
  super
99
99
  end
100
100
 
101
+ def self.describe(shallow = false, **opts)
102
+ hash = super(**opts)
103
+ hash.merge!(family: 'multipart')
104
+ hash
105
+ end
101
106
  end
102
107
 
103
108
 
@@ -52,8 +52,13 @@ module Praxis
52
52
  end
53
53
 
54
54
  def self.payload_type(type=nil, **opts, &block)
55
- return @payload_type if type.nil?
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'
@@ -1,3 +1,3 @@
1
1
  module Praxis
2
- VERSION = '0.17.1'
2
+ VERSION = '0.18.0'
3
3
  end
data/lib/praxis.rb CHANGED
@@ -47,6 +47,9 @@ module Praxis
47
47
  autoload :Notifications, 'praxis/notifications'
48
48
 
49
49
  autoload :RestfulDocGenerator, 'praxis/restful_doc_generator'
50
+ module Docs
51
+ autoload :Generator, 'praxis/docs/generator'
52
+ end
50
53
 
51
54
  # types
52
55
  module Types
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.0.0'
28
- spec.add_dependency 'praxis-blueprints', '>= 2.0.0'
29
- spec.add_dependency 'attributor', '>= 4.0.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'
@@ -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 speficied' do
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 speficied, but wrong' do
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 speficied API version = \"50.0\". Available versions = \"1.0\".")
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