1
- use aws_config:: meta:: region:: RegionProviderChain ;
1
+ use std:: io:: Cursor ;
2
+
2
3
use aws_lambda_events:: { event:: s3:: S3Event , s3:: S3EventRecord } ;
3
4
use aws_sdk_s3:: Client as S3Client ;
4
5
use lambda_runtime:: { run, service_fn, Error , LambdaEvent } ;
5
- use s3client:: { GetFile , GetThumbnail , PutFile } ;
6
+ use s3:: { GetFile , PutFile } ;
7
+ use thumbnailer:: { create_thumbnails, ThumbnailSize } ;
6
8
7
- mod s3client ;
9
+ mod s3 ;
8
10
9
11
/**
10
12
This lambda handler
@@ -19,38 +21,40 @@ Make sure that
19
21
* this lambda only gets event from png file creation
20
22
* this lambda has permission to put file into the "-thumbs" bucket
21
23
*/
22
- pub ( crate ) async fn function_handler < T : PutFile + GetFile + GetThumbnail > (
24
+ pub ( crate ) async fn function_handler < T : PutFile + GetFile > (
23
25
event : LambdaEvent < S3Event > ,
26
+ size : u32 ,
24
27
client : & T ,
25
- ) -> Result < String , String > {
26
- let result = Ok ( "" . to_string ( ) ) ;
28
+ ) -> Result < ( ) , Error > {
27
29
let records = event. payload . records ;
30
+
28
31
for record in records. iter ( ) {
29
32
let ( bucket, key) = get_file_props ( record) ;
33
+
30
34
if bucket. is_empty ( ) || key. is_empty ( ) {
31
35
// The event is not a create event or bucket/object key is missing
32
- println ! ( "record skipped" ) ;
36
+ tracing :: info !( "record skipped" ) ;
33
37
continue ;
34
38
}
35
39
36
40
let reader = client. get_file ( & bucket, & key) . await ;
37
41
38
- if reader. is_none ( ) {
42
+ if reader. is_err ( ) {
39
43
continue ;
40
44
}
41
45
42
- let thumbnail = client . get_thumbnail ( reader. unwrap ( ) ) ;
46
+ let thumbnail = get_thumbnail ( reader. unwrap ( ) , size ) ;
43
47
44
48
let mut thumbs_bucket = bucket. to_owned ( ) ;
45
49
thumbs_bucket. push_str ( "-thumbs" ) ;
46
50
47
- // It uplaods the thumbnail into a bucket name suffixed with "-thumbs"
51
+ // It uploads the thumbnail into a bucket name suffixed with "-thumbs"
48
52
// So it needs file creation permission into that bucket
49
53
50
- return client. put_file ( & thumbs_bucket, & key, thumbnail) . await ;
54
+ let _ = client. put_file ( & thumbs_bucket, & key, thumbnail) . await ;
51
55
}
52
56
53
- return result ;
57
+ Ok ( ( ) )
54
58
}
55
59
56
60
fn get_file_props ( record : & S3EventRecord ) -> ( String , String ) {
@@ -59,6 +63,7 @@ fn get_file_props(record: &S3EventRecord) -> (String, String) {
59
63
if record. event_name . is_none ( ) {
60
64
return empty_response;
61
65
}
66
+
62
67
if !record. event_name . as_ref ( ) . unwrap ( ) . starts_with ( "ObjectCreated" ) {
63
68
return empty_response;
64
69
}
@@ -71,23 +76,24 @@ fn get_file_props(record: &S3EventRecord) -> (String, String) {
71
76
let object_key = record. s3 . object . key . to_owned ( ) . unwrap ( ) ;
72
77
73
78
if bucket_name. is_empty ( ) || object_key. is_empty ( ) {
74
- println ! ( "Bucket name or object_key is empty" ) ;
79
+ tracing :: info !( "Bucket name or object_key is empty" ) ;
75
80
return empty_response;
76
81
}
77
82
78
- println ! ( "Bucket: {}, Object key: {}" , bucket_name, object_key) ;
83
+ tracing :: info !( "Bucket: {}, Object key: {}" , bucket_name, object_key) ;
79
84
80
- return ( bucket_name, object_key) ;
85
+ ( bucket_name, object_key)
81
86
}
82
87
83
- async fn get_client ( ) -> S3Client {
84
- let region_provider = RegionProviderChain :: default_provider ( ) . or_else ( "us-east-2" ) ;
85
- let config = aws_config:: from_env ( ) . region ( region_provider) . load ( ) . await ;
86
- let client = S3Client :: new ( & config) ;
88
+ fn get_thumbnail ( vec : Vec < u8 > , size : u32 ) -> Vec < u8 > {
89
+ let reader = Cursor :: new ( vec) ;
90
+ let mut thumbnails = create_thumbnails ( reader, mime:: IMAGE_PNG , [ ThumbnailSize :: Custom ( ( size, size) ) ] ) . unwrap ( ) ;
87
91
88
- println ! ( "client region {}" , client. conf( ) . region( ) . unwrap( ) . to_string( ) ) ;
92
+ let thumbnail = thumbnails. pop ( ) . unwrap ( ) ;
93
+ let mut buf = Cursor :: new ( Vec :: new ( ) ) ;
94
+ thumbnail. write_png ( & mut buf) . unwrap ( ) ;
89
95
90
- return client ;
96
+ buf . into_inner ( )
91
97
}
92
98
93
99
#[ tokio:: main]
@@ -104,10 +110,11 @@ async fn main() -> Result<(), Error> {
104
110
. without_time ( )
105
111
. init ( ) ;
106
112
107
- let client = get_client ( ) . await ;
113
+ let shared_config = aws_config:: load_from_env ( ) . await ;
114
+ let client = S3Client :: new ( & shared_config) ;
108
115
let client_ref = & client;
109
116
110
- let func = service_fn ( move |event| async move { function_handler ( event, client_ref) . await } ) ;
117
+ let func = service_fn ( move |event| async move { function_handler ( event, 128 , client_ref) . await } ) ;
111
118
112
119
run ( func) . await ?;
113
120
@@ -117,7 +124,9 @@ async fn main() -> Result<(), Error> {
117
124
#[ cfg( test) ]
118
125
mod tests {
119
126
use std:: collections:: HashMap ;
120
- use std:: io:: Cursor ;
127
+ use std:: fs:: File ;
128
+ use std:: io:: BufReader ;
129
+ use std:: io:: Read ;
121
130
122
131
use super :: * ;
123
132
use async_trait:: async_trait;
@@ -127,12 +136,11 @@ mod tests {
127
136
use aws_lambda_events:: s3:: S3Object ;
128
137
use aws_lambda_events:: s3:: S3RequestParameters ;
129
138
use aws_lambda_events:: s3:: S3UserIdentity ;
130
- use aws_sdk_s3:: types :: ByteStream ;
139
+ use aws_sdk_s3:: error :: GetObjectError ;
131
140
use lambda_runtime:: { Context , LambdaEvent } ;
132
141
use mockall:: mock;
133
- use mockall:: predicate:: eq;
134
- use s3client:: GetFile ;
135
- use s3client:: PutFile ;
142
+ use s3:: GetFile ;
143
+ use s3:: PutFile ;
136
144
137
145
#[ tokio:: test]
138
146
async fn response_is_good ( ) {
@@ -147,38 +155,43 @@ mod tests {
147
155
148
156
#[ async_trait]
149
157
impl GetFile for FakeS3Client {
150
- pub async fn get_file( & self , bucket: & str , key: & str ) -> Option < Cursor < Vec <u8 >> >;
158
+ pub async fn get_file( & self , bucket: & str , key: & str ) -> Result < Vec <u8 >, GetObjectError >;
151
159
}
152
160
#[ async_trait]
153
161
impl PutFile for FakeS3Client {
154
- pub async fn put_file( & self , bucket: & str , key: & str , bytes: ByteStream ) -> Result <String , String >;
155
- }
156
-
157
- impl GetThumbnail for FakeS3Client {
158
- fn get_thumbnail( & self , reader: Cursor <Vec <u8 >>) -> ByteStream ;
162
+ pub async fn put_file( & self , bucket: & str , key: & str , bytes: Vec <u8 >) -> Result <String , String >;
159
163
}
160
164
}
161
165
162
166
let mut mock = MockFakeS3Client :: new ( ) ;
163
167
164
168
mock. expect_get_file ( )
165
169
. withf ( |b : & str , k : & str | b. eq ( bucket) && k. eq ( key) )
166
- . returning ( |_1, _2| Some ( Cursor :: new ( b"IMAGE" . to_vec ( ) ) ) ) ;
167
-
168
- mock. expect_get_thumbnail ( )
169
- . with ( eq ( Cursor :: new ( b"IMAGE" . to_vec ( ) ) ) )
170
- . returning ( |_| ByteStream :: from_static ( b"THUMBNAIL" ) ) ;
170
+ . returning ( |_1, _2| Ok ( get_file ( "testdata/image.png" ) ) ) ;
171
171
172
172
mock. expect_put_file ( )
173
- . withf ( |bu : & str , ke : & str , _by| bu. eq ( "test-bucket-thumbs" ) && ke. eq ( key) )
173
+ . withf ( |bu : & str , ke : & str , by| {
174
+ let thumbnail = get_file ( "testdata/thumbnail.png" ) ;
175
+ return bu. eq ( "test-bucket-thumbs" ) && ke. eq ( key) && by == & thumbnail;
176
+ } )
174
177
. returning ( |_1, _2, _3| Ok ( "Done" . to_string ( ) ) ) ;
175
178
176
179
let payload = get_s3_event ( "ObjectCreated" , bucket, key) ;
177
180
let event = LambdaEvent { payload, context } ;
178
181
179
- let result = function_handler ( event, & mock) . await . unwrap ( ) ;
182
+ let result = function_handler ( event, 10 , & mock) . await . unwrap ( ) ;
183
+
184
+ assert_eq ! ( ( ) , result) ;
185
+ }
186
+
187
+ fn get_file ( name : & str ) -> Vec < u8 > {
188
+ let f = File :: open ( name) ;
189
+ let mut reader = BufReader :: new ( f. unwrap ( ) ) ;
190
+ let mut buffer = Vec :: new ( ) ;
191
+
192
+ reader. read_to_end ( & mut buffer) . unwrap ( ) ;
180
193
181
- assert_eq ! ( "Done" , result ) ;
194
+ return buffer ;
182
195
}
183
196
184
197
fn get_s3_event ( event_name : & str , bucket_name : & str , object_key : & str ) -> S3Event {
0 commit comments