kolosy kolosy - 3 months ago 8
Scala Question

sangria graphql query returning 1 element list

I'm using sangria as a GraphQL server. The pertinent part of the schema is:

val Account =
ObjectType(
"Account",
"An account with a municipal unit",
fields[Unit, Account](
Field("id", StringType, Some("The account id"), resolve = _.value.id),
Field("mu", OptionType(MunicipalUnit), Some("The municipal unit this account is with"), resolve = ctx => ctx.ctx.asInstanceOf[ObjectResolver].resolve[MunicipalUnit](ctx.value.mu)),
Field("eu", OptionType(EconomicUnit), Some("The economic unit this account belongs to"), resolve = ctx => ctx.ctx.asInstanceOf[ObjectResolver].resolve[EconomicUnit](ctx.value.eu)),
Field("location", OptionType(Location), Some("The physical location associated with this account"), resolve = ctx => ctx.ctx.asInstanceOf[ObjectResolver].resolve[Location](ctx.value.location)),
Field("amountDue", BigDecimalType, Some("The amount currently due"), resolve = _.value.amountDue)
))

val Citizen =
ObjectType(
"Citizen",
"A Citizen",
interfaces[Unit, Citizen](EconomicUnit),
fields[Unit, Citizen](
Field("id", StringType, Some("The ID of the citizen"), resolve = _.value.id),
Field("name", StringType, Some("The name of the citizen"), resolve = _.value.id),
Field("delegates", OptionType(ListType(OptionType(EconomicUnit))), Some("The delegates of the citizen"), resolve = ctx => DeferDelegates(ctx.value.delegates)),
Field("locations", OptionType(ListType(OptionType(Location))), Some("The locations of the citizen"), resolve = ctx => DeferLocations(ctx.value.locations)),
Field("accounts", OptionType(ListType(OptionType(Account))), Some("The accounts of the citizen"), resolve = ctx => DeferAccounts(ctx.value.accounts))
)
)


with the deferral code being

def resolveByType[T](ids: List[Any])(implicit m: Manifest[T]) = ids map (id => resolver.resolve[T](id))

override def resolve(deferred: Vector[Deferred[Any]], ctx: Any) = deferred flatMap {
case DeferAccounts(ids) => resolveByType[Account](ids)
case DeferLocations(ids) => resolveByType[Location](ids)
case DeferDelegates(ids) => resolveByType[EconomicUnit](ids)
case DeferMUs(ids) => resolveByType[MunicipalUnit](ids)

case _ =>
List(Future.fromTry(Try(List[Any]())))
}


Everything works for individual objects, however when I try to request an object with its children, I only get one child coming back

query:

{
citizen(id: "12345") {
name
accounts {
id
amountDue
}
}
}


response:

{
"data": {
"citizen": {
"name": "12345",
"accounts": [
{
"id": "12345",
"amountDue": 12.34
}
]
}
}
}


So - it's correct, and I can see on the back end that all elements of the list are being loaded, but they don't seem to be returned.

Answer

The problem is that you are using flatMap and merging all of the elements of unrelated lists in to the one resulting list.

I think these small changes will achieve the desirable result:

def resolveByType[T](ids: List[Any])(implicit m: Manifest[T]): Future[Seq[T]] = 
  Future.sequence(ids map (id => resolver.resolve[T](id)))

override def resolve(deferred: Vector[Deferred[Any]], ctx: Any) = deferred map {
  case DeferAccounts(ids) => resolveByType[Account](ids)
  case DeferLocations(ids) => resolveByType[Location](ids)
  case DeferDelegates(ids) => resolveByType[EconomicUnit](ids)
  case DeferMUs(ids) => resolveByType[MunicipalUnit](ids)

  case _ =>
    List(Future.fromTry(Try(List[Any]())))
}

It is import to ensure, that for every Deferred value in the deferred vector there is exactly one singe Future element in the resulting list (and it should be in the same position within the list).

It's pretty low-level API optimized for performance, so there is not much type-safety in the signature of the resolve method. I just created an issue to improve an error reporting in this case.