simple_jsonapi 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rubocop.yml +131 -0
- data/CHANGELOG.md +2 -0
- data/Gemfile +5 -0
- data/Jenkinsfile +92 -0
- data/LICENSE.txt +22 -0
- data/README.md +532 -0
- data/Rakefile +10 -0
- data/lib/simple_jsonapi.rb +112 -0
- data/lib/simple_jsonapi/definition/attribute.rb +45 -0
- data/lib/simple_jsonapi/definition/base.rb +50 -0
- data/lib/simple_jsonapi/definition/concerns/has_links_object.rb +36 -0
- data/lib/simple_jsonapi/definition/concerns/has_meta_object.rb +36 -0
- data/lib/simple_jsonapi/definition/error.rb +70 -0
- data/lib/simple_jsonapi/definition/error_source.rb +29 -0
- data/lib/simple_jsonapi/definition/link.rb +27 -0
- data/lib/simple_jsonapi/definition/meta.rb +27 -0
- data/lib/simple_jsonapi/definition/relationship.rb +60 -0
- data/lib/simple_jsonapi/definition/resource.rb +104 -0
- data/lib/simple_jsonapi/error_serializer.rb +76 -0
- data/lib/simple_jsonapi/errors/bad_request.rb +11 -0
- data/lib/simple_jsonapi/errors/exception_serializer.rb +6 -0
- data/lib/simple_jsonapi/errors/wrapped_error.rb +35 -0
- data/lib/simple_jsonapi/errors/wrapped_error_serializer.rb +35 -0
- data/lib/simple_jsonapi/helpers/exceptions.rb +39 -0
- data/lib/simple_jsonapi/helpers/serializer_inferrer.rb +136 -0
- data/lib/simple_jsonapi/helpers/serializer_methods.rb +36 -0
- data/lib/simple_jsonapi/node/attributes.rb +51 -0
- data/lib/simple_jsonapi/node/base.rb +91 -0
- data/lib/simple_jsonapi/node/data/collection.rb +25 -0
- data/lib/simple_jsonapi/node/data/singular.rb +26 -0
- data/lib/simple_jsonapi/node/document/base.rb +62 -0
- data/lib/simple_jsonapi/node/document/collection.rb +17 -0
- data/lib/simple_jsonapi/node/document/errors.rb +17 -0
- data/lib/simple_jsonapi/node/document/singular.rb +17 -0
- data/lib/simple_jsonapi/node/error.rb +55 -0
- data/lib/simple_jsonapi/node/error_source.rb +40 -0
- data/lib/simple_jsonapi/node/errors.rb +28 -0
- data/lib/simple_jsonapi/node/included.rb +45 -0
- data/lib/simple_jsonapi/node/object_links.rb +40 -0
- data/lib/simple_jsonapi/node/object_meta.rb +40 -0
- data/lib/simple_jsonapi/node/relationship.rb +79 -0
- data/lib/simple_jsonapi/node/relationship_data/base.rb +53 -0
- data/lib/simple_jsonapi/node/relationship_data/collection.rb +32 -0
- data/lib/simple_jsonapi/node/relationship_data/singular.rb +33 -0
- data/lib/simple_jsonapi/node/relationships.rb +60 -0
- data/lib/simple_jsonapi/node/resource/base.rb +21 -0
- data/lib/simple_jsonapi/node/resource/full.rb +49 -0
- data/lib/simple_jsonapi/node/resource/linkage.rb +25 -0
- data/lib/simple_jsonapi/parameters/fields_spec.rb +45 -0
- data/lib/simple_jsonapi/parameters/include_spec.rb +57 -0
- data/lib/simple_jsonapi/parameters/sort_spec.rb +107 -0
- data/lib/simple_jsonapi/serializer.rb +89 -0
- data/lib/simple_jsonapi/version.rb +3 -0
- data/simple_jsonapi.gemspec +29 -0
- data/test/errors/bad_request_test.rb +34 -0
- data/test/errors/error_serializer_test.rb +229 -0
- data/test/errors/exception_serializer_test.rb +25 -0
- data/test/errors/wrapped_error_serializer_test.rb +91 -0
- data/test/errors/wrapped_error_test.rb +44 -0
- data/test/parameters/fields_spec_test.rb +56 -0
- data/test/parameters/include_spec_test.rb +58 -0
- data/test/parameters/sort_spec_test.rb +65 -0
- data/test/resources/attributes_test.rb +109 -0
- data/test/resources/extras_test.rb +70 -0
- data/test/resources/id_and_type_test.rb +76 -0
- data/test/resources/inclusion_test.rb +134 -0
- data/test/resources/links_test.rb +63 -0
- data/test/resources/meta_test.rb +49 -0
- data/test/resources/relationships_test.rb +262 -0
- data/test/resources/sorting_test.rb +79 -0
- data/test/resources/sparse_fieldset_test.rb +160 -0
- data/test/root_objects_test.rb +165 -0
- data/test/test_helper.rb +31 -0
- metadata +235 -0
@@ -0,0 +1,229 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ErrorSerializerTest < Minitest::Spec
|
4
|
+
class Problem < TestModel
|
5
|
+
attr_accessor :id, :name, :number, :date
|
6
|
+
end
|
7
|
+
|
8
|
+
class DefaultsSerializer < SimpleJsonapi::ErrorSerializer
|
9
|
+
end
|
10
|
+
|
11
|
+
class BlocksSerializer < SimpleJsonapi::ErrorSerializer
|
12
|
+
id(&:id)
|
13
|
+
status { "422" }
|
14
|
+
code(&:number)
|
15
|
+
title(&:name)
|
16
|
+
detail { |err| "#{err.name} #{err.number}" }
|
17
|
+
source do
|
18
|
+
pointer { |err| "/to/#{err.name}" }
|
19
|
+
parameter { |err| err.class.name }
|
20
|
+
end
|
21
|
+
about_link { |err| "https://www.patientslikeme.com/error/#{err.id}" }
|
22
|
+
meta(:date) { |err| err.date&.iso8601 }
|
23
|
+
end
|
24
|
+
|
25
|
+
class ProcsSerializer < SimpleJsonapi::ErrorSerializer
|
26
|
+
id ->(err) { err.id }
|
27
|
+
status ->(_err) { "422" }
|
28
|
+
code ->(err) { err.number }
|
29
|
+
title ->(err) { err.name }
|
30
|
+
detail ->(err) { "#{err.name} #{err.number}" }
|
31
|
+
source do
|
32
|
+
pointer ->(err) { "/to/#{err.name}" }
|
33
|
+
parameter ->(err) { err.class.name }
|
34
|
+
end
|
35
|
+
about_link ->(err) { "https://www.patientslikeme.com/error/#{err.id}" }
|
36
|
+
meta :date, ->(err) { err.date&.iso8601 }
|
37
|
+
end
|
38
|
+
|
39
|
+
class ValuesSerializer < SimpleJsonapi::ErrorSerializer
|
40
|
+
id "42"
|
41
|
+
status "422"
|
42
|
+
code "the_code"
|
43
|
+
title "the title"
|
44
|
+
detail "the details"
|
45
|
+
source do
|
46
|
+
pointer "/to/somewhere"
|
47
|
+
parameter "the_parameter"
|
48
|
+
end
|
49
|
+
about_link "https://www.patientslikeme.com/errors"
|
50
|
+
meta :date, "2017-01-01"
|
51
|
+
end
|
52
|
+
|
53
|
+
let(:problem) { Problem.new(id: 12, name: "BadThing", number: "999", date: Date.new(2017, 7, 1)) }
|
54
|
+
|
55
|
+
let(:default_serialized) do
|
56
|
+
SimpleJsonapi.render_errors(problem, serializer: DefaultsSerializer)[:errors].first
|
57
|
+
end
|
58
|
+
let(:blocks_serialized) do
|
59
|
+
SimpleJsonapi.render_errors(problem, serializer: BlocksSerializer)[:errors].first
|
60
|
+
end
|
61
|
+
let(:procs_serialized) do
|
62
|
+
SimpleJsonapi.render_errors(problem, serializer: ProcsSerializer)[:errors].first
|
63
|
+
end
|
64
|
+
let(:values_serialized) do
|
65
|
+
SimpleJsonapi.render_errors(problem, serializer: ValuesSerializer)[:errors].first
|
66
|
+
end
|
67
|
+
|
68
|
+
describe SimpleJsonapi::ErrorSerializer do
|
69
|
+
describe "id property" do
|
70
|
+
it "is excluded by default" do
|
71
|
+
refute default_serialized.key?(:id)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "accepts a block" do
|
75
|
+
assert_equal "12", blocks_serialized[:id]
|
76
|
+
end
|
77
|
+
|
78
|
+
it "accepts a proc" do
|
79
|
+
assert_equal "12", procs_serialized[:id]
|
80
|
+
end
|
81
|
+
|
82
|
+
it "accepts a value" do
|
83
|
+
assert_equal "42", values_serialized[:id]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "status property" do
|
88
|
+
it "is excluded by default" do
|
89
|
+
refute default_serialized.key?(:status)
|
90
|
+
end
|
91
|
+
|
92
|
+
it "accepts a block" do
|
93
|
+
assert_equal "422", blocks_serialized[:status]
|
94
|
+
end
|
95
|
+
|
96
|
+
it "accepts a proc" do
|
97
|
+
assert_equal "422", procs_serialized[:status]
|
98
|
+
end
|
99
|
+
|
100
|
+
it "accepts a value" do
|
101
|
+
assert_equal "422", values_serialized[:status]
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "code property" do
|
106
|
+
it "is excluded by default" do
|
107
|
+
refute default_serialized.key?(:code)
|
108
|
+
end
|
109
|
+
|
110
|
+
it "accepts a block" do
|
111
|
+
assert_equal "999", blocks_serialized[:code]
|
112
|
+
end
|
113
|
+
|
114
|
+
it "accepts a proc" do
|
115
|
+
assert_equal "999", procs_serialized[:code]
|
116
|
+
end
|
117
|
+
|
118
|
+
it "accepts a value" do
|
119
|
+
assert_equal "the_code", values_serialized[:code]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe "title property" do
|
124
|
+
it "is excluded by default" do
|
125
|
+
refute default_serialized.key?(:title)
|
126
|
+
end
|
127
|
+
|
128
|
+
it "accepts a block" do
|
129
|
+
assert_equal "BadThing", blocks_serialized[:title]
|
130
|
+
end
|
131
|
+
|
132
|
+
it "accepts a proc" do
|
133
|
+
assert_equal "BadThing", procs_serialized[:title]
|
134
|
+
end
|
135
|
+
|
136
|
+
it "accepts a value" do
|
137
|
+
assert_equal "the title", values_serialized[:title]
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe "detail property" do
|
142
|
+
it "is excluded by default" do
|
143
|
+
refute default_serialized.key?(:detail)
|
144
|
+
end
|
145
|
+
|
146
|
+
it "accepts a block" do
|
147
|
+
assert_equal "BadThing 999", blocks_serialized[:detail]
|
148
|
+
end
|
149
|
+
|
150
|
+
it "accepts a proc" do
|
151
|
+
assert_equal "BadThing 999", procs_serialized[:detail]
|
152
|
+
end
|
153
|
+
|
154
|
+
it "accepts a value" do
|
155
|
+
assert_equal "the details", values_serialized[:detail]
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
describe "source object" do
|
160
|
+
it "is excluded by default" do
|
161
|
+
refute default_serialized.key?(:source)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
describe "source pointer property" do
|
166
|
+
it "accepts a block" do
|
167
|
+
assert_equal "/to/BadThing", blocks_serialized.dig(:source, :pointer)
|
168
|
+
end
|
169
|
+
|
170
|
+
it "accepts a proc" do
|
171
|
+
assert_equal "/to/BadThing", procs_serialized.dig(:source, :pointer)
|
172
|
+
end
|
173
|
+
|
174
|
+
it "accepts a value" do
|
175
|
+
assert_equal "/to/somewhere", values_serialized.dig(:source, :pointer)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
describe "source parameter property" do
|
180
|
+
it "accepts a block" do
|
181
|
+
assert_equal problem.class.name, blocks_serialized.dig(:source, :parameter)
|
182
|
+
end
|
183
|
+
|
184
|
+
it "accepts a proc" do
|
185
|
+
assert_equal problem.class.name, procs_serialized.dig(:source, :parameter)
|
186
|
+
end
|
187
|
+
|
188
|
+
it "accepts a value" do
|
189
|
+
assert_equal "the_parameter", values_serialized.dig(:source, :parameter)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
describe "about link" do
|
194
|
+
it "is excluded by default" do
|
195
|
+
refute default_serialized.key?(:links)
|
196
|
+
end
|
197
|
+
|
198
|
+
it "accepts a block" do
|
199
|
+
assert_equal "https://www.patientslikeme.com/error/12", blocks_serialized.dig(:links, :about)
|
200
|
+
end
|
201
|
+
|
202
|
+
it "accepts a proc" do
|
203
|
+
assert_equal "https://www.patientslikeme.com/error/12", procs_serialized.dig(:links, :about)
|
204
|
+
end
|
205
|
+
|
206
|
+
it "accepts a value" do
|
207
|
+
assert_equal "https://www.patientslikeme.com/errors", values_serialized.dig(:links, :about)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
describe "meta information" do
|
212
|
+
it "is excluded by default" do
|
213
|
+
refute default_serialized.key?(:meta)
|
214
|
+
end
|
215
|
+
|
216
|
+
it "accepts a block" do
|
217
|
+
assert_equal "2017-07-01", blocks_serialized.dig(:meta, :date)
|
218
|
+
end
|
219
|
+
|
220
|
+
it "accepts a proc" do
|
221
|
+
assert_equal "2017-07-01", procs_serialized.dig(:meta, :date)
|
222
|
+
end
|
223
|
+
|
224
|
+
it "accepts a value" do
|
225
|
+
assert_equal "2017-01-01", values_serialized.dig(:meta, :date)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ExceptionSerializerTest < Minitest::Spec
|
4
|
+
class SampleException < StandardError
|
5
|
+
end
|
6
|
+
|
7
|
+
let(:exception) { SampleException.new("the message") }
|
8
|
+
let(:serialized) do
|
9
|
+
SimpleJsonapi.render_errors(exception, serializer: SimpleJsonapi::Errors::ExceptionSerializer)[:errors].first
|
10
|
+
end
|
11
|
+
|
12
|
+
describe SimpleJsonapi::Errors::ExceptionSerializer do
|
13
|
+
it "sets code to the class name in snake case" do
|
14
|
+
assert_equal "exception_serializer_test_sample_exception", serialized[:code]
|
15
|
+
end
|
16
|
+
|
17
|
+
it "sets title to the class name" do
|
18
|
+
assert_equal "ExceptionSerializerTest::SampleException", serialized[:title]
|
19
|
+
end
|
20
|
+
|
21
|
+
it "sets detail to the message" do
|
22
|
+
assert_equal "the message", serialized[:detail]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class WrappedErrorSerializerTest < Minitest::Spec
|
4
|
+
let(:cause) { StandardError.new("the message") }
|
5
|
+
|
6
|
+
let(:wrapped) do
|
7
|
+
SimpleJsonapi::Errors::WrappedError.new(
|
8
|
+
cause,
|
9
|
+
id: "the id",
|
10
|
+
status: "the status",
|
11
|
+
code: "the code",
|
12
|
+
title: "the title",
|
13
|
+
detail: "the detail",
|
14
|
+
source_pointer: "the source pointer",
|
15
|
+
source_parameter: "the source parameter",
|
16
|
+
about_link: "the about link",
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
let(:serialized) do
|
21
|
+
rendered_errors = SimpleJsonapi.render_errors(
|
22
|
+
wrapped,
|
23
|
+
serializer: SimpleJsonapi::Errors::WrappedErrorSerializer,
|
24
|
+
)
|
25
|
+
rendered_errors[:errors].first
|
26
|
+
end
|
27
|
+
|
28
|
+
describe SimpleJsonapi::Errors::WrappedErrorSerializer do
|
29
|
+
it "outputs the id property" do
|
30
|
+
assert_equal "the id", serialized[:id]
|
31
|
+
end
|
32
|
+
|
33
|
+
it "outputs the status property" do
|
34
|
+
assert_equal "the status", serialized[:status]
|
35
|
+
end
|
36
|
+
|
37
|
+
it "outputs the code property" do
|
38
|
+
assert_equal "the code", serialized[:code]
|
39
|
+
end
|
40
|
+
|
41
|
+
it "outputs the title property" do
|
42
|
+
assert_equal "the title", serialized[:title]
|
43
|
+
end
|
44
|
+
|
45
|
+
it "outputs the detail property" do
|
46
|
+
assert_equal "the detail", serialized[:detail]
|
47
|
+
end
|
48
|
+
|
49
|
+
it "outputs the nested source pointer" do
|
50
|
+
assert_equal "the source pointer", serialized[:source][:pointer]
|
51
|
+
end
|
52
|
+
|
53
|
+
it "outputs the nested source parameter" do
|
54
|
+
assert_equal "the source parameter", serialized[:source][:parameter]
|
55
|
+
end
|
56
|
+
|
57
|
+
it "outputs an about link" do
|
58
|
+
assert_equal "the about link", serialized[:links][:about]
|
59
|
+
end
|
60
|
+
|
61
|
+
it "omits blank properties" do
|
62
|
+
blank_error = SimpleJsonapi::Errors::WrappedError.new(
|
63
|
+
cause,
|
64
|
+
id: " ",
|
65
|
+
status: " ",
|
66
|
+
code: " ",
|
67
|
+
title: " ",
|
68
|
+
detail: " ",
|
69
|
+
source_pointer: " ",
|
70
|
+
source_parameter: " ",
|
71
|
+
about_link: " ",
|
72
|
+
)
|
73
|
+
|
74
|
+
rendered_errors = SimpleJsonapi.render_errors(
|
75
|
+
blank_error, serializer:
|
76
|
+
SimpleJsonapi::Errors::WrappedErrorSerializer,
|
77
|
+
)
|
78
|
+
|
79
|
+
serialized_blank = rendered_errors[:errors].first
|
80
|
+
|
81
|
+
refute_includes serialized_blank.keys, :id
|
82
|
+
refute_includes serialized_blank.keys, :status
|
83
|
+
refute_includes serialized_blank.keys, :code
|
84
|
+
refute_includes serialized_blank.keys, :title
|
85
|
+
refute_includes serialized_blank.keys, :detail
|
86
|
+
refute_includes serialized_blank.keys, :source
|
87
|
+
refute_includes serialized_blank.keys, :source
|
88
|
+
refute_includes serialized_blank.keys, :links
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class WrappedErrorTest < Minitest::Spec
|
4
|
+
let(:cause) { StandardError.new("the message") }
|
5
|
+
|
6
|
+
let(:wrapped) do
|
7
|
+
SimpleJsonapi::Errors::WrappedError.new(
|
8
|
+
cause,
|
9
|
+
id: "the id",
|
10
|
+
status: "the status",
|
11
|
+
code: "the code",
|
12
|
+
title: "the title",
|
13
|
+
detail: "the detail",
|
14
|
+
source_pointer: "the source pointer",
|
15
|
+
source_parameter: "the source parameter",
|
16
|
+
about_link: "the about link",
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
describe SimpleJsonapi::Errors::WrappedError do
|
21
|
+
describe "cause" do
|
22
|
+
it "defaults to nil" do
|
23
|
+
assert_nil SimpleJsonapi::Errors::WrappedError.new.cause
|
24
|
+
end
|
25
|
+
|
26
|
+
it "is stored" do
|
27
|
+
assert_equal cause, wrapped.cause
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "other properties" do
|
32
|
+
it "are stored" do
|
33
|
+
assert_equal "the id", wrapped.id
|
34
|
+
assert_equal "the status", wrapped.status
|
35
|
+
assert_equal "the code", wrapped.code
|
36
|
+
assert_equal "the title", wrapped.title
|
37
|
+
assert_equal "the detail", wrapped.detail
|
38
|
+
assert_equal "the source pointer", wrapped.source_pointer
|
39
|
+
assert_equal "the source parameter", wrapped.source_parameter
|
40
|
+
assert_equal "the about link", wrapped.about_link
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class FieldsSpecTest < Minitest::Spec
|
4
|
+
describe SimpleJsonapi::Parameters::FieldsSpec do
|
5
|
+
describe "parsing" do
|
6
|
+
it "accepts comma-delimited strings" do
|
7
|
+
spec = SimpleJsonapi::Parameters::FieldsSpec.new(
|
8
|
+
"orders" => "customer_name,description",
|
9
|
+
"line_items" => "order_id,product_id",
|
10
|
+
)
|
11
|
+
|
12
|
+
assert_equal [:customer_name, :description], spec[:orders]
|
13
|
+
assert_equal [:order_id, :product_id], spec[:line_items]
|
14
|
+
end
|
15
|
+
|
16
|
+
it "accepts arrays of strings" do
|
17
|
+
spec = SimpleJsonapi::Parameters::FieldsSpec.new(
|
18
|
+
"orders" => ["customer_name", "description"],
|
19
|
+
"line_items" => ["order_id", "product_id"],
|
20
|
+
)
|
21
|
+
|
22
|
+
assert_equal [:customer_name, :description], spec[:orders]
|
23
|
+
assert_equal [:order_id, :product_id], spec[:line_items]
|
24
|
+
end
|
25
|
+
|
26
|
+
it "accepts arrays of symbols" do
|
27
|
+
spec = SimpleJsonapi::Parameters::FieldsSpec.new(
|
28
|
+
orders: [:customer_name, :description],
|
29
|
+
line_items: [:order_id, :product_id],
|
30
|
+
)
|
31
|
+
|
32
|
+
assert_equal [:customer_name, :description], spec[:orders]
|
33
|
+
assert_equal [:order_id, :product_id], spec[:line_items]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "all_fields?" do
|
38
|
+
it "is true if nothing is defined for the type" do
|
39
|
+
spec = SimpleJsonapi::Parameters::FieldsSpec.new
|
40
|
+
assert_equal true, spec.all_fields?(:orders)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "is false if the type is assigned any fields" do
|
44
|
+
spec = SimpleJsonapi::Parameters::FieldsSpec.new(
|
45
|
+
"orders" => "customer_name,description",
|
46
|
+
)
|
47
|
+
assert_equal false, spec.all_fields?(:orders)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "is false if the type is assigned an empty list" do
|
51
|
+
spec = SimpleJsonapi::Parameters::FieldsSpec.new("orders" => "")
|
52
|
+
assert_equal false, spec.all_fields?(:orders)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|