Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to provide nested list of case class as an argument to the GQL query #715

Open
er-rishi opened this issue Sep 3, 2021 · 5 comments
Open

Comments

@er-rishi
Copy link

er-rishi commented Sep 3, 2021

One of the input of the GQL query is the nested list of case class.
For ex:
case class User (id: Int, name:String)

val user: InputObjectType[User] = deriveInputObjectType[User]
val arg = Argument("users", OptionInputType(ListInputType(ListInputType(user))))

when I run the application I am getting the following error:

java.lang.ClassCastException: spray.json.JsArray cannot be cast to scala.collection.Seq
	at sangria.marshalling.FromInput$SeqFromInput.fromResult(FromInput.scala:23)
	at sangria.marshalling.FromInput$SeqFromInput.fromResult(FromInput.scala:19)
	at sangria.marshalling.FromInput$SeqFromInput.$anonfun$fromResult$1(FromInput.scala:29)
	at scala.collection.TraversableLike.$anonfun$map$1(TraversableLike.scala:237)
	at scala.collection.Iterator.foreach(Iterator.scala:941)
	at scala.collection.Iterator.foreach$(Iterator.scala:941)
	at scala.collection.AbstractIterator.foreach(Iterator.scala:1429)
	at scala.collection.IterableLike.foreach(IterableLike.scala:74)
	at scala.collection.IterableLike.foreach$(IterableLike.scala:73)
	at scala.collection.AbstractIterable.foreach(Iterable.scala:56)
	at scala.collection.TraversableLike.map(TraversableLike.scala:237)
	at scala.collection.TraversableLike.map$(TraversableLike.scala:230)
	at scala.collection.AbstractTraversable.map(Traversable.scala:108)
	at sangria.marshalling.FromInput$SeqFromInput.fromResult(FromInput.scala:25)
	at sangria.marshalling.FromInput$SeqFromInput.fromResult(FromInput.scala:19)
	at sangria.execution.ValueCollector$.$anonfun$getArgumentValues$6(ValueCollector.scala:149)
	at sangria.execution.ValueCoercionHelper.resolveMapValue(ValueCoercionHelper.scala:162)
	at sangria.execution.ValueCollector$.$anonfun$getArgumentValues$4(ValueCollector.scala:156)
	at scala.collection.LinearSeqOptimized.foldLeft(LinearSeqOptimized.scala:126)
	at scala.collection.LinearSeqOptimized.foldLeft$(LinearSeqOptimized.scala:122)
	at scala.collection.immutable.List.foldLeft(List.scala:89)
	at sangria.execution.ValueCollector$.getArgumentValues(ValueCollector.scala:132)
	at sangria.execution.ValueCollector.getArgumentValues(ValueCollector.scala:103)
	at sangria.execution.ValueCollector.$anonfun$getFieldArgumentValues$1(ValueCollector.scala:87)
	at sangria.util.ConcurrentHashMapCache.getOrElseUpdate(ConcurrentHashMapCache.scala:29)
	at sangria.execution.ValueCollector.getFieldArgumentValues(ValueCollector.scala:87)
	at sangria.execution.Resolver.resolveField(Resolver.scala:1412)
	at sangria.execution.Resolver.$anonfun$collectActionsPar$1(Resolver.scala:709)
	at scala.collection.TraversableOnce.$anonfun$foldLeft$1(TraversableOnce.scala:160)
	at scala.collection.TraversableOnce.$anonfun$foldLeft$1$adapted(TraversableOnce.scala:160)
	at scala.collection.Iterator.foreach(Iterator.scala:941)
	at scala.collection.Iterator.foreach$(Iterator.scala:941)
	at scala.collection.AbstractIterator.foreach(Iterator.scala:1429)
	at scala.collection.IterableLike.foreach(IterableLike.scala:74)
	at scala.collection.IterableLike.foreach$(IterableLike.scala:73)
	at scala.collection.AbstractIterable.foreach(Iterable.scala:56)
	at scala.collection.TraversableOnce.foldLeft(TraversableOnce.scala:160)
	at scala.collection.TraversableOnce.foldLeft$(TraversableOnce.scala:158)
	at scala.collection.AbstractTraversable.foldLeft(Traversable.scala:108)
	at sangria.execution.Resolver.collectActionsPar(Resolver.scala:699)
	at sangria.execution.Resolver.resolveFieldsPar(Resolver.scala:45)
	at sangria.execution.Executor.executeOperation(Executor.scala:275)
	at sangria.execution.Executor.$anonfun$execute$7(Executor.scala:206)
	at scala.concurrent.Future.$anonfun$flatMap$1(Future.scala:307)
	at scala.concurrent.impl.Promise.$anonfun$transformWith$1(Promise.scala:41)
	at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:64)
	at akka.dispatch.BatchingExecutor$AbstractBatch.processBatch(BatchingExecutor.scala:55)
	at akka.dispatch.BatchingExecutor$BlockableBatch.$anonfun$run$1(BatchingExecutor.scala:92)
	at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)
	at scala.concurrent.BlockContext$.withBlockContext(BlockContext.scala:85)
	at akka.dispatch.BatchingExecutor$BlockableBatch.run(BatchingExecutor.scala:92)
	at akka.dispatch.TaskInvocation.run(AbstractDispatcher.scala:41)
	at akka.dispatch.ForkJoinExecutorConfigurator$AkkaForkJoinTask.exec(ForkJoinExecutorConfigurator.scala:49)
	at akka.dispatch.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
	at akka.dispatch.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
	at akka.dispatch.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
	at akka.dispatch.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)

input request:

{
  "users": [
    [
      {
        "id": "8",
        "name": "BEGINS_WITH"
      },
      {
        "id": "8",
        "name": "BEGINS_WITH"
      }
    ],
    [
      {
        "id": "8",
        "name": "BEGINS_WITH"
      }
    ]
  ]
}

Any help will be appriciated. Thanks

@liam923
Copy link

liam923 commented Apr 6, 2022

I've been running into the same issue and believe that it's a bug. Did you find a way around this?

@yanns
Copy link
Contributor

yanns commented Apr 7, 2022

If you are using spray-json, you should use https://github.com/sangria-graphql/sangria-spray-json/blob/main/src/main/scala/sangria/marshalling/sprayJson.scala somewhere. It's strange that it does not appear in the stack trace.

@liam923
Copy link

liam923 commented Apr 7, 2022

@yanns I've looked into the issue more, and I'm convinced it is a bug with sangria. I created a test case for it on my own fork, located here. I should note that although this test and the above issue use spray-json, I have been running into the same issue with circe.

The issue seems to stem from a lack of type safety within the default FromInput implementations. The cast located here in SeqFromInput is where that error is occurring. With a array of depth 1 ([SomeInputObject!]! for example), this works fine, because the node it is given is actually a Vector. However, when it recursively calls itself, which it does when the depth is greater than 1 ([[SomeInputObject!]!]! for example), node is instead a representation of an array in whatever json library is being used. For spray-json, this is JsArray, which explains the java.lang.ClassCastException: spray.json.JsArray cannot be cast to scala.collection.Seq error. For circe, this is a Json.JArray.

I have been able to circumvent this issue by creating my own implementation of SeqFromInput, as well as optionInput. Those are below:

case class SeqInput[T](delegate: FromInput[T]) extends FromInput.SeqFromInput[T](delegate) {
  override def fromResult(node: marshaller.Node): Seq[T] = {
    super.fromResult(nodeToVector(node).asInstanceOf[marshaller.Node])
  }

  private def nodeToVector(node: marshaller.Node): Vector[_] =
    node match {
      case json: Json =>
        json.asArray
          // the get or else will fail, but there's no way to recover
          .getOrElse(json.asInstanceOf[Vector[_]])
      case _ => node.asInstanceOf[Vector[_]]
    }
}

case class OptInput[T](delegate: FromInput[T]) extends FromInput[Option[T]] {
  override val marshaller: ResultMarshaller = delegate.marshaller

  override def fromResult(node: marshaller.Node): Option[T] = {
    node match {
      case opt: Option[_] => opt.map(elem => delegate.fromResult(elem.asInstanceOf[delegate.marshaller.Node]))
      case elem           => Some(delegate.fromResult(elem.asInstanceOf[delegate.marshaller.Node]))
    }
  }
}

Clearly, these are circe specific and do not work in the general case. However, they help demonstrate what the issue is and function as a stopgap solution for now. Unfortunately, I am not too familiar with the sangria source code, so I'm not sure if I'd be able to fix this myself. It seems to me that a solution might involve having FromInput be given a reference to an InputUnmarshaller and then doing something similar to above.

@yanns
Copy link
Contributor

yanns commented Apr 7, 2022

Thanks for the detailed information. Yes, it seems you found something..

How to tackle this:

If someone wants to try this, please comment.

@muuki88
Copy link

muuki88 commented Nov 30, 2022

Related to #449

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants