json_api_client 1.22.0 → 1.24.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.
- checksums.yaml +4 -4
- data/LICENSE +20 -20
- data/README.md +723 -705
- data/Rakefile +32 -32
- data/lib/json_api_client/associations/base_association.rb +33 -33
- data/lib/json_api_client/associations/belongs_to.rb +31 -31
- data/lib/json_api_client/associations/has_many.rb +7 -7
- data/lib/json_api_client/associations/has_one.rb +16 -16
- data/lib/json_api_client/associations.rb +7 -7
- data/lib/json_api_client/connection.rb +41 -41
- data/lib/json_api_client/error_collector.rb +91 -91
- data/lib/json_api_client/errors.rb +125 -125
- data/lib/json_api_client/formatter.rb +145 -145
- data/lib/json_api_client/helpers/associatable.rb +88 -88
- data/lib/json_api_client/helpers/callbacks.rb +27 -27
- data/lib/json_api_client/helpers/dirty.rb +75 -75
- data/lib/json_api_client/helpers/dynamic_attributes.rb +78 -78
- data/lib/json_api_client/helpers/uri.rb +9 -9
- data/lib/json_api_client/helpers.rb +9 -9
- data/lib/json_api_client/implementation.rb +11 -11
- data/lib/json_api_client/included_data.rb +58 -58
- data/lib/json_api_client/linking/links.rb +21 -21
- data/lib/json_api_client/linking/top_level_links.rb +39 -39
- data/lib/json_api_client/linking.rb +5 -5
- data/lib/json_api_client/meta_data.rb +19 -19
- data/lib/json_api_client/middleware/json_request.rb +26 -26
- data/lib/json_api_client/middleware/status.rb +67 -67
- data/lib/json_api_client/middleware.rb +6 -7
- data/lib/json_api_client/paginating/nested_param_paginator.rb +140 -140
- data/lib/json_api_client/paginating/paginator.rb +89 -89
- data/lib/json_api_client/paginating.rb +6 -6
- data/lib/json_api_client/parsers/parser.rb +102 -102
- data/lib/json_api_client/parsers.rb +4 -4
- data/lib/json_api_client/query/builder.rb +239 -239
- data/lib/json_api_client/query/requestor.rb +73 -73
- data/lib/json_api_client/query.rb +5 -5
- data/lib/json_api_client/relationships/relations.rb +55 -55
- data/lib/json_api_client/relationships/top_level_relations.rb +30 -30
- data/lib/json_api_client/relationships.rb +5 -5
- data/lib/json_api_client/request_params.rb +57 -57
- data/lib/json_api_client/resource.rb +671 -658
- data/lib/json_api_client/result_set.rb +25 -25
- data/lib/json_api_client/schema.rb +154 -154
- data/lib/json_api_client/utils.rb +53 -53
- data/lib/json_api_client/version.rb +3 -3
- data/lib/json_api_client.rb +30 -30
- metadata +74 -36
- data/lib/json_api_client/middleware/parse_json.rb +0 -31
|
@@ -1,125 +1,125 @@
|
|
|
1
|
-
require 'rack'
|
|
2
|
-
|
|
3
|
-
module JsonApiClient
|
|
4
|
-
module Errors
|
|
5
|
-
class ApiError < StandardError
|
|
6
|
-
attr_reader :env
|
|
7
|
-
|
|
8
|
-
def initialize(env, msg = nil)
|
|
9
|
-
@env = env
|
|
10
|
-
# Try to fetch json_api errors from response
|
|
11
|
-
msg = track_json_api_errors(msg)
|
|
12
|
-
|
|
13
|
-
super msg
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
private
|
|
17
|
-
|
|
18
|
-
# Try to fetch json_api errors from response
|
|
19
|
-
def track_json_api_errors(msg)
|
|
20
|
-
return msg unless env.try(:body).kind_of?(Hash) || env.body.key?('errors')
|
|
21
|
-
|
|
22
|
-
errors_msg = env.body['errors'].map { |e| e['title'] }.compact.join('; ').presence
|
|
23
|
-
return msg unless errors_msg
|
|
24
|
-
|
|
25
|
-
msg.nil? ? errors_msg : "#{msg} (#{errors_msg})"
|
|
26
|
-
# Just to be sure that it is back compatible
|
|
27
|
-
rescue StandardError
|
|
28
|
-
msg
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
class ClientError < ApiError
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
class ResourceImmutableError < StandardError
|
|
36
|
-
def initialize(msg = 'Resource immutable')
|
|
37
|
-
super msg
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
class AccessDenied < ClientError
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
class NotAuthorized < ClientError
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
class NotFound < ClientError
|
|
48
|
-
attr_reader :uri
|
|
49
|
-
def initialize(env_or_uri, msg = nil)
|
|
50
|
-
env = nil
|
|
51
|
-
|
|
52
|
-
if env_or_uri.kind_of?(Faraday::Env)
|
|
53
|
-
env = env_or_uri
|
|
54
|
-
@uri = env[:url]
|
|
55
|
-
else
|
|
56
|
-
@uri = env_or_uri
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
msg ||= "Resource not found: #{uri.to_s}"
|
|
60
|
-
super env, msg
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
class RequestTimeout < ClientError
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
class Conflict < ClientError
|
|
68
|
-
def initialize(env, msg = 'Resource already exists')
|
|
69
|
-
super env, msg
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
class TooManyRequests < ClientError
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
class ConnectionError < ApiError
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
class ServerError < ApiError
|
|
80
|
-
def initialize(env, msg = nil)
|
|
81
|
-
msg ||= begin
|
|
82
|
-
status = env.status
|
|
83
|
-
message = ::Rack::Utils::HTTP_STATUS_CODES[status]
|
|
84
|
-
"#{status} #{message}"
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
super env, msg
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
class InternalServerError < ServerError
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
class BadGateway < ServerError
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
class ServiceUnavailable < ServerError
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
class GatewayTimeout < ServerError
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
class UnexpectedStatus < ServerError
|
|
104
|
-
attr_reader :code, :uri
|
|
105
|
-
def initialize(code, uri)
|
|
106
|
-
@code = code
|
|
107
|
-
@uri = uri
|
|
108
|
-
|
|
109
|
-
msg = "Unexpected response status: #{code} from: #{uri.to_s}"
|
|
110
|
-
super nil, msg
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
class RecordNotSaved < ServerError
|
|
115
|
-
attr_reader :record
|
|
116
|
-
|
|
117
|
-
def initialize(message = nil, record = nil)
|
|
118
|
-
@record = record
|
|
119
|
-
end
|
|
120
|
-
def message
|
|
121
|
-
"Record not saved"
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
|
-
end
|
|
1
|
+
require 'rack'
|
|
2
|
+
|
|
3
|
+
module JsonApiClient
|
|
4
|
+
module Errors
|
|
5
|
+
class ApiError < StandardError
|
|
6
|
+
attr_reader :env
|
|
7
|
+
|
|
8
|
+
def initialize(env, msg = nil)
|
|
9
|
+
@env = env
|
|
10
|
+
# Try to fetch json_api errors from response
|
|
11
|
+
msg = track_json_api_errors(msg)
|
|
12
|
+
|
|
13
|
+
super msg
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
# Try to fetch json_api errors from response
|
|
19
|
+
def track_json_api_errors(msg)
|
|
20
|
+
return msg unless env.try(:body).kind_of?(Hash) || env.body.key?('errors')
|
|
21
|
+
|
|
22
|
+
errors_msg = env.body['errors'].map { |e| e['title'] }.compact.join('; ').presence
|
|
23
|
+
return msg unless errors_msg
|
|
24
|
+
|
|
25
|
+
msg.nil? ? errors_msg : "#{msg} (#{errors_msg})"
|
|
26
|
+
# Just to be sure that it is back compatible
|
|
27
|
+
rescue StandardError
|
|
28
|
+
msg
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class ClientError < ApiError
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class ResourceImmutableError < StandardError
|
|
36
|
+
def initialize(msg = 'Resource immutable')
|
|
37
|
+
super msg
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
class AccessDenied < ClientError
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class NotAuthorized < ClientError
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class NotFound < ClientError
|
|
48
|
+
attr_reader :uri
|
|
49
|
+
def initialize(env_or_uri, msg = nil)
|
|
50
|
+
env = nil
|
|
51
|
+
|
|
52
|
+
if env_or_uri.kind_of?(Faraday::Env)
|
|
53
|
+
env = env_or_uri
|
|
54
|
+
@uri = env[:url]
|
|
55
|
+
else
|
|
56
|
+
@uri = env_or_uri
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
msg ||= "Resource not found: #{uri.to_s}"
|
|
60
|
+
super env, msg
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
class RequestTimeout < ClientError
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
class Conflict < ClientError
|
|
68
|
+
def initialize(env, msg = 'Resource already exists')
|
|
69
|
+
super env, msg
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
class TooManyRequests < ClientError
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
class ConnectionError < ApiError
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
class ServerError < ApiError
|
|
80
|
+
def initialize(env, msg = nil)
|
|
81
|
+
msg ||= begin
|
|
82
|
+
status = env.status
|
|
83
|
+
message = ::Rack::Utils::HTTP_STATUS_CODES[status]
|
|
84
|
+
"#{status} #{message}"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
super env, msg
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
class InternalServerError < ServerError
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
class BadGateway < ServerError
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
class ServiceUnavailable < ServerError
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
class GatewayTimeout < ServerError
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
class UnexpectedStatus < ServerError
|
|
104
|
+
attr_reader :code, :uri
|
|
105
|
+
def initialize(code, uri)
|
|
106
|
+
@code = code
|
|
107
|
+
@uri = uri
|
|
108
|
+
|
|
109
|
+
msg = "Unexpected response status: #{code} from: #{uri.to_s}"
|
|
110
|
+
super nil, msg
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
class RecordNotSaved < ServerError
|
|
115
|
+
attr_reader :record
|
|
116
|
+
|
|
117
|
+
def initialize(message = nil, record = nil)
|
|
118
|
+
@record = record
|
|
119
|
+
end
|
|
120
|
+
def message
|
|
121
|
+
"Record not saved"
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -1,145 +1,145 @@
|
|
|
1
|
-
# Taken form jsonapi_resources formatter
|
|
2
|
-
|
|
3
|
-
require 'active_support/inflector'
|
|
4
|
-
|
|
5
|
-
module JsonApiClient
|
|
6
|
-
class Formatter
|
|
7
|
-
class << self
|
|
8
|
-
def format(arg)
|
|
9
|
-
arg.to_s
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def unformat(arg)
|
|
13
|
-
# We call to_s() here so that unformat consistently returns a string
|
|
14
|
-
# (instead of a symbol) regardless which Formatter subclass it is called on
|
|
15
|
-
arg.to_s
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def formatter_for(format)
|
|
19
|
-
formatter_class_name = "JsonApiClient::#{format.to_s.camelize}Formatter"
|
|
20
|
-
formatter_class_name.safe_constantize
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
class KeyFormatter < Formatter
|
|
26
|
-
class << self
|
|
27
|
-
def format(key)
|
|
28
|
-
super
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def format_keys(hash)
|
|
32
|
-
Hash[
|
|
33
|
-
hash.map do |key, value|
|
|
34
|
-
[format(key).to_sym, value]
|
|
35
|
-
end
|
|
36
|
-
]
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def unformat(formatted_key)
|
|
40
|
-
super
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
class RouteFormatter < Formatter
|
|
46
|
-
class << self
|
|
47
|
-
def format(route)
|
|
48
|
-
super
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def unformat(formatted_route)
|
|
52
|
-
super
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
class ValueFormatter < Formatter
|
|
58
|
-
class << self
|
|
59
|
-
def format(raw_value)
|
|
60
|
-
super(raw_value)
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def unformat(value)
|
|
64
|
-
super(value)
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def value_formatter_for(type)
|
|
68
|
-
formatter_name = "#{type.to_s.camelize}Value"
|
|
69
|
-
formatter_for(formatter_name)
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
class UnderscoredKeyFormatter < KeyFormatter
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
class CamelizedKeyFormatter < KeyFormatter
|
|
78
|
-
class << self
|
|
79
|
-
def format(key)
|
|
80
|
-
super.camelize(:lower)
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def unformat(formatted_key)
|
|
84
|
-
formatted_key.to_s.underscore
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
class DasherizedKeyFormatter < KeyFormatter
|
|
90
|
-
class << self
|
|
91
|
-
def format(key)
|
|
92
|
-
super.dasherize
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def unformat(formatted_key)
|
|
96
|
-
formatted_key.to_s.underscore
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
class DefaultValueFormatter < ValueFormatter
|
|
102
|
-
class << self
|
|
103
|
-
def format(raw_value)
|
|
104
|
-
raw_value
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
class IdValueFormatter < ValueFormatter
|
|
110
|
-
class << self
|
|
111
|
-
def format(raw_value)
|
|
112
|
-
return if raw_value.nil?
|
|
113
|
-
raw_value.to_s
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
class UnderscoredRouteFormatter < RouteFormatter
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
class CamelizedRouteFormatter < RouteFormatter
|
|
122
|
-
class << self
|
|
123
|
-
def format(route)
|
|
124
|
-
super.camelize(:lower)
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
def unformat(formatted_route)
|
|
128
|
-
formatted_route.to_s.underscore
|
|
129
|
-
end
|
|
130
|
-
end
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
class DasherizedRouteFormatter < RouteFormatter
|
|
134
|
-
class << self
|
|
135
|
-
def format(route)
|
|
136
|
-
super.dasherize
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
def unformat(formatted_route)
|
|
140
|
-
formatted_route.to_s.underscore
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
end
|
|
1
|
+
# Taken form jsonapi_resources formatter
|
|
2
|
+
|
|
3
|
+
require 'active_support/inflector'
|
|
4
|
+
|
|
5
|
+
module JsonApiClient
|
|
6
|
+
class Formatter
|
|
7
|
+
class << self
|
|
8
|
+
def format(arg)
|
|
9
|
+
arg.to_s
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def unformat(arg)
|
|
13
|
+
# We call to_s() here so that unformat consistently returns a string
|
|
14
|
+
# (instead of a symbol) regardless which Formatter subclass it is called on
|
|
15
|
+
arg.to_s
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def formatter_for(format)
|
|
19
|
+
formatter_class_name = "JsonApiClient::#{format.to_s.camelize}Formatter"
|
|
20
|
+
formatter_class_name.safe_constantize
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class KeyFormatter < Formatter
|
|
26
|
+
class << self
|
|
27
|
+
def format(key)
|
|
28
|
+
super
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def format_keys(hash)
|
|
32
|
+
Hash[
|
|
33
|
+
hash.map do |key, value|
|
|
34
|
+
[format(key).to_sym, value]
|
|
35
|
+
end
|
|
36
|
+
]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def unformat(formatted_key)
|
|
40
|
+
super
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class RouteFormatter < Formatter
|
|
46
|
+
class << self
|
|
47
|
+
def format(route)
|
|
48
|
+
super
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def unformat(formatted_route)
|
|
52
|
+
super
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class ValueFormatter < Formatter
|
|
58
|
+
class << self
|
|
59
|
+
def format(raw_value)
|
|
60
|
+
super(raw_value)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def unformat(value)
|
|
64
|
+
super(value)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def value_formatter_for(type)
|
|
68
|
+
formatter_name = "#{type.to_s.camelize}Value"
|
|
69
|
+
formatter_for(formatter_name)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
class UnderscoredKeyFormatter < KeyFormatter
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
class CamelizedKeyFormatter < KeyFormatter
|
|
78
|
+
class << self
|
|
79
|
+
def format(key)
|
|
80
|
+
super.camelize(:lower)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def unformat(formatted_key)
|
|
84
|
+
formatted_key.to_s.underscore
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
class DasherizedKeyFormatter < KeyFormatter
|
|
90
|
+
class << self
|
|
91
|
+
def format(key)
|
|
92
|
+
super.dasherize
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def unformat(formatted_key)
|
|
96
|
+
formatted_key.to_s.underscore
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
class DefaultValueFormatter < ValueFormatter
|
|
102
|
+
class << self
|
|
103
|
+
def format(raw_value)
|
|
104
|
+
raw_value
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
class IdValueFormatter < ValueFormatter
|
|
110
|
+
class << self
|
|
111
|
+
def format(raw_value)
|
|
112
|
+
return if raw_value.nil?
|
|
113
|
+
raw_value.to_s
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
class UnderscoredRouteFormatter < RouteFormatter
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
class CamelizedRouteFormatter < RouteFormatter
|
|
122
|
+
class << self
|
|
123
|
+
def format(route)
|
|
124
|
+
super.camelize(:lower)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def unformat(formatted_route)
|
|
128
|
+
formatted_route.to_s.underscore
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
class DasherizedRouteFormatter < RouteFormatter
|
|
134
|
+
class << self
|
|
135
|
+
def format(route)
|
|
136
|
+
super.dasherize
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def unformat(formatted_route)
|
|
140
|
+
formatted_route.to_s.underscore
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
end
|