Skip to content

Instantly share code, notes, and snippets.

@erdeszt
Last active May 10, 2024 06:59
Show Gist options
  • Save erdeszt/cccac947ef6141a7444a00c56b14ce87 to your computer and use it in GitHub Desktop.
Save erdeszt/cccac947ef6141a7444a00c56b14ce87 to your computer and use it in GitHub Desktop.
Direct style scala effect tracking
package direct
import java.util.UUID
import scala.language.experimental.saferExceptions
import Contains.given
import izumi.reflect.macrortti.LightTypeTag
import izumi.reflect.{*, given}
import sttp.tapir.*
import sttp.tapir.server.netty.sync.{Id, NettySyncServer}
case class InvalidAge(age: Int) extends Exception(s"Invalid age: ${age}")
case class InvalidName(name: String) extends Exception(s"Invalid name: ${name}")
case class RequestId(id: UUID)
case class SessionId(id: UUID)
case class UserId(id: UUID)
def validateAge(age: Int): Unit throws InvalidAge =
if age < 20 || age > 80 then throw InvalidAge(age)
def validateName(name: String): Unit throws InvalidName =
if name.contains("hacker") then throw InvalidName(name)
def getUserId(using env: Env[SessionId]): UserId =
val sessionId = env.get[SessionId]
// or: val sessionId = Env.get[SessionId].!
UserId(UUID.fromString(sessionId.id.toString.replaceAll("4", "9")))
def logRequest(userId: UserId)(using env: Env[RequestId]): Unit =
val requestId = env.get[RequestId]
println(s"[${requestId.id}] User: `${userId}` made a request")
def businessLogic(name: String, age: Int)(using
env: Env[RequestId & SessionId]
): String throws InvalidAge | InvalidName =
validateAge(age)
validateName(name)
val user = getUserId
logRequest(user)
val reqId = env.get[RequestId]
s"Hello ${name}(${age}) with id: ${reqId}"
val helloWorld: PublicEndpoint[(String, Int), String, String, Any] =
endpoint.get
.in("hello" / path[String]("name") / path[Int]("age"))
.out(stringBody)
.errorOut(stringBody)
val hwe = helloWorld.serverLogic[Id] { (name, age) =>
try
given env: Env[RequestId & SessionId] = EnvBuilder.empty
.add(RequestId(UUID.randomUUID()))
.add(SessionId(UUID.randomUUID()))
.build
val result = businessLogic(name, age)
Right(result)
catch
case error: InvalidName =>
println(s"Error: ${error}")
Left(s"Invalid name: ${error.name}")
case error: InvalidAge =>
println(s"Error: ${error}")
Left(s"Invalid age: ${error.age}")
}
@main
def main(): Unit =
NettySyncServer().addEndpoint(hwe).port(8080).startAndWait()
trait Env[+R]:
def get[A: Tag](using Contains[R, A]): A
trait Contains[-R, A]
trait LowPrioContains:
given containsBase[A: Tag]: Contains[A, A] =
new Contains[A, A] {}
object Contains extends LowPrioContains:
given containsRec[A, R, R0](using
contains: Contains[R, A]
): Contains[R0 & R, A] =
new Contains[R0 & R, A] {}
case class EnvBuilder[+R](map: Map[LightTypeTag, Any]):
def add[A](instance: A)(using tag: Tag[A]): EnvBuilder[A & R] =
EnvBuilder(
map + (tag.tag -> instance)
)
def build: Env[R] =
new Env[R] {
override def get[A](using tag: Tag[A], _ev: Contains[R, A]): A =
map(tag.tag).asInstanceOf[A]
}
object EnvBuilder:
def empty: EnvBuilder[Any] = EnvBuilder(Map.empty)
object Env:
def get[A: Tag]: EnvGetter[A] =
new EnvGetter[A] {}
class EnvGetter[A: Tag]:
def ![R](using contains: Contains[R, A], env: Env[R]): A =
env.get[A]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment