diff --git a/lambda/shared/src/main/scala/feral/lambda/events/ConfigEvent.scala b/lambda/shared/src/main/scala/feral/lambda/events/ConfigEvent.scala new file mode 100644 index 00000000..fefe3bf9 --- /dev/null +++ b/lambda/shared/src/main/scala/feral/lambda/events/ConfigEvent.scala @@ -0,0 +1,92 @@ +/* + * 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 + +// AWS Config custom rule Lambda event. DefinitelyTyped trigger/config is TODO. +// See: https://docs.aws.amazon.com/config/latest/developerguide/evaluate-config_develop-rules_lambda-functions.html +// invokingEvent and ruleParameters are JSON strings; parse separately if needed. + +sealed abstract class ConfigEvent { + def invokingEvent: String + def ruleParameters: String + def resultToken: String + def eventLeftScope: Boolean + def executionRoleArn: String + def configRuleArn: String + def configRuleName: String + def configRuleId: String + def accountId: String + def version: String +} + +object ConfigEvent { + def apply( + invokingEvent: String, + ruleParameters: String, + resultToken: String, + eventLeftScope: Boolean, + executionRoleArn: String, + configRuleArn: String, + configRuleName: String, + configRuleId: String, + accountId: String, + version: String + ): ConfigEvent = + new Impl( + invokingEvent, + ruleParameters, + resultToken, + eventLeftScope, + executionRoleArn, + configRuleArn, + configRuleName, + configRuleId, + accountId, + version + ) + + implicit val decoder: Decoder[ConfigEvent] = + Decoder.forProduct10( + "invokingEvent", + "ruleParameters", + "resultToken", + "eventLeftScope", + "executionRoleArn", + "configRuleArn", + "configRuleName", + "configRuleId", + "accountId", + "version" + )(ConfigEvent.apply) + + private final case class Impl( + invokingEvent: String, + ruleParameters: String, + resultToken: String, + eventLeftScope: Boolean, + executionRoleArn: String, + configRuleArn: String, + configRuleName: String, + configRuleId: String, + accountId: String, + version: String + ) extends ConfigEvent { + override def productPrefix = "ConfigEvent" + } +} diff --git a/lambda/shared/src/main/scala/feral/lambda/events/IoTButtonEvent.scala b/lambda/shared/src/main/scala/feral/lambda/events/IoTButtonEvent.scala new file mode 100644 index 00000000..f773731e --- /dev/null +++ b/lambda/shared/src/main/scala/feral/lambda/events/IoTButtonEvent.scala @@ -0,0 +1,67 @@ +/* + * 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 + +// AWS IoT Button payload. DefinitelyTyped trigger/iot.d.ts models IoTEvent as string | number | T +// (no fixed structure). This model follows the documented AWS IoT Button payload: +// https://docs.aws.amazon.com/lambda/latest/dg/services-iot.html (IoT as trigger) +// clickType: SINGLE | DOUBLE | LONG, serialNumber, batteryVoltage (e.g. "1584mV") + +sealed abstract class IoTButtonEvent { + def clickType: IoTButtonEvent.ClickType + def serialNumber: String + def batteryVoltage: String +} + +object IoTButtonEvent { + + sealed abstract class ClickType + object ClickType { + case object Single extends ClickType + case object Double extends ClickType + case object Long extends ClickType + + implicit val decoder: Decoder[ClickType] = Decoder.decodeString.emap { + case "SINGLE" => Right(Single) + case "DOUBLE" => Right(Double) + case "LONG" => Right(Long) + case other => Left(s"Unknown click type: $other") + } + } + + def apply( + clickType: ClickType, + serialNumber: String, + batteryVoltage: String + ): IoTButtonEvent = + new Impl(clickType, serialNumber, batteryVoltage) + + implicit val decoder: Decoder[IoTButtonEvent] = + Decoder.forProduct3("clickType", "serialNumber", "batteryVoltage")( + IoTButtonEvent.apply + ) + + private final case class Impl( + clickType: ClickType, + serialNumber: String, + batteryVoltage: String + ) extends IoTButtonEvent { + override def productPrefix = "IoTButtonEvent" + } +} diff --git a/lambda/shared/src/test/scala/feral/lambda/events/ConfigEventSuite.scala b/lambda/shared/src/test/scala/feral/lambda/events/ConfigEventSuite.scala new file mode 100644 index 00000000..b6b8396e --- /dev/null +++ b/lambda/shared/src/test/scala/feral/lambda/events/ConfigEventSuite.scala @@ -0,0 +1,63 @@ +/* + * 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.Json +import io.circe.literal._ +import munit.FunSuite + +class ConfigEventSuite extends FunSuite { + import ConfigEventSuite._ + + test("decoder") { + assertEquals(configEvent.as[ConfigEvent].toTry.get, decoded) + } +} + +object ConfigEventSuite { + + val decoded: ConfigEvent = + ConfigEvent( + invokingEvent = + """{"messageType":"ConfigurationItemChangeNotification","configurationItem":{}}""", + ruleParameters = """{"desiredInstanceType":"t2.micro"}""", + resultToken = "testResultToken123", + eventLeftScope = false, + executionRoleArn = "arn:aws:iam::123456789012:role/config-role", + configRuleArn = "arn:aws:config:us-east-1:123456789012:config-rule/config-rule-012345", + configRuleName = "instance-type-check", + configRuleId = "config-rule-012345", + accountId = "123456789012", + version = "1.0" + ) + + // https://docs.aws.amazon.com/config/latest/developerguide/evaluate-config_develop-rules_lambda-functions.html + val configEvent: Json = json""" + { + "invokingEvent": "{\"messageType\":\"ConfigurationItemChangeNotification\",\"configurationItem\":{}}", + "ruleParameters": "{\"desiredInstanceType\":\"t2.micro\"}", + "resultToken": "testResultToken123", + "eventLeftScope": false, + "executionRoleArn": "arn:aws:iam::123456789012:role/config-role", + "configRuleArn": "arn:aws:config:us-east-1:123456789012:config-rule/config-rule-012345", + "configRuleName": "instance-type-check", + "configRuleId": "config-rule-012345", + "accountId": "123456789012", + "version": "1.0" + } + """ +} diff --git a/lambda/shared/src/test/scala/feral/lambda/events/IoTButtonEventSuite.scala b/lambda/shared/src/test/scala/feral/lambda/events/IoTButtonEventSuite.scala new file mode 100644 index 00000000..d3aef8ab --- /dev/null +++ b/lambda/shared/src/test/scala/feral/lambda/events/IoTButtonEventSuite.scala @@ -0,0 +1,67 @@ +/* + * 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.Json +import io.circe.literal._ +import munit.FunSuite + +class IoTButtonEventSuite extends FunSuite { + import IoTButtonEventSuite._ + + test("decoder SINGLE click") { + assertEquals(singleClickEvent.as[IoTButtonEvent].toTry.get, decodedSingle) + } + + test("decoder LONG click") { + assertEquals(longClickEvent.as[IoTButtonEvent].toTry.get, decodedLong) + } +} + +object IoTButtonEventSuite { + + val decodedSingle: IoTButtonEvent = + IoTButtonEvent( + clickType = IoTButtonEvent.ClickType.Single, + serialNumber = "G030PM00000000000000", + batteryVoltage = "1584mV" + ) + + val decodedLong: IoTButtonEvent = + IoTButtonEvent( + clickType = IoTButtonEvent.ClickType.Long, + serialNumber = "G030PM00000000000000", + batteryVoltage = "1584mV" + ) + + // AWS IoT Button payload: https://docs.aws.amazon.com/lambda/latest/dg/services-iot.html + val singleClickEvent: Json = json""" + { + "clickType": "SINGLE", + "serialNumber": "G030PM00000000000000", + "batteryVoltage": "1584mV" + } + """ + + val longClickEvent: Json = json""" + { + "clickType": "LONG", + "serialNumber": "G030PM00000000000000", + "batteryVoltage": "1584mV" + } + """ +}