-
-
Notifications
You must be signed in to change notification settings - Fork 49
Add ApplicationLoadBalancerRequestEvent #590
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
56aec7f
25b7b8b
ba95f35
85aee56
fd5316c
2cbbeed
3e77117
e0d2bff
d62086e
babc30f
b23c456
f2521ce
33a6b4d
ad2a0f9
71b6567
789d714
fbabd37
4f219a8
829f70a
0c09c43
042cd17
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,120 @@ | ||||||
| /* | ||||||
| * 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 feral.lambda.events.codecs.decodeKeyCIString | ||||||
| import io.circe.Decoder | ||||||
| import org.typelevel.ci.CIString | ||||||
| import scodec.bits.ByteVector | ||||||
|
|
||||||
| sealed abstract class Elb { | ||||||
| def targetGroupArn: String | ||||||
| } | ||||||
|
|
||||||
| object Elb { | ||||||
| def apply(targetGroupArn: String): Elb = Impl(targetGroupArn) | ||||||
|
|
||||||
| implicit val decoder: Decoder[Elb] = Decoder.forProduct1("targetGroupArn")(Elb.apply) | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| private final case class Impl(targetGroupArn: String) extends Elb | ||||||
| } | ||||||
|
|
||||||
| sealed abstract class ApplicationLoadBalancerRequestContext { | ||||||
| def elb: Elb | ||||||
| } | ||||||
|
|
||||||
| object ApplicationLoadBalancerRequestContext { | ||||||
|
|
||||||
| def apply(elb: Elb): ApplicationLoadBalancerRequestContext = | ||||||
| Impl(elb) | ||||||
|
|
||||||
| implicit val decoder: Decoder[ApplicationLoadBalancerRequestContext] = | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| Decoder.forProduct1("elb")(ApplicationLoadBalancerRequestContext.apply) | ||||||
|
|
||||||
| private final case class Impl(elb: Elb) extends ApplicationLoadBalancerRequestContext | ||||||
| } | ||||||
|
|
||||||
| sealed abstract class ApplicationLoadBalancerRequestEvent { | ||||||
| def requestContext: ApplicationLoadBalancerRequestContext | ||||||
| def httpMethod: String | ||||||
| def path: String | ||||||
| def queryStringParameters: Option[Map[String, String]] | ||||||
| def headers: Option[Map[CIString, String]] | ||||||
| def multiValueQueryStringParameters: Option[Map[String, List[String]]] | ||||||
| def multiValueHeaders: Option[Map[CIString, List[String]]] | ||||||
| def body: Option[String] | ||||||
| def isBase64Encoded: Boolean | ||||||
| def bodyDecoded: Option[ByteVector] = | ||||||
| if (isBase64Encoded) body.flatMap(ByteVector.fromBase64(_)) else None | ||||||
| } | ||||||
|
|
||||||
| object ApplicationLoadBalancerRequestEvent { | ||||||
| private implicit val mapStringDecoder: Decoder[Map[CIString, String]] = | ||||||
| Decoder.decodeMap[CIString, String](decodeKeyCIString, Decoder.decodeString) | ||||||
| private implicit val mapListDecoder: Decoder[Map[CIString, List[String]]] = | ||||||
| Decoder.decodeMap[CIString, List[String]]( | ||||||
| decodeKeyCIString, | ||||||
| Decoder.decodeList(Decoder.decodeString)) | ||||||
|
|
||||||
| def apply( | ||||||
| requestContext: ApplicationLoadBalancerRequestContext, | ||||||
| httpMethod: String, | ||||||
| path: String, | ||||||
| queryStringParameters: Option[Map[String, String]], | ||||||
| headers: Option[Map[CIString, String]], | ||||||
| multiValueQueryStringParameters: Option[Map[String, List[String]]], | ||||||
| multiValueHeaders: Option[Map[CIString, List[String]]], | ||||||
| body: Option[String], | ||||||
| isBase64Encoded: Boolean | ||||||
| ): ApplicationLoadBalancerRequestEvent = | ||||||
| Impl( | ||||||
| requestContext, | ||||||
| httpMethod, | ||||||
| path, | ||||||
| queryStringParameters, | ||||||
| headers, | ||||||
| multiValueQueryStringParameters, | ||||||
| multiValueHeaders, | ||||||
| body, | ||||||
| isBase64Encoded | ||||||
| ) | ||||||
|
|
||||||
| implicit val decoder: Decoder[ApplicationLoadBalancerRequestEvent] = | ||||||
| Decoder.forProduct9( | ||||||
| "requestContext", | ||||||
| "httpMethod", | ||||||
| "path", | ||||||
| "queryStringParameters", | ||||||
| "headers", | ||||||
| "multiValueQueryStringParameters", | ||||||
| "multiValueHeaders", | ||||||
| "body", | ||||||
| "isBase64Encoded" | ||||||
| )(ApplicationLoadBalancerRequestEvent.apply) | ||||||
|
|
||||||
| private final case class Impl( | ||||||
| requestContext: ApplicationLoadBalancerRequestContext, | ||||||
| httpMethod: String, | ||||||
| path: String, | ||||||
| queryStringParameters: Option[Map[String, String]], | ||||||
| headers: Option[Map[CIString, String]], | ||||||
| multiValueQueryStringParameters: Option[Map[String, List[String]]], | ||||||
| multiValueHeaders: Option[Map[CIString, List[String]]], | ||||||
| body: Option[String], | ||||||
| isBase64Encoded: Boolean | ||||||
| ) extends ApplicationLoadBalancerRequestEvent | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,193 @@ | ||||||||||||
| /* | ||||||||||||
| * 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 | ||||||||||||
| import scodec.bits.ByteVector | ||||||||||||
|
|
||||||||||||
| object ApplicationLoadBalancerEventSuite { | ||||||||||||
| def allFieldsEvent = json""" | ||||||||||||
| { | ||||||||||||
| "requestContext": { | ||||||||||||
| "elb": { | ||||||||||||
| "targetGroupArn": "arn:aws:elasticloadbalancing:us-west-2:123456789012:targetgroup/my-target-group/6d0ecf831eec9f09" | ||||||||||||
| } | ||||||||||||
| }, | ||||||||||||
| "httpMethod": "GET", | ||||||||||||
| "path": "/lambda", | ||||||||||||
| "queryStringParameters": { | ||||||||||||
| "query": "1234ABCD" | ||||||||||||
| }, | ||||||||||||
| "headers": { | ||||||||||||
| "accept": "text/html,application/xhtml+xml", | ||||||||||||
| "accept-language": "en-US,en;q=0.8", | ||||||||||||
| "content-type": "text/plain", | ||||||||||||
| "cookie": "cookies", | ||||||||||||
| "host": "lambda-846800462-us-west-2.elb.amazonaws.com", | ||||||||||||
| "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6)", | ||||||||||||
| "x-amzn-trace-id": "Root=1-58337364-23a8c76965a2ef7629b2b5c2", | ||||||||||||
| "x-forwarded-for": "72.21.198.64", | ||||||||||||
| "x-forwarded-port": "443", | ||||||||||||
| "x-forwarded-proto": "https" | ||||||||||||
| }, | ||||||||||||
| "multiValueQueryStringParameters": null, | ||||||||||||
| "multiValueHeaders": null, | ||||||||||||
| "body": null, | ||||||||||||
| "isBase64Encoded": false | ||||||||||||
| } | ||||||||||||
| """ | ||||||||||||
|
|
||||||||||||
| def withBodyEvent = { | ||||||||||||
| val encodedBody = | ||||||||||||
| java.util.Base64.getEncoder.encodeToString("hello world".getBytes("UTF-8")) | ||||||||||||
| Json.obj( | ||||||||||||
| "requestContext" -> Json.obj( | ||||||||||||
| "elb" -> Json.obj( | ||||||||||||
| "targetGroupArn" -> Json.fromString( | ||||||||||||
| "arn:aws:elasticloadbalancing:us-west-2:123456789012:targetgroup/my-target-group/6d0ecf831eec9f09") | ||||||||||||
| ) | ||||||||||||
| ), | ||||||||||||
| "httpMethod" -> Json.fromString("POST"), | ||||||||||||
| "path" -> Json.fromString("/submit"), | ||||||||||||
| "queryStringParameters" -> Json.Null, | ||||||||||||
| "headers" -> Json.Null, | ||||||||||||
| "multiValueQueryStringParameters" -> Json.Null, | ||||||||||||
| "multiValueHeaders" -> Json.Null, | ||||||||||||
| "body" -> Json.fromString(encodedBody), | ||||||||||||
| "isBase64Encoded" -> Json.fromBoolean(true) | ||||||||||||
| ) | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| def missingOptionalsEvent = json""" | ||||||||||||
| { | ||||||||||||
| "requestContext": { | ||||||||||||
| "elb": { | ||||||||||||
| "targetGroupArn": "arn:aws:elasticloadbalancing:us-west-2:123456789012:targetgroup/my-target-group/6d0ecf831eec9f09" | ||||||||||||
| } | ||||||||||||
| }, | ||||||||||||
| "httpMethod": "GET", | ||||||||||||
| "path": "/only-required", | ||||||||||||
| "isBase64Encoded": false | ||||||||||||
| } | ||||||||||||
| """ | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| class ApplicationLoadBalancerEventSuite extends FunSuite { | ||||||||||||
| import ApplicationLoadBalancerEventSuite.* | ||||||||||||
|
|
||||||||||||
| test("decode with all fields populated") { | ||||||||||||
| val decoded = allFieldsEvent.as[ApplicationLoadBalancerRequestEvent].toTry.get | ||||||||||||
| val expected = ApplicationLoadBalancerRequestEvent( | ||||||||||||
| requestContext = ApplicationLoadBalancerRequestContext( | ||||||||||||
| elb = Elb( | ||||||||||||
| "arn:aws:elasticloadbalancing:us-west-2:123456789012:targetgroup/my-target-group/6d0ecf831eec9f09") | ||||||||||||
| ), | ||||||||||||
| httpMethod = "GET", | ||||||||||||
| path = "/lambda", | ||||||||||||
| queryStringParameters = Some(Map("query" -> "1234ABCD")), | ||||||||||||
| headers = Some( | ||||||||||||
| Map( | ||||||||||||
| org.typelevel.ci.CIString("accept") -> "text/html,application/xhtml+xml", | ||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a special syntax to shorten this.
Suggested change
|
||||||||||||
| org.typelevel.ci.CIString("accept-language") -> "en-US,en;q=0.8", | ||||||||||||
| org.typelevel.ci.CIString("content-type") -> "text/plain", | ||||||||||||
| org.typelevel.ci.CIString("cookie") -> "cookies", | ||||||||||||
| org.typelevel.ci.CIString("host") -> "lambda-846800462-us-west-2.elb.amazonaws.com", | ||||||||||||
| org | ||||||||||||
| .typelevel | ||||||||||||
| .ci | ||||||||||||
| .CIString("user-agent") -> "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6)", | ||||||||||||
| org | ||||||||||||
| .typelevel | ||||||||||||
| .ci | ||||||||||||
| .CIString("x-amzn-trace-id") -> "Root=1-58337364-23a8c76965a2ef7629b2b5c2", | ||||||||||||
| org.typelevel.ci.CIString("x-forwarded-for") -> "72.21.198.64", | ||||||||||||
| org.typelevel.ci.CIString("x-forwarded-port") -> "443", | ||||||||||||
| org.typelevel.ci.CIString("x-forwarded-proto") -> "https" | ||||||||||||
| ) | ||||||||||||
| ), | ||||||||||||
| multiValueQueryStringParameters = None, | ||||||||||||
| multiValueHeaders = None, | ||||||||||||
| body = None, | ||||||||||||
| isBase64Encoded = false | ||||||||||||
| ) | ||||||||||||
| assertEquals(decoded, expected) | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| test("decode with only body present") { | ||||||||||||
| val decoded = withBodyEvent.as[ApplicationLoadBalancerRequestEvent].toTry.get | ||||||||||||
| val encodedBody = | ||||||||||||
| java.util.Base64.getEncoder.encodeToString("hello world".getBytes("UTF-8")) | ||||||||||||
|
Comment on lines
+134
to
+135
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. scodec-bits has a nice syntax for this:
Suggested change
|
||||||||||||
| val expected = ApplicationLoadBalancerRequestEvent( | ||||||||||||
| requestContext = ApplicationLoadBalancerRequestContext( | ||||||||||||
| elb = Elb( | ||||||||||||
| "arn:aws:elasticloadbalancing:us-west-2:123456789012:targetgroup/my-target-group/6d0ecf831eec9f09") | ||||||||||||
| ), | ||||||||||||
| httpMethod = "POST", | ||||||||||||
| path = "/submit", | ||||||||||||
| queryStringParameters = None, | ||||||||||||
| headers = None, | ||||||||||||
| multiValueQueryStringParameters = None, | ||||||||||||
| multiValueHeaders = None, | ||||||||||||
| body = Some(encodedBody), | ||||||||||||
| isBase64Encoded = true | ||||||||||||
| ) | ||||||||||||
| assertEquals(decoded, expected) | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| test("decode with only required fields") { | ||||||||||||
| val decoded = missingOptionalsEvent.as[ApplicationLoadBalancerRequestEvent].toTry.get | ||||||||||||
| val expected = ApplicationLoadBalancerRequestEvent( | ||||||||||||
| requestContext = ApplicationLoadBalancerRequestContext( | ||||||||||||
| elb = Elb( | ||||||||||||
| "arn:aws:elasticloadbalancing:us-west-2:123456789012:targetgroup/my-target-group/6d0ecf831eec9f09") | ||||||||||||
| ), | ||||||||||||
| httpMethod = "GET", | ||||||||||||
| path = "/only-required", | ||||||||||||
| queryStringParameters = None, | ||||||||||||
| headers = None, | ||||||||||||
| multiValueQueryStringParameters = None, | ||||||||||||
| multiValueHeaders = None, | ||||||||||||
| body = None, | ||||||||||||
| isBase64Encoded = false | ||||||||||||
| ) | ||||||||||||
| assertEquals(decoded, expected) | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| test("decode base64 body correctly") { | ||||||||||||
| val base64String = | ||||||||||||
| java.util.Base64.getEncoder.encodeToString("hello world".getBytes("UTF-8")) | ||||||||||||
| val eventJson = Json.obj( | ||||||||||||
| "requestContext" -> Json.obj( | ||||||||||||
| "elb" -> Json.obj( | ||||||||||||
| "targetGroupArn" -> Json.fromString( | ||||||||||||
| "arn:aws:elasticloadbalancing:us-west-2:123456789012:targetgroup/my-target-group/6d0ecf831eec9f09") | ||||||||||||
| ) | ||||||||||||
| ), | ||||||||||||
| "httpMethod" -> Json.fromString("POST"), | ||||||||||||
| "path" -> Json.fromString("/submit"), | ||||||||||||
| "body" -> Json.fromString(base64String), | ||||||||||||
| "isBase64Encoded" -> Json.fromBoolean(true) | ||||||||||||
| ) | ||||||||||||
| val decoded = eventJson.as[ApplicationLoadBalancerRequestEvent].toTry.get | ||||||||||||
| assert( | ||||||||||||
| decoded | ||||||||||||
| .bodyDecoded | ||||||||||||
| .exists(_ == ByteVector.encodeUtf8("hello world").getOrElse(ByteVector.empty))) | ||||||||||||
|
Comment on lines
+188
to
+191
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
| } | ||||||||||||
| } | ||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe this name is more clear :)