praxis 0.14.0 → 0.15.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/CHANGELOG.md +15 -1
  3. data/Gemfile +5 -0
  4. data/bin/praxis +7 -4
  5. data/lib/api_browser/package.json +1 -1
  6. data/lib/praxis/action_definition.rb +10 -5
  7. data/lib/praxis/application.rb +30 -0
  8. data/lib/praxis/bootloader_stages/subgroup_loader.rb +2 -2
  9. data/lib/praxis/bootloader_stages/warn_unloaded_files.rb +7 -2
  10. data/lib/praxis/file_group.rb +1 -1
  11. data/lib/praxis/handlers/json.rb +32 -0
  12. data/lib/praxis/handlers/www_form.rb +20 -0
  13. data/lib/praxis/handlers/xml.rb +78 -0
  14. data/lib/praxis/links.rb +4 -1
  15. data/lib/praxis/media_type.rb +55 -0
  16. data/lib/praxis/media_type_identifier.rb +198 -0
  17. data/lib/praxis/multipart/part.rb +16 -0
  18. data/lib/praxis/request.rb +34 -25
  19. data/lib/praxis/response.rb +22 -3
  20. data/lib/praxis/response_definition.rb +11 -36
  21. data/lib/praxis/restful_doc_generator.rb +1 -1
  22. data/lib/praxis/simple_media_type.rb +6 -1
  23. data/lib/praxis/types/media_type_common.rb +8 -3
  24. data/lib/praxis/types/multipart.rb +6 -6
  25. data/lib/praxis/version.rb +1 -1
  26. data/lib/praxis.rb +7 -1
  27. data/praxis.gemspec +1 -1
  28. data/spec/functional_spec.rb +3 -1
  29. data/spec/praxis/application_spec.rb +58 -1
  30. data/spec/praxis/handlers/json_spec.rb +37 -0
  31. data/spec/praxis/handlers/xml_spec.rb +155 -0
  32. data/spec/praxis/media_type_identifier_spec.rb +209 -0
  33. data/spec/praxis/media_type_spec.rb +50 -3
  34. data/spec/praxis/plugins/praxis_mapper_plugin_spec.rb +2 -2
  35. data/spec/praxis/request_spec.rb +39 -1
  36. data/spec/praxis/response_definition_spec.rb +12 -9
  37. data/spec/praxis/response_spec.rb +37 -6
  38. data/spec/praxis/types/collection_spec.rb +2 -2
  39. data/spec/praxis/types/multipart_spec.rb +17 -0
  40. data/spec/spec_app/app/controllers/instances.rb +1 -1
  41. data/spec/support/spec_media_types.rb +19 -0
  42. metadata +11 -6
  43. data/lib/praxis/content_type_parser.rb +0 -62
  44. data/spec/praxis/content_type_parser_spec.rb +0 -91
@@ -0,0 +1,209 @@
1
+ require "spec_helper"
2
+
3
+ describe Praxis::MediaTypeIdentifier do
4
+ let(:example) { 'application/ice-cream+sundae; nuts="true"; fudge="true"' }
5
+
6
+ subject { described_class.new(example) }
7
+
8
+ context '.load' do
9
+ it 'parses type/subtype' do
10
+ expect(subject.type).to eq('application')
11
+ expect(subject.subtype).to eq('ice-cream')
12
+ end
13
+
14
+ it 'parses suffix' do
15
+ expect(subject.suffix).to eq('sundae')
16
+ end
17
+
18
+ context 'given parameters' do
19
+ let(:example) { 'application/vnd.widget; encoding="martian"' }
20
+ let(:unquoted_example) { 'application/vnd.widget; encoding=martian' }
21
+ let(:tricky_example) { 'application/vnd.widget; sauce="yes; absolutely"' }
22
+
23
+ it 'handles quoted values' do
24
+ expect(described_class.new(example).parameters['encoding']).to eq('martian')
25
+ end
26
+
27
+ it 'handles unquoted values' do
28
+ expect(described_class.new(unquoted_example).parameters['encoding']).to eq('martian')
29
+ end
30
+
31
+ it 'handles quoted semicolons in values' do
32
+ pending("need to stop using a regexp to do a context-free parser's job")
33
+ expect(described_class.new(tricky_example).parameters['sauce']).to eq('yes; absolutely')
34
+ end
35
+
36
+
37
+ end
38
+
39
+ context 'given a malformed type' do
40
+ it 'tolerates extra slash or plus' do
41
+ ['man/bear/pig', 'c/c++', 'application/ice-cream+cone+dipped'].each do |eg|
42
+ expect(described_class.new(eg).to_s).to eq(eg)
43
+ end
44
+ end
45
+
46
+ it 'assumes application/XYZ' do
47
+ ['nachos', 'tacos with extra cheese!'].each do |eg|
48
+ prefix = eg.split(/\s+/).first
49
+ expect(described_class.new(eg).to_s).to eq("application/#{prefix}")
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ context '#match' do
56
+ subject { described_class }
57
+
58
+ # @example match anything
59
+ # MediaTypeIdentifier.load('*/*').match('application/icecream+cone; flavor=vanilla') # => true
60
+ #
61
+ # @example match a subtype wildcard
62
+ # MediaTypeIdentifier.load('image/*').match('image/jpeg') # => true
63
+ #
64
+ # @example match a specific type irrespective of structured syntax
65
+ # MediaTypeIdentifier.load('application/vnd.widget').match('application/vnd.widget+json') # => true
66
+ #
67
+ # @example match a specific type, respective of important parameters but irrespective of extra parameters or structured syntax
68
+ # MediaTypeIdentifier.load('application/vnd.widget; type=collection').match('application/vnd.widget+json; material=steel; type=collection') # => true
69
+
70
+ it 'accepts String' do
71
+ expect(subject.new('image/jpeg').match('image/jpeg')).to be_truthy
72
+ expect(subject.new('image/jpeg').match('image/png')).to be_falsey
73
+ end
74
+
75
+ it 'accepts MediaTypeIdentifier' do
76
+ expect(subject.new('image/jpeg').match(subject.new('image/jpeg'))).to be_truthy
77
+ expect(subject.new('image/jpeg').match(subject.new('image/png'))).to be_falsey
78
+ end
79
+
80
+ it 'understands type wildcards' do
81
+ expect(subject.new('*/*').match('application/pizza')).to be_truthy
82
+ end
83
+
84
+ it 'understands subtype wildcards' do
85
+ expect(subject.new('application/*').match('application/pizza')).to be_truthy
86
+ expect(subject.new('application/*').match('image/jpeg')).to be_falsey
87
+ end
88
+
89
+ it 'understands structured-syntax suffixes' do
90
+ expect(subject.new('application/vnd.widget').match('application/vnd.widget+json')).to be_truthy
91
+ expect(subject.new('application/vnd.widget+json').match('application/vnd.widget+xml')).to be_falsey
92
+ end
93
+
94
+ it 'understands parameters' do
95
+ expect(subject.new('application/vnd.widget; type=collection').match('application/vnd.widget; type=collection; material=steel')).to be_truthy
96
+ expect(subject.new('application/vnd.widget+json; material=steel').match('application/vnd.widget+xml; material=copper')).to be_falsey
97
+ end
98
+ end
99
+
100
+ context '#=~' do
101
+ subject { described_class.new('image/jpeg') }
102
+ it 'delegates to #match' do
103
+ expect(subject).to receive(:match).once
104
+ (described_class.new('image/jpeg') =~ subject)
105
+ end
106
+ end
107
+
108
+ context '#==' do
109
+ let(:example) { 'application/vnd.widget+xml; charset=UTF8' }
110
+ subject { described_class }
111
+
112
+ it 'compares all attributes' do
113
+ expect(subject.new('application/json')).not_to eq(subject.new('application/xml'))
114
+ expect(subject.new('application/vnd.widget+json')).not_to eq(subject.new('application/vnd.widget+xml'))
115
+ expect(subject.new('text/plain; charset=UTF8')).not_to eq(subject.new('text/plain; charset=martian'))
116
+
117
+ expect(subject.new(example)).to eq(subject.new(example))
118
+ end
119
+
120
+ it 'ignores parameter ordering' do
121
+ expect(subject.new('text/plain; a=1; b=2')).to eq(subject.new('text/plain; b=2; a=1'))
122
+ end
123
+ end
124
+
125
+ context '#to_s' do
126
+ subject { described_class.new(example) }
127
+
128
+ it 'includes type/subtype' do
129
+ expect(subject.to_s).to start_with('application/ice-cream')
130
+ end
131
+
132
+ it 'includes suffix' do
133
+ expect(subject.to_s).to start_with('application/ice-cream+sundae')
134
+ end
135
+
136
+ it 'canonicalizes parameter ordering' do
137
+ expect(subject.to_s).to end_with('; fudge=true; nuts=true')
138
+ end
139
+ end
140
+
141
+ context '#without_parameters' do
142
+ it 'excludes parameters' do
143
+ expect(subject.without_parameters.to_s).to eq('application/ice-cream+sundae')
144
+ end
145
+ end
146
+
147
+ context '#handler_name' do
148
+ subject { described_class }
149
+
150
+ let(:with_suffix) { 'application/vnd.widget+xml' }
151
+ let(:with_subtype) { 'text/xml' }
152
+ let(:with_both) { 'text/json+xml' } #nonsensical but valid!
153
+
154
+ it 'uses the suffix' do
155
+ expect(subject.new(with_suffix).handler_name).to eq('xml')
156
+ end
157
+
158
+ it 'uses the subtype' do
159
+ expect(subject.new(with_subtype).handler_name).to eq('xml')
160
+ end
161
+
162
+ it 'prefers the suffix' do
163
+ expect(subject.new(with_both).handler_name).to eq('xml')
164
+ end
165
+ end
166
+
167
+ context '#+' do
168
+ let(:simple_subject) { described_class.new('application/vnd.icecream') }
169
+ let(:complex_subject) { described_class.new('application/vnd.icecream+json; nuts="true"') }
170
+
171
+ it 'adds a suffix' do
172
+ expect(simple_subject + 'xml').to \
173
+ eq(described_class.new('application/vnd.icecream+xml'))
174
+ expect(simple_subject + '+xml').to \
175
+ eq(described_class.new('application/vnd.icecream+xml'))
176
+ end
177
+
178
+ it 'adds parameters' do
179
+ expect(simple_subject + 'nuts=true').to \
180
+ eq(described_class.new('application/vnd.icecream; nuts=true'))
181
+
182
+ expect(simple_subject + '; nuts=true').to \
183
+ eq(described_class.new('application/vnd.icecream; nuts=true'))
184
+ end
185
+
186
+ it 'adds suffix and parameters' do
187
+ expect(simple_subject + 'xml; nuts=true').to \
188
+ eq(described_class.new('application/vnd.icecream+xml; nuts=true'))
189
+ end
190
+
191
+ it 'replaces the suffix' do
192
+ expect(complex_subject + 'xml').to \
193
+ eq(described_class.new('application/vnd.icecream+xml; nuts=true'))
194
+ end
195
+
196
+ it 'replaces existing parameters and adds new ones' do
197
+ expect(complex_subject + 'nuts=false; cherry=true').to \
198
+ eq(described_class.new('application/vnd.icecream+json; cherry=true; nuts=false'))
199
+
200
+ expect(complex_subject + '; nuts=false; cherry=true').to \
201
+ eq(described_class.new('application/vnd.icecream+json; cherry=true; nuts=false'))
202
+ end
203
+
204
+ it 'replaces suffix and parameters and adds new ones' do
205
+ expect(complex_subject + 'json; nuts=false; cherry=true').to \
206
+ eq(described_class.new('application/vnd.icecream+json; cherry=true; nuts=false'))
207
+ end
208
+ end
209
+ end
@@ -3,9 +3,20 @@ require 'spec_helper'
3
3
  describe Praxis::MediaType do
4
4
  let(:owner_resource) { instance_double(Person, id: 100, name: /[:name:]/.gen, href: '/', links: ['one','two']) }
5
5
  let(:manager_resource) { instance_double(Person, id: 101, name: /[:name:]/.gen, href: '/', links: []) }
6
+ let(:custodian_resource) { instance_double(Person, id: 102, name: /[:name:]/.gen, href: '/', links: []) }
7
+ let(:residents_summary_resource) do
8
+ instance_double(Person::CollectionSummary, href: "/people", size: 2)
9
+ end
6
10
 
7
11
  let(:resource) do
8
- double('address', id: 1, name: 'Home',owner: owner_resource, manager: manager_resource)
12
+ double('address',
13
+ id: 1,
14
+ name: 'Home',
15
+ owner: owner_resource,
16
+ manager: manager_resource,
17
+ custodian: custodian_resource,
18
+ residents_summary: residents_summary_resource
19
+ )
9
20
  end
10
21
 
11
22
  subject(:address) { Address.new(resource) }
@@ -20,7 +31,9 @@ describe Praxis::MediaType do
20
31
  it 'respects using' do
21
32
  expect(address.links.super).to be_kind_of(Person)
22
33
  expect(address.links.super.object).to be(resource.manager)
34
+ expect(address.links.caretaker.object).to be(resource.custodian)
23
35
  end
36
+
24
37
  end
25
38
  end
26
39
 
@@ -29,7 +42,25 @@ describe Praxis::MediaType do
29
42
  context 'accessor methods' do
30
43
  subject(:address_klass) { address.class }
31
44
 
32
- its(:identifier) { should be_kind_of(String) }
45
+ context '#identifier' do
46
+ context 'in praxis v0' do
47
+ it 'should be a kind of String' do
48
+ if Praxis::VERSION =~ /^0/
49
+ expect(subject.identifier).to be_kind_of(String)
50
+ else
51
+ raise 'Please remove this spec which is no longer pertinent'
52
+ end
53
+ end
54
+ end
55
+
56
+ context 'in praxis v1.0 and beyond' do
57
+ it 'should be a kind of Praxis::MediaTypeIdentifier' do
58
+ pending('interface-breaking change') if Praxis::VERSION =~ /^0/
59
+ expect(subject.identifier).to be_kind_of(Praxis::MediaTypeIdentifier)
60
+ end
61
+ end
62
+ end
63
+
33
64
  its(:description) { should be_kind_of(String) }
34
65
 
35
66
  context 'links' do
@@ -43,8 +74,24 @@ describe Praxis::MediaType do
43
74
  context 'using the links DSL' do
44
75
  subject(:address) { Address.new(resource) }
45
76
  its(:links) { should be_kind_of(Address::Links) }
77
+
78
+ it 'inherits types appropriately' do
79
+ links_attribute = Address::Links.attributes
80
+ expect(links_attribute[:owner].type).to be(Person)
81
+ expect(links_attribute[:super].type).to be(Person)
82
+ expect(links_attribute[:caretaker].type).to be(Person)
83
+ end
84
+
85
+ context 'loading returned values' do
86
+ subject(:residents) { address.links.residents }
87
+ let(:residents_summary_resource) do
88
+ {href: "/people", size: 2}
89
+ end
90
+
91
+ its(:href) { should eq('/people') }
92
+ its(:size) { should eq(2) }
93
+ end
46
94
  end
47
-
48
95
  end
49
96
  end
50
97
 
@@ -55,8 +55,8 @@ describe Praxis::Plugins::PraxisMapperPlugin do
55
55
  expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to receive(:log).
56
56
  with(kind_of(Praxis::Request),kind_of(Praxis::Mapper::IdentityMap), 'detailed').
57
57
  and_call_original
58
-
59
- get '/clouds/1/instances/2?junk=foo&api_version=1.0', nil, 'global_session' => session
58
+ the_body = StringIO.new("{}") # This is a funny, GET request expecting a body
59
+ get '/clouds/1/instances/2?junk=foo&api_version=1.0', nil, 'rack.input' => the_body,'CONTENT_TYPE' => "application/json", 'global_session' => session
60
60
 
61
61
  expect(last_response.status).to eq(200)
62
62
  end
@@ -2,10 +2,11 @@ require 'spec_helper'
2
2
 
3
3
  describe Praxis::Request do
4
4
  let(:rack_input) { StringIO.new('something=given') }
5
+ let(:env_content_type){ 'application/x-www-form-urlencoded' }
5
6
  let(:env) do
6
7
  env = Rack::MockRequest.env_for('/instances/1?junk=foo&api_version=1.0')
7
8
  env['rack.input'] = rack_input
8
- env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'
9
+ env['CONTENT_TYPE'] = env_content_type
9
10
  env['HTTP_VERSION'] = 'HTTP/1.1'
10
11
  env['HTTP_AUTHORIZATION'] = 'Secret'
11
12
  env
@@ -174,5 +175,42 @@ describe Praxis::Request do
174
175
  expect(request.validate_payload(context[:payload])).to eq(['some_error'])
175
176
  end
176
177
  end
178
+
179
+ context '#load_payload' do
180
+ let(:load_context){ context[:payload] }
181
+ let(:parsed_result){ double("parsed") }
182
+
183
+ before do
184
+ Praxis::Application.instance.handler 'xml', Praxis::Handlers::XML
185
+ end
186
+
187
+ after do
188
+ expect(request.action.payload).to receive(:load).with(parsed_result, load_context, content_type: request.content_type.to_s )
189
+ request.load_payload( load_context )
190
+ end
191
+
192
+ context 'that is url-encoded' do
193
+ let(:env_content_type){ 'application/x-www-form-urlencoded' }
194
+ let(:parsed_result) { {"something" => "given"} }
195
+
196
+ it 'decodes it the www-form handler' do end
197
+ end
198
+
199
+ context 'that is json encoded' do
200
+ let(:rack_input) { StringIO.new('{"one":1,"sub_hash":{"first":"hello"}}') }
201
+ let(:env_content_type){ 'application/json' }
202
+ let(:parsed_result) { Praxis::Handlers::JSON.new.parse(request.raw_payload) }
203
+
204
+ it 'decodes using the JSON handler' do end
205
+ end
206
+ context 'that is xml encoded' do
207
+ let(:rack_input) { StringIO.new('<hash><one type="integer">1</one><sub_hash><first>hello</first></sub_hash></hash>') }
208
+ let(:env_content_type) { 'application/xml' }
209
+ let(:parsed_result) { Praxis::Handlers::XML.new.parse(request.raw_payload) }
210
+
211
+ it 'decodes using the XML handler' do end
212
+ end
213
+
214
+ end
177
215
  end
178
216
  end
@@ -8,7 +8,7 @@ describe Praxis::ResponseDefinition do
8
8
  Proc.new do
9
9
  status 200
10
10
  description 'test description'
11
- headers({ "X-Header" => "value", "Content-Type" => "some_type" })
11
+ headers({ "X-Header" => "value", "Content-Type" => "application/some-type" })
12
12
  end
13
13
  end
14
14
 
@@ -16,9 +16,10 @@ describe Praxis::ResponseDefinition do
16
16
  its(:description) { should == 'test description' }
17
17
  its(:parts) { should be(nil) }
18
18
  let(:response_status) { 200 }
19
- let(:response_headers) { { "X-Header" => "value", "Content-Type" => "some_type"} }
19
+ let(:response_content_type) { "application/some-type" }
20
+ let(:response_headers) { { "X-Header" => "value", "Content-Type" => response_content_type} }
20
21
 
21
- let(:response) { instance_double("Praxis::Response", status: response_status , headers: response_headers ) }
22
+ let(:response) { instance_double("Praxis::Response", status: response_status , headers: response_headers, content_type: response_content_type ) }
22
23
 
23
24
 
24
25
  context '#media_type' do
@@ -66,14 +67,14 @@ describe Praxis::ResponseDefinition do
66
67
 
67
68
  context '#headers' do
68
69
  it 'accepts a Hash' do
69
- response_definition.headers Hash["X-Header" => "value", "Content-Type" => "some_type"]
70
+ response_definition.headers Hash["X-Header" => "value", "Content-Type" => "application/some-type"]
70
71
  expect(response_definition.headers).to be_a(Hash)
71
72
  end
72
73
 
73
74
  it 'accepts an Array' do
74
- response_definition.headers ["X-Header: value", "Content-Type: some_type"]
75
+ response_definition.headers ["X-Header: value", "Content-Type: application/some-type"]
75
76
  expect(response_definition.headers).to be_a(Hash)
76
- expect(response_definition.headers.keys).to include("X-Header: value", "Content-Type: some_type")
77
+ expect(response_definition.headers.keys).to include("X-Header: value", "Content-Type: application/some-type")
77
78
  end
78
79
 
79
80
  it 'accepts a String' do
@@ -364,7 +365,8 @@ describe Praxis::ResponseDefinition do
364
365
  before { response_definition.media_type(media_type) }
365
366
 
366
367
  context 'when content type matches the mediatype of the spec' do
367
- let(:response_headers) { {'Content-Type' => content_type } }
368
+ let(:response_content_type) { content_type }
369
+
368
370
  it 'validates successfully' do
369
371
  expect {
370
372
  response_definition.validate_content_type!(response)
@@ -373,7 +375,7 @@ describe Praxis::ResponseDefinition do
373
375
  end
374
376
 
375
377
  context 'when content type includes a parameter' do
376
- let(:response_headers) { {'Content-Type' => "#{content_type};collection=true" } }
378
+ let(:response_content_type) { "#{content_type}; collection=true" }
377
379
  it 'validates successfully' do
378
380
  expect {
379
381
  response_definition.validate_content_type!(response)
@@ -382,7 +384,8 @@ describe Praxis::ResponseDefinition do
382
384
  end
383
385
 
384
386
  context 'when content type does not match' do
385
- let(:response_headers) { {'Content-Type' => "will_never_match" } }
387
+ let(:response_content_type) { "application/will_never_match" }
388
+
386
389
  it 'should raise error telling you so' do
387
390
  expect {
388
391
  response_definition.validate_content_type!(response)
@@ -50,12 +50,6 @@ describe Praxis::Response do
50
50
 
51
51
  subject(:response) { Praxis::Responses::Ok.new(status: response_status, headers: response_headers) }
52
52
 
53
- # before :each do
54
- # allow(response.class).to receive(:response_name).and_return(:spec)
55
- # #allow(response).to receive(:headers).and_return(response_headers)
56
- # #allow(response).to receive(:status).and_return(response_status)
57
- # end
58
-
59
53
  describe '#validate' do
60
54
  before do
61
55
  allow(action).to receive(:responses).and_return({response_spec.name => response_spec })
@@ -84,6 +78,43 @@ describe Praxis::Response do
84
78
 
85
79
  end
86
80
 
81
+ describe '#encode!' do
82
+ let(:response_body_structured_data) { [{"moo" => "bah"}] }
83
+ let(:response_body_entity) { '[{"moo": "bah"}]' }
84
+
85
+ context 'given a String body' do
86
+ before { response.body = response_body_entity }
87
+
88
+ it 'leaves well enough alone' do
89
+ response.encode!
90
+ expect(response.body).to eq(response_body_entity)
91
+ end
92
+ end
93
+
94
+ context 'given a structured body' do
95
+ before { response.body = response_body_structured_data }
96
+
97
+ it 'encodes using a suitable handler' do
98
+ response.encode!
99
+ expect(JSON.load(response.body)).to eq(response_body_structured_data)
100
+ end
101
+ end
102
+
103
+ context 'when no Content-Type is defined' do
104
+ before { response.body = response_body_structured_data }
105
+
106
+ let(:response_headers) {
107
+ { 'X-Header' => 'Foobar',
108
+ 'Location' => '/api/resources/123' }
109
+ }
110
+ it 'still defaults to the default handler' do
111
+ response.encode!
112
+ expect(JSON.load(response.body)).to eq(response_body_structured_data)
113
+ end
114
+ end
115
+
116
+
117
+ end
87
118
 
88
119
  context 'multipart responses' do
89
120
  let(:part) { Praxis::MultipartPart.new('not so ok', {'Status' => 400, "Location" => "somewhere"}) }
@@ -14,7 +14,7 @@ describe Praxis::Collection do
14
14
  context '.of' do
15
15
  let(:media_type) do
16
16
  Class.new(Praxis::MediaType) do
17
- identifier 'an-awesome-type'
17
+ identifier 'application/an-awesome-type'
18
18
  end
19
19
  end
20
20
 
@@ -22,7 +22,7 @@ describe Praxis::Collection do
22
22
  Praxis::Collection.of(media_type)
23
23
  end
24
24
 
25
- its(:identifier) { should eq 'an-awesome-type;type=collection' }
25
+ its(:identifier) { should eq 'application/an-awesome-type; type=collection' }
26
26
 
27
27
  it 'sets the collection on the media type' do
28
28
  expect(media_type::Collection).to be(collection)
@@ -78,4 +78,21 @@ describe Praxis::Multipart do
78
78
 
79
79
  end
80
80
 
81
+ context '.load' do
82
+ it 'returns nil when loading nil' do
83
+ expect(type.load(nil)).to be nil
84
+ end
85
+ it 'returns the same value when loading an instance of a Multipart class' do
86
+ instance = Praxis::Multipart.example
87
+ expect(type.load(instance)).to be instance
88
+ end
89
+
90
+ it 'complains when the value is not a String (besides a Multipart instance or nil)' do
91
+ expect{
92
+ type.load( {a: "hash"} )
93
+ }.to raise_error(Attributor::CoercionError)
94
+ end
95
+
96
+ pending 'complete the load tests'
97
+ end
81
98
  end
@@ -46,7 +46,7 @@ class Instances < BaseClass
46
46
 
47
47
  def show(cloud_id:, id:, junk:, **other_params)
48
48
  payload = request.payload
49
- response.body = {cloud_id: cloud_id, id: id, junk: junk, other_params: other_params, payload: payload.dump}
49
+ response.body = {cloud_id: cloud_id, id: id, junk: junk, other_params: other_params, payload: payload && payload.dump}
50
50
  response.headers['Content-Type'] = 'application/vnd.acme.instance'
51
51
  response
52
52
  end
@@ -17,6 +17,19 @@ class Person < Praxis::MediaType
17
17
  attribute :name
18
18
  attribute :href
19
19
  end
20
+
21
+ class CollectionSummary < Praxis::MediaType
22
+ attributes do
23
+ attribute :href, String
24
+ attribute :size, Integer
25
+ end
26
+
27
+ view :link do
28
+ attribute :href
29
+ attribute :size
30
+ end
31
+
32
+ end
20
33
  end
21
34
 
22
35
 
@@ -30,10 +43,16 @@ class Address < Praxis::MediaType
30
43
  attribute :name, String
31
44
 
32
45
  attribute :owner, Person
46
+ attribute :custodian, Person
47
+
48
+ attribute :residents, Praxis::Collection.of(Person)
49
+ attribute :residents_summary, Person::CollectionSummary
33
50
 
34
51
  links do
35
52
  link :owner
36
53
  link :super, Person, using: :manager
54
+ link :caretaker, using: :custodian
55
+ link :residents, using: :residents_summary
37
56
  end
38
57
 
39
58
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: praxis
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.0
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josep M. Blanquer
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-03-17 00:00:00.000000000 Z
12
+ date: 2015-04-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rack
@@ -101,14 +101,14 @@ dependencies:
101
101
  requirements:
102
102
  - - "~>"
103
103
  - !ruby/object:Gem::Version
104
- version: 2.6.0
104
+ version: '2.6'
105
105
  type: :runtime
106
106
  prerelease: false
107
107
  version_requirements: !ruby/object:Gem::Requirement
108
108
  requirements:
109
109
  - - "~>"
110
110
  - !ruby/object:Gem::Version
111
- version: 2.6.0
111
+ version: '2.6'
112
112
  - !ruby/object:Gem::Dependency
113
113
  name: thor
114
114
  requirement: !ruby/object:Gem::Requirement
@@ -834,7 +834,6 @@ files:
834
834
  - lib/praxis/callbacks.rb
835
835
  - lib/praxis/collection.rb
836
836
  - lib/praxis/config.rb
837
- - lib/praxis/content_type_parser.rb
838
837
  - lib/praxis/controller.rb
839
838
  - lib/praxis/dispatcher.rb
840
839
  - lib/praxis/error_handler.rb
@@ -848,9 +847,13 @@ files:
848
847
  - lib/praxis/exceptions/stage_not_found.rb
849
848
  - lib/praxis/exceptions/validation.rb
850
849
  - lib/praxis/file_group.rb
850
+ - lib/praxis/handlers/json.rb
851
+ - lib/praxis/handlers/www_form.rb
852
+ - lib/praxis/handlers/xml.rb
851
853
  - lib/praxis/links.rb
852
854
  - lib/praxis/media_type.rb
853
855
  - lib/praxis/media_type_collection.rb
856
+ - lib/praxis/media_type_identifier.rb
854
857
  - lib/praxis/multipart/parser.rb
855
858
  - lib/praxis/multipart/part.rb
856
859
  - lib/praxis/notifications.rb
@@ -899,12 +902,14 @@ files:
899
902
  - spec/praxis/bootloader_spec.rb
900
903
  - spec/praxis/callbacks_spec.rb
901
904
  - spec/praxis/config_spec.rb
902
- - spec/praxis/content_type_parser_spec.rb
903
905
  - spec/praxis/controller_spec.rb
904
906
  - spec/praxis/dispatcher_spec.rb
905
907
  - spec/praxis/file_group_spec.rb
908
+ - spec/praxis/handlers/json_spec.rb
909
+ - spec/praxis/handlers/xml_spec.rb
906
910
  - spec/praxis/links_spec.rb
907
911
  - spec/praxis/media_type_collection_spec.rb
912
+ - spec/praxis/media_type_identifier_spec.rb
908
913
  - spec/praxis/media_type_spec.rb
909
914
  - spec/praxis/multipart/parser_spec.rb
910
915
  - spec/praxis/notifications_spec.rb