Cats integration
The smithy4s-cats module provides cats-based integrations for Smithy4s. It contains instances of cats typeclasses for types defined in Smithy4s, schema-driven derivations of Show and Hash, and utilities to bridge generated service algebras with cats.data.EitherT.
This module is provided at the following coordinates:
- sbt
- mill
"com.disneystreaming.smithy4s" %% "smithy4s-cats" % "0.19.5-M1"
"com.disneystreaming.smithy4s::smithy4s-cats:0.19.5-M1"
Typeclass instances
Instances of cats typeclasses for primitive types used by Smithy4s (e.g. Blob, Document, ShapeId, Timestamp) are available under smithy4s.interopcats.instances. Instances for Nullable[A] (Monoid, Hash, Show) are also provided whenever the underlying A has the corresponding instance.
Deriving Show and Hash from schemas
The module exposes CachedSchemaCompilers that produce cats.Show and cats.Hash instances for any type that has a Smithy4s Schema:
smithy4s.interopcats.SchemaVisitorShowsmithy4s.interopcats.SchemaVisitorHash
The recommended way to use them is to let smithy4s place the resulting instances directly in the generated companion objects via the smithy4s.meta#typeclass trait - that page walks through the cats.Hash case end-to-end.
If you need an instance for a specific type on an ad-hoc basis, you can also invoke the visitor directly:
import cats.Show
import smithy4s.example.hello.Person
import smithy4s.interopcats.SchemaVisitorShow
implicit val personShow: Show[Person] =
SchemaVisitorShow.fromSchema(Person.schema)
Show[Person].show(Person(name = "John Doe"))
// res0: String = "Person(name = John Doe, town = None)"
MonadThrow interoperability
Smithy4s defines an internal MonadThrowLike capability that is used by some of its interpreters. Importing smithy4s.interopcats._ brings in an implicit bridge that derives a MonadThrowLike[F] from any cats.MonadThrow[F], making it easy to use effect types like cats.effect.IO wherever MonadThrowLike is required.
Monoid for endpoint middleware
A cats.Monoid instance is provided for smithy4s.Endpoint.Middleware, with combine defined as andThen and empty as the no-op middleware. This makes it convenient to compose middlewares using cats syntax.
EitherT transformations
The smithy4s.interopcats.EitherTTransformations object provides ready-made error-related transformations for cats.data.EitherT. This lets you switch between a mono-functor view of a service (errors as Throwable in F) and a bi-functor view (errors surfaced as the left side of EitherT[F, E, A]) without writing the transformation by hand.
kvstore.smithy spec used in the examples below
$version: "2"
namespace smithy4s.example
service KVStore {
operations: [
Get
Put
Delete
]
errors: [
UnauthorizedError
]
}
operation Put {
input: KeyValue
}
operation Get {
input: Key
output: Value
errors: [
KeyNotFoundError
]
}
operation Delete {
input: Key
errors: [
KeyNotFoundError
]
}
structure Key {
@required
key: String
}
structure KeyValue {
@required
key: String
@required
value: String
}
structure Value {
@required
value: String
}
@error("client")
structure UnauthorizedError {
@required
reason: String
}
@error("client")
structure KeyNotFoundError {
@required
message: String
}
Surfacing errors into EitherT
import cats.effect.IO
import cats.data.EitherT
import smithy4s.example._
import smithy4s.example.KVStore
import smithy4s.interopcats.EitherTTransformations
val kvStoreIO: KVStore[IO] = new KVStore[IO] {
def delete(key: String): IO[Unit] = IO.unit
def put(key: String, value: String): IO[Unit] = IO.unit
def get(key: String): IO[Value] =
IO.raiseError(KeyNotFoundError(s"Key $key wasn't found"))
}
// kvStoreIO: KVStore[IO] = repl.MdocSession$MdocApp2$$anon$1@4ca22268
val kvStoreEitherT: KVStore.ErrorAware[EitherT[IO, *, *]] =
kvStoreIO.transform(EitherTTransformations.surfaceError[IO])
// kvStoreEitherT: KVStore.ErrorAware[[β$0$, γ$1$]EitherT[IO, β$0$, γ$1$]] = smithy4s.example.KVStoreOperation$Transformed@35ed13e8
Absorbing errors from EitherT
val kvStoreEitherTImpl: KVStore.ErrorAware[EitherT[IO, *, *]] =
new KVStore.ErrorAware[EitherT[IO, *, *]] {
def delete(key: String): EitherT[IO, KVStore.DeleteError, Unit] =
EitherT.rightT(())
def put(key: String, value: String): EitherT[IO, KVStore.PutError, Unit] =
EitherT.rightT(())
def get(key: String): EitherT[IO, KVStore.GetError, Value] =
EitherT.leftT(
KVStore.GetError.KeyNotFoundErrorCase(
KeyNotFoundError(s"Key $key wasn't found")
)
)
}
// kvStoreEitherTImpl: KVStore.ErrorAware[[β$2$, γ$3$]EitherT[IO, β$2$, γ$3$]] = repl.MdocSession$MdocApp2$$anon$2@7e0da6e0
val kvStoreIOAgain: KVStore[IO] =
kvStoreEitherTImpl.transform(EitherTTransformations.absorbError[IO])
// kvStoreIOAgain: KVStore[IO] = smithy4s.example.KVStoreOperation$Transformed@3de65ceb
See the Smithy4s Transformations guide for more on the underlying SurfaceError / AbsorbError abstractions.