Skip to content

Instantly share code, notes, and snippets.

@OlegIlyenko
Last active March 4, 2022 16:48
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save OlegIlyenko/5b96f4b54f656aac226d3c4bc33fd2a6 to your computer and use it in GitHub Desktop.
Save OlegIlyenko/5b96f4b54f656aac226d3c4bc33fd2a6 to your computer and use it in GitHub Desktop.
An example of custom raw JSON scalar type in sangria. DON'T USE IT! By using it you lose many benefits of GraphQL. This just demonstrates that it is possible. If you tempted to expose it, then definitely think twice before using it.
import sangria.ast
import sangria.execution.Executor
import sangria.marshalling.{InputUnmarshaller, ScalarValueInfo, ArrayMapBuilder, ResultMarshaller}
import sangria.schema._
import sangria.validation.{ValueCoercionViolation, IntCoercionViolation, BigIntCoercionViolation}
import spray.json._
import sangria.macros._
import scala.concurrent.ExecutionContext.Implicits.global
implicit object CustomSprayJsonResultMarshaller extends ResultMarshaller {
type Node = JsValue
type MapBuilder = ArrayMapBuilder[Node]
def emptyMapNode(keys: Seq[String]) = new ArrayMapBuilder[Node](keys)
def addMapNodeElem(builder: MapBuilder, key: String, value: Node, optional: Boolean) = builder.add(key, value)
def mapNode(builder: MapBuilder) = JsObject(builder.toMap)
def mapNode(keyValues: Seq[(String, JsValue)]) = JsObject(keyValues: _*)
def arrayNode(values: Vector[JsValue]) = JsArray(values.toVector)
def optionalArrayNodeValue(value: Option[JsValue]) = value match {
case Some(v) ⇒ v
case None ⇒ nullNode
}
def scalarNode(value: Any, typeName: String, info: Set[ScalarValueInfo]) = value match {
case v: String ⇒ JsString(v)
case v: Boolean ⇒ JsBoolean(v)
case v: Int ⇒ JsNumber(v)
case v: Long ⇒ JsNumber(v)
case v: Float ⇒ JsNumber(v)
case v: Double ⇒ JsNumber(v)
case v: BigInt ⇒ JsNumber(v)
case v: BigDecimal ⇒ JsNumber(v)
case v: JsValue ⇒ v
case v ⇒ throw new IllegalArgumentException("Unsupported scalar value: " + v)
}
def enumNode(value: String, typeName: String) = JsString(value)
def nullNode = JsNull
def renderCompact(node: JsValue) = node.compactPrint
def renderPretty(node: JsValue) = node.prettyPrint
}
implicit object SprayJsonInputUnmarshaller extends InputUnmarshaller[JsValue] {
def getRootMapValue(node: JsValue, key: String) = node.asInstanceOf[JsObject].fields get key
def isListNode(node: JsValue) = node.isInstanceOf[JsArray]
def getListValue(node: JsValue) = node.asInstanceOf[JsArray].elements
def isMapNode(node: JsValue) = node.isInstanceOf[JsObject]
def getMapValue(node: JsValue, key: String) = node.asInstanceOf[JsObject].fields get key
def getMapKeys(node: JsValue) = node.asInstanceOf[JsObject].fields.keys
def isDefined(node: JsValue) = node != JsNull
def getScalarValue(node: JsValue) = node match {
case JsBoolean(b) ⇒ b
case JsNumber(d) ⇒ d.toBigIntExact getOrElse d
case JsString(s) ⇒ s
case n ⇒ n
}
def getScalaScalarValue(node: JsValue) = getScalarValue(node)
def isEnumNode(node: JsValue) = node.isInstanceOf[JsString]
def isScalarNode(node: JsValue) = true
def isVariableNode(node: JsValue) = false
def getVariableName(node: JsValue) = throw new IllegalArgumentException("variables are not supported")
def render(node: JsValue) = node.compactPrint
}
case object JsonCoercionViolation extends ValueCoercionViolation("Not valid JSON")
implicit val JsonType = ScalarType[JsValue]("Json",
description = Some("Raw JSON value"),
coerceOutput = (value, _) ⇒ value,
coerceUserInput = {
case v: String ⇒ Right(JsString(v))
case v: Boolean ⇒ Right(JsBoolean(v))
case v: Int ⇒ Right(JsNumber(v))
case v: Long ⇒ Right(JsNumber(v))
case v: Float ⇒ Right(JsNumber(v))
case v: Double ⇒ Right(JsNumber(v))
case v: BigInt ⇒ Right(JsNumber(v))
case v: BigDecimal ⇒ Right(JsNumber(v))
case v: JsValue ⇒ Right(v)
},
coerceInput = {
case ast.StringValue(jsonStr, _, _) ⇒
Right(jsonStr.parseJson)
case _ ⇒
Left(JsonCoercionViolation)
})
val ProductType = ObjectType("Product", fields[Unit, Unit](
Field("name", StringType, resolve = _ ⇒ "Rusty Sword"),
Field("withArg", StringType,
arguments = Argument("jsonArg1", JsonType) :: Argument("jsonArg2", JsonType) :: Nil,
resolve = c ⇒ s"Rusty Sword ${c.arg[Any]("jsonArg1").getClass} ${c.arg[Any]("jsonArg1")} ${c.arg[Any]("jsonArg2").getClass} ${c.arg[Any]("jsonArg2")}"),
Field("attributes", JsonType, resolve = _ ⇒ JsObject(
"damage" → JsNumber(10),
"durability" → JsNumber(3),
"magical" → JsBoolean(true),
"magicElements" → JsArray(
JsObject(
"element" → JsString("fire"),
"manaDrainSpeed" → JsNumber(1.5)))))
))
val schema = Schema(ObjectType("Query", fields[Unit, Unit](
Field("products", ListType(ProductType), resolve = _ ⇒ List((), ()))
)))
val vars = """{"foo": {"a": 1, "b": [{"c": true}]}}""".parseJson
val query =
graphql"""
query ($$foo: Json!) {
products {
name
withArg(jsonArg1: "{\"aaa\": \"aaa\", \"bbb\": [{\"a\": 1, \"b\": true}]}", jsonArg2: $$foo)
attributes
}
}
"""
println(Executor.execute(schema, query, variables = vars).await.prettyPrint)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment