Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/main/scala/sangria/gateway/http/GatewayServer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class GatewayServer extends Logging {
val client = new PlayHttpClient(new StandaloneAhcWSClient(new DefaultAsyncHttpClient))

val directiveProviders = Map(
"http" → new HttpDirectiveProvider(client),
"http" → new HttpDirectiveProvider,
"graphql" → new GraphQLDirectiveProvider,
"faker" → new FakerDirectiveProvider,
"basic" → new BasicDirectiveProvider)
Expand All @@ -43,7 +43,7 @@ class GatewayServer extends Logging {

schemaProvider.schemaInfo // trigger initial schema load at startup

val routing = new GraphQLRouting(config, schemaProvider)
val routing = new GraphQLRouting(config, schemaProvider, new RequestResolver(client))

Http().bindAndHandle(routing.route, config.bindHost, config.port).andThen {
case Success(_) ⇒
Expand Down
12 changes: 9 additions & 3 deletions src/main/scala/sangria/gateway/http/GraphQLRouting.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ import io.circe.optics.JsonPath._
import io.circe.parser._
import sangria.ast.Document
import sangria.execution._
import sangria.execution.deferred.DeferredResolver
import sangria.gateway.AppConfig
import sangria.gateway.schema.SchemaProvider
import sangria.gateway.schema.materializer.GatewayContext
import sangria.gateway.schema.materializer.directive.RequestResolver
import sangria.gateway.util.Logging
import sangria.parser.{QueryParser, SyntaxError}
import sangria.marshalling.circe._
Expand All @@ -28,7 +30,11 @@ import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success}
import scala.util.control.NonFatal

class GraphQLRouting[Val](config: AppConfig, schemaProvider: SchemaProvider[GatewayContext, Val])(implicit ec: ExecutionContext) extends Logging {
class GraphQLRouting[Val](
config: AppConfig,
schemaProvider: SchemaProvider[GatewayContext, Val],
deferredResolver: DeferredResolver[GatewayContext]
)(implicit ec: ExecutionContext) extends Logging {
val route: Route =
path("graphql") {
get {
Expand Down Expand Up @@ -126,7 +132,7 @@ class GraphQLRouting[Val](config: AppConfig, schemaProvider: SchemaProvider[Gate
else
withSlowLog
}

def executeGraphQL(query: Document, operationName: Option[String], variables: Json) =
complete(schemaProvider.schemaInfo.flatMap {
case Some(schemaInfo) ⇒
Expand All @@ -139,7 +145,7 @@ class GraphQLRouting[Val](config: AppConfig, schemaProvider: SchemaProvider[Gate
queryReducers = reducers.asInstanceOf[List[QueryReducer[GatewayContext, _]]],
middleware = middleware ++ schemaInfo.middleware,
exceptionHandler = exceptionHandler,
deferredResolver = schemaInfo.deferredResolver)
deferredResolver = deferredResolver)
.map(res ⇒ TRM(OK → res))
.recover {
case error: QueryAnalysisError ⇒ TRM(BadRequest → error.resolveError)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,50 +1,89 @@
package sangria.gateway.schema.materializer.directive

import io.circe.Json
import sangria.execution.deferred.{Deferred, DeferredResolver}
import sangria.gateway.http.client.HttpClient
import sangria.gateway.json.CirceJsonPath
import sangria.gateway.schema.materializer.GatewayContext
import sangria.gateway.schema.materializer.GatewayContext.{convertArgs, namedType}
import sangria.schema.ResolverBasedAstSchemaBuilder.extractValue
import sangria.schema._
import sangria.marshalling.circe._
import sangria.schema.InputObjectType.DefaultInput

import scala.concurrent.{ExecutionContext, Future}

class HttpDirectiveProvider(client: HttpClient)(implicit ec: ExecutionContext) extends DirectiveProvider {
class HttpDirectiveProvider extends DirectiveProvider {
import HttpDirectiveProvider._

def resolvers(ctx: GatewayContext) = Seq(
DirectiveResolver(Dirs.HttpGet,
complexity = Some(_ ⇒ (_, _, _) ⇒ 1000.0),
resolve = c ⇒ c.withArgs(Args.Url, Args.Headers, Args.QueryParams, Args.ForAll) { (rawUrl, rawHeaders, rawQueryParams, forAll) ⇒
val args = Some(convertArgs(c.ctx.args, c.ctx.astFields.head))
resolve = c ⇒ {
val json = c.ctx.value match {
case j: Json => j
case _ => Json.Null
}
RequestDescription(
c.ctx,
c.arg(Args.Url),
c.arg(Args.Headers),
c.arg(Args.QueryParams),
c.arg(Args.ForAll),
c.arg(Args.BatchUrl),
c.arg(Args.IdFieldInBatchResponse),
json
)
}
)
)
}

case class RequestDescription(
context: Context[GatewayContext, _],
rawUrl: String,
rawHeaders: Option[Seq[DefaultInput]],
rawQueryParams: Option[Seq[DefaultInput]],
forAll: Option[String],
batchUrl: Option[String],
idFieldInBatchResponse: Option[String],
id: Json
) extends Deferred[Json]

class RequestResolver(client: HttpClient) extends DeferredResolver[GatewayContext] {
def resolve(deferred: Vector[Deferred[Any]], gwCtx: GatewayContext, queryState: Any)(implicit ec: ExecutionContext): Vector[Future[Any]] = {
deferred.map {
case rd: RequestDescription =>
import rd._
val args = Some(convertArgs(context.args, context.astFields.head))

def extractMap(in: Option[scala.Seq[InputObjectType.DefaultInput]], elem: Json) =
rawHeaders.map(_.map(h ⇒ h("name").asInstanceOf[String] → c.ctx.ctx.fillPlaceholders(c.ctx, h("value").asInstanceOf[String], args, elem))).getOrElse(Nil)
rawHeaders.map(_.map(h ⇒ h("name").asInstanceOf[String] → gwCtx.fillPlaceholders(context, h("value").asInstanceOf[String], args, elem))).getOrElse(Nil)

def makeRequest(tpe: OutputType[_], c: Context[GatewayContext, _], args: Option[Json], elem: Json = Json.Null) = {
val url = c.ctx.fillPlaceholders(c, rawUrl, args, elem)
def makeRequest(tpe: OutputType[_], c: Context[GatewayContext, _], args: Option[Json], elem: Json = Json.Null): Future[Json] = {
val url = gwCtx.fillPlaceholders(c, rawUrl, args, elem)
val headers = extractMap(rawHeaders, elem)
val query = extractMap(rawQueryParams, elem)

client.request(HttpClient.Method.Get, url, query, headers).flatMap(GatewayContext.parseJson)
}

val fieldType: OutputType[_] = context.field.fieldType
forAll match {
case Some(elem) ⇒
CirceJsonPath.query(c.ctx.value.asInstanceOf[Json], elem) match {
case Some(jsonPath) ⇒
CirceJsonPath.query(id, jsonPath) match {
case json: Json if json.isArray ⇒
Future.sequence(json.asArray.get.map(e ⇒ makeRequest(namedType(c.ctx.field.fieldType), c.ctx, args, e))) map { v ⇒
extractValue(c.ctx.field.fieldType, Some(Json.arr(v.asInstanceOf[Seq[Json]]: _*)))
Future.sequence(json.asArray.get.map(e ⇒ makeRequest(namedType(fieldType), context, args, e))) map { v ⇒
extractValue(fieldType, Some(Json.arr(v.asInstanceOf[Seq[Json]]: _*)))
}
case e ⇒
makeRequest(c.ctx.field.fieldType, c.ctx, args, e)
makeRequest(fieldType, context, args, e)
}
case None ⇒
makeRequest(c.ctx.field.fieldType, c.ctx, args)
makeRequest(fieldType, context, args)
}
}))
}
}
}

object HttpDirectiveProvider {
Expand All @@ -61,11 +100,13 @@ object HttpDirectiveProvider {
val Headers = Argument("headers", OptionInputType(ListInputType(HeaderType)))
val QueryParams = Argument("query", OptionInputType(ListInputType(QueryParamType)))
val ForAll = Argument("forAll", OptionInputType(StringType))
val BatchUrl = Argument("batchUrl", OptionInputType(StringType))
val IdFieldInBatchResponse = Argument("idFieldInBatchResponse", OptionInputType(StringType))
}

object Dirs {
val HttpGet = Directive("httpGet",
arguments = Args.Url :: Args.Headers :: Args.QueryParams :: Args.ForAll :: Nil,
arguments = Args.Url :: Args.Headers :: Args.QueryParams :: Args.ForAll :: Args.BatchUrl :: Args.IdFieldInBatchResponse :: Nil,
locations = Set(DirectiveLocation.FieldDefinition))
}
}