representors 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +18 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +126 -0
- data/LICENSE.md +19 -0
- data/README.md +28 -0
- data/Rakefile +10 -0
- data/lib/representor_support/utilities.rb +39 -0
- data/lib/representors.rb +5 -0
- data/lib/representors/errors.rb +7 -0
- data/lib/representors/field.rb +108 -0
- data/lib/representors/options.rb +67 -0
- data/lib/representors/representor.rb +161 -0
- data/lib/representors/representor_builder.rb +64 -0
- data/lib/representors/representor_hash.rb +59 -0
- data/lib/representors/serialization.rb +4 -0
- data/lib/representors/serialization/deserializer_base.rb +29 -0
- data/lib/representors/serialization/deserializer_factory.rb +13 -0
- data/lib/representors/serialization/hal_deserializer.rb +44 -0
- data/lib/representors/serialization/hal_serializer.rb +91 -0
- data/lib/representors/serialization/hale_deserializer.rb +162 -0
- data/lib/representors/serialization/hale_serializer.rb +110 -0
- data/lib/representors/serialization/serialization_base.rb +27 -0
- data/lib/representors/serialization/serialization_factory_base.rb +54 -0
- data/lib/representors/serialization/serializer_base.rb +20 -0
- data/lib/representors/serialization/serializer_factory.rb +17 -0
- data/lib/representors/transition.rb +130 -0
- data/lib/representors/version.rb +4 -0
- data/spec/fixtures/complex_hal.json +92 -0
- data/spec/fixtures/complex_hale_document.json +81 -0
- data/spec/fixtures/drds_hash.rb +120 -0
- data/spec/fixtures/hale_spec_examples/basic.json +77 -0
- data/spec/fixtures/hale_spec_examples/complex_reference_objects.json +157 -0
- data/spec/fixtures/hale_spec_examples/data.json +17 -0
- data/spec/fixtures/hale_spec_examples/data_objects.json +96 -0
- data/spec/fixtures/hale_spec_examples/link_objects.json +18 -0
- data/spec/fixtures/hale_spec_examples/nested_ref.json +43 -0
- data/spec/fixtures/hale_spec_examples/reference_objects.json +89 -0
- data/spec/fixtures/hale_tutorial_examples/basic_links.json +85 -0
- data/spec/fixtures/hale_tutorial_examples/basic_links_with_orders.json +96 -0
- data/spec/fixtures/hale_tutorial_examples/basic_links_with_references.json +108 -0
- data/spec/fixtures/hale_tutorial_examples/embedded.json +182 -0
- data/spec/fixtures/hale_tutorial_examples/empty.json +1 -0
- data/spec/fixtures/hale_tutorial_examples/enctype.json +14 -0
- data/spec/fixtures/hale_tutorial_examples/final.json +141 -0
- data/spec/fixtures/hale_tutorial_examples/get_link.json +17 -0
- data/spec/fixtures/hale_tutorial_examples/get_link_with_data.json +29 -0
- data/spec/fixtures/hale_tutorial_examples/links.json +11 -0
- data/spec/fixtures/hale_tutorial_examples/links_only.json +3 -0
- data/spec/fixtures/hale_tutorial_examples/meta.json +208 -0
- data/spec/fixtures/hale_tutorial_examples/self_link.json +7 -0
- data/spec/fixtures/single_drd.rb +266 -0
- data/spec/lib/representors/complex_representor_spec.rb +288 -0
- data/spec/lib/representors/field_spec.rb +141 -0
- data/spec/lib/representors/representor_builder_spec.rb +223 -0
- data/spec/lib/representors/representor_spec.rb +285 -0
- data/spec/lib/representors/serialization/deserializer_factory_spec.rb +118 -0
- data/spec/lib/representors/serialization/hal_deserializer_spec.rb +34 -0
- data/spec/lib/representors/serialization/hal_serializer_spec.rb +171 -0
- data/spec/lib/representors/serialization/hale_deserializer_spec.rb +59 -0
- data/spec/lib/representors/serialization/hale_roundtrip_spec.rb +34 -0
- data/spec/lib/representors/serialization/hale_serializer_spec.rb +659 -0
- data/spec/lib/representors/serialization/serializer_factory_spec.rb +108 -0
- data/spec/lib/representors/transition_spec.rb +349 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/support/basic-hale.json +12 -0
- data/spec/support/hal_representor_shared.rb +206 -0
- data/spec/support/helpers.rb +8 -0
- data/tasks/benchmark.rake +75 -0
- data/tasks/complex_hal_document.json +98 -0
- data/tasks/test_specs.rake +37 -0
- data/tasks/yard.rake +22 -0
- metadata +232 -0
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'representors/serialization/serializer_base'
|
3
|
+
|
4
|
+
module Representors
|
5
|
+
|
6
|
+
describe SerializerFactory do
|
7
|
+
let(:factory) { Class.new(SerializerFactory) } # prevent singleton pollution from spec
|
8
|
+
|
9
|
+
describe '.register_serializers' do
|
10
|
+
it 'adds classes to the registered serializers' do
|
11
|
+
serializer_classes = [create_serializer('serializer1'), create_serializer('serializer2')]
|
12
|
+
factory.register_serializers(*serializer_classes)
|
13
|
+
|
14
|
+
expect(factory.registered_serializers).to include(*serializer_classes)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '.registered_serializers' do
|
19
|
+
before do
|
20
|
+
@serializer_class = create_serializer('serializer3')
|
21
|
+
factory.register_serializers(@serializer_class)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'memoizes' do
|
25
|
+
registered_serializers = factory.registered_serializers.object_id
|
26
|
+
|
27
|
+
expect(registered_serializers).to eq(factory.registered_serializers.object_id)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'returns a frozen list of registered_serializers' do
|
31
|
+
expect(factory.registered_serializers).to be_frozen
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'returns registered serializers' do
|
35
|
+
expect(factory.registered_serializers).to include(@serializer_class)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#build' do
|
41
|
+
let(:representor) { Representor.new }
|
42
|
+
subject(:serializer) { SerializerFactory.build(media_type, representor) }
|
43
|
+
|
44
|
+
shared_examples_for 'a built serializer' do
|
45
|
+
it 'sets the correct target in the serializer' do
|
46
|
+
expect(serializer.target).to eq(representor)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
shared_examples_for 'a hal serializer' do
|
51
|
+
it 'returns a Halserializer' do
|
52
|
+
expect(serializer).to be_instance_of(Serialization::HalSerializer)
|
53
|
+
end
|
54
|
+
|
55
|
+
it_behaves_like 'a built serializer'
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'with hal+json media type as a string' do
|
59
|
+
let(:media_type) { 'application/hal+json' }
|
60
|
+
|
61
|
+
it_behaves_like 'a hal serializer'
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'with hal+json media type as a symbol' do
|
65
|
+
let(:media_type) { :hal_json }
|
66
|
+
|
67
|
+
it_behaves_like 'a hal serializer'
|
68
|
+
end
|
69
|
+
|
70
|
+
shared_examples_for 'a hale serializer' do
|
71
|
+
it 'returns a Haleserializer' do
|
72
|
+
expect(serializer).to be_instance_of(Serialization::HaleSerializer)
|
73
|
+
end
|
74
|
+
|
75
|
+
it_behaves_like 'a built serializer'
|
76
|
+
end
|
77
|
+
|
78
|
+
context 'with hale+json media type as a string' do
|
79
|
+
let(:media_type) { 'application/vnd.hale+json' }
|
80
|
+
|
81
|
+
it_behaves_like 'a hale serializer'
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'with hale+json media type as a symbol' do
|
85
|
+
let(:media_type) { :hale_json }
|
86
|
+
|
87
|
+
it_behaves_like 'a hale serializer'
|
88
|
+
end
|
89
|
+
|
90
|
+
shared_examples_for 'an unknown media type' do
|
91
|
+
it 'raises an unknown media type error' do
|
92
|
+
expect { serializer }.to raise_error(UnknownMediaTypeError, "Unknown media-type: #{media_type}.")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context 'unknown media type string' do
|
97
|
+
let(:media_type) { 'unknown' }
|
98
|
+
|
99
|
+
it_behaves_like 'an unknown media type'
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'unknown media type symbol' do
|
103
|
+
let(:media_type) { :unknown }
|
104
|
+
|
105
|
+
it_behaves_like 'an unknown media type'
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,349 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'yaml'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
module Representors
|
6
|
+
describe Transition do
|
7
|
+
before do
|
8
|
+
@self_transition = {
|
9
|
+
doc: 'Returns a list of DRDs.',
|
10
|
+
rt: 'drds',
|
11
|
+
type: 'safe',
|
12
|
+
href: 'some.example.com/list',
|
13
|
+
rel: 'self'
|
14
|
+
}
|
15
|
+
@search_transition = {
|
16
|
+
doc: 'Returns a list of DRDs that satisfy the search term.',
|
17
|
+
rt: 'drds',
|
18
|
+
type: 'safe',
|
19
|
+
method: 'post',
|
20
|
+
href: '/{?name}',
|
21
|
+
rel: 'search',
|
22
|
+
links: {
|
23
|
+
self: 'DRDs#drds/create',
|
24
|
+
help: 'Forms/create'
|
25
|
+
},
|
26
|
+
descriptors: {
|
27
|
+
name: {
|
28
|
+
doc: 'Name to search',
|
29
|
+
profile: 'http://alps.io/schema.org/Text',
|
30
|
+
sample: 'drdname',
|
31
|
+
scope: 'href',
|
32
|
+
options: { list: ['one', 'two'] }
|
33
|
+
},
|
34
|
+
status: {
|
35
|
+
doc: 'How is the DRD.',
|
36
|
+
profile: 'http://alps.io/schema.org/Text',
|
37
|
+
sample: 'renegade',
|
38
|
+
options: { list: ['renegade', 'compliant'], id: 'status_list' }
|
39
|
+
}
|
40
|
+
}
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
let(:representor_hash) { @representor_hash || @self_transition }
|
45
|
+
let(:subject) { Transition.new(representor_hash) }
|
46
|
+
|
47
|
+
describe '#to_s' do
|
48
|
+
it 'retuns a string representation' do
|
49
|
+
expect(subject.to_s).to eq(representor_hash.to_s)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '#to_hash' do
|
54
|
+
let(:hash_with_symbol_keys) do
|
55
|
+
{
|
56
|
+
href: 'some.niceplace.com/list',
|
57
|
+
rel: 'self'
|
58
|
+
}
|
59
|
+
end
|
60
|
+
let(:hash_with_string_keys) do
|
61
|
+
{
|
62
|
+
'href' => 'some.niceplace.com/list',
|
63
|
+
'rel' => 'self'
|
64
|
+
}
|
65
|
+
end
|
66
|
+
context 'the keys are strings' do
|
67
|
+
let(:representor_hash) {hash_with_string_keys}
|
68
|
+
it 'returns a hash with the keys as strings' do
|
69
|
+
expect(subject.to_hash).to eq(hash_with_string_keys)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
context 'the keys are symbols' do
|
73
|
+
let(:representor_hash) {hash_with_symbol_keys}
|
74
|
+
it 'returns a hash with the keys as strings' do
|
75
|
+
expect(subject.to_hash).to eq(hash_with_string_keys)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe '#[]' do
|
81
|
+
let(:representor_hash) { {key => value}}
|
82
|
+
let(:value) { 'http://www.dontknow.com'}
|
83
|
+
let(:key) {'href'}
|
84
|
+
|
85
|
+
it 'retuns the value for the keys' do
|
86
|
+
expect(subject[key]).to eq(value)
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'returns nil if the key is not in this transition' do
|
90
|
+
expect(subject['Ido not exists']).to be_nil
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'has indiferent access to the hash' do
|
94
|
+
expect(subject[key.to_sym]).to eq(value)
|
95
|
+
expect(subject[key.to_s]).to eq(value)
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
describe '#has_key?' do
|
101
|
+
let(:representor_hash) { {key => value}}
|
102
|
+
let(:value) { 'http://www.dontknow.com'}
|
103
|
+
let(:key) {'href'}
|
104
|
+
|
105
|
+
it 'retuns the value for the keys' do
|
106
|
+
expect(subject.has_key?(key)).to eq(true)
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'returns nil if the key is not in this transition' do
|
110
|
+
expect(subject.has_key?('Ido not exists')).to eq(false)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe '.new' do
|
115
|
+
it 'returns a Representors::Transition instance' do
|
116
|
+
expect(subject).to be_an_instance_of(Transition)
|
117
|
+
end
|
118
|
+
|
119
|
+
describe '#rel' do
|
120
|
+
it 'returns the transition key' do
|
121
|
+
expect(subject.rel).to eq('self')
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe '#interface_method' do
|
126
|
+
it 'returns the uniform interface method' do
|
127
|
+
expect(subject.interface_method).to eq('GET')
|
128
|
+
end
|
129
|
+
it 'retuns the interface_method provided by the hash' do
|
130
|
+
@representor_hash = @search_transition
|
131
|
+
expect(subject.interface_method).to eq('post')
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe '#parameters' do
|
136
|
+
it 'returns a list of fields representing the link parameters' do
|
137
|
+
@representor_hash = @search_transition
|
138
|
+
expect(subject.parameters.size).to eq(1)
|
139
|
+
field = subject.parameters.first
|
140
|
+
expect(field).to be_an_instance_of(Field)
|
141
|
+
expect(field.scope).to eq('href')
|
142
|
+
expect(field.name).to eq(:name)
|
143
|
+
end
|
144
|
+
context 'there are no params' do
|
145
|
+
let(:transition) do
|
146
|
+
{
|
147
|
+
href: 'some.example.com',
|
148
|
+
rel: 'filter'
|
149
|
+
}
|
150
|
+
end
|
151
|
+
it 'returns an empty array' do
|
152
|
+
expect(Transition.new(transition).parameters).to eq([])
|
153
|
+
end
|
154
|
+
end
|
155
|
+
context 'the uri template has information we do not have in data' do
|
156
|
+
let(:transition) do
|
157
|
+
{
|
158
|
+
href: 'some.place.com{?name,localization}',
|
159
|
+
rel: 'filter',
|
160
|
+
descriptors: {
|
161
|
+
name: {
|
162
|
+
doc: name_doc,
|
163
|
+
type: 'Integer',
|
164
|
+
profile: name_profile,
|
165
|
+
scope: 'href'
|
166
|
+
},
|
167
|
+
localization: {
|
168
|
+
doc: 'wrong scope',
|
169
|
+
type: 'Something crazy',
|
170
|
+
profile: 'Because this key has no scope should not be used'
|
171
|
+
}
|
172
|
+
}
|
173
|
+
}
|
174
|
+
end
|
175
|
+
let(:name_doc) {'di place of Trusmis'}
|
176
|
+
let(:name_profile) {'http://alps.io/schema.org/Text'}
|
177
|
+
|
178
|
+
it 'returns all the variables in the uri template' do
|
179
|
+
expect(Transition.new(transition).parameters.size).to eq(2)
|
180
|
+
end
|
181
|
+
it 'returns the information about the variable described by the document' do
|
182
|
+
param = Transition.new(transition).parameters.find{|param| param.name == :name}
|
183
|
+
expect(param.scope).to eq('href')
|
184
|
+
expect(param.type).to eq('Integer')
|
185
|
+
end
|
186
|
+
it 'returns default information for the variable not described by the document' do
|
187
|
+
param = Transition.new(transition).parameters.find{|param| param.name == :localization}
|
188
|
+
expect(param.scope).to eq('href')
|
189
|
+
expect(param.type).to eq('string')
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
describe '#attributes' do
|
195
|
+
it 'returns a list of fields representing the link attributes' do
|
196
|
+
@representor_hash = @search_transition
|
197
|
+
expect(subject.attributes.size).to eq(1)
|
198
|
+
field = subject.attributes.first
|
199
|
+
expect(field).to be_an_instance_of(Field)
|
200
|
+
expect(field.scope).to eq('attribute')
|
201
|
+
expect(field.name).to eq(:status)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
describe 'descriptors' do
|
206
|
+
it 'returns a list of fields representing the link attributes' do
|
207
|
+
@representor_hash = @search_transition
|
208
|
+
expect(subject.descriptors.size).to eq(2)
|
209
|
+
fields = subject.descriptors.all? { |item| item.instance_of?(Field) }
|
210
|
+
expect(fields).to eq(true)
|
211
|
+
end
|
212
|
+
|
213
|
+
it 'returns params as part of the descriptors' do
|
214
|
+
@representor_hash = @search_transition
|
215
|
+
field = subject.descriptors.first
|
216
|
+
expect(field).to be_an_instance_of(Field)
|
217
|
+
expect(field.scope).to eq('attribute')
|
218
|
+
end
|
219
|
+
|
220
|
+
it 'returns attributes as part of the descriptors' do
|
221
|
+
@representor_hash = @search_transition
|
222
|
+
field = subject.descriptors[1]
|
223
|
+
expect(field).to be_an_instance_of(Field)
|
224
|
+
expect(field.scope).to eq('href')
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
describe '#meta_links' do
|
229
|
+
context 'no metalinks' do
|
230
|
+
it 'returns an empty array' do
|
231
|
+
expect(subject.meta_links).to eq([])
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
it 'returns a list of Transitions' do
|
236
|
+
@representor_hash = @search_transition
|
237
|
+
links = subject.meta_links.all? { |item| item.instance_of?(Transition) }
|
238
|
+
expect(links).to eq(true)
|
239
|
+
end
|
240
|
+
it 'returns self as the first meta link' do
|
241
|
+
@representor_hash = @search_transition
|
242
|
+
link = subject.meta_links[0]
|
243
|
+
expect(link.rel).to eq(:self)
|
244
|
+
expect(link.uri).to eq('DRDs#drds/create')
|
245
|
+
end
|
246
|
+
it 'returns self as the first meta link' do
|
247
|
+
@representor_hash = @search_transition
|
248
|
+
link = subject.meta_links[1]
|
249
|
+
expect(link.rel).to eq(:help)
|
250
|
+
expect(link.uri).to eq("Forms/create")
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
describe '#uri' do
|
255
|
+
context 'the url does not have a template variable in the url' do
|
256
|
+
let(:transition) do
|
257
|
+
{
|
258
|
+
href: 'some.example.com',
|
259
|
+
rel: 'filter'
|
260
|
+
}
|
261
|
+
end
|
262
|
+
context 'params provided' do
|
263
|
+
it 'returns the url as it is' do
|
264
|
+
expect(Transition.new(transition).uri(uuid: 'uuid')).to eq(transition[:href])
|
265
|
+
end
|
266
|
+
end
|
267
|
+
context 'params not provided' do
|
268
|
+
it 'returns the url as it is' do
|
269
|
+
expect(Transition.new(transition).uri).to eq(transition[:href])
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
context 'the url has template variables in the base url and the query parameters' do
|
275
|
+
let(:transition) do
|
276
|
+
{
|
277
|
+
href: 'some.example.com/{uuid}?first_param=goodstuff{&filter}',
|
278
|
+
rel: 'filter'
|
279
|
+
}
|
280
|
+
end
|
281
|
+
let(:uuid) { SecureRandom.uuid}
|
282
|
+
let(:filter) {'cows'}
|
283
|
+
let(:full_url) {"some.example.com/#{uuid}?first_param=goodstuff&filter=cows"}
|
284
|
+
let(:not_templated_url) {'some.example.com/?first_param=goodstuff'}
|
285
|
+
|
286
|
+
it 'allows to create the url with the correct parameters' do
|
287
|
+
expect(Transition.new(transition).uri(uuid: uuid, filter: filter)).to eq(full_url)
|
288
|
+
end
|
289
|
+
it 'returns the url without template variables when there are no params' do
|
290
|
+
expect(Transition.new(transition).uri).to eq(not_templated_url)
|
291
|
+
end
|
292
|
+
it 'returns the url without template variables when the params does not match the template variable' do
|
293
|
+
expect(Transition.new(transition).uri(stuff: filter)).to eq(not_templated_url)
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
describe '#templated_uri' do
|
299
|
+
context 'the transition has a templated url' do
|
300
|
+
let(:transition) do
|
301
|
+
{
|
302
|
+
href: 'some.example.com/{uuid}?first_param=goodstuff{&filter}',
|
303
|
+
rel: 'filter'
|
304
|
+
}
|
305
|
+
end
|
306
|
+
it 'returns the templated url' do
|
307
|
+
expect(Transition.new(transition).templated_uri).to eq(transition[:href])
|
308
|
+
end
|
309
|
+
end
|
310
|
+
context 'the transition has a non-templated url' do
|
311
|
+
let(:transition) do
|
312
|
+
{
|
313
|
+
href: 'some.example.com',
|
314
|
+
rel: 'filter'
|
315
|
+
}
|
316
|
+
end
|
317
|
+
it 'returns the url' do
|
318
|
+
expect(Transition.new(transition).templated_uri).to eq(transition[:href])
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
describe '#templated?' do
|
324
|
+
context 'the transition has a templated url' do
|
325
|
+
let(:transition) do
|
326
|
+
{
|
327
|
+
href: 'some.example.com/{uuid}?first_param=goodstuff{&filter}',
|
328
|
+
rel: 'filter'
|
329
|
+
}
|
330
|
+
end
|
331
|
+
it 'returns true' do
|
332
|
+
expect(Transition.new(transition)).to be_templated
|
333
|
+
end
|
334
|
+
end
|
335
|
+
context 'the transition has a non-templated url' do
|
336
|
+
let(:transition) do
|
337
|
+
{
|
338
|
+
href: 'some.example.com',
|
339
|
+
rel: 'filter'
|
340
|
+
}
|
341
|
+
end
|
342
|
+
it 'returns false' do
|
343
|
+
expect(Transition.new(transition)).not_to be_templated
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|