I'm building a library that uses Sttp.client3
as the foundation that can be implemented with Synchronous and Asynchronous environments, I'm using zio-http for my service and sttp-client to interact with other services.
I have the following trait:
trait CoingeckoApiClient extends CoingeckoClient {
.....
override def ping: Either[CoingeckoApiError, PingResponse] =
get[PingResponse](endpoint = "ping", QueryParams())
def get[T](endpoint: String, queryParams: QueryParams)(
using Format[T]
): Either[CoingeckoApiError, T]
}
And the API
class CoingeckoApi[F[_], P](using val backend: SttpBackend[F, P]) {
def get(endpoint: String, params: QueryParams): F[Response[Either[String, String]]] = {
val apiUrl = s"${CoingeckoApi.baseUrl}/$endpoint"
basicRequest
.get(
uri"$apiUrl"
.withParams(params)
)
.send(backend)
}
}
A synchronous implementation is as follows:
class CoingeckoApiBasic(api: CoingeckoApi[Identity, Any]) extends CoingeckoApiClient {
def get[T](endpoint: String, queryParams: QueryParams)(using Format[T]): Either[CoingeckoApiError, T] =
api.get(endpoint, queryParams).body match {
case Left(json) =>
Json.parse(json).validate[CoingeckoApiError] match {
case JsSuccess(value, _) => Left(value)
case JsError(errors) =>
Left(CoingeckoApiError.internalApiError(Some("Unknown Api Error")))
}
case Right(json) =>
Json.parse(json).validate[T] match {
case JsSuccess(value, _) =>
Right(value)
case JsError(errors) =>
Left(
CoingeckoApiError
.internalApiError(Some(s"Invalid Response for $endpoint"))
)
}
}
}
So I'm looking to offer an asyncrhonous implementation with ZIO
class CoingeckoApiZIO(api: CoingeckoApi[UIO, Any]) extends CoingeckoApiClient {
def get[T](endpoint: String, queryParams: QueryParams)(using Format[T]): Either[CoingeckoApiError, T] =
Runtime.unsafeRun {
api.get(endpoint, queryParams).map(r => r.body match {
case Left(json) =>
Json.parse(json).validate[CoingeckoApiError] match {
case JsSuccess(value, _) => Left(value)
case JsError(errors) =>
Left(CoingeckoApiError.internalApiError(Some("Unknown Api Error")))
}
case Right(json) =>
Json.parse(json).validate[T] match {
case JsSuccess(value, _) =>
Right(value)
case JsError(errors) =>
Left(
CoingeckoApiError
.internalApiError(Some(s"Invalid Response for $endpoint"))
)
}
})
}
}
Does that mean, I need to provide a Runtime at this level? It seems to me that is a bit harder to offer an API that is flexible enough to be used by ZIO, Future and others, and probably I'm missing something important here.
I probably need to change the signature of class CoingeckoApi[F[_], P]
to support an environment?
I'm trying to follow in the steps of sttp that can use multiple backends, but It seems it's a bit difficult to scale or I need to rewrite my API.