From efd0976cd90df8dd5f0ef7fa17956a46da37e1b8 Mon Sep 17 00:00:00 2001
From: Flavio Brasil
Date: Fri, 13 Sep 2024 07:58:54 -0700
Subject: [PATCH] integrations: scaladocs (#642)
---
.../shared/src/main/scala/kyo/Cache.scala | 143 ++++++++++++++++++
.../src/main/scala/kyo/Resolvers.scala | 75 +++++++++
.../scala/kyo/{direct.scala => Direct.scala} | 27 ++++
.../shared/src/main/scala/kyo/Requests.scala | 58 +++++++
.../scala/kyo/{routes.scala => Routes.scala} | 43 ++++++
kyo-zio/shared/src/main/scala/kyo/ZIOs.scala | 51 +++++++
.../kyo/{ziosTest.scala => ZIOsTest.scala} | 0
7 files changed, 397 insertions(+)
rename kyo-direct/shared/src/main/scala/kyo/{direct.scala => Direct.scala} (69%)
rename kyo-tapir/shared/src/main/scala/kyo/{routes.scala => Routes.scala} (58%)
rename kyo-zio/shared/src/test/scala/kyo/{ziosTest.scala => ZIOsTest.scala} (100%)
diff --git a/kyo-cache/shared/src/main/scala/kyo/Cache.scala b/kyo-cache/shared/src/main/scala/kyo/Cache.scala
index 30b8b0bd0..249f92985 100644
--- a/kyo-cache/shared/src/main/scala/kyo/Cache.scala
+++ b/kyo-cache/shared/src/main/scala/kyo/Cache.scala
@@ -5,7 +5,30 @@ import com.github.benmanes.caffeine
import com.github.benmanes.caffeine.cache.Caffeine
import java.util.concurrent.TimeUnit
+/** A caching utility class that provides memoization functionality via Caffeine.
+ *
+ * Each memoized function created by this cache has its own isolated keyspace. This means that different memoized functions, even if they
+ * have the same input types, will not interfere with each other's cached results. The isolation is achieved by including a reference to
+ * the cache instance itself in the key, along with the input value.
+ *
+ * @param store
+ * The underlying cache store
+ */
class Cache(private[kyo] val store: Store):
+
+ /** Memoizes a function with a single argument.
+ *
+ * @tparam A
+ * The input type
+ * @tparam B
+ * The output type (must have a Flat instance)
+ * @tparam S
+ * The effect type
+ * @param f
+ * The function to memoize
+ * @return
+ * A memoized version of the input function
+ */
def memo[A, B: Flat, S](
f: A => B < S
)(using Frame): A => B < (Async & S) =
@@ -39,6 +62,21 @@ class Cache(private[kyo] val store: Store):
}
}
+ /** Memoizes a function with two arguments.
+ *
+ * @tparam T1
+ * The first input type
+ * @tparam T2
+ * The second input type
+ * @tparam S
+ * The effect type
+ * @tparam B
+ * The output type (must have a Flat instance)
+ * @param f
+ * The function to memoize
+ * @return
+ * A memoized version of the input function
+ */
def memo2[T1, T2, S, B: Flat](
f: (T1, T2) => B < S
)(using Frame): (T1, T2) => B < (Async & S) =
@@ -46,6 +84,23 @@ class Cache(private[kyo] val store: Store):
(t1, t2) => m((t1, t2))
end memo2
+ /** Memoizes a function with three arguments.
+ *
+ * @tparam T1
+ * The first input type
+ * @tparam T2
+ * The second input type
+ * @tparam T3
+ * The third input type
+ * @tparam S
+ * The effect type
+ * @tparam B
+ * The output type (must have a Flat instance)
+ * @param f
+ * The function to memoize
+ * @return
+ * A memoized version of the input function
+ */
def memo3[T1, T2, T3, S, B: Flat](
f: (T1, T2, T3) => B < S
)(using Frame): (T1, T2, T3) => B < (Async & S) =
@@ -53,6 +108,25 @@ class Cache(private[kyo] val store: Store):
(t1, t2, t3) => m((t1, t2, t3))
end memo3
+ /** Memoizes a function with four arguments.
+ *
+ * @tparam T1
+ * The first input type
+ * @tparam T2
+ * The second input type
+ * @tparam T3
+ * The third input type
+ * @tparam T4
+ * The fourth input type
+ * @tparam S
+ * The effect type
+ * @tparam B
+ * The output type (must have a Flat instance)
+ * @param f
+ * The function to memoize
+ * @return
+ * A memoized version of the input function
+ */
def memo4[T1, T2, T3, T4, S, B: Flat](
f: (T1, T2, T3, T4) => B < S
)(using Frame): (T1, T2, T3, T4) => B < (Async & S) =
@@ -62,25 +136,94 @@ class Cache(private[kyo] val store: Store):
end Cache
object Cache:
+
+ /** The type of the underlying cache store. */
type Store = caffeine.cache.Cache[Any, Promise[Nothing, Any]]
+ /** A builder class for configuring Cache instances.
+ *
+ * @param b
+ * The underlying Caffeine builder
+ */
case class Builder(private[kyo] val b: Caffeine[Any, Any]) extends AnyVal:
+ /** Sets the maximum size of the cache.
+ *
+ * @param v
+ * The maximum number of entries the cache may contain
+ * @return
+ * An updated Builder
+ */
def maxSize(v: Int): Builder =
copy(b.maximumSize(v))
+
+ /** Specifies that cache should use weak references for keys.
+ *
+ * @return
+ * An updated Builder
+ */
def weakKeys(): Builder =
copy(b.weakKeys())
+
+ /** Specifies that cache should use weak references for values.
+ *
+ * @return
+ * An updated Builder
+ */
def weakValues(): Builder =
copy(b.weakValues())
+
+ /** Specifies that each entry should be automatically removed from the cache once a fixed duration has elapsed after the entry's
+ * creation, or the most recent replacement of its value.
+ *
+ * @param d
+ * The length of time after an entry is created that it should be automatically removed
+ * @return
+ * An updated Builder
+ */
def expireAfterAccess(d: Duration): Builder =
copy(b.expireAfterAccess(d.toMillis, TimeUnit.MILLISECONDS))
+
+ /** Specifies that each entry should be automatically removed from the cache once a fixed duration has elapsed after the entry's
+ * creation, the most recent replacement of its value, or its last access.
+ *
+ * @param d
+ * The length of time after an entry is last accessed that it should be automatically removed
+ * @return
+ * An updated Builder
+ */
def expireAfterWrite(d: Duration): Builder =
copy(b.expireAfterWrite(d.toMillis, TimeUnit.MILLISECONDS))
+
+ /** Sets the minimum total size for the internal data structures.
+ *
+ * @param v
+ * The minimum total size for the internal data structures
+ * @return
+ * An updated Builder
+ */
def initialCapacity(v: Int) =
copy(b.initialCapacity(v))
+
+ /** Specifies that active entries are eligible for automatic refresh once a fixed duration has elapsed after the entry's creation,
+ * or the most recent replacement of its value.
+ *
+ * @param d
+ * The duration after which an entry should be considered stale
+ * @return
+ * An updated Builder
+ */
def refreshAfterWrite(d: Duration) =
copy(b.refreshAfterWrite(d.toMillis, TimeUnit.MILLISECONDS))
+
end Builder
+ /** Initializes a new Cache instance with the given configuration.
+ *
+ * @param f
+ * A function that configures the Cache using a Builder
+ * @return
+ * A new Cache instance wrapped in an IO effect
+ */
def init(f: Builder => Builder)(using Frame): Cache < IO =
IO {
new Cache(
diff --git a/kyo-caliban/src/main/scala/kyo/Resolvers.scala b/kyo-caliban/src/main/scala/kyo/Resolvers.scala
index 6e5c8e697..4d7945d6c 100644
--- a/kyo-caliban/src/main/scala/kyo/Resolvers.scala
+++ b/kyo-caliban/src/main/scala/kyo/Resolvers.scala
@@ -19,6 +19,7 @@ import zio.ZEnvironment
import zio.ZIO
import zio.stream.ZStream
+/** Effect for interacting with Caliban GraphQL resolvers. */
opaque type Resolvers <: (Abort[CalibanError] & ZIOs) = Abort[CalibanError] & ZIOs
object Resolvers:
@@ -26,16 +27,49 @@ object Resolvers:
private given StreamConstructor[Nothing] =
(_: ZStream[Any, Throwable, Byte]) => throw new Throwable("Streaming is not supported")
+ /** Runs a GraphQL server with default NettyKyoServer configuration.
+ *
+ * @param v
+ * The HttpInterpreter to be used
+ * @param Frame
+ * Implicit Frame parameter
+ * @return
+ * A NettyKyoServerBinding wrapped in ZIOs and Abort effects
+ */
def run[A, S](v: HttpInterpreter[Any, CalibanError] < (Resolvers & S))(
using Frame
): NettyKyoServerBinding < (ZIOs & Abort[CalibanError] & S) =
run[A, S](NettyKyoServer())(v)
+ /** Runs a GraphQL server with a custom NettyKyoServer configuration.
+ *
+ * @param server
+ * The custom NettyKyoServer configuration
+ * @param v
+ * The HttpInterpreter to be used
+ * @param Frame
+ * Implicit Frame parameter
+ * @return
+ * A NettyKyoServerBinding wrapped in ZIOs and Abort effects
+ */
def run[A, S](server: NettyKyoServer)(v: HttpInterpreter[Any, CalibanError] < (Resolvers & S))(
using Frame
): NettyKyoServerBinding < (ZIOs & Abort[CalibanError] & S) =
ZIOs.get(ZIO.runtime[Any]).map(runtime => run(server, runtime)(v))
+ /** Runs a GraphQL server with a custom Runner.
+ *
+ * @param runner
+ * The custom Runner to be used
+ * @param v
+ * The HttpInterpreter to be used
+ * @param tag
+ * Implicit Tag for Runner[R]
+ * @param frame
+ * Implicit Frame parameter
+ * @return
+ * A NettyKyoServerBinding wrapped in ZIOs and Abort effects
+ */
def run[R, A, S](runner: Runner[R])(v: HttpInterpreter[Runner[R], CalibanError] < (Resolvers & S))(
using
tag: Tag[Runner[R]],
@@ -43,6 +77,21 @@ object Resolvers:
): NettyKyoServerBinding < (ZIOs & Abort[CalibanError] & S) =
run[R, A, S](NettyKyoServer(), runner)(v)
+ /** Runs a GraphQL server with a custom NettyKyoServer configuration and Runner.
+ *
+ * @param server
+ * The custom NettyKyoServer configuration
+ * @param runner
+ * The custom Runner to be used
+ * @param v
+ * The HttpInterpreter to be used
+ * @param tag
+ * Implicit Tag for Runner[R]
+ * @param frame
+ * Implicit Frame parameter
+ * @return
+ * A NettyKyoServerBinding wrapped in ZIOs and Abort effects
+ */
def run[R, A, S](server: NettyKyoServer, runner: Runner[R])(v: HttpInterpreter[Runner[R], CalibanError] < (Resolvers & S))(
using
tag: Tag[Runner[R]],
@@ -50,6 +99,19 @@ object Resolvers:
): NettyKyoServerBinding < (ZIOs & Abort[CalibanError] & S) =
ZIOs.get(ZIO.runtime[Any]).map(runtime => run(server, runtime.withEnvironment(ZEnvironment(runner)))(v))
+ /** Runs a GraphQL server with a custom NettyKyoServer configuration and Runtime.
+ *
+ * @param server
+ * The custom NettyKyoServer configuration
+ * @param runtime
+ * The custom Runtime to be used
+ * @param v
+ * The HttpInterpreter to be used
+ * @param Frame
+ * Implicit Frame parameter
+ * @return
+ * A NettyKyoServerBinding wrapped in ZIOs and Abort effects
+ */
def run[R, A, S](
server: NettyKyoServer,
runtime: Runtime[R]
@@ -60,6 +122,19 @@ object Resolvers:
bindings <- IO(server.addEndpoints(endpoints).start())
yield bindings
+ /** Creates an HttpInterpreter from a GraphQL API.
+ *
+ * @param api
+ * The GraphQL API to be interpreted
+ * @param requestCodec
+ * Implicit JsonCodec for GraphQLRequest
+ * @param responseValueCodec
+ * Implicit JsonCodec for ResponseValue
+ * @param Frame
+ * Implicit Frame parameter
+ * @return
+ * An HttpInterpreter wrapped in Resolvers effect
+ */
def get[R](api: GraphQL[R])(using
requestCodec: JsonCodec[GraphQLRequest],
responseValueCodec: JsonCodec[ResponseValue]
diff --git a/kyo-direct/shared/src/main/scala/kyo/direct.scala b/kyo-direct/shared/src/main/scala/kyo/Direct.scala
similarity index 69%
rename from kyo-direct/shared/src/main/scala/kyo/direct.scala
rename to kyo-direct/shared/src/main/scala/kyo/Direct.scala
index 034837bcb..7d9f56bed 100644
--- a/kyo-direct/shared/src/main/scala/kyo/direct.scala
+++ b/kyo-direct/shared/src/main/scala/kyo/Direct.scala
@@ -7,8 +7,35 @@ import directInternal.*
import scala.annotation.tailrec
import scala.quoted.*
+/** Defers the execution of a block of code, allowing the use of `await` within it.
+ *
+ * This macro transforms the given block of code to work with effectful computations, enabling the use of `await` to handle various types
+ * of effects. The `defer` block is desugared into regular monadic composition using `map`, making it equivalent to writing out the monadic
+ * code explicitly.
+ *
+ * @tparam A
+ * The type of the value returned by the deferred block
+ * @param f
+ * The block of code to be deferred
+ * @return
+ * A value of type `A < S`, where `S` represents the combined effects of all `await` calls
+ */
transparent inline def defer[A](inline f: A) = ${ impl[A]('f) }
+/** Awaits the result of an effectful computation.
+ *
+ * This function can only be used within a `defer` block. It suspends the execution of the current block until the result of the effectful
+ * computation is available. In the desugared monadic composition, `await` calls are transformed into appropriate `map` operations.
+ *
+ * @tparam A
+ * The type of the value being awaited
+ * @tparam S
+ * The type of the effect (e.g., IO, Abort, Choice, etc.)
+ * @param v
+ * The effectful computation to await
+ * @return
+ * The result of type A, once the computation completes
+ */
inline def await[A, S](v: A < S): A =
compiletime.error("`await` must be used within a `defer` block")
diff --git a/kyo-sttp/shared/src/main/scala/kyo/Requests.scala b/kyo-sttp/shared/src/main/scala/kyo/Requests.scala
index a643eb062..c2106f11a 100644
--- a/kyo-sttp/shared/src/main/scala/kyo/Requests.scala
+++ b/kyo-sttp/shared/src/main/scala/kyo/Requests.scala
@@ -2,38 +2,96 @@ package kyo
import sttp.client3.*
+/** Represents a failed HTTP request.
+ *
+ * This exception is thrown when an HTTP request fails for any reason.
+ *
+ * @param cause
+ * A string describing the reason for the failure
+ */
case class FailedRequest(cause: String) extends Exception
object Requests:
+ /** Abstract class representing a backend for sending HTTP requests */
abstract class Backend:
self =>
+ /** Sends an HTTP request
+ *
+ * @tparam A
+ * The type of the response body
+ * @param r
+ * The request to send
+ * @return
+ * The response wrapped in an effect
+ */
def send[A](r: Request[A, Any]): Response[A] < Async
+ /** Wraps the Backend with a meter
+ *
+ * @param m
+ * The meter to use
+ * @return
+ * A new Backend that uses the meter
+ */
def withMeter(m: Meter)(using Frame): Backend =
new Backend:
def send[A](r: Request[A, Any]) =
m.run(self.send(r))
end Backend
+ /** The default live backend implementation */
val live: Backend = PlatformBackend.default
private val local = Local.init[Backend](live)
+ /** Executes an effect with a custom backend
+ *
+ * @param b
+ * The backend to use
+ * @param v
+ * The effect to execute
+ * @return
+ * The result of the effect
+ */
def let[A, S](b: Backend)(v: A < S)(using Frame): A < (Async & S) =
local.let(b)(v)
+ /** Type alias for a basic request */
type BasicRequest = RequestT[Empty, Either[FailedRequest, String], Any]
+ /** A basic request with error handling */
val basicRequest: BasicRequest = sttp.client3.basicRequest.mapResponse {
case Left(value) => Left(FailedRequest(value))
case Right(value) => Right(value)
}
+ /** Sends an HTTP request using a function to modify the basic request
+ *
+ * @tparam E
+ * The error type
+ * @tparam A
+ * The success type of the response
+ * @param f
+ * A function that takes a BasicRequest and returns a Request
+ * @return
+ * The response body wrapped in an effect
+ */
def apply[E, A](f: BasicRequest => Request[Either[E, A], Any])(using Frame): A < (Async & Abort[E]) =
request(f(basicRequest))
+ /** Sends an HTTP request
+ *
+ * @tparam E
+ * The error type
+ * @tparam A
+ * The success type of the response
+ * @param req
+ * The request to send
+ * @return
+ * The response body wrapped in an effect
+ */
def request[E, A](req: Request[Either[E, A], Any])(using Frame): A < (Async & Abort[E]) =
local.use(_.send(req)).map { r =>
Abort.get(r.body)
diff --git a/kyo-tapir/shared/src/main/scala/kyo/routes.scala b/kyo-tapir/shared/src/main/scala/kyo/Routes.scala
similarity index 58%
rename from kyo-tapir/shared/src/main/scala/kyo/routes.scala
rename to kyo-tapir/shared/src/main/scala/kyo/Routes.scala
index 255eac001..367398b5c 100644
--- a/kyo-tapir/shared/src/main/scala/kyo/routes.scala
+++ b/kyo-tapir/shared/src/main/scala/kyo/Routes.scala
@@ -8,21 +8,48 @@ import sttp.tapir.server.ServerEndpoint
import sttp.tapir.server.netty.NettyKyoServer
import sttp.tapir.server.netty.NettyKyoServerBinding
+/** Represents a single route with a server endpoint. */
case class Route(endpoint: ServerEndpoint[Any, KyoSttpMonad.M]) extends AnyVal
+/** Represents an effectful collection of routes with asynchronous capabilities. */
opaque type Routes <: (Emit[Route] & Async) = Emit[Route] & Async
object Routes:
+ /** Runs the routes using the default NettyKyoServer.
+ *
+ * @param v
+ * The routes to run
+ * @return
+ * A NettyKyoServerBinding wrapped in an asynchronous effect
+ */
def run[A, S](v: Unit < (Routes & S))(using Frame): NettyKyoServerBinding < (Async & S) =
run[A, S](NettyKyoServer())(v)
+ /** Runs the routes using a specified NettyKyoServer.
+ *
+ * @param server
+ * The NettyKyoServer to use
+ * @param v
+ * The routes to run
+ * @return
+ * A NettyKyoServerBinding wrapped in an asynchronous effect
+ */
def run[A, S](server: NettyKyoServer)(v: Unit < (Routes & S))(using Frame): NettyKyoServerBinding < (Async & S) =
Emit.run[Route].apply[Unit, Async & S](v).map { (routes, _) =>
IO(server.addEndpoints(routes.toSeq.map(_.endpoint).toList).start()): NettyKyoServerBinding < (Async & S)
}
end run
+ /** Adds a new route to the collection.
+ *
+ * @param e
+ * The endpoint to add
+ * @param f
+ * The function to handle the endpoint logic
+ * @return
+ * Unit wrapped in Routes effect
+ */
def add[A: Tag, I, E: Tag: ClassTag, O: Flat](e: Endpoint[A, I, E, O, Any])(
f: I => O < (Async & Env[A] & Abort[E])
)(using Frame): Unit < Routes =
@@ -39,6 +66,15 @@ object Routes:
)
).unit
+ /** Adds a new route to the collection, starting from a PublicEndpoint.
+ *
+ * @param e
+ * A function to create an Endpoint from a PublicEndpoint
+ * @param f
+ * The function to handle the endpoint logic
+ * @return
+ * Unit wrapped in Routes effect
+ */
def add[A: Tag, I, E: Tag: ClassTag, O: Flat](
e: PublicEndpoint[Unit, Unit, Unit, Any] => Endpoint[A, I, E, O, Any]
)(
@@ -46,6 +82,13 @@ object Routes:
)(using Frame): Unit < Routes =
add(e(endpoint))(f)
+ /** Collects multiple route initializations into a single Routes effect.
+ *
+ * @param init
+ * A sequence of route initializations
+ * @return
+ * Unit wrapped in Routes effect
+ */
def collect(init: (Unit < Routes)*)(using Frame): Unit < Routes =
Kyo.collect(init).unit
diff --git a/kyo-zio/shared/src/main/scala/kyo/ZIOs.scala b/kyo-zio/shared/src/main/scala/kyo/ZIOs.scala
index 412544683..a338e850d 100644
--- a/kyo-zio/shared/src/main/scala/kyo/ZIOs.scala
+++ b/kyo-zio/shared/src/main/scala/kyo/ZIOs.scala
@@ -6,20 +6,71 @@ import scala.util.control.NonFatal
import zio.Exit
import zio.ZIO
+/** Effect to integrate ZIO with Kyo */
opaque type ZIOs <: Async = GetZIO & Async
object ZIOs:
+ /** Executes a ZIO effect and returns its result within the Kyo effect system.
+ *
+ * @param v
+ * The ZIO effect to execute
+ * @param ev
+ * Evidence that E is a subtype of Nothing
+ * @param tag
+ * Tag for E
+ * @param frame
+ * Implicit Frame
+ * @tparam E
+ * The error type (must be a subtype of Nothing)
+ * @tparam A
+ * The result type
+ * @return
+ * A Kyo effect that will produce A or abort with E
+ */
def get[E >: Nothing: Tag, A](v: ZIO[Any, E, A])(using Frame): A < (Abort[E] & ZIOs) =
val task = v.fold(Result.fail, Result.success)
ArrowEffect.suspendMap(Tag[GetZIO], task)(Abort.get(_))
+ /** Executes a ZIO effect that cannot fail and returns its result within the Kyo effect system.
+ *
+ * @param v
+ * The ZIO effect to execute
+ * @param frame
+ * Implicit Frame
+ * @tparam A
+ * The result type
+ * @return
+ * A Kyo effect that will produce A
+ */
def get[A](v: ZIO[Any, Nothing, A])(using Frame): A < ZIOs =
ArrowEffect.suspend(Tag[GetZIO], v)
+ /** Placeholder for ZIO effects with environments (currently not supported).
+ *
+ * @tparam R
+ * The environment type
+ * @tparam E
+ * The error type
+ * @tparam A
+ * The result type
+ */
inline def get[R: zio.Tag, E, A](v: ZIO[R, E, A])(using Tag[Env[R]], Frame): A < (Env[R] & ZIOs) =
compiletime.error("ZIO environments are not supported yet. Please handle them before calling this method.")
+ /** Runs a Kyo effect within the ZIO runtime.
+ *
+ * @param v
+ * The Kyo effect to run
+ * @param frame
+ * Implicit Frame
+ * @tparam E
+ * The error type
+ * @tparam A
+ * The result type
+ * @return
+ * A ZIO effect that will produce A or fail with E
+ */
def run[E, A](v: => A < (Abort[E] & ZIOs))(using frame: Frame): ZIO[Any, E, A] =
ZIO.suspendSucceed {
try
diff --git a/kyo-zio/shared/src/test/scala/kyo/ziosTest.scala b/kyo-zio/shared/src/test/scala/kyo/ZIOsTest.scala
similarity index 100%
rename from kyo-zio/shared/src/test/scala/kyo/ziosTest.scala
rename to kyo-zio/shared/src/test/scala/kyo/ZIOsTest.scala