Last active
April 6, 2021 10:51
-
-
Save frekw/61c786b83e043fd347ed8e958f2c85d6 to your computer and use it in GitHub Desktop.
Caliban remote schemas
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package gql.remoteschema | |
import zio._ | |
import zio.query._ | |
import caliban._ | |
import caliban.schema._ | |
import caliban.introspection.adt._ | |
import caliban.execution.Field | |
import gql.remoteschema.json._ | |
import caliban.interop.circe.json._ | |
import io.circe.syntax._ | |
import sttp.client3._ | |
import sttp.client3.circe._ | |
import sttp.client3.asynchttpclient.zio._ | |
object Executor { | |
def fromRemoteAPI(s: __Schema, uri: String): GraphQL[SttpClient] = { | |
new GraphQL[SttpClient] { | |
protected val additionalDirectives: List[__Directive] = List() | |
protected val schemaBuilder: caliban.schema.RootSchemaBuilder[SttpClient] = | |
toRemoteSchemaBuilder(s, uri) | |
protected val wrappers: List[caliban.wrappers.Wrapper[SttpClient]] = | |
List() | |
} | |
} | |
private def toRemoteSchemaBuilder(s: __Schema, uri: String): RootSchemaBuilder[SttpClient] = { | |
RootSchemaBuilder( | |
query = Some( | |
Operation[SttpClient]( | |
s.queryType, | |
Step.MetadataFunctionStep((args: caliban.execution.Field) => { | |
val q = GraphQLRequest( | |
query = Some(renderQuery(args)) | |
) | |
val outgoing = | |
basicRequest | |
.post(uri"$uri") | |
.body(q) | |
.response(asJson[GraphQLResponse[CalibanError]]) | |
val eff = | |
(for { | |
res <- send(outgoing) | |
body <- ZIO | |
.fromEither(res.body) | |
.catchAll(e => { | |
ZIO.succeed( | |
GraphQLResponse[CalibanError]( | |
data = ResponseValue.ObjectValue(List()), | |
errors = List( | |
CalibanError.ExecutionError("No body from upstream") | |
), | |
None | |
) | |
) | |
}) | |
} yield Step.PureStep( | |
body.data | |
)) | |
Step.QueryStep(ZQuery.fromEffect(eff)) | |
}) | |
) | |
), | |
mutation = None, | |
subscription = None | |
) | |
} | |
def renderQuery(field: Field): String = { | |
val children = | |
if (field.fields.size > 0) | |
field.fields.map(renderQuery(_)).mkString("{ ", " ", " }") | |
else "" | |
val args = | |
if (field.arguments.size > 0) | |
field.arguments | |
.map({ | |
case (k, v: Value.EnumValue) => s"$k: ${v.value}" | |
case (k, v) => s"$k: $v" | |
}) | |
.mkString("(", ", ", ") ") | |
else "" | |
val alias = field.alias.map(a => s"$a: ").getOrElse("") | |
val res = s"$alias${field.name}$args$children" | |
println(s"renderQuery: $res") | |
res | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package gql.remoteschema | |
import io.circe._, io.circe.syntax._, io.circe.generic.semiauto._ | |
import caliban.interop.circe.json._ | |
import caliban._ | |
package object json { | |
implicit val decodeCalibanError: io.circe.Decoder[CalibanError] = | |
Decoder.instance(cursor => | |
cursor | |
.downField("message") | |
.as[String] | |
.map(e => CalibanError.ExecutionError(e)) | |
) | |
implicit val decodeGraphQLResponse: io.circe.Decoder[GraphQLResponse[CalibanError]] = | |
Decoder.instance(cursor => | |
for { | |
data <- cursor | |
.downField("data") | |
.as[ResponseValue] | |
errors <- cursor | |
.downField("errors") | |
.as[Option[List[CalibanError]]] | |
} yield GraphQLResponse[CalibanError]( | |
data = data, | |
errors = errors.getOrElse(List()), | |
extensions = None | |
) | |
) | |
implicit val encodeGraphQLRequest: io.circe.Encoder[GraphQLRequest] = | |
Encoder.instance[GraphQLRequest](r => | |
Json.obj( | |
"query" -> r.query.asJson, | |
"operationName" -> r.operationName.asJson, | |
"variables" -> r.variables.asJson, | |
"extensions" -> r.extensions.asJson | |
) | |
) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package gql | |
import zio._ | |
import zio.query._ | |
import caliban.{GraphQLRequest, GraphQLResponse, CalibanError, ResponseValue} | |
import caliban.schema._ | |
import caliban.introspection.adt._ | |
import gql.remoteschema.Executor.renderQuery | |
import zio.query._ | |
import gql.remoteschema.json._ | |
import caliban.interop.circe.json._ | |
import io.circe.syntax._ | |
import sttp.client3._ | |
import sttp.client3.circe._ | |
import sttp.client3.asynchttpclient.zio._ | |
package object remoteschema { | |
case class RemoteResolver(typeMap: Map[String, __Type], apiURL: String) { | |
def remoteResolver[R, A](typeName: String)( | |
prepare: (A, caliban.execution.Field) => caliban.execution.Field, | |
beforeSend: RequestT[Identity, Either[String, String], Any] => RequestT[ | |
Identity, | |
Either[String, String], | |
Any | |
] = identity | |
) = new PartialSchema[SttpClient, R, A] { | |
def resolve(a: A, args: caliban.execution.Field): ZIO[SttpClient, CalibanError, ResponseValue] = { | |
val q = GraphQLRequest( | |
query = Some( | |
"{ " + renderQuery( | |
prepare(a, args) | |
) + " }" | |
) | |
) | |
val req = beforeSend(basicRequest.post(uri"$apiURL")) | |
.body(q) | |
.response(asJson[GraphQLResponse[CalibanError]]) | |
(for { | |
res <- send(req) | |
body <- ZIO.fromEither(res.body) | |
} yield body.data).mapError(e => CalibanError.ExecutionError(e.toString())) | |
} | |
def toType(isInput: Boolean, isSubscription: Boolean): __Type = typeMap(typeName) | |
} | |
} | |
object RemoteResolver { | |
def fromSchema(schema: __Schema, apiURL: String): RemoteResolver = { | |
val typeMap = schema.types | |
.collect({ t => | |
(t.name) match { | |
case Some(name) => name -> t | |
} | |
}) | |
.toMap | |
RemoteResolver(typeMap, apiURL) | |
} | |
} | |
trait PartialSchema[R0, R, A] { self => | |
def toType(isInput: Boolean, isSubscription: Boolean): __Type | |
def resolve(value: A, args: caliban.execution.Field): ZIO[R0, CalibanError, ResponseValue] | |
def provide[R1 <: R0](env: R1): Schema[R, A] = new Schema[R, A] { | |
def resolve(value: A): Step[R] = | |
Step.MetadataFunctionStep((args: caliban.execution.Field) => { | |
Step.QueryStep(ZQuery.fromEffect(self.resolve(value, args).map(Step.PureStep(_)).provide(env))) | |
}) | |
protected def toType(isInput: Boolean, isSubscription: Boolean): __Type = | |
self.toType(isInput, isSubscription) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment