From 4a2ac19f251506db1adab8f166458ab2ae0bc3ba Mon Sep 17 00:00:00 2001 From: Uwe Reimann Date: Fri, 2 Jun 2023 11:11:59 +0200 Subject: [PATCH] Add S3Event --- .../scala/feral/lambda/events/S3Event.scala | 145 ++++++++++++++++++ .../feral/lambda/events/S3EventSuite.scala | 105 +++++++++++++ 2 files changed, 250 insertions(+) create mode 100644 lambda/shared/src/main/scala/feral/lambda/events/S3Event.scala create mode 100644 lambda/shared/src/test/scala/feral/lambda/events/S3EventSuite.scala diff --git a/lambda/shared/src/main/scala/feral/lambda/events/S3Event.scala b/lambda/shared/src/main/scala/feral/lambda/events/S3Event.scala new file mode 100644 index 00000000..3a0ebd8b --- /dev/null +++ b/lambda/shared/src/main/scala/feral/lambda/events/S3Event.scala @@ -0,0 +1,145 @@ +/* + * Copyright 2021 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package feral.lambda.events + +import io.circe.Decoder + +import java.time.Instant + +// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/aws-lambda/trigger/s3.d.ts + +final case class S3Event(records: List[S3EventRecord]) + +object S3Event { + implicit val decoder: Decoder[S3Event] = Decoder.forProduct1("Records")(S3Event.apply) +} + +final case class S3EventRecord( + eventVersion: String, + eventSource: String, + awsRegion: String, + eventTime: Instant, + eventName: String, + userIdentity: S3UserIdentity, + requestParameters: S3RequestParameters, + responseElements: S3ResponseElements, + s3: S3, + glacierEventData: Option[S3EventRecordGlacierEventData]) + +object S3EventRecord { + + implicit val decoder: Decoder[S3EventRecord] = + Decoder.forProduct10( + "eventVersion", + "eventSource", + "awsRegion", + "eventTime", + "eventName", + "userIdentity", + "requestParameters", + "responseElements", + "s3", + "glacierEventData" + )(S3EventRecord.apply) + +} + +final case class S3UserIdentity(principalId: String) + +object S3UserIdentity { + + implicit val decoder: Decoder[S3UserIdentity] = + Decoder.forProduct1("principalId")(S3UserIdentity.apply) + +} + +final case class S3RequestParameters(sourceIPAddress: String) + +object S3RequestParameters { + + implicit val decoder: Decoder[S3RequestParameters] = + Decoder.forProduct1("sourceIPAddress")(S3RequestParameters.apply) + +} + +final case class S3ResponseElements(`x-amz-request-id`: String, `x-amz-id-2`: String) + +object S3ResponseElements { + + implicit val decoder: Decoder[S3ResponseElements] = + Decoder.forProduct2("x-amz-request-id", "x-amz-id-2")(S3ResponseElements.apply) + +} + +final case class S3( + s3SchemaVersion: String, + configurationId: String, + bucket: S3Bucket, + `object`: S3Object) + +object S3 { + + implicit val decoder: Decoder[S3] = + Decoder.forProduct4("s3SchemaVersion", "configurationId", "bucket", "object")(S3.apply) + +} + +final case class S3Bucket(name: String, ownerIdentity: S3UserIdentity, arn: String) + +object S3Bucket { + + implicit val decoder: Decoder[S3Bucket] = + Decoder.forProduct3("name", "ownerIdentity", "arn")(S3Bucket.apply) + +} + +final case class S3Object( + key: String, + size: Long, + eTag: String, + versionId: Option[String], + sequencer: String) + +object S3Object { + + implicit val decoder: Decoder[S3Object] = + Decoder.forProduct5("key", "size", "eTag", "versionId", "sequencer")(S3Object.apply) + +} + +final case class S3EventRecordGlacierEventData( + restoreEventData: S3EventRecordGlacierRestoreEventData) + +object S3EventRecordGlacierEventData { + + implicit val decoder: Decoder[S3EventRecordGlacierEventData] = + Decoder.forProduct1("restoreEventData")(S3EventRecordGlacierEventData.apply) + +} + +final case class S3EventRecordGlacierRestoreEventData( + lifecycleRestorationExpiryTime: Instant, + lifecycleRestoreStorageClass: String) + +object S3EventRecordGlacierRestoreEventData { + + implicit val decoder: Decoder[S3EventRecordGlacierRestoreEventData] = + Decoder.forProduct2("lifecycleRestorationExpiryTime", "lifecycleRestoreStorageClass")( + S3EventRecordGlacierRestoreEventData.apply + ) + +} diff --git a/lambda/shared/src/test/scala/feral/lambda/events/S3EventSuite.scala b/lambda/shared/src/test/scala/feral/lambda/events/S3EventSuite.scala new file mode 100644 index 00000000..3861dd69 --- /dev/null +++ b/lambda/shared/src/test/scala/feral/lambda/events/S3EventSuite.scala @@ -0,0 +1,105 @@ +/* + * Copyright 2021 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package feral.lambda.events + +import io.circe.literal._ +import munit.FunSuite + +import java.time.Instant + +class S3EventSuite extends FunSuite { + + test("decoder") { + assertEquals(event.as[S3Event].toTry.get, result) + } + + def event = json""" + { + "Records":[ + { + "eventVersion":"2.1", + "eventSource":"aws:s3", + "awsRegion":"us-west-2", + "eventTime":"1970-01-01T00:00:00.000Z", + "eventName":"ObjectCreated:Put", + "userIdentity":{ + "principalId":"AIDAJDPLRKLG7UEXAMPLE" + }, + "requestParameters":{ + "sourceIPAddress":"127.0.0.1" + }, + "responseElements":{ + "x-amz-request-id":"C3D13FE58DE4C810", + "x-amz-id-2":"FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD" + }, + "s3":{ + "s3SchemaVersion":"1.0", + "configurationId":"testConfigRule", + "bucket":{ + "name":"mybucket", + "ownerIdentity":{ + "principalId":"A3NL1KOZZKExample" + }, + "arn":"arn:aws:s3:::mybucket" + }, + "object":{ + "key":"HappyFace.jpg", + "size":1024, + "eTag":"d41d8cd98f00b204e9800998ecf8427e", + "versionId":"096fKKXTRTtl3on89fVO.nfljtsv6qko", + "sequencer":"0055AED6DCD90281E5" + } + } + } + ] + } + """ + + def result = S3Event(records = List( + S3EventRecord( + eventVersion = "2.1", + eventSource = "aws:s3", + awsRegion = "us-west-2", + eventTime = Instant.ofEpochSecond(0), + eventName = "ObjectCreated:Put", + userIdentity = S3UserIdentity("AIDAJDPLRKLG7UEXAMPLE"), + requestParameters = S3RequestParameters("127.0.0.1"), + responseElements = S3ResponseElements( + "C3D13FE58DE4C810", + "FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD" + ), + s3 = S3( + s3SchemaVersion = "1.0", + configurationId = "testConfigRule": String, + bucket = S3Bucket( + name = "mybucket", + ownerIdentity = S3UserIdentity("A3NL1KOZZKExample"), + arn = "arn:aws:s3:::mybucket" + ), + `object` = S3Object( + key = "HappyFace.jpg", + size = 1024L, + eTag = "d41d8cd98f00b204e9800998ecf8427e", + versionId = Option("096fKKXTRTtl3on89fVO.nfljtsv6qko"), + sequencer = "0055AED6DCD90281E5" + ) + ), + glacierEventData = None + ) + )) + +}