Skip to content
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

Custom Lambda Runtime #276

Open
wants to merge 59 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
7ea939b
Bump base version
armanbilge Sep 25, 2022
26b1622
Shuffle build for custom runtime
armanbilge Sep 25, 2022
95efe18
http4s and cloudformation only depend on kernel
armanbilge Sep 25, 2022
be96317
WIP: added runtime skeleton, needs error handling
scott-thomson239 Sep 26, 2022
ebf5e97
fixed context creation from environment variables
scott-thomson239 Sep 27, 2022
6f990ad
added comments to unimplemented parts
scott-thomson239 Sep 28, 2022
f6a904b
used new cats effect Env instead of custom env var solution
scott-thomson239 Sep 29, 2022
defb679
added function to obtain LambdaRequest from response and added error …
scott-thomson239 Oct 2, 2022
c95cecb
renamed module and fixed handler error handling
scott-thomson239 Oct 3, 2022
2de1748
changed hander fiber to not raise error after cancellation
scott-thomson239 Oct 4, 2022
b7e6054
created custom invocation response header models
scott-thomson239 Oct 6, 2022
b07406c
tests work in progress
scott-thomson239 Oct 12, 2022
0eb162e
fixed all but one test
scott-thomson239 Oct 13, 2022
24c4261
added test to verify runtime can handle invocation errors correctly
scott-thomson239 Oct 15, 2022
602460b
fixed failing tests, although tests need refactored
scott-thomson239 Oct 16, 2022
25403d0
refactored tests
scott-thomson239 Oct 17, 2022
a65d045
added init error handling and tests
scott-thomson239 Oct 19, 2022
206ec6a
fixed failing test
scott-thomson239 Oct 20, 2022
aa06238
general clean up
scott-thomson239 Oct 20, 2022
39562d6
fix missing import
scott-thomson239 Oct 20, 2022
108c2cb
ran scalafmt
scott-thomson239 Oct 20, 2022
016df57
ran sbt githubWorkflowGenerate
scott-thomson239 Oct 20, 2022
7ce4f85
added file headers
scott-thomson239 Oct 20, 2022
fe73a65
added .settings(commonSettings) to lambdaRuntime and lambdaKernel
scott-thomson239 Oct 22, 2022
ed8cb7d
Merge branch 'main' into feature/custom-lambda-runtime
scott-thomson239 Oct 22, 2022
f2b7151
ran sbt githubWorkflowGenerate
scott-thomson239 Oct 22, 2022
0cf6033
fix type erasure warning
scott-thomson239 Oct 22, 2022
e3f8553
Merge branch 'main' into feature/custom-lambda-runtime
armanbilge Oct 22, 2022
7c69d9e
fix for potential type inference problem in expect method and correct…
scott-thomson239 Oct 23, 2022
cfd8f0a
modified sbtLambda in build.sbt to include lambdaKernel.js in scripte…
scott-thomson239 Oct 23, 2022
accb4b6
forgot to format buld.sbt oops
scott-thomson239 Oct 23, 2022
6acf432
updated natchez-core in lambdaKernel and updated scalajs plugin to 1.…
scott-thomson239 Oct 23, 2022
2449d3e
fix for 'withFilter is not a member of IO' error
scott-thomson239 Oct 23, 2022
9d79651
added snapshot resolver to sbt-lambda build.sbt
scott-thomson239 Oct 23, 2022
3c4974c
added lambdaKernel and lambdaRuntime to build.sbt unidocs
scott-thomson239 Oct 23, 2022
2548296
test to see if removing rethrow from IOSetup solves compiler error
scott-thomson239 Oct 23, 2022
35d797f
added MatrixExclude for 2.12/native
scott-thomson239 Oct 23, 2022
93d55aa
changed cats-effect-kernel to cats-effect in lambdaKernel and removed…
scott-thomson239 Oct 23, 2022
af3b527
Make lambda headers package-private
armanbilge Nov 30, 2022
f3022e4
Parse/decode in single op
armanbilge Nov 30, 2022
d630597
More package private models
armanbilge Nov 30, 2022
366248a
Build error body from `Throwable`
armanbilge Nov 30, 2022
ab6fa05
Define `EntityEncoder` for `LmabdaErrorBody`
armanbilge Nov 30, 2022
54c3032
Reduce noise
armanbilge Dec 4, 2022
b5e3019
Simplifications
armanbilge Dec 4, 2022
d6de562
Use `Uri` for `lambdaRuntimeApi`
armanbilge Dec 4, 2022
13aa8e9
Running lambda returns `Nothing`
armanbilge Dec 4, 2022
3517cb7
Refactor `processEvents`
armanbilge Dec 4, 2022
ffa7bb3
`deadlineTimeInMs` -> `deadlineTime`
armanbilge Dec 4, 2022
f1f7a2c
Header names are ci strings
armanbilge Dec 4, 2022
f83a9de
Do not leak input in sanitized error msg
armanbilge Dec 4, 2022
c700129
Fix compile
armanbilge Dec 4, 2022
9a49c7d
Use `embedError`
armanbilge Dec 6, 2022
d53f8be
refactored to have environment variables read in the initialization p…
scott-thomson239 Dec 6, 2022
f284899
readded Nothing instead of Unit in LambdaRuntime
scott-thomson239 Dec 6, 2022
3da24a9
fixed failing tests from refactoring initialization phase
scott-thomson239 Dec 6, 2022
8cdfc9b
remove accidental encoder import
scott-thomson239 Dec 6, 2022
541704e
reverted to using F[Nothing] as output type
scott-thomson239 Dec 7, 2022
89d1c69
changed init error test env to better reflect real behaviour
scott-thomson239 Dec 7, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 39 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,16 @@ jobs:
os: [ubuntu-latest]
scala: [2.12.17, 3.1.3, 2.13.9]
java: [corretto@8, corretto@11]
project: [rootJS, rootJVM]
project: [rootJS, rootJVM, rootNative]
exclude:
- scala: 2.12.17
java: corretto@11
- scala: 3.1.3
java: corretto@11
- project: rootJS
java: corretto@11
- project: rootNative
java: corretto@11
- project: rootJS
scala: 2.12.17
runs-on: ${{ matrix.os }}
Expand Down Expand Up @@ -108,6 +110,10 @@ jobs:
if: matrix.project == 'rootJS'
run: sbt 'project ${{ matrix.project }}' '++${{ matrix.scala }}' Test/scalaJSLinkerResult

- name: nativeLink
if: matrix.project == 'rootNative'
run: sbt 'project ${{ matrix.project }}' '++${{ matrix.scala }}' Test/nativeLink

- name: Test
if: matrix.scala != '2.12.17'
run: sbt 'project ${{ matrix.project }}' '++${{ matrix.scala }}' test
Expand All @@ -126,11 +132,11 @@ jobs:

- name: Make target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
run: mkdir -p target lambda-cloudformation-custom-resource/.js/target lambda-http4s/.jvm/target unidocs/target .js/target core/.js/target examples/.js/target lambda-http4s/.js/target core/.jvm/target .jvm/target lambda/js/target .native/target examples/.jvm/target lambda/jvm/target sbt-lambda/target lambda-cloudformation-custom-resource/.jvm/target project/target
run: mkdir -p lambda-kernel/.jvm/target target lambda-cloudformation-custom-resource/.js/target lambda-http4s/.jvm/target unidocs/target .js/target core/.js/target examples/.js/target lambda-http4s/.js/target core/.jvm/target .jvm/target lambda/js/target lambda-kernel/.js/target .native/target lambda-kernel/.native/target examples/.jvm/target lambda/jvm/target feral-lambda-runtime/.jvm/target sbt-lambda/target feral-lambda-runtime/.native/target lambda-cloudformation-custom-resource/.jvm/target feral-lambda-runtime/.js/target project/target

- name: Compress target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
run: tar cf targets.tar target lambda-cloudformation-custom-resource/.js/target lambda-http4s/.jvm/target unidocs/target .js/target core/.js/target examples/.js/target lambda-http4s/.js/target core/.jvm/target .jvm/target lambda/js/target .native/target examples/.jvm/target lambda/jvm/target sbt-lambda/target lambda-cloudformation-custom-resource/.jvm/target project/target
run: tar cf targets.tar lambda-kernel/.jvm/target target lambda-cloudformation-custom-resource/.js/target lambda-http4s/.jvm/target unidocs/target .js/target core/.js/target examples/.js/target lambda-http4s/.js/target core/.jvm/target .jvm/target lambda/js/target lambda-kernel/.js/target .native/target lambda-kernel/.native/target examples/.jvm/target lambda/jvm/target feral-lambda-runtime/.jvm/target sbt-lambda/target feral-lambda-runtime/.native/target lambda-cloudformation-custom-resource/.jvm/target feral-lambda-runtime/.js/target project/target

- name: Upload target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
Expand Down Expand Up @@ -209,6 +215,16 @@ jobs:
tar xf targets.tar
rm targets.tar

- name: Download target directories (2.12.17, rootNative)
uses: actions/download-artifact@v2
with:
name: target-${{ matrix.os }}-${{ matrix.java }}-2.12.17-rootNative

- name: Inflate target directories (2.12.17, rootNative)
run: |
tar xf targets.tar
rm targets.tar

- name: Download target directories (3.1.3, rootJS)
uses: actions/download-artifact@v2
with:
Expand All @@ -229,6 +245,16 @@ jobs:
tar xf targets.tar
rm targets.tar

- name: Download target directories (3.1.3, rootNative)
uses: actions/download-artifact@v2
with:
name: target-${{ matrix.os }}-${{ matrix.java }}-3.1.3-rootNative

- name: Inflate target directories (3.1.3, rootNative)
run: |
tar xf targets.tar
rm targets.tar

- name: Download target directories (2.13.9, rootJS)
uses: actions/download-artifact@v2
with:
Expand All @@ -249,6 +275,16 @@ jobs:
tar xf targets.tar
rm targets.tar

- name: Download target directories (2.13.9, rootNative)
uses: actions/download-artifact@v2
with:
name: target-${{ matrix.os }}-${{ matrix.java }}-2.13.9-rootNative

- name: Inflate target directories (2.13.9, rootNative)
run: |
tar xf targets.tar
rm targets.tar

- name: Import signing key
if: env.PGP_SECRET != '' && env.PGP_PASSPHRASE == ''
run: echo $PGP_SECRET | base64 -di | gpg --import
Expand Down
64 changes: 49 additions & 15 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

name := "feral"

ThisBuild / tlBaseVersion := "0.1"
ThisBuild / tlBaseVersion := "0.2"
ThisBuild / startYear := Some(2021)

ThisBuild / developers := List(
Expand Down Expand Up @@ -61,9 +61,11 @@ val circeVersion = "0.14.3"
val fs2Version = "3.3.0"
val http4sVersion = "0.23.16"
val natchezVersion = "0.1.6"
val munitVersion = "0.7.29"
val munitCEVersion = "1.0.7"
val scalacheckEffectVersion = "1.0.4"
val munitVersion = "1.0.0-M6"
val munitCEVersion = "2.0.0-M3"
val scalacheckEffectVersion = "2.0.0-M2"

ThisBuild / resolvers ++= Resolver.sonatypeOssRepos("snapshots")

lazy val commonSettings = Seq(
crossScalaVersions := Seq(Scala3, Scala213)
Expand All @@ -72,6 +74,8 @@ lazy val commonSettings = Seq(
lazy val root =
tlCrossRootProject.aggregate(
core,
lambdaKernel,
lambdaRuntime,
lambda,
sbtLambda,
lambdaHttp4s,
Expand All @@ -91,25 +95,55 @@ lazy val core = crossProject(JSPlatform, JVMPlatform)
)
.settings(commonSettings)

lazy val lambda = crossProject(JSPlatform, JVMPlatform)
.in(file("lambda"))
lazy val lambdaKernel = crossProject(JSPlatform, JVMPlatform, NativePlatform)
.crossType(CrossType.Pure)
.in(file("lambda-kernel"))
.settings(
name := "feral-lambda",
name := "feral-lambda-kernel",
libraryDependencies ++= Seq(
"org.tpolecat" %%% "natchez-core" % natchezVersion,
"org.typelevel" %%% "cats-effect-kernel" % catsEffectVersion,
"com.armanbilge" %%% "natchez-core" % "0.1.6-SNAPSHOT",
"io.circe" %%% "circe-scodec" % circeVersion,
"io.circe" %%% "circe-jawn" % circeVersion,
"org.scodec" %%% "scodec-bits" % "1.1.34",
"org.scalameta" %%% "munit-scalacheck" % munitVersion % Test,
"org.typelevel" %%% "munit-cats-effect-3" % munitCEVersion % Test,
"org.typelevel" %%% "munit-cats-effect" % munitCEVersion % Test,
"io.circe" %%% "circe-literal" % circeVersion % Test
)
)
.platformsSettings(JSPlatform, NativePlatform)(
libraryDependencies ++= Seq(
"io.github.cquiroz" %%% "scala-java-time" % "2.4.0"
)
)

lazy val lambdaRuntime = crossProject(JSPlatform, JVMPlatform, NativePlatform)
.crossType(CrossType.Pure)
.in(file("feral-lambda-runtime"))
.settings(
name := "feral-lambda-kernel",
libraryDependencies ++= Seq(
"io.circe" %%% "circe-jawn" % circeVersion,
"org.http4s" %%% "http4s-client" % http4sVersion,
"org.http4s" %%% "http4s-circe" % http4sVersion
)
)
.dependsOn(lambdaKernel)

lazy val lambda = crossProject(JSPlatform, JVMPlatform)
.in(file("lambda"))
.settings(
name := "feral-lambda",
libraryDependencies ++= Seq(
"org.scalameta" %%% "munit-scalacheck" % munitVersion % Test,
"org.typelevel" %%% "munit-cats-effect" % munitCEVersion % Test,
"io.circe" %%% "circe-literal" % circeVersion % Test
)
)
.settings(commonSettings)
.jsSettings(
libraryDependencies ++= Seq(
"io.circe" %%% "circe-scalajs" % circeVersion,
"io.github.cquiroz" %%% "scala-java-time" % "2.4.0"
"io.circe" %%% "circe-scalajs" % circeVersion
)
)
.jvmSettings(
Expand All @@ -119,7 +153,7 @@ lazy val lambda = crossProject(JSPlatform, JVMPlatform)
"io.circe" %%% "circe-fs2" % "0.14.0"
)
)
.dependsOn(core)
.dependsOn(core, lambdaKernel)

lazy val sbtLambda = project
.in(file("sbt-lambda"))
Expand Down Expand Up @@ -147,7 +181,7 @@ lazy val lambdaHttp4s = crossProject(JSPlatform, JVMPlatform)
)
)
.settings(commonSettings)
.dependsOn(lambda % "compile->compile;test->test")
.dependsOn(lambdaKernel % "compile->compile;test->test")

lazy val lambdaCloudFormationCustomResource = crossProject(JSPlatform, JVMPlatform)
.crossType(CrossType.Pure)
Expand All @@ -164,15 +198,15 @@ lazy val lambdaCloudFormationCustomResource = crossProject(JSPlatform, JVMPlatfo
"org.http4s" %%% "http4s-circe" % http4sVersion,
"org.http4s" %%% "http4s-dsl" % http4sVersion % Test,
"org.scalameta" %%% "munit-scalacheck" % munitVersion % Test,
"org.typelevel" %%% "munit-cats-effect-3" % munitCEVersion % Test,
"org.typelevel" %%% "munit-cats-effect" % munitCEVersion % Test,
"org.typelevel" %%% "scalacheck-effect" % scalacheckEffectVersion % Test,
"org.typelevel" %%% "scalacheck-effect-munit" % scalacheckEffectVersion % Test,
"com.eed3si9n.expecty" %%% "expecty" % "0.16.0" % Test,
"io.circe" %%% "circe-testing" % circeVersion % Test
)
)
.settings(commonSettings)
.dependsOn(lambda)
.dependsOn(lambdaKernel)

lazy val examples = crossProject(JSPlatform, JVMPlatform)
.crossType(CrossType.Pure)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package feral.lambda.runtime

import cats.ApplicativeError
import io.circe._

import java.time.Instant
import org.http4s.Response

case class LambdaRequest(
deadlineTimeInMs: Instant,
id: String,
invokedFunctionArn: String,
body: Json
)
Copy link
Member

Choose a reason for hiding this comment

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

We should prefer class instead of case class since that is better for binary-compatibility, unfortunately. Indeed, I'm in the process of fixing this across the entire project 😕


object LambdaRequest {
// Still need to decide how to handle failed request or invalid header values
def fromResponse[F[_]](response: Response[F])(implicit F: ApplicativeError[F, Throwable]): F[LambdaRequest] = {
F.pure(LambdaRequest(???))
//Unsure on best way to unpack the response headers into LambdaRequest case class
Copy link
Member

Choose a reason for hiding this comment

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

The Right:tm: way to do this is to define our own header models for each of the AWS headers. Take your pick from examples :)

https://github.com/http4s/http4s/tree/v0.23.16/core/shared/src/main/scala/org/http4s/headers

Copy link
Author

Choose a reason for hiding this comment

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

I had a go at creating header models using a similar way how the headers defined in the headers package are defined, but they use functions that are private to the http4s package. Is it possible to create header models not using these private functions?

Copy link
Member

Choose a reason for hiding this comment

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

Which private methods specifically?

Copy link
Author

@scott-thomson239 scott-thomson239 Oct 4, 2022

Choose a reason for hiding this comment

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

The main one was the ParseResult.fromParser method which seems needed to create an instance of Header. An example of its use is in https://github.com/http4s/http4s/blob/v0.23.16/core/shared/src/main/scala/org/http4s/headers/Accept-Post.scala.

edit: maybe I didn't have a close enough look at other files, there seems to be other ways of defining a ParseResult so I'll have another go.

Copy link
Member

Choose a reason for hiding this comment

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

I think you can just inline that from here, it's pretty straightforward.
https://github.com/http4s/http4s/blob/14afe7a41ee66ef81af4861269b3f574cd9aea8c/core/shared/src/main/scala/org/http4s/MessageFailure.scala#L84-L88

I suspect the try/catch is no longer necessary, it is an artifact of old code 😂

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package feral.lambda.runtime

import cats.effect.kernel.Sync

object LambdaReservedEnvVars {
val HANDLER = "_HANDLER"
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
val HANDLER = "_HANDLER"
final val HANDLER = "_HANDLER"

val AWS_REGION = "AWS_REGION"
val AWS_EXECUTION_ENV = "AWS_EXECUTION_ENV"
val AWS_LAMBDA_FUNCTION_NAME = "AWS_LAMBDA_FUNCTION_NAME"
val AWS_LAMBDA_FUNCTION_MEMORY_SIZE = "AWS_LAMBDA_FUNCTION_MEMORY_SIZE"
val AWS_LAMBDA_FUNCTION_VERSION = "AWS_LAMBDA_FUNCTION_VERSION"
val AWS_LAMBDA_LOG_GROUP_NAME = "AWS_LAMBDA_LOG_GROUP_NAME"
val AWS_LAMBDA_LOG_STREAM_NAME = "AWS_LAMBDA_LOG_STREAM_NAME"
val AWS_ACCESS_KEY_ID = "AWS_ACCESS_KEY_ID"
val AWS_SECRET_ACCESS_KEY = "AWS_SECRET_ACCESS_KEY"
val AWS_LAMBDA_RUNTIME_API = "AWS_LAMBDA_RUNTIME_API"
val LAMBDA_TASK_ROOT = "LAMBDA_TASK_ROOT"
val LAMBDA_RUNTIME_DIR = "LAMBDA_RUNTIME_DIR"
val TZ = "TZ"
}
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we should expose this as e.g.

trait LambdaRuntimeEnv[F] {
  def lambdaRuntimeDir: F[String] = ???
  // ...
}

object LambdaRuntimeEnv {
  def apply[F[_]](env: Env[F]): LambdaRuntimeEnv[F] = ???
}

Copy link
Author

Choose a reason for hiding this comment

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

Hi, I'm not sure I 100% understand this part. Isn't the Env[F] from cats.effect.std not included in the current version of cats effect? Or do you mean that I should create a custom Env[F] for the moment until it becomes available?

Also, I'm not sure how I would implement the methods within the LambdaRuntimeEnv[F] trait. I would have assumed that the implementations of each of the methods would be given here

object LambdaRuntimeEnv {
	def apply[F[_]](env: Env[F]): LambdaRuntimeEnv[F] = new LambdaRuntimeEnv[F] {
		def lambdaRuntimeDir: F[String] = ???
		// ...
	}
}

instead of giving implementations directly within the trait.

Copy link
Member

Choose a reason for hiding this comment

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

Isn't the Env[F] from cats.effect.std not included in the current version of cats effect?

It is now available in v3.4.0-RC1, you can update to that and use it :)

Also, I'm not sure how I would implement the methods within the LambdaRuntimeEnv[F] trait.

Sorry, I typed that out too hasty :) you are right, the implementations should go in the apply method as you wrote. Also I think I meant to write apply[F[_]](implicit env: Env[F]) ...

Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* 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.
*/

// TODO implement a runtime here
// it should retrieve incoming events and handle them with the handler
// it will run on a background fiber, whose lifecycle is controlled by the resource

package feral.lambda
package runtime

import cats.Applicative
import cats.syntax.all._
import cats.effect.kernel.{Resource, Sync}
import io.circe.Json
import org.http4s.Method.POST
import org.http4s.client.Client
import org.http4s.circe.jsonEncoderWithPrinter

import scala.concurrent.duration.FiniteDuration
import java.util.concurrent.TimeUnit
import org.http4s.{EntityEncoder, Uri}
import org.http4s.client.dsl.Http4sClientDsl
import io.circe._

import java.time.Instant //safe to use in native?
Copy link
Member

Choose a reason for hiding this comment

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

import cats.effect.kernel.Async

object FeralLambdaRuntime {

val LAMBDA_VERSION_DATE = "2018-06-01"
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
val LAMBDA_VERSION_DATE = "2018-06-01"
final val ApiVersion = "2018-06-01"


def apply[F[_]](client: Client[F])(handler: (Json, Context[F]) => F[Json])(implicit F: Async[F]): Resource[F, Unit] = {
F.background {
Copy link
Member

Choose a reason for hiding this comment

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

Looking at it again, I think I made a mistake, sorry! We don't want .background here, we should just return the result of foreverM maybe 🤔 leave it up to the caller to background or not. This way we can properly raise an error (with .background any error would be swallowed).

Suggested change
def apply[F[_]](client: Client[F])(handler: (Json, Context[F]) => F[Json])(implicit F: Async[F]): Resource[F, Unit] = {
F.background {
def apply[F[_]](client: Client[F])(handler: (Json, Context[F]) => F[Json])(implicit F: Async[F]): F[Nothing] = {

val runtimeUrl = getRuntimeUrl(LambdaReservedEnvVars.AWS_LAMBDA_RUNTIME_API)
implicit val jsonEncoder: EntityEncoder[F, Json] = jsonEncoderWithPrinter[F](Printer.noSpaces.copy(dropNullValues = true))
val http4sClientDsl = new Http4sClientDsl[F] {}
import http4sClientDsl._
(for {
request <- client.get(runtimeUrl)(LambdaRequest.fromResponse) // unsure how to deal with bad response
context <- createContext(request)
result <- handler(request.body, context)
invocationUrl = getInvocationUrl(LambdaReservedEnvVars.AWS_LAMBDA_RUNTIME_API, request.id)
_ <- client.successful(POST(result, invocationUrl))
} yield ()).foreverM
}
}.as(()) // how to handle Outcome error and cancellation?
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
}.as(()) // how to handle Outcome error and cancellation?
}.void


private def createContext[F[_]](request: LambdaRequest)(implicit F: Sync[F]): F[Context[F]] = for {
functionName <- envVar(LambdaReservedEnvVars.AWS_LAMBDA_FUNCTION_NAME)
functionVersion <- envVar(LambdaReservedEnvVars.AWS_LAMBDA_FUNCTION_VERSION)
functionMemorySize <- envVar(LambdaReservedEnvVars.AWS_LAMBDA_FUNCTION_MEMORY_SIZE).map(_.toInt)
logGroupName <- envVar(LambdaReservedEnvVars.AWS_LAMBDA_LOG_GROUP_NAME)
logStreamName <- envVar(LambdaReservedEnvVars.AWS_LAMBDA_LOG_STREAM_NAME)
} yield {
new Context[F](
functionName,
functionVersion,
request.invokedFunctionArn,
functionMemorySize,
request.id,
logGroupName,
logStreamName,
None, //need
None, //need
Copy link
Member

Choose a reason for hiding this comment

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

I have some memory these can just be parsed from JSON.

F.delay(FiniteDuration(request.deadlineTimeInMs.toEpochMilli - Instant.now.toEpochMilli, TimeUnit.MILLISECONDS))
Copy link
Member

Choose a reason for hiding this comment

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

Instead of Instant.now you can use F.realTime. This is better for tests/mocking as well.

)
}

private def getRuntimeUrl(api: String) = Uri.unsafeFromString(s"http://$api/$LAMBDA_VERSION_DATE/runtime/invocation/next") //need to be unsafe?
Copy link
Member

Choose a reason for hiding this comment

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

No need to call unsafe. If we error on these fundamentals it's a fatal error and we should just kill the lambda without reporting to the error url.


private def getInvocationUrl(api: String, id: String) = Uri.unsafeFromString(s"http://$api/$LAMBDA_VERSION_DATE/runtime/invocation/$id/response")

// Called if initialization of handler function fails, seems impossible here since handler function is provided as constructor?
private def getInitErrorUrl(api: String) = Uri.unsafeFromString(s"http://$api/$LAMBDA_VERSION_DATE/runtime/init/error")

// from docs, called "if the function returns an error or the runtime encounters an error", will be used after error handling implemented
private def getInvocationErrorUrl(api: String, errorType: String) = Uri.unsafeFromString(s"http://$api/$LAMBDA_VERSION_DATE/runtime/invocation/$errorType/error")

private def envVar[F[_]](envVar: String)(implicit F: Sync[F]): F[String] = {
F.delay(sys.env(envVar))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ final class Context[F[_]] private[lambda] (
f(remainingTime))
}

object Context extends ContextCompanionPlatform
object Context

final class CognitoIdentity(
val identityId: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import io.circe.scalajs._

import scala.concurrent.duration._

private[lambda] trait ContextCompanionPlatform {
private[lambda] object ContextPlatform {

private[lambda] def fromJS[F[_]: Sync](context: facade.Context): Context[F] =
new Context(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ private[lambda] trait IOLambdaPlatform[Event, Result] {
(for {
lambda <- setupMemo
event <- IO.fromEither(decodeJs[Event](event))
result <- lambda(event, Context.fromJS(context))
result <- lambda(event, ContextPlatform.fromJS(context))
} yield result.map(_.asJsAny).orUndefined).unsafeToPromise()(runtime)
}
}
Loading