Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
/*
* 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
package events

import com.comcast.ip4s.Hostname
import feral.lambda.KernelSource
import io.circe.Decoder
import natchez.Kernel
import org.typelevel.ci.CIString

import codecs.decodeHostname
import codecs.decodeKeyCIString

sealed abstract class AuthorizerRequestContext {
def resourceId: String
def resourcePath: String
def httpMethod: String
def extendedRequestId: String
def requestTime: String

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see there is requestTime: String and requestTimeEpoch: Long. Probably they are redundant, and we could use requestTime: Instant for the best UX?

def path: String
def accountId: String
def protocol: String
def stage: String
def domainPrefix: String
def requestTimeEpoch: Long
def requestId: String
def identity: Map[String, Option[String]]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type looks a bit strange 🤔 I wonder in what case would there be a key would no value? (instead of just not having the key at all).

def domainName: Hostname
def deploymentId: String
def apiId: String
}

object AuthorizerRequestContext {

def apply(
resourceId: String,
resourcePath: String,
httpMethod: String,
extendedRequestId: String,
requestTime: String,
path: String,
accountId: String,
protocol: String,
stage: String,
domainPrefix: String,
requestTimeEpoch: Long,
requestId: String,
identity: Map[String, Option[String]],
domainName: Hostname,
deploymentId: String,
apiId: String
): AuthorizerRequestContext =
new Impl(
resourceId,
resourcePath,
httpMethod,
extendedRequestId,
requestTime,
path,
accountId,
protocol,
stage,
domainPrefix,
requestTimeEpoch,
requestId,
identity,
domainName,
deploymentId,
apiId
)

implicit def decoder: Decoder[AuthorizerRequestContext] = Decoder.forProduct16(
"resourceId",
"resourcePath",
"httpMethod",
"extendedRequestId",
"requestTime",
"path",
"accountId",
"protocol",
"stage",
"domainPrefix",
"requestTimeEpoch",
"requestId",
"identity",
"domainName",
"deploymentId",
"apiId"
)(AuthorizerRequestContext.apply)

private case class Impl(
resourceId: String,
resourcePath: String,
httpMethod: String,
extendedRequestId: String,
requestTime: String,
path: String,
accountId: String,
protocol: String,
stage: String,
domainPrefix: String,
requestTimeEpoch: Long,
requestId: String,
identity: Map[String, Option[String]],
domainName: Hostname,
deploymentId: String,
apiId: String
) extends AuthorizerRequestContext {
override def productPrefix = "AuthorizerRequestContext"
}
}

sealed abstract class ApiGatewayCustomAuthorizerEvent {
def eventType: CustomAuthorizerEventType
def methodArn: String
def resource: String
def path: String
def httpMethod: String
def headers: Option[Map[CIString, String]]
def multiValueHeaders: Map[CIString, List[String]]
def queryStringParameters: Map[CIString, Option[String]]
def multiValueQueryStringParameters: Map[CIString, Option[List[String]]]
def pathParameters: Map[CIString, String]
def stageVariables: Map[CIString, String]
def requestContext: AuthorizerRequestContext
}

object ApiGatewayCustomAuthorizerEvent {

def apply(
eventType: CustomAuthorizerEventType,
methodArn: String,
resource: String,
path: String,
httpMethod: String,
headers: Option[Map[CIString, String]],
multiValueHeaders: Map[CIString, List[String]],
queryStringParameters: Map[CIString, Option[String]],
multiValueQueryStringParameters: Map[CIString, Option[List[String]]],
pathParameters: Map[CIString, String],
stageVariables: Map[CIString, String],
requestContext: AuthorizerRequestContext
): ApiGatewayCustomAuthorizerEvent =
new Impl(
eventType,
methodArn,
resource,
path,
httpMethod,
headers,
multiValueHeaders,
queryStringParameters,
multiValueQueryStringParameters,
pathParameters,
stageVariables,
requestContext
)

implicit def kernelSource: KernelSource[ApiGatewayCustomAuthorizerEvent] =
e => Kernel(e.headers.getOrElse(Map.empty))

implicit def decoder: Decoder[ApiGatewayCustomAuthorizerEvent] = Decoder.forProduct12(
"type",
"methodArn",
"resource",
"path",
"httpMethod",
"headers",
"multiValueHeaders",
"queryStringParameters",
"multiValueQueryStringParameters",
"pathParameters",
"stageVariables",
"requestContext"
)(ApiGatewayCustomAuthorizerEvent.apply)

private case class Impl(
eventType: CustomAuthorizerEventType,
methodArn: String,
resource: String,
path: String,
httpMethod: String,
headers: Option[Map[CIString, String]],
multiValueHeaders: Map[CIString, List[String]],
queryStringParameters: Map[CIString, Option[String]],
multiValueQueryStringParameters: Map[CIString, Option[List[String]]],
pathParameters: Map[CIString, String],
stageVariables: Map[CIString, String],
requestContext: AuthorizerRequestContext
) extends ApiGatewayCustomAuthorizerEvent {
override def productPrefix = "ApiGatewayCustomAuthorizerEvent"
}
}

sealed abstract class CustomAuthorizerEventType

object CustomAuthorizerEventType {
case object Token extends CustomAuthorizerEventType
case object Request extends CustomAuthorizerEventType

private[events] implicit val decoder: Decoder[CustomAuthorizerEventType] =
Decoder.decodeString.map {
case "TOKEN" => Token
case "REQUEST" => Request
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* 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
package events

import io.circe.Encoder

import java.time.LocalDate

import codecs.encodeDate

sealed abstract class ApiGatewayCustomAuthorizerEventResult {
def principalId: String
def policyDocument: PolicyDocument
}

object ApiGatewayCustomAuthorizerEventResult {

def apply(
principalId: String,
policyDocument: PolicyDocument): ApiGatewayCustomAuthorizerEventResult =
Impl(principalId, policyDocument)

implicit def encoder: Encoder[ApiGatewayCustomAuthorizerEventResult] = Encoder.forProduct2(
"principalId",
"policyDocument"
)(r => (r.principalId, r.policyDocument))

private case class Impl(
principalId: String,
policyDocument: PolicyDocument
) extends ApiGatewayCustomAuthorizerEventResult {
override def productPrefix = "ApiGatewayCustomAuthorizerEventResult"
}
}

sealed abstract class PolicyDocument {
def version: LocalDate
def statement: List[Statement]
}

object PolicyDocument {
def apply(version: LocalDate, statement: List[Statement]): PolicyDocument =
Impl(version, statement)

implicit def encoder: Encoder[PolicyDocument] = Encoder.forProduct2(
"Version",
"Statement"
)(r => (r.version, r.statement))

private case class Impl(
version: LocalDate,
statement: List[Statement]
) extends PolicyDocument {
override def productPrefix = "PolicyDocument"
}
}

sealed abstract class Statement {
def action: String
def effect: String
def resource: String
}

object Statement {
def apply(action: String, effect: String, resource: String): Statement =
Impl(action, effect, resource)

implicit def encoder: Encoder[Statement] = Encoder.forProduct3(
"Action",
"Effect",
"Resource"
)(r => (r.action, r.effect, r.resource))

private case class Impl(
action: String,
effect: String,
resource: String
) extends Statement {
override def productPrefix = "Statement"
}
}
8 changes: 8 additions & 0 deletions lambda/shared/src/main/scala/feral/lambda/events/codecs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ package feral.lambda.events
import com.comcast.ip4s.Hostname
import com.comcast.ip4s.IpAddress
import io.circe.Decoder
import io.circe.Encoder
import io.circe.KeyDecoder
import io.circe.KeyEncoder
import org.typelevel.ci.CIString

import java.time.Instant
import java.time.format.DateTimeFormatter
import scala.util.Try

private object codecs {
Expand All @@ -38,6 +40,12 @@ private object codecs {
}
}

implicit def encodeDate: Encoder[java.time.LocalDate] =
Encoder.encodeString.contramap { str =>
val dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
str.format(dateFormatter)
}

implicit def decodeIpAddress: Decoder[IpAddress] =
Decoder.decodeString.emap(IpAddress.fromString(_).toRight("Cannot parse IP address"))

Expand Down
Loading
Loading