Question/Bug Around Custom `FromInput`s and `ResultMarshaller`s
Closed this issue · 4 comments
It seems to me that it should be impossible to define a custom FromInput
and ResultMarshaller
on an Argument
unless
the custom ResultMarshaller
has it's Node
type defined as Any
. This would mean that
sangria.marshalling.circe.circeFromInput
from sangria-circe can't be used for Arguments.
Looking at sangria.execution.ValueCollector#getArgumentValues
we can see that we call
sangria.execution.ValueCoercionHelper#resolveMapValue
with marshaller
defined as CoercedScalaResultMarshaller.default
.
We also pass in our custom FromInput Marshaller under firstKindMarshaller
. The problem is that valueMap
is defined as
fromInput.fromResult
which means if we have a custom marshaller we expect the type passed into fromInput.fromResult
to be our custom marshaller Node
type which causes a ClassCastException.
Example
If we define a FromInput
as
object OurMarshaller extends ResultMarshaller {
type Node = OurType
}
object OurFromInput extends FromInput[Any] {
val marshaller: ResultMarshaller = OurMarshaller
def fromResult(node: marshaller.Node): Any
???
}
}
When we call resolveMapValue
with types annotated
// sangria.execution.ValueCollector#getArgumentValue
resolveMapValue(
...
marshaller, // ResultMarshaller { type Node = Any }
fromInput.marshaller, // ResultMarshaller { type Node = OurType }
valueMap = fromInput.fromResult, // OurType => Any
...
)(
acc,
astValue.map(
coerceInputValue(
argDef.argumentType,
argPath,
_,
forAstNode,
Some(variables),
marshaller,
fromInput.marshaller,
fromScalarMiddleware = fromScalarMiddleware,
isArgument = true))
)
With types annotated
// sangria.execution.ValueCoercionHelper.resolveMapValue
def resolveMapValue(
...
marshaller: ResultMarshaller, // ResultMarshaller { type Node = Any }
firstKindMarshaller: ResultMarshaller, // ResultMarshaller { type Node = OurType }
valueMap: Nothing => Any = defaultValueMapFn, // OurType => Any
...
)(
value: Option[Either[Vector[Violation], Trinary[marshaller.Node]]] // Option[Either[Vector[Violation], Trinary[Any]]]
): marshaller.MapBuilder = {
// right here we cast OurType => Any to Any => Any
val valueMapTyped = valueMap.asInstanceOf[Any => marshaller.Node]
// when trying to use valueMapTyped, we are calling it with value, extract into v which has the type Any
marshaller.addMapNodeElem(acc, fieldName, valueMapTyped(v), ofType.isOptional)
}
This means we're effectively calling
OurFromInput.fromResult(v: Any)
when v must have the type OurType
to actually succeed. This causes a ClassCastException
To me it seems that it isn't possible to use our own custom ResultMarshaller
that doesn't take in Any
for coercing
Arguments. This would mean that sangria.marshalling.circe.circeFromInput
wouldn't work for Arguments either.
Am I going about defining a custom ResultMarshaller and using it for Arguments the correct way? Is there some way that
circeFromInput
could be used for parsing Arguments?
Are you maybe looking at federation? If yes you can have a look at https://github.com/sangria-graphql/sangria-federated
Hi @yanns I've been looking at the Argument
class and more specifically how we resolve its value at execution time ValueCoercionHelper.resolveMapValue
I'm trying to write my own custom FromInput
, but in the process of trying to figure this out, I started wondering if it's even possible to use sangria.marshalling.circe.circeFromInput
as the FromInput
for an Argument?
Maybe @xsoufiane can help here?
Hi @dylanowen hope that I am understanding you correctly, did you check this part of the doc. From what I saw, the FromInput in Circe is only for Json, and If you wanna define a FromInput for a different type, then you define another instance FromInput instance for it. First, the input is unmarshalled to the correct Node type (in our case Json), then your FromInput[T] instance takes care of deserializing T from Node.