Last active
March 4, 2022 16:48
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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