praxis 0.14.0 → 0.15.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/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