aws-sdk-core 3.46.2 → 3.47.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/VERSION +1 -1
- data/lib/aws-sdk-core.rb +1 -0
- data/lib/aws-sdk-core/async_client_stubs.rb +80 -0
- data/lib/aws-sdk-core/binary.rb +3 -0
- data/lib/aws-sdk-core/binary/decode_handler.rb +21 -1
- data/lib/aws-sdk-core/binary/encode_handler.rb +32 -0
- data/lib/aws-sdk-core/binary/event_builder.rb +122 -0
- data/lib/aws-sdk-core/binary/event_parser.rb +48 -18
- data/lib/aws-sdk-core/binary/event_stream_decoder.rb +5 -2
- data/lib/aws-sdk-core/binary/event_stream_encoder.rb +53 -0
- data/lib/aws-sdk-core/client_stubs.rb +1 -1
- data/lib/aws-sdk-core/errors.rb +4 -0
- data/lib/aws-sdk-core/event_emitter.rb +42 -0
- data/lib/aws-sdk-core/json/handler.rb +19 -1
- data/lib/aws-sdk-core/param_validator.rb +9 -1
- data/lib/aws-sdk-core/plugins/event_stream_configuration.rb +14 -0
- data/lib/aws-sdk-core/plugins/invocation_id.rb +33 -0
- data/lib/aws-sdk-core/plugins/stub_responses.rb +19 -7
- data/lib/aws-sdk-core/stubbing/protocols/rest.rb +19 -0
- data/lib/aws-sdk-core/stubbing/stub_data.rb +1 -1
- data/lib/aws-sdk-sts.rb +1 -1
- data/lib/aws-sdk-sts/client.rb +1 -1
- data/lib/seahorse.rb +9 -0
- data/lib/seahorse/client/async_base.rb +50 -0
- data/lib/seahorse/client/async_response.rb +73 -0
- data/lib/seahorse/client/base.rb +1 -1
- data/lib/seahorse/client/h2/connection.rb +242 -0
- data/lib/seahorse/client/h2/handler.rb +149 -0
- data/lib/seahorse/client/http/async_response.rb +42 -0
- data/lib/seahorse/client/http/response.rb +10 -5
- data/lib/seahorse/client/networking_error.rb +28 -0
- data/lib/seahorse/client/plugins/h2.rb +64 -0
- data/lib/seahorse/model/api.rb +4 -0
- data/lib/seahorse/model/operation.rb +4 -0
- metadata +35 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e240abd1c190c9f5b7b6dac9395342881be33c53
|
4
|
+
data.tar.gz: 6a08d6319fd1baef15af46a41edd756c8d23ed5a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e8ed167ed3c41ae1e255235c7794abdb85cf60bc022c49c1dd30327d3ea1000a5a3c8f03e37dccd6a01778ae132ea365a4313e7f1e2e79e955b3e1c55f235a71
|
7
|
+
data.tar.gz: 421c2b9d17a58946a01a9181392af7da9e21158f9dc9b64d399ea13dfd299a1f74c5c16321098ed46f09241dd7a50019fbef89895e22f9ea9bca0b7764e11456
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.
|
1
|
+
3.47.0
|
data/lib/aws-sdk-core.rb
CHANGED
@@ -19,6 +19,7 @@ require_relative 'aws-sdk-core/process_credentials'
|
|
19
19
|
# client modules
|
20
20
|
|
21
21
|
require_relative 'aws-sdk-core/client_stubs'
|
22
|
+
require_relative 'aws-sdk-core/async_client_stubs'
|
22
23
|
require_relative 'aws-sdk-core/eager_loader'
|
23
24
|
require_relative 'aws-sdk-core/errors'
|
24
25
|
require_relative 'aws-sdk-core/pageable_response'
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Aws
|
2
|
+
module AsyncClientStubs
|
3
|
+
|
4
|
+
include Aws::ClientStubs
|
5
|
+
|
6
|
+
# @api private
|
7
|
+
def setup_stubbing
|
8
|
+
@stubs = {}
|
9
|
+
@stub_mutex = Mutex.new
|
10
|
+
if Hash === @config.stub_responses
|
11
|
+
@config.stub_responses.each do |operation_name, stubs|
|
12
|
+
apply_stubs(operation_name, Array === stubs ? stubs : [stubs])
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# When a client is stubbed allow the user to access the requests made
|
17
|
+
@api_requests = []
|
18
|
+
|
19
|
+
# allow to access signaled events when client is stubbed
|
20
|
+
@send_events = []
|
21
|
+
|
22
|
+
requests = @api_requests
|
23
|
+
send_events = @send_events
|
24
|
+
|
25
|
+
self.handle do |context|
|
26
|
+
if input_stream = context[:input_event_stream_handler]
|
27
|
+
stub_stream = StubStream.new
|
28
|
+
stub_stream.send_events = send_events
|
29
|
+
input_stream.event_emitter.stream = stub_stream
|
30
|
+
input_stream.event_emitter.validate_event = context.config.validate_params
|
31
|
+
end
|
32
|
+
requests << {
|
33
|
+
operation_name: context.operation_name,
|
34
|
+
params: context.params,
|
35
|
+
context: context
|
36
|
+
}
|
37
|
+
@handler.call(context)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def send_events
|
42
|
+
if config.stub_responses
|
43
|
+
@send_events
|
44
|
+
else
|
45
|
+
msg = 'This method is only implemented for stubbed clients, and is '
|
46
|
+
msg << 'available when you enable stubbing in the constructor with `stub_responses: true`'
|
47
|
+
raise NotImplementedError.new(msg)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class StubStream
|
52
|
+
|
53
|
+
def initialize
|
54
|
+
@state = :open
|
55
|
+
end
|
56
|
+
|
57
|
+
attr_accessor :send_events
|
58
|
+
|
59
|
+
attr_reader :state
|
60
|
+
|
61
|
+
def data(bytes, options = {})
|
62
|
+
if options[:end_stream]
|
63
|
+
@state = :closed
|
64
|
+
else
|
65
|
+
decoder = Aws::EventStream::Decoder.new
|
66
|
+
event = decoder.decode_chunk(bytes).first
|
67
|
+
@send_events << decoder.decode_chunk(event.payload.read).first
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def closed?
|
72
|
+
@state == :closed
|
73
|
+
end
|
74
|
+
|
75
|
+
def close
|
76
|
+
@state = :closed
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/lib/aws-sdk-core/binary.rb
CHANGED
@@ -23,11 +23,31 @@ module Aws
|
|
23
23
|
|
24
24
|
context.http_response.on_headers(200) do
|
25
25
|
protocol = context.config.api.metadata['protocol']
|
26
|
+
output_handler = context[:output_event_stream_handler] || context[:event_stream_handler]
|
26
27
|
context.http_response.body = EventStreamDecoder.new(
|
27
28
|
protocol,
|
28
29
|
rules,
|
30
|
+
context.operation.output,
|
31
|
+
context.operation.errors,
|
29
32
|
context.http_response.body,
|
30
|
-
|
33
|
+
output_handler)
|
34
|
+
if input_emitter = context[:input_event_emitter]
|
35
|
+
# events need to be send in order
|
36
|
+
# every event signature requries prior signature
|
37
|
+
thread = Thread.new do
|
38
|
+
# polling for buffered emit events until stream not active
|
39
|
+
while input_emitter.stream.state == :open
|
40
|
+
while callback = input_emitter.buffer.shift
|
41
|
+
callback.call if input_emitter.stream.state == :open
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
thread.abort_on_exception = true
|
46
|
+
# attach thread to current stream context
|
47
|
+
# make sure when stream closes (#wait or #join! is called)
|
48
|
+
# input signal thread is also killed
|
49
|
+
context[:input_signal_thread] = thread
|
50
|
+
end
|
31
51
|
end
|
32
52
|
|
33
53
|
context.http_response.on_success(200) do
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Aws
|
2
|
+
module Binary
|
3
|
+
|
4
|
+
# @api private
|
5
|
+
class EncodeHandler < Seahorse::Client::Handler
|
6
|
+
|
7
|
+
def call(context)
|
8
|
+
if eventstream_member = eventstream_input?(context)
|
9
|
+
input_es_handler = context[:input_event_stream_handler]
|
10
|
+
input_es_handler.event_emitter.encoder = EventStreamEncoder.new(
|
11
|
+
context.config.api.metadata['protocol'],
|
12
|
+
eventstream_member,
|
13
|
+
context.operation.input,
|
14
|
+
context.config.sigv4_signer
|
15
|
+
)
|
16
|
+
context[:input_event_emitter] = input_es_handler.event_emitter
|
17
|
+
end
|
18
|
+
@handler.call(context)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def eventstream_input?(ctx)
|
24
|
+
ctx.operation.input.shape.members.each do |_, ref|
|
25
|
+
return ref if ref.eventstream
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module Aws
|
2
|
+
module Binary
|
3
|
+
# @api private
|
4
|
+
class EventBuilder
|
5
|
+
|
6
|
+
include Seahorse::Model::Shapes
|
7
|
+
|
8
|
+
# @param [Class] parser_class
|
9
|
+
# @param [Seahorse::Model::ShapeRef] rules (of eventstream member)
|
10
|
+
def initialize(serializer_class, rules)
|
11
|
+
@serializer_class = serializer_class
|
12
|
+
@rules = rules
|
13
|
+
end
|
14
|
+
|
15
|
+
def apply(event_type, params)
|
16
|
+
event_ref = @rules.shape.member(event_type)
|
17
|
+
_event_stream_message(event_ref, params)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def _event_stream_message(event_ref, params)
|
23
|
+
es_headers = {}
|
24
|
+
payload = ""
|
25
|
+
|
26
|
+
es_headers[":message-type"] = Aws::EventStream::HeaderValue.new(
|
27
|
+
type: "string", value: "event")
|
28
|
+
es_headers[":event-type"] = Aws::EventStream::HeaderValue.new(
|
29
|
+
type: "string", value: event_ref.shape.name)
|
30
|
+
|
31
|
+
explicit_payload = false
|
32
|
+
implicit_payload_members = {}
|
33
|
+
event_ref.shape.members.each do |member_name, member_ref|
|
34
|
+
unless member_ref.eventheader
|
35
|
+
if member_ref.eventpayload
|
36
|
+
explicit_payload = true
|
37
|
+
else
|
38
|
+
implicit_payload_members[member_name] = member_ref
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# implict payload
|
44
|
+
if !explicit_payload && !implicit_payload_members.empty?
|
45
|
+
if implicit_payload_members.size > 1
|
46
|
+
payload_shape = Shapes::StructureShape.new
|
47
|
+
implicit_payload_members.each do |m_name, m_ref|
|
48
|
+
payload_shape.add_member(m_name, m_ref)
|
49
|
+
end
|
50
|
+
payload_ref = Shapes::ShapeRef.new(shape: payload_shape)
|
51
|
+
|
52
|
+
payload = build_payload_members(payload_ref, params)
|
53
|
+
else
|
54
|
+
m_name, m_ref = implicit_payload_members.first
|
55
|
+
streaming, content_type = _content_type(m_ref.shape)
|
56
|
+
|
57
|
+
es_headers[":content-type"] = Aws::EventStream::HeaderValue.new(
|
58
|
+
type: "string", value: content_type)
|
59
|
+
payload = _build_payload(streaming, m_ref, params[m_name])
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
event_ref.shape.members.each do |member_name, member_ref|
|
65
|
+
if member_ref.eventheader && params[member_name]
|
66
|
+
header_value = params[member_name]
|
67
|
+
es_headers[member_ref.shape.name] = Aws::EventStream::HeaderValue.new(
|
68
|
+
type: _header_value_type(member_ref.shape, header_value),
|
69
|
+
value: header_value
|
70
|
+
)
|
71
|
+
elsif member_ref.eventpayload && params[member_name]
|
72
|
+
# explicit payload
|
73
|
+
streaming, content_type = _content_type(member_ref.shape)
|
74
|
+
|
75
|
+
es_headers[":content-type"] = Aws::EventStream::HeaderValue.new(
|
76
|
+
type: "string", value: content_type)
|
77
|
+
payload = _build_payload(streaming, member_ref, params[member_name])
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
Aws::EventStream::Message.new(
|
82
|
+
headers: es_headers,
|
83
|
+
payload: StringIO.new(payload)
|
84
|
+
)
|
85
|
+
end
|
86
|
+
|
87
|
+
def _content_type(shape)
|
88
|
+
case shape
|
89
|
+
when BlobShape then [true, "application/octet-stream"]
|
90
|
+
when StringShape then [true, "text/plain"]
|
91
|
+
when StructureShape then
|
92
|
+
if @serializer_class.name.include?('Xml')
|
93
|
+
[false, "text/xml"]
|
94
|
+
elsif @serializer_class.name.include?('Json')
|
95
|
+
[false, "application/json"]
|
96
|
+
end
|
97
|
+
else
|
98
|
+
raise Aws::Errors::EventStreamBuilderError.new(
|
99
|
+
"Unsupport eventpayload shape: #{shape.name}")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def _header_value_type(shape, value)
|
104
|
+
case shape
|
105
|
+
when StringShape then "string"
|
106
|
+
when IntegerShape then "integer"
|
107
|
+
when TimestampShape then "timestamp"
|
108
|
+
when BlobShape then "bytes"
|
109
|
+
when BooleanShape then !!value ? "bool_true" : "bool_false"
|
110
|
+
else
|
111
|
+
raise Aws::Errors::EventStreamBuilderError.new(
|
112
|
+
"Unsupported eventheader shape: #{shape.name}")
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def _build_payload(streaming, ref, value)
|
117
|
+
streaming ? value : @serializer_class.new(ref).serialize(value)
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -6,10 +6,14 @@ module Aws
|
|
6
6
|
include Seahorse::Model::Shapes
|
7
7
|
|
8
8
|
# @param [Class] parser_class
|
9
|
-
# @param [Seahorse::Model::ShapeRef] rules
|
10
|
-
|
9
|
+
# @param [Seahorse::Model::ShapeRef] rules (of eventstream member)
|
10
|
+
# @param [Array] error_refs array of errors ShapeRef
|
11
|
+
# @param [Seahorse::Model::ShapeRef] output_ref
|
12
|
+
def initialize(parser_class, rules, error_refs, output_ref)
|
11
13
|
@parser_class = parser_class
|
12
14
|
@rules = rules
|
15
|
+
@error_refs = error_refs
|
16
|
+
@output_ref = output_ref
|
13
17
|
end
|
14
18
|
|
15
19
|
# Parse raw event message into event struct
|
@@ -31,9 +35,7 @@ module Aws
|
|
31
35
|
when 'event'
|
32
36
|
parse_event(raw_event)
|
33
37
|
when 'exception'
|
34
|
-
|
35
|
-
raise Aws::Errors::EventStreamParserError.new(
|
36
|
-
':exception event parsing is not supported')
|
38
|
+
parse_exception(raw_event)
|
37
39
|
else
|
38
40
|
raise Aws::Errors::EventStreamParserError.new(
|
39
41
|
'Unrecognized :message-type value for the event')
|
@@ -44,6 +46,15 @@ module Aws
|
|
44
46
|
end
|
45
47
|
end
|
46
48
|
|
49
|
+
def parse_exception(raw_event)
|
50
|
+
exception_type = raw_event.headers.delete(":exception-type").value
|
51
|
+
name, ref = @rules.shape.member_by_location_name(exception_type)
|
52
|
+
# exception lives in payload implictly
|
53
|
+
exception = parse_payload(raw_event.payload.read, ref)
|
54
|
+
exception.event_type = name
|
55
|
+
exception
|
56
|
+
end
|
57
|
+
|
47
58
|
def parse_error_event(raw_event)
|
48
59
|
error_code = raw_event.headers.delete(":error-code")
|
49
60
|
error_message = raw_event.headers.delete(":error-message")
|
@@ -58,35 +69,54 @@ module Aws
|
|
58
69
|
event_type = raw_event.headers.delete(":event-type").value
|
59
70
|
# content_type = raw_event.headers.delete(":content-type").value
|
60
71
|
|
61
|
-
# Pending
|
62
72
|
if event_type == 'initial-response'
|
63
|
-
|
64
|
-
|
65
|
-
)
|
73
|
+
event = Struct.new(:event_type, :response).new
|
74
|
+
event.event_type = :initial_response
|
75
|
+
event.response = parse_payload(raw_event.payload.read, @output_ref)
|
76
|
+
return event
|
66
77
|
end
|
67
78
|
|
68
79
|
# locate event from eventstream
|
69
80
|
name, ref = @rules.shape.member_by_location_name(event_type)
|
70
|
-
|
81
|
+
unless ref.event
|
82
|
+
raise Aws::Errors::EventStreamParserError.new(
|
83
|
+
"Failed to locate event shape for the event")
|
84
|
+
end
|
71
85
|
|
72
86
|
event = ref.shape.struct_class.new
|
87
|
+
|
88
|
+
explicit_payload = false
|
89
|
+
implicit_payload_members = {}
|
90
|
+
ref.shape.members.each do |member_name, member_ref|
|
91
|
+
unless member_ref.eventheader
|
92
|
+
if member_ref.eventpayload
|
93
|
+
explicit_payload = true
|
94
|
+
else
|
95
|
+
implicit_payload_members[member_name] = member_ref
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# implicit payload
|
101
|
+
if !explicit_payload && !implicit_payload_members.empty?
|
102
|
+
event = parse_payload(raw_event.payload.read, ref)
|
103
|
+
end
|
73
104
|
event.event_type = name
|
105
|
+
|
74
106
|
# locate payload and headers in the event
|
75
107
|
ref.shape.members.each do |member_name, member_ref|
|
76
|
-
if member_ref.
|
77
|
-
eventpayload_streaming?(member_ref) ?
|
78
|
-
event.send("#{member_name}=", raw_event.payload) :
|
79
|
-
event.send("#{member_name}=", parse_payload(raw_event.payload.read, member_ref))
|
80
|
-
elsif member_ref.eventheader
|
108
|
+
if member_ref.eventheader
|
81
109
|
# allow incomplete event members in response
|
82
110
|
if raw_event.headers.key?(member_ref.location_name)
|
83
111
|
event.send("#{member_name}=", raw_event.headers[member_ref.location_name].value)
|
84
112
|
end
|
85
|
-
|
86
|
-
|
113
|
+
elsif member_ref.eventpayload
|
114
|
+
# explicit payload
|
115
|
+
eventpayload_streaming?(member_ref) ?
|
116
|
+
event.send("#{member_name}=", raw_event.payload) :
|
117
|
+
event.send("#{member_name}=", parse_payload(raw_event.payload.read, member_ref))
|
87
118
|
end
|
88
119
|
end
|
89
|
-
|
90
120
|
event
|
91
121
|
end
|
92
122
|
|
@@ -7,11 +7,13 @@ module Aws
|
|
7
7
|
|
8
8
|
# @param [String] protocol
|
9
9
|
# @param [ShapeRef] rules ShapeRef of the eventstream member
|
10
|
+
# @param [ShapeRef] output_ref ShapeRef of output shape
|
11
|
+
# @param [Array] error_refs array of ShapeRefs for errors
|
10
12
|
# @param [EventStream|nil] event_stream_handler A Service EventStream object
|
11
13
|
# that registered with callbacks for processing events when they arrive
|
12
|
-
def initialize(protocol, rules, io, event_stream_handler = nil)
|
14
|
+
def initialize(protocol, rules, output_ref, error_refs, io, event_stream_handler = nil)
|
13
15
|
@decoder = Aws::EventStream::Decoder.new
|
14
|
-
@event_parser = EventParser.new(parser_class(protocol), rules)
|
16
|
+
@event_parser = EventParser.new(parser_class(protocol), rules, error_refs, output_ref)
|
15
17
|
@stream_class = extract_stream_class(rules.shape.struct_class)
|
16
18
|
@emitter = event_stream_handler.event_emitter
|
17
19
|
@events = []
|
@@ -42,6 +44,7 @@ module Aws
|
|
42
44
|
case protocol
|
43
45
|
when 'rest-xml' then Aws::Xml::Parser
|
44
46
|
when 'rest-json' then Aws::Json::Parser
|
47
|
+
when 'json' then Aws::Json::Parser
|
45
48
|
else raise "unsupported protocol #{protocol} for event stream"
|
46
49
|
end
|
47
50
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'aws-eventstream'
|
2
|
+
|
3
|
+
module Aws
|
4
|
+
module Binary
|
5
|
+
# @api private
|
6
|
+
class EventStreamEncoder
|
7
|
+
|
8
|
+
# @param [String] protocol
|
9
|
+
# @param [ShapeRef] rules ShapeRef of the eventstream member
|
10
|
+
# @param [ShapeRef] input_ref ShapeRef of the input shape
|
11
|
+
# @param [Aws::Sigv4::Signer] signer
|
12
|
+
def initialize(protocol, rules, input_ref, signer)
|
13
|
+
@encoder = Aws::EventStream::Encoder.new
|
14
|
+
@event_builder = EventBuilder.new(serializer_class(protocol), rules)
|
15
|
+
@input_ref = input_ref
|
16
|
+
@rules = rules
|
17
|
+
@signer = signer
|
18
|
+
@prior_signature = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :rules
|
22
|
+
|
23
|
+
attr_accessor :prior_signature
|
24
|
+
|
25
|
+
def encode(event_type, params)
|
26
|
+
if event_type == :end_stream
|
27
|
+
payload = ''
|
28
|
+
else
|
29
|
+
payload = @encoder.encode(@event_builder.apply(event_type, params))
|
30
|
+
end
|
31
|
+
headers, signature = @signer.sign_event(@prior_signature, payload, @encoder)
|
32
|
+
@prior_signature = signature
|
33
|
+
message = Aws::EventStream::Message.new(
|
34
|
+
headers: headers,
|
35
|
+
payload: StringIO.new(payload)
|
36
|
+
)
|
37
|
+
@encoder.encode(message)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def serializer_class(protocol)
|
43
|
+
case protocol
|
44
|
+
when 'rest-xml' then Xml::Builder
|
45
|
+
when 'rest-json' then Json::Builder
|
46
|
+
when 'json' then Json::Builder
|
47
|
+
else raise "unsupported protocol #{protocol} for event stream"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|