my_api_client 0.13.0 → 0.16.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +102 -36
- data/.dependabot/config.yml +34 -0
- data/.envrc.skeleton +1 -0
- data/.rubocop.yml +8 -10
- data/.rubocop_challenge.yml +3 -0
- data/.rubocop_todo.yml +5 -10
- data/CHANGELOG.md +229 -0
- data/Gemfile.lock +48 -43
- data/README.jp.md +98 -23
- data/bin/console +4 -0
- data/example/api_clients/application_api_client.rb +13 -0
- data/example/api_clients/my_error_api_client.rb +34 -0
- data/example/api_clients/my_errors.rb +27 -0
- data/example/api_clients/my_pagination_api_client.rb +18 -0
- data/example/api_clients/my_rest_api_client.rb +48 -0
- data/example/api_clients/my_status_api_client.rb +22 -0
- data/lib/generators/rails/templates/application_api_client.rb.erb +0 -11
- data/lib/my_api_client.rb +10 -2
- data/lib/my_api_client/base.rb +2 -18
- data/lib/my_api_client/config.rb +0 -29
- data/lib/my_api_client/default_error_handlers.rb +64 -0
- data/lib/my_api_client/error_handling.rb +13 -23
- data/lib/my_api_client/error_handling/generator.rb +30 -10
- data/lib/my_api_client/error_handling/{process_retry_option.rb → retry_option_processor.rb} +1 -1
- data/lib/my_api_client/errors.rb +0 -53
- data/lib/my_api_client/errors/api_limit_error.rb +6 -0
- data/lib/my_api_client/errors/client_error.rb +93 -0
- data/lib/my_api_client/errors/network_error.rb +43 -0
- data/lib/my_api_client/errors/server_error.rb +42 -0
- data/lib/my_api_client/params/request.rb +7 -10
- data/lib/my_api_client/request.rb +48 -70
- data/lib/my_api_client/request/basic.rb +32 -0
- data/lib/my_api_client/request/executor.rb +89 -0
- data/lib/my_api_client/request/logger.rb +37 -0
- data/lib/my_api_client/request/pagination.rb +39 -0
- data/lib/my_api_client/rspec/matcher_helper.rb +2 -2
- data/lib/my_api_client/rspec/matchers/be_handled_as_an_error.rb +2 -0
- data/lib/my_api_client/rspec/matchers/request_to.rb +3 -4
- data/lib/my_api_client/version.rb +1 -1
- data/my_api/.envrc.skeleton +3 -0
- data/my_api/.gitignore +14 -0
- data/my_api/.jetskeep +1 -0
- data/my_api/.rspec +3 -0
- data/my_api/.ruby-version +1 -0
- data/my_api/Gemfile +23 -0
- data/my_api/Gemfile.lock +243 -0
- data/my_api/Procfile +7 -0
- data/my_api/README.md +54 -0
- data/my_api/Rakefile +4 -0
- data/my_api/app/controllers/application_controller.rb +5 -0
- data/my_api/app/controllers/error_controller.rb +21 -0
- data/my_api/app/controllers/pagination_controller.rb +58 -0
- data/my_api/app/controllers/rest_controller.rb +60 -0
- data/my_api/app/controllers/status_controller.rb +11 -0
- data/my_api/app/helpers/application_helper.rb +5 -0
- data/my_api/app/jobs/application_job.rb +7 -0
- data/my_api/app/models/application_item.rb +5 -0
- data/my_api/config.ru +7 -0
- data/my_api/config/application.rb +73 -0
- data/my_api/config/dynamodb.yml +22 -0
- data/my_api/config/environments/development.rb +9 -0
- data/my_api/config/environments/production.rb +11 -0
- data/my_api/config/environments/test.rb +9 -0
- data/my_api/config/routes.rb +17 -0
- data/my_api/db/.gitkeep +0 -0
- data/my_api/public/404.html +67 -0
- data/my_api/public/422.html +67 -0
- data/my_api/public/500.html +66 -0
- data/my_api/public/favicon.ico +0 -0
- data/my_api/public/index.html +91 -0
- data/my_api/spec/controllers/error_controller_spec.rb +43 -0
- data/my_api/spec/controllers/pagination_controller_spec.rb +73 -0
- data/my_api/spec/controllers/rest_controller_spec.rb +99 -0
- data/my_api/spec/controllers/status_controller_spec.rb +47 -0
- data/my_api/spec/fixtures/payloads/posts-index.json +51 -0
- data/my_api/spec/fixtures/payloads/posts-show.json +53 -0
- data/my_api/spec/spec_helper.rb +31 -0
- data/my_api_client.gemspec +1 -1
- metadata +62 -9
- data/lib/my_api_client/logger.rb +0 -36
- data/renovate.json +0 -5
@@ -0,0 +1,67 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>The change you wanted was rejected (422)</title>
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
6
|
+
<style>
|
7
|
+
.rails-default-error-page {
|
8
|
+
background-color: #EFEFEF;
|
9
|
+
color: #2E2F30;
|
10
|
+
text-align: center;
|
11
|
+
font-family: arial, sans-serif;
|
12
|
+
margin: 0;
|
13
|
+
}
|
14
|
+
|
15
|
+
.rails-default-error-page div.dialog {
|
16
|
+
width: 95%;
|
17
|
+
max-width: 33em;
|
18
|
+
margin: 4em auto 0;
|
19
|
+
}
|
20
|
+
|
21
|
+
.rails-default-error-page div.dialog > div {
|
22
|
+
border: 1px solid #CCC;
|
23
|
+
border-right-color: #999;
|
24
|
+
border-left-color: #999;
|
25
|
+
border-bottom-color: #BBB;
|
26
|
+
border-top: #B00100 solid 4px;
|
27
|
+
border-top-left-radius: 9px;
|
28
|
+
border-top-right-radius: 9px;
|
29
|
+
background-color: white;
|
30
|
+
padding: 7px 12% 0;
|
31
|
+
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
32
|
+
}
|
33
|
+
|
34
|
+
.rails-default-error-page h1 {
|
35
|
+
font-size: 100%;
|
36
|
+
color: #730E15;
|
37
|
+
line-height: 1.5em;
|
38
|
+
}
|
39
|
+
|
40
|
+
.rails-default-error-page div.dialog > p {
|
41
|
+
margin: 0 0 1em;
|
42
|
+
padding: 1em;
|
43
|
+
background-color: #F7F7F7;
|
44
|
+
border: 1px solid #CCC;
|
45
|
+
border-right-color: #999;
|
46
|
+
border-left-color: #999;
|
47
|
+
border-bottom-color: #999;
|
48
|
+
border-bottom-left-radius: 4px;
|
49
|
+
border-bottom-right-radius: 4px;
|
50
|
+
border-top-color: #DADADA;
|
51
|
+
color: #666;
|
52
|
+
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
53
|
+
}
|
54
|
+
</style>
|
55
|
+
</head>
|
56
|
+
|
57
|
+
<body class="rails-default-error-page">
|
58
|
+
<!-- This file lives in public/422.html -->
|
59
|
+
<div class="dialog">
|
60
|
+
<div>
|
61
|
+
<h1>The change you wanted was rejected.</h1>
|
62
|
+
<p>Maybe you tried to change something you didn't have access to.</p>
|
63
|
+
</div>
|
64
|
+
<p>If you are the application owner check the logs for more information.</p>
|
65
|
+
</div>
|
66
|
+
</body>
|
67
|
+
</html>
|
@@ -0,0 +1,66 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>We're sorry, but something went wrong (500)</title>
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
6
|
+
<style>
|
7
|
+
.rails-default-error-page {
|
8
|
+
background-color: #EFEFEF;
|
9
|
+
color: #2E2F30;
|
10
|
+
text-align: center;
|
11
|
+
font-family: arial, sans-serif;
|
12
|
+
margin: 0;
|
13
|
+
}
|
14
|
+
|
15
|
+
.rails-default-error-page div.dialog {
|
16
|
+
width: 95%;
|
17
|
+
max-width: 33em;
|
18
|
+
margin: 4em auto 0;
|
19
|
+
}
|
20
|
+
|
21
|
+
.rails-default-error-page div.dialog > div {
|
22
|
+
border: 1px solid #CCC;
|
23
|
+
border-right-color: #999;
|
24
|
+
border-left-color: #999;
|
25
|
+
border-bottom-color: #BBB;
|
26
|
+
border-top: #B00100 solid 4px;
|
27
|
+
border-top-left-radius: 9px;
|
28
|
+
border-top-right-radius: 9px;
|
29
|
+
background-color: white;
|
30
|
+
padding: 7px 12% 0;
|
31
|
+
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
32
|
+
}
|
33
|
+
|
34
|
+
.rails-default-error-page h1 {
|
35
|
+
font-size: 100%;
|
36
|
+
color: #730E15;
|
37
|
+
line-height: 1.5em;
|
38
|
+
}
|
39
|
+
|
40
|
+
.rails-default-error-page div.dialog > p {
|
41
|
+
margin: 0 0 1em;
|
42
|
+
padding: 1em;
|
43
|
+
background-color: #F7F7F7;
|
44
|
+
border: 1px solid #CCC;
|
45
|
+
border-right-color: #999;
|
46
|
+
border-left-color: #999;
|
47
|
+
border-bottom-color: #999;
|
48
|
+
border-bottom-left-radius: 4px;
|
49
|
+
border-bottom-right-radius: 4px;
|
50
|
+
border-top-color: #DADADA;
|
51
|
+
color: #666;
|
52
|
+
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
53
|
+
}
|
54
|
+
</style>
|
55
|
+
</head>
|
56
|
+
|
57
|
+
<body class="rails-default-error-page">
|
58
|
+
<!-- This file lives in public/500.html -->
|
59
|
+
<div class="dialog">
|
60
|
+
<div>
|
61
|
+
<h1>We're sorry, but something went wrong.</h1>
|
62
|
+
</div>
|
63
|
+
<p>If you are the application owner check the logs for more information.</p>
|
64
|
+
</div>
|
65
|
+
</body>
|
66
|
+
</html>
|
Binary file
|
@@ -0,0 +1,91 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Ruby on Jets</title>
|
5
|
+
<meta charset="utf-8">
|
6
|
+
<meta name="viewport" content="width=device-width">
|
7
|
+
<style type="text/css" media="screen" charset="utf-8">
|
8
|
+
body {
|
9
|
+
text-align: center;
|
10
|
+
margin: 0;
|
11
|
+
}
|
12
|
+
img {
|
13
|
+
max-width: 320px;
|
14
|
+
}
|
15
|
+
.container {
|
16
|
+
max-width: 648px;
|
17
|
+
}
|
18
|
+
@media (min-width: 768px) {
|
19
|
+
img {
|
20
|
+
max-width: none;
|
21
|
+
}
|
22
|
+
.container {
|
23
|
+
max-width: none;
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
.header {
|
28
|
+
padding: 20px;
|
29
|
+
/*background-color: #b81513;*/
|
30
|
+
/*color: white;*/
|
31
|
+
}
|
32
|
+
.title {
|
33
|
+
font-size: 1.5em;
|
34
|
+
}
|
35
|
+
@media (max-width: 500px) {
|
36
|
+
.title {
|
37
|
+
font-size: 1.3em;
|
38
|
+
}
|
39
|
+
}
|
40
|
+
.intro {
|
41
|
+
text-align: left;
|
42
|
+
font-size: large;
|
43
|
+
margin: 0 auto;
|
44
|
+
padding: 0 20px;
|
45
|
+
max-width: 640px;
|
46
|
+
}
|
47
|
+
pre, code {
|
48
|
+
padding: 0;
|
49
|
+
margin: 0;
|
50
|
+
}
|
51
|
+
pre {
|
52
|
+
margin-left: 20px;
|
53
|
+
width: 99%;
|
54
|
+
overflow: auto;
|
55
|
+
}
|
56
|
+
@media (max-width: 500px) {
|
57
|
+
pre {
|
58
|
+
margin-left: -30px;
|
59
|
+
font-size: 0.8em;
|
60
|
+
}
|
61
|
+
}
|
62
|
+
</style>
|
63
|
+
</head>
|
64
|
+
|
65
|
+
<body>
|
66
|
+
<div class="container">
|
67
|
+
<header class="header">
|
68
|
+
<img src="https://s3.amazonaws.com/jets-public/jets/images/jets.png" class="logo" alt="logo" />
|
69
|
+
<h1 class="title">Welcome and congrats!<br /> Jets is running.</h1>
|
70
|
+
</header>
|
71
|
+
<div class="intro">
|
72
|
+
<p>
|
73
|
+
To get started:
|
74
|
+
</p>
|
75
|
+
<div class="code"><pre><code>
|
76
|
+
$ jets generate scaffold post title:string
|
77
|
+
$ jets db:create db:migrate
|
78
|
+
$ jets server
|
79
|
+
$ open http://localhost:8888/posts
|
80
|
+
$ jets help
|
81
|
+
</code></pre></div>
|
82
|
+
<p>More on info: <a href="http://rubyonjets.com">rubyonjets.com</a></p>
|
83
|
+
<p>Also check out the <a href="http://rubyonjets.com/reference">Jets CLI reference</a>.</p>
|
84
|
+
</div>
|
85
|
+
<p class="version">
|
86
|
+
<strong>Jets version:</strong> 2.3.13<br />
|
87
|
+
<strong>Ruby version:</strong> 2.5.3
|
88
|
+
</p>
|
89
|
+
</div>
|
90
|
+
</body>
|
91
|
+
</html>
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe ErrorController, type: :controller do
|
4
|
+
describe '#show' do
|
5
|
+
context 'when request error code 10' do
|
6
|
+
let(:code) { 10 }
|
7
|
+
|
8
|
+
let(:expected_response) do
|
9
|
+
{
|
10
|
+
error: {
|
11
|
+
code: code,
|
12
|
+
message: 'You requested error code: 10',
|
13
|
+
},
|
14
|
+
}.to_json
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'returns 400 Bad request with error code 10' do
|
18
|
+
get '/error/:code', code: code
|
19
|
+
expect(response.status).to eq 400
|
20
|
+
expect(response.body).to eq expected_response
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'when request error code 20' do
|
25
|
+
let(:code) { 20 }
|
26
|
+
|
27
|
+
let(:expected_response) do
|
28
|
+
{
|
29
|
+
error: {
|
30
|
+
code: code,
|
31
|
+
message: 'You requested error code: 20',
|
32
|
+
},
|
33
|
+
}.to_json
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'returns 400 Bad request with error code 20' do
|
37
|
+
get '/error/:code', code: code
|
38
|
+
expect(response.status).to eq 400
|
39
|
+
expect(response.body).to eq expected_response
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe PaginationController, type: :controller do
|
4
|
+
describe '#index' do
|
5
|
+
let(:first_page) do
|
6
|
+
{
|
7
|
+
links: {
|
8
|
+
next: 'https://example.com/test/pagination?page=2',
|
9
|
+
},
|
10
|
+
page: 1,
|
11
|
+
}.to_json
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:second_page) do
|
15
|
+
{
|
16
|
+
links: {
|
17
|
+
next: 'https://example.com/test/pagination?page=3',
|
18
|
+
previous: 'https://example.com/test/pagination?page=1',
|
19
|
+
},
|
20
|
+
page: 2,
|
21
|
+
}.to_json
|
22
|
+
end
|
23
|
+
|
24
|
+
let(:third_page) do
|
25
|
+
{
|
26
|
+
links: {
|
27
|
+
previous: 'https://example.com/test/pagination?page=2',
|
28
|
+
},
|
29
|
+
page: 3,
|
30
|
+
}.to_json
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'without page' do
|
34
|
+
it 'returns a 1st page contents including 2nd page link' do
|
35
|
+
get '/pagination'
|
36
|
+
expect(response.status).to eq 200
|
37
|
+
expect(response.body).to eq first_page
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'with page = 1' do
|
42
|
+
it 'returns a 1st page contents including 2nd page link' do
|
43
|
+
get '/pagination', page: 1
|
44
|
+
expect(response.status).to eq 200
|
45
|
+
expect(response.body).to eq first_page
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'with page = 2' do
|
50
|
+
it 'returns a 2nd page contents including 3rd page link' do
|
51
|
+
get '/pagination', page: 2
|
52
|
+
expect(response.status).to eq 200
|
53
|
+
expect(response.body).to eq second_page
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'with page = 3' do
|
58
|
+
it 'returns a 3rd page contents not including next page link' do
|
59
|
+
get '/pagination', page: 3
|
60
|
+
expect(response.status).to eq 200
|
61
|
+
expect(response.body).to eq third_page
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'with page = 4' do
|
66
|
+
it 'returns 404 NOT FOUND' do
|
67
|
+
get '/pagination', page: 4
|
68
|
+
expect(response.status).to eq 404
|
69
|
+
expect(response.body).to be_blank
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe RestController, type: :controller do
|
4
|
+
describe '#index' do
|
5
|
+
context 'with order = asc' do
|
6
|
+
let(:array_of_posts) do
|
7
|
+
[
|
8
|
+
{ id: 1, title: 'Title 1' },
|
9
|
+
{ id: 2, title: 'Title 2' },
|
10
|
+
{ id: 3, title: 'Title 3' },
|
11
|
+
].to_json
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'returns an array of posts ordered by id' do
|
15
|
+
get '/rest', order: 'asc'
|
16
|
+
expect(response.status).to eq 200
|
17
|
+
expect(response.body).to eq array_of_posts
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'with order = desc' do
|
22
|
+
let(:array_of_posts) do
|
23
|
+
[
|
24
|
+
{ id: 3, title: 'Title 3' },
|
25
|
+
{ id: 2, title: 'Title 2' },
|
26
|
+
{ id: 1, title: 'Title 1' },
|
27
|
+
].to_json
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'returns an array of posts reverse ordered by id' do
|
31
|
+
get '/rest', order: 'desc'
|
32
|
+
expect(response.status).to eq 200
|
33
|
+
expect(response.body).to eq array_of_posts
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '#show' do
|
39
|
+
let(:post) do
|
40
|
+
{ id: 1, title: 'Title 1' }.to_json
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'returns a post' do
|
44
|
+
get '/rest/:id', id: 1
|
45
|
+
expect(response.status).to eq 200
|
46
|
+
expect(response.body).to eq post
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '#create' do
|
51
|
+
let(:new_post) do
|
52
|
+
{ id: 4, title: 'New title' }.to_json
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'returns a created post' do
|
56
|
+
post '/rest', title: 'New title'
|
57
|
+
expect(response.status).to eq 201
|
58
|
+
expect(response.body).to eq new_post
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe '#update' do
|
63
|
+
let(:the_post) do
|
64
|
+
{ id: 1, title: 'Modified title' }.to_json
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'with POST method' do
|
68
|
+
it 'returns a updated post' do
|
69
|
+
post '/rest/:id', id: 1, title: 'Modified title'
|
70
|
+
expect(response.status).to eq 200
|
71
|
+
expect(response.body).to eq the_post
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'with PUT method' do
|
76
|
+
it 'returns a updated post' do
|
77
|
+
put '/rest/:id', id: 1, title: 'Modified title'
|
78
|
+
expect(response.status).to eq 200
|
79
|
+
expect(response.body).to eq the_post
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'with PATCH method' do
|
84
|
+
it 'returns a updated post' do
|
85
|
+
patch '/rest/:id', id: 1, title: 'Modified title'
|
86
|
+
expect(response.status).to eq 200
|
87
|
+
expect(response.body).to eq the_post
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe '#delete' do
|
93
|
+
it 'returns no body' do
|
94
|
+
delete '/rest/:id', id: 123
|
95
|
+
expect(response.status).to eq 204
|
96
|
+
expect(response.body).to be_empty
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe StatusController, type: :controller do
|
4
|
+
describe '#show' do
|
5
|
+
context 'when request status code 200' do
|
6
|
+
let(:status) { 200 }
|
7
|
+
|
8
|
+
let(:expected_response) do
|
9
|
+
{ message: 'You requested status code: 200' }.to_json
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'returns 200 OK' do
|
13
|
+
get '/status/:status', status: status
|
14
|
+
expect(response.status).to eq status
|
15
|
+
expect(response.body).to eq expected_response
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'when request status code 400' do
|
20
|
+
let(:status) { 400 }
|
21
|
+
|
22
|
+
let(:expected_response) do
|
23
|
+
{ message: 'You requested status code: 400' }.to_json
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'returns 400 Bad request' do
|
27
|
+
get '/status/:status', status: status
|
28
|
+
expect(response.status).to eq status
|
29
|
+
expect(response.body).to eq expected_response
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'when request status code 500' do
|
34
|
+
let(:status) { 500 }
|
35
|
+
|
36
|
+
let(:expected_response) do
|
37
|
+
{ message: 'You requested status code: 500' }.to_json
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'returns 500 Internal server error' do
|
41
|
+
get '/status/:status', status: status
|
42
|
+
expect(response.status).to eq status
|
43
|
+
expect(response.body).to eq expected_response
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|