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.
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