1- use aws_config:: meta:: region:: RegionProviderChain ;
1+ use std:: io:: Cursor ;
2+
23use aws_lambda_events:: { event:: s3:: S3Event , s3:: S3EventRecord } ;
34use aws_sdk_s3:: Client as S3Client ;
45use lambda_runtime:: { run, service_fn, Error , LambdaEvent } ;
5- use s3client:: { GetFile , GetThumbnail , PutFile } ;
6+ use s3:: { GetFile , PutFile } ;
7+ use thumbnailer:: { create_thumbnails, ThumbnailSize } ;
68
7- mod s3client ;
9+ mod s3 ;
810
911/**
1012This lambda handler
@@ -19,38 +21,40 @@ Make sure that
1921 * this lambda only gets event from png file creation
2022 * this lambda has permission to put file into the "-thumbs" bucket
2123*/
22- pub ( crate ) async fn function_handler < T : PutFile + GetFile + GetThumbnail > (
24+ pub ( crate ) async fn function_handler < T : PutFile + GetFile > (
2325 event : LambdaEvent < S3Event > ,
26+ size : u32 ,
2427 client : & T ,
25- ) -> Result < String , String > {
26- let result = Ok ( "" . to_string ( ) ) ;
28+ ) -> Result < ( ) , Error > {
2729 let records = event. payload . records ;
30+
2831 for record in records. iter ( ) {
2932 let ( bucket, key) = get_file_props ( record) ;
33+
3034 if bucket. is_empty ( ) || key. is_empty ( ) {
3135 // The event is not a create event or bucket/object key is missing
32- println ! ( "record skipped" ) ;
36+ tracing :: info !( "record skipped" ) ;
3337 continue ;
3438 }
3539
3640 let reader = client. get_file ( & bucket, & key) . await ;
3741
38- if reader. is_none ( ) {
42+ if reader. is_err ( ) {
3943 continue ;
4044 }
4145
42- let thumbnail = client . get_thumbnail ( reader. unwrap ( ) ) ;
46+ let thumbnail = get_thumbnail ( reader. unwrap ( ) , size ) ;
4347
4448 let mut thumbs_bucket = bucket. to_owned ( ) ;
4549 thumbs_bucket. push_str ( "-thumbs" ) ;
4650
47- // It uplaods the thumbnail into a bucket name suffixed with "-thumbs"
51+ // It uploads the thumbnail into a bucket name suffixed with "-thumbs"
4852 // So it needs file creation permission into that bucket
4953
50- return client. put_file ( & thumbs_bucket, & key, thumbnail) . await ;
54+ let _ = client. put_file ( & thumbs_bucket, & key, thumbnail) . await ;
5155 }
5256
53- return result ;
57+ Ok ( ( ) )
5458}
5559
5660fn get_file_props ( record : & S3EventRecord ) -> ( String , String ) {
@@ -59,6 +63,7 @@ fn get_file_props(record: &S3EventRecord) -> (String, String) {
5963 if record. event_name . is_none ( ) {
6064 return empty_response;
6165 }
66+
6267 if !record. event_name . as_ref ( ) . unwrap ( ) . starts_with ( "ObjectCreated" ) {
6368 return empty_response;
6469 }
@@ -71,23 +76,24 @@ fn get_file_props(record: &S3EventRecord) -> (String, String) {
7176 let object_key = record. s3 . object . key . to_owned ( ) . unwrap ( ) ;
7277
7378 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" ) ;
7580 return empty_response;
7681 }
7782
78- println ! ( "Bucket: {}, Object key: {}" , bucket_name, object_key) ;
83+ tracing :: info !( "Bucket: {}, Object key: {}" , bucket_name, object_key) ;
7984
80- return ( bucket_name, object_key) ;
85+ ( bucket_name, object_key)
8186}
8287
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 ( ) ;
8791
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 ( ) ;
8995
90- return client ;
96+ buf . into_inner ( )
9197}
9298
9399#[ tokio:: main]
@@ -104,10 +110,11 @@ async fn main() -> Result<(), Error> {
104110 . without_time ( )
105111 . init ( ) ;
106112
107- let client = get_client ( ) . await ;
113+ let shared_config = aws_config:: load_from_env ( ) . await ;
114+ let client = S3Client :: new ( & shared_config) ;
108115 let client_ref = & client;
109116
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 } ) ;
111118
112119 run ( func) . await ?;
113120
@@ -117,7 +124,9 @@ async fn main() -> Result<(), Error> {
117124#[ cfg( test) ]
118125mod tests {
119126 use std:: collections:: HashMap ;
120- use std:: io:: Cursor ;
127+ use std:: fs:: File ;
128+ use std:: io:: BufReader ;
129+ use std:: io:: Read ;
121130
122131 use super :: * ;
123132 use async_trait:: async_trait;
@@ -127,12 +136,11 @@ mod tests {
127136 use aws_lambda_events:: s3:: S3Object ;
128137 use aws_lambda_events:: s3:: S3RequestParameters ;
129138 use aws_lambda_events:: s3:: S3UserIdentity ;
130- use aws_sdk_s3:: types :: ByteStream ;
139+ use aws_sdk_s3:: error :: GetObjectError ;
131140 use lambda_runtime:: { Context , LambdaEvent } ;
132141 use mockall:: mock;
133- use mockall:: predicate:: eq;
134- use s3client:: GetFile ;
135- use s3client:: PutFile ;
142+ use s3:: GetFile ;
143+ use s3:: PutFile ;
136144
137145 #[ tokio:: test]
138146 async fn response_is_good ( ) {
@@ -147,38 +155,43 @@ mod tests {
147155
148156 #[ async_trait]
149157 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 >;
151159 }
152160 #[ async_trait]
153161 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 >;
159163 }
160164 }
161165
162166 let mut mock = MockFakeS3Client :: new ( ) ;
163167
164168 mock. expect_get_file ( )
165169 . 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" ) ) ) ;
171171
172172 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+ } )
174177 . returning ( |_1, _2, _3| Ok ( "Done" . to_string ( ) ) ) ;
175178
176179 let payload = get_s3_event ( "ObjectCreated" , bucket, key) ;
177180 let event = LambdaEvent { payload, context } ;
178181
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 ( ) ;
180193
181- assert_eq ! ( "Done" , result ) ;
194+ return buffer ;
182195 }
183196
184197 fn get_s3_event ( event_name : & str , bucket_name : & str , object_key : & str ) -> S3Event {
0 commit comments