Skip to content

Caliban integration#423

Merged
hearnadam merged 24 commits intogetkyo:mainfrom
ghostdogpr:caliban
May 30, 2024
Merged

Caliban integration#423
hearnadam merged 24 commits intogetkyo:mainfrom
ghostdogpr:caliban

Conversation

@ghostdogpr
Copy link
Copy Markdown
Contributor

@ghostdogpr ghostdogpr commented May 27, 2024

/fixes #216
/claim #216

Schema derivation

You can now use kyo effects in your resolvers and schemas are derived with 2 different givens:

  • if your kyo effect is a subtype of (Aborts[Throwable] & ZIOs), it works out of the box
  • if your kyo effect is something else, it will require using SchemaDerivation[Runner[S]] to derive the schema, and you will need to provide the Runner on run
// works out of the box
case class Query(
    k1: Int < Aborts[Throwable],
    k2: Int < ZIOs,
    k3: Int < (Aborts[Throwable] & ZIOs),
    k4: Int < IOs
) derives Schema.SemiAuto

// other kyo effects require extending `SchemaDerivation`
object schema extends SchemaDerivation[Runner[Vars[Int] & Consoles]]
case class Query(k: Int < (Vars[Int] & Consoles)) derives schema.SemiAuto

Resolvers effect

  • you can use Resolvers.run { Resolvers.get(api) } to simply turn a caliban GraphQL api into a running server
  • when a runner is required, you can use Resolvers.run(runner) { Resolvers.get(api) }
  • there are also variants that take a NettyKyoServer and a ZIO Runtime
// works out of the box
Resolvers.run { Resolvers.get(api) }

// when using a Runner
val runner = new Runner[Env]:
    def apply[T: Flat](v: T < Env): Task[T] = ZIOs.run(Consoles.run(Vars.run(0)(v)))

Resolvers.run(runner) { Resolvers.get(api) }

Note that Resolvers.get returns Caliban's HttpInterpreter which allows customizing configuration, adding middleware, changing the endpoints (e.g. path), etc

Further work

  • Documentation (planning to do this after I get some reviews first)
  • Planning to open an issue afterwards to add Streaming and WebSocket support once it's supported by the tapir integration

@ghostdogpr
Copy link
Copy Markdown
Contributor Author

I found a new approach that supports the ZIO environment and using arbitrary Kyo effects 🎉

Copy link
Copy Markdown
Collaborator

@fwbrasil fwbrasil left a comment

Choose a reason for hiding this comment

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

LGTM! Thank you for working on this @ghostdogpr, it'll be quite useful :)

object Resolvers:

private given StreamConstructor[Nothing] =
(_: ZStream[Any, Throwable, Byte]) => throw new Throwable("Streaming is not supported")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Will this work? compiletime.error("Streaming is not yet supported.")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think it'd require inline given as well

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

But we don't want a compile error here, we need to provide a StreamConstructor for NoStreams, which is the only stream implementation we can use now. Once streaming is supported by NettyKyoServer, this can be removed.

def run[R, T, S](server: NettyKyoServer, runner: Runner[R])(v: HttpInterpreter[Runner[R], CalibanError] < (Resolvers & S))(using
tag: Tag[Runner[R]]
): NettyKyoServerBinding < (ZIOs & Aborts[CalibanError] & S) =
ZIOs.get(ZIO.runtime[Any]).map(runtime => run(server, runtime.withEnvironment(ZEnvironment(runner)))(v))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Clever!

def run[R, T, S](runner: Runner[R])(v: HttpInterpreter[Runner[R], CalibanError] < (Resolvers & S))(using
tag: Tag[Runner[R]]
): NettyKyoServerBinding < (ZIOs & Aborts[CalibanError] & S) =
run[R, T, S](NettyKyoServer(), runner)(v)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Perhaps we should provide the NettyKyoServer() with a Locals?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I did the same thing as in the existing Routes effect.

import sttp.tapir.capabilities.NoStreams
import sttp.tapir.server.ServerEndpoint
import sttp.tapir.server.netty.*
import zio.*
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

How many places do you use ZIO? It might be good to only import the types you use due to probability of collisions, eg: Tags

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

runtime: Runtime[R]
): ServerEndpoint[Any, KyoSttpMonad.M] =
ServerEndpoint[Unit, Unit, I, TapirResponse, CalibanResponse[NoStreams.BinaryStream], Any, KyoSttpMonad.M](
endpoint.endpoint.asInstanceOf[Endpoint[Unit, I, TapirResponse, CalibanResponse[NoStreams.BinaryStream], Any]],
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is there a way to avoid casting?

Copy link
Copy Markdown
Contributor Author

@ghostdogpr ghostdogpr May 30, 2024

Choose a reason for hiding this comment

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

I couldn't find a way. The problem is with the last type parameter, R which should be Any. But we're using NoStreams because Caliban does require a Stream implementation and it's the only one we can use for now. The cast can also be removed once streams are supported in NettyKyoServer.

): ServerEndpoint[Any, KyoSttpMonad.M] =
ServerEndpoint[Unit, Unit, I, TapirResponse, CalibanResponse[NoStreams.BinaryStream], Any, KyoSttpMonad.M](
endpoint.endpoint.asInstanceOf[Endpoint[Unit, I, TapirResponse, CalibanResponse[NoStreams.BinaryStream], Any]],
_ => _ => Right(()),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If this will be invoked more than once, let's create a rightUnit constant.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point, done!

Comment on lines +50 to +51
"arbitrary kyo effects" in runZIO {
type Env = Vars[Int] & Envs[String]
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔥

@ghostdogpr
Copy link
Copy Markdown
Contributor Author

Documentation added! I'll create the additional issue for Streaming/WebSocket support after this is merged.

Copy link
Copy Markdown
Collaborator

@hearnadam hearnadam left a comment

Choose a reason for hiding this comment

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

Nice work!

README.md Outdated
Comment on lines +2164 to +2165
// for other effects, you need to extend `SchemaDerivation[Runner[YourEnv]]`
type Env = Vars[Int] & Envs[String]
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I would prefer not to overload the term Env - potentially confusing with Envs[A] or ZIO's R (which is often type aliased to Env....

Can we use Effects? or Pending?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Changed to CustomEffects

README.md Outdated
`Resolvers` integrates with the [Caliban](https://github.com/ghostdogpr/caliban) library to help setup GraphQL servers.

The first integration is that you can use Kyo effects inside your Caliban schemas.
- If your Kyo effects is `(Aborts[Throwable] & ZIOs)` or a subtype of it, a Caliban `Schema` can be derived automatically.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Might be worth noting that ZIOs includes Fibers/IOs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added 👍

@hearnadam hearnadam merged commit eb1c11c into getkyo:main May 30, 2024
@ghostdogpr ghostdogpr deleted the caliban branch May 30, 2024 03:27
@ghostdogpr
Copy link
Copy Markdown
Contributor Author

Created follow-up issues: #441 #442

@fwbrasil
Copy link
Copy Markdown
Collaborator

I'm happy to see this merged!!!! Thanks folks!!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Resolvers: GraphQL via Caliban

3 participants