
sangria.schema.Schema.scala Maven / Gradle / Ivy
package sangria.schema
import sangria.execution.FieldTag
import sangria.marshalling.FromInput.{InputObjectResult, CoercedScalaResult}
import sangria.marshalling._
import language.{implicitConversions, existentials}
import sangria.{introspection, ast}
import sangria.validation.{ConflictingTypeDefinitionViolation, EnumValueCoercionViolation, EnumCoercionViolation, Violation}
import sangria.introspection._
import scala.annotation.implicitNotFound
import scala.reflect.ClassTag
import sangria.util.tag._
sealed trait Type
sealed trait InputType[+T] extends Type
sealed trait OutputType[+T] extends Type
sealed trait LeafType extends Type with Named
sealed trait CompositeType[T] extends Type with Named with OutputType[T]
sealed trait AbstractType extends Type with Named {
def name: String
def typeOf[Ctx](value: Any, schema: Schema[Ctx, _]): Option[ObjectType[Ctx, _]] =
schema.possibleTypes get name flatMap (_.find(_ isInstanceOf value).asInstanceOf[Option[ObjectType[Ctx, _]]])
}
sealed trait NullableType
sealed trait UnmodifiedType
sealed trait Named {
def name: String
def description: Option[String]
}
object Named {
private val NameRegexp = """^[_a-zA-Z][_a-zA-Z0-9]*$""".r
private[sangria] def doCheckNonEmptyFields(fields: Seq[Named]): Unit =
if (fields.isEmpty)
throw new IllegalArgumentException("No fields provided! You need to provide at least one field to a Type.")
private[sangria] def doCheckUniqueFields(fields: Seq[Named]): Unit =
if (fields.map(_.name).toSet.size != fields.size)
throw new IllegalArgumentException("All fields within a Type should have unique names!")
private[sangria] def doCheckFieldNames(fields: Seq[Named]): Unit =
fields.foreach(f ⇒ checkName(f.name))
def checkObjFields[T <: Seq[Named]](fields: T): T = {
doCheckUniqueFields(fields)
doCheckFieldNames(fields)
fields
}
def checkIntFields[T <: Seq[Named]](fields: T): T = {
doCheckNonEmptyFields(fields)
doCheckUniqueFields(fields)
doCheckFieldNames(fields)
fields
}
def checkObjFieldsFn[T <: Seq[Named]](fields: T): () ⇒ T = {
doCheckUniqueFields(fields)
doCheckFieldNames(fields)
() ⇒ fields
}
def checkIntFieldsFn[T <: Seq[Named]](fields: T): () ⇒ T = {
doCheckUniqueFields(fields)
doCheckNonEmptyFields(fields)
doCheckFieldNames(fields)
() ⇒ fields
}
def checkObjFields[T <: Seq[Named]](fieldsFn: () ⇒ T): () ⇒ T =
() ⇒ checkObjFields(fieldsFn())
def checkIntFields[T <: Seq[Named]](fieldsFn: () ⇒ T): () ⇒ T =
() ⇒ checkIntFields(fieldsFn())
def checkName(name: String) = {
if (!NameRegexp.pattern.matcher(name).matches())
throw new IllegalArgumentException(s"Name '$name' is not valid GraphQL name! Valid name should satisfy following regex: /$NameRegexp/.")
name
}
}
/**
* Defines a GraphQL scalar value type.
*
* `coerceOutput` is allowed to return following scala values:
*
* - String
* - Boolean
* - Int
* - Long
* - Float
* - Double
* - scala.BigInt
* - scala.BigDecimal
* - sangria.ast.Value (it would be converted to raw scala value before given to a marshalling API)
*
* It may also return other values as well as long as underlying marshalling library supports them.
*
* You can provide additional meta-information to marshalling API with `scalarInfo`.
*/
case class ScalarType[T](
name: String,
description: Option[String] = None,
coerceUserInput: Any ⇒ Either[Violation, T],
coerceOutput: (T, Set[MarshallerCapability]) ⇒ Any,
coerceInput: ast.Value ⇒ Either[Violation, T],
complexity: Double = 0.0D,
scalarInfo: Set[ScalarValueInfo] = Set.empty) extends InputType[T @@ CoercedScalaResult] with OutputType[T] with LeafType with NullableType with UnmodifiedType with Named
sealed trait ObjectLikeType[Ctx, Val] extends OutputType[Val] with CompositeType[Val] with NullableType with UnmodifiedType with Named {
def interfaces: List[InterfaceType[Ctx, _]]
def fieldsFn: () ⇒ List[Field[Ctx, Val]]
lazy val ownFields = fieldsFn().toVector
def removeDuplicates[T, E](list: Vector[T], valueFn: T ⇒ E) =
list.foldLeft((Vector.empty, Vector.empty): (Vector[E], Vector[T])) {
case (a @ (visited, acc), e) if visited contains valueFn(e) ⇒ a
case ((visited, acc), e) ⇒ (visited :+ valueFn(e), acc :+ e)
}._2
lazy val allInterfaces: Vector[InterfaceType[Ctx, _]] =
removeDuplicates(interfaces.toVector.flatMap(i ⇒ i +: i.allInterfaces), (i: InterfaceType[Ctx, _]) ⇒ i.name)
lazy val fields: Vector[Field[Ctx, _]] = ownFields ++ interfaces.flatMap(i ⇒ i.fields.asInstanceOf[Vector[Field[Ctx, _]]])
lazy val uniqueFields: Vector[Field[Ctx, _]] = removeDuplicates(fields, (e: Field[Ctx, _]) ⇒ e.name)
private lazy val fieldsByName = fields groupBy (_.name)
def getField(schema: Schema[_, _], fieldName: String): Vector[Field[Ctx, _]] =
if (sangria.introspection.MetaFieldNames contains fieldName)
if (fieldName == SchemaMetaField.name && name == schema.query.name) Vector(SchemaMetaField.asInstanceOf[Field[Ctx, _]])
else if (fieldName == TypeMetaField.name && name == schema.query.name) Vector(TypeMetaField.asInstanceOf[Field[Ctx, _]])
else if (fieldName == TypeNameMetaField.name) Vector(TypeNameMetaField.asInstanceOf[Field[Ctx, _]])
else Vector.empty
else fieldsByName.getOrElse(fieldName, Vector.empty)
}
case class ObjectType[Ctx, Val: ClassTag] (
name: String,
description: Option[String],
fieldsFn: () ⇒ List[Field[Ctx, Val]],
interfaces: List[InterfaceType[Ctx, _]]
) extends ObjectLikeType[Ctx, Val] {
protected lazy val valClass = implicitly[ClassTag[Val]].runtimeClass
def isInstanceOf(value: Any) = valClass.isAssignableFrom(value.getClass)
}
object ObjectType {
def apply[Ctx, Val: ClassTag](name: String, fields: List[Field[Ctx, Val]]): ObjectType[Ctx, Val] =
ObjectType(Named.checkName(name), None, fieldsFn = Named.checkObjFieldsFn(fields), Nil)
def apply[Ctx, Val: ClassTag](name: String, description: String, fields: List[Field[Ctx, Val]]): ObjectType[Ctx, Val] =
ObjectType(Named.checkName(name), Some(description), fieldsFn = Named.checkObjFieldsFn(fields), Nil)
def apply[Ctx, Val: ClassTag](name: String, interfaces: List[PossibleInterface[Ctx, Val]], fields: List[Field[Ctx, Val]]): ObjectType[Ctx, Val] =
ObjectType(Named.checkName(name), None, fieldsFn = Named.checkObjFieldsFn(fields), interfaces map (_.interfaceType))
def apply[Ctx, Val: ClassTag](name: String, description: String, interfaces: List[PossibleInterface[Ctx, Val]], fields: List[Field[Ctx, Val]]): ObjectType[Ctx, Val] =
ObjectType(Named.checkName(name), Some(description), fieldsFn = Named.checkObjFieldsFn(fields), interfaces map (_.interfaceType))
def apply[Ctx, Val: ClassTag](name: String, fieldsFn: () ⇒ List[Field[Ctx, Val]]): ObjectType[Ctx, Val] =
ObjectType(Named.checkName(name), None, Named.checkObjFields(fieldsFn), Nil)
def apply[Ctx, Val: ClassTag](name: String, description: String, fieldsFn: () ⇒ List[Field[Ctx, Val]]): ObjectType[Ctx, Val] =
ObjectType(Named.checkName(name), Some(description), Named.checkObjFields(fieldsFn), Nil)
def apply[Ctx, Val: ClassTag](name: String, interfaces: List[PossibleInterface[Ctx, Val]], fieldsFn: () ⇒ List[Field[Ctx, Val]]): ObjectType[Ctx, Val] =
ObjectType(Named.checkName(name), None, Named.checkObjFields(fieldsFn), interfaces map (_.interfaceType))
def apply[Ctx, Val: ClassTag](name: String, description: String, interfaces: List[PossibleInterface[Ctx, Val]], fieldsFn: () ⇒ List[Field[Ctx, Val]]): ObjectType[Ctx, Val] =
ObjectType(Named.checkName(name), Some(description), Named.checkObjFields(fieldsFn), interfaces map (_.interfaceType))
def createFromMacro[Ctx, Val: ClassTag](name: String, description: Option[String], interfaces: List[InterfaceType[Ctx, _]], fieldsFn: () ⇒ List[Field[Ctx, Val]]) =
ObjectType(Named.checkName(name), description, Named.checkObjFields(fieldsFn), interfaces)
implicit def acceptUnitCtx[Ctx, Val](objectType: ObjectType[Unit, Val]): ObjectType[Ctx, Val] =
objectType.asInstanceOf[ObjectType[Ctx, Val]]
}
case class InterfaceType[Ctx, Val](
name: String,
description: Option[String] = None,
fieldsFn: () ⇒ List[Field[Ctx, Val]],
interfaces: List[InterfaceType[Ctx, _]],
manualPossibleTypes: () ⇒ List[ObjectType[_, _]]
) extends ObjectLikeType[Ctx, Val] with AbstractType {
def withPossibleTypes(possible: PossibleObject[Ctx, Val]*) = copy(manualPossibleTypes = () ⇒ possible.toList map (_.objectType))
def withPossibleTypes(possible: () ⇒ List[PossibleObject[Ctx, Val]]) = copy(manualPossibleTypes = () ⇒ possible() map (_.objectType))
}
object InterfaceType {
val emptyPossibleTypes: () ⇒ List[ObjectType[_, _]] = () ⇒ Nil
def apply[Ctx, Val](name: String, fields: List[Field[Ctx, Val]]): InterfaceType[Ctx, Val] =
InterfaceType(Named.checkName(name), None, fieldsFn = Named.checkIntFieldsFn(fields), Nil, emptyPossibleTypes)
def apply[Ctx, Val](name: String, description: String, fields: List[Field[Ctx, Val]]): InterfaceType[Ctx, Val] =
InterfaceType(Named.checkName(name), Some(description), fieldsFn = Named.checkIntFieldsFn(fields), Nil, emptyPossibleTypes)
def apply[Ctx, Val](name: String, fields: List[Field[Ctx, Val]], interfaces: List[PossibleInterface[Ctx, Val]]): InterfaceType[Ctx, Val] =
InterfaceType(Named.checkName(name), None, fieldsFn = Named.checkIntFieldsFn(fields), interfaces map (_.interfaceType), emptyPossibleTypes)
def apply[Ctx, Val](name: String, description: String, fields: List[Field[Ctx, Val]], interfaces: List[PossibleInterface[Ctx, Val]]): InterfaceType[Ctx, Val] =
InterfaceType(Named.checkName(name), Some(description), fieldsFn = Named.checkIntFieldsFn(fields), interfaces map (_.interfaceType), emptyPossibleTypes)
def apply[Ctx, Val](name: String, fieldsFn: () ⇒ List[Field[Ctx, Val]]): InterfaceType[Ctx, Val] =
InterfaceType(Named.checkName(name), None, Named.checkIntFields(fieldsFn), Nil, emptyPossibleTypes)
def apply[Ctx, Val](name: String, description: String, fieldsFn: () ⇒ List[Field[Ctx, Val]]): InterfaceType[Ctx, Val] =
InterfaceType(Named.checkName(name), Some(description), Named.checkIntFields(fieldsFn), Nil, emptyPossibleTypes)
def apply[Ctx, Val](name: String, fieldsFn: () ⇒ List[Field[Ctx, Val]], interfaces: List[PossibleInterface[Ctx, Val]]): InterfaceType[Ctx, Val] =
InterfaceType(Named.checkName(name), None, Named.checkIntFields(fieldsFn), interfaces map (_.interfaceType), emptyPossibleTypes)
def apply[Ctx, Val](name: String, description: String, fieldsFn: () ⇒ List[Field[Ctx, Val]], interfaces: List[PossibleInterface[Ctx, Val]]): InterfaceType[Ctx, Val] =
InterfaceType(Named.checkName(name), Some(description), Named.checkIntFields(fieldsFn), interfaces map (_.interfaceType), emptyPossibleTypes)
}
case class PossibleInterface[Ctx, Concrete](interfaceType: InterfaceType[Ctx, _])
object PossibleInterface extends PossibleInterfaceLowPrioImplicits {
implicit def apply[Ctx, Abstract, Concrete](interface: InterfaceType[Ctx, Abstract])(implicit ev: PossibleType[Abstract, Concrete]): PossibleInterface[Ctx, Concrete] =
PossibleInterface[Ctx, Concrete](interface)
}
trait PossibleInterfaceLowPrioImplicits {
implicit def applyUnit[Ctx, Abstract, Concrete](interface: InterfaceType[Ctx, Abstract])(implicit ev: PossibleType[Abstract, Concrete]): PossibleInterface[Unit, Concrete] =
PossibleInterface[Unit, Concrete](interface.asInstanceOf[InterfaceType[Unit, Abstract]])
}
case class PossibleObject[Ctx, Abstract](objectType: ObjectType[Ctx, _])
object PossibleObject {
implicit def apply[Ctx, Abstract, Concrete](obj: ObjectType[Ctx, Concrete])(implicit ev: PossibleType[Abstract, Concrete]): PossibleObject[Ctx, Abstract] =
PossibleObject[Ctx, Abstract](obj)
implicit def applyUnit[Ctx, Abstract, Concrete](obj: ObjectType[Unit, Concrete])(implicit ev: PossibleType[Abstract, Concrete]): PossibleObject[Ctx, Abstract] =
PossibleObject[Ctx, Abstract](obj.asInstanceOf[ObjectType[Ctx, Concrete]])
}
trait PossibleType[AbstrType, ConcreteType]
object PossibleType {
private object SingletonPossibleType extends PossibleType[AnyRef, AnyRef]
def create[AbstrType, ConcreteType] = SingletonPossibleType.asInstanceOf[PossibleType[AbstrType, ConcreteType]]
implicit def InheritanceBasedPossibleType[Abstract, Concrete](implicit ev: Concrete <:< Abstract): PossibleType[Abstract, Concrete] =
create[Abstract, Concrete]
}
case class UnionType[Ctx](
name: String,
description: Option[String] = None,
types: List[ObjectType[Ctx, _]]) extends OutputType[Any] with CompositeType[Any] with AbstractType with NullableType with UnmodifiedType
case class Field[Ctx, Val](
name: String,
fieldType: OutputType[_],
description: Option[String],
arguments: List[Argument[_]],
resolve: Context[Ctx, Val] ⇒ Action[Ctx, _],
deprecationReason: Option[String],
tags: List[FieldTag],
complexity: Option[(Ctx, Args, Double) ⇒ Double],
manualPossibleTypes: () ⇒ List[ObjectType[_, _]]) extends Named with HasArguments {
def withPossibleTypes(possible: PossibleObject[Ctx, Val]*) = copy(manualPossibleTypes = () ⇒ possible.toList map (_.objectType))
def withPossibleTypes(possible: () ⇒ List[PossibleObject[Ctx, Val]]) = copy(manualPossibleTypes = () ⇒ possible() map (_.objectType))
}
object Field {
def apply[Ctx, Val, Res, Out](
name: String,
fieldType: OutputType[Out],
description: Option[String] = None,
arguments: List[Argument[_]] = Nil,
resolve: Context[Ctx, Val] ⇒ Action[Ctx, Res],
possibleTypes: ⇒ List[PossibleObject[_, _]] = Nil,
tags: List[FieldTag] = Nil,
complexity: Option[(Ctx, Args, Double) ⇒ Double] = None,
deprecationReason: Option[String] = None)(implicit ev: ValidOutType[Res, Out]) =
Field[Ctx, Val](Named.checkName(name), fieldType, description, arguments, resolve, deprecationReason, tags, complexity, () ⇒ possibleTypes map (_.objectType))
}
@implicitNotFound(msg = "${Res} is invalid type for the resulting GraphQL type ${Out}.")
trait ValidOutType[-Res, +Out]
object ValidOutType extends LowPrioValidOutType {
implicit def validSubclass[Res, Out](implicit ev: Res <:< Out) = valid.asInstanceOf[ValidOutType[Res, Out]]
implicit def validNothing[Out] = valid.asInstanceOf[ValidOutType[Nothing, Out]]
implicit def validOption[Res, Out](implicit ev: Res <:< Out) = valid.asInstanceOf[ValidOutType[Res, Option[Out]]]
}
trait LowPrioValidOutType {
val valid = new ValidOutType[Any, Any] {}
implicit def validSeq[Res, Out](implicit ev: Res <:< Out) = valid.asInstanceOf[ValidOutType[Res, Seq[Out]]]
}
trait InputValue[T] {
def name: String
def inputValueType: InputType[_]
def description: Option[String]
def defaultValue: Option[(_, ToInput[_, _])]
}
case class Argument[T](
name: String,
argumentType: InputType[_],
description: Option[String],
defaultValue: Option[(_, ToInput[_, _])],
fromInput: FromInput[_]) extends InputValue[T] with Named {
if (!argumentType.isInstanceOf[OptionInputType[_]] && defaultValue.isDefined)
throw new IllegalArgumentException(s"Argument '$name' is has NotNull type and defines a default value, which is not allowed! You need to either make this argument nullable or remove the default value.")
def inputValueType = argumentType
}
object Argument {
def apply[T, Default](
name: String,
argumentType: InputType[T],
description: String,
defaultValue: Default)(implicit toInput: ToInput[Default, _], fromInput: FromInput[T], res: ArgumentType[T]): Argument[res.Res] =
Argument(Named.checkName(name), argumentType, Some(description), Some(defaultValue → toInput), fromInput)
def apply[T, Default](
name: String,
argumentType: InputType[T],
defaultValue: Default)(implicit toInput: ToInput[Default, _], fromInput: FromInput[T], res: ArgumentType[T]): Argument[res.Res] =
Argument(Named.checkName(name), argumentType, None, Some(defaultValue → toInput), fromInput)
def apply[T](
name: String,
argumentType: InputType[T],
description: String)(implicit fromInput: FromInput[T], res: WithoutInputTypeTags[T]): Argument[res.Res] =
Argument(Named.checkName(name), argumentType, Some(description), None, fromInput)
def apply[T](
name: String,
argumentType: InputType[T])(implicit fromInput: FromInput[T], res: WithoutInputTypeTags[T]): Argument[res.Res] =
Argument(Named.checkName(name), argumentType, None, None, fromInput)
def createWithoutDefault[T](
name: String,
argumentType: InputType[T],
description: Option[String])(implicit fromInput: FromInput[T], res: ArgumentType[T]): Argument[res.Res] =
Argument(Named.checkName(name), argumentType, description, None, fromInput)
def createWithDefault[T, Default](
name: String,
argumentType: InputType[T],
description: Option[String],
defaultValue: Default)(implicit toInput: ToInput[Default, _], fromInput: FromInput[T], res: ArgumentType[T]): Argument[res.Res] =
Argument(Named.checkName(name), argumentType, description, Some(defaultValue → toInput), fromInput)
}
trait WithoutInputTypeTags[T] {
type Res
}
object WithoutInputTypeTags extends WithoutInputTypeTagsLowPrio {
implicit def coercedArgTpe[T] = new WithoutInputTypeTags[T @@ CoercedScalaResult] {
type Res = T
}
implicit def coercedOptArgTpe[T] = new WithoutInputTypeTags[Option[T @@ CoercedScalaResult]] {
type Res = Option[T]
}
implicit def coercedSeqOptArgTpe[T] = new WithoutInputTypeTags[Seq[Option[T @@ CoercedScalaResult]]] {
type Res = Seq[Option[T]]
}
implicit def coercedOptSeqArgTpe[T] = new WithoutInputTypeTags[Option[Seq[T @@ CoercedScalaResult]]] {
type Res = Option[Seq[T]]
}
implicit def coercedOptSeqOptArgTpe[T] = new WithoutInputTypeTags[Option[Seq[Option[T @@ CoercedScalaResult]]]] {
type Res = Option[Seq[Option[T]]]
}
implicit def ioArgTpe[T] = new WithoutInputTypeTags[T @@ InputObjectResult] {
type Res = T
}
implicit def ioOptArgTpe[T] = new WithoutInputTypeTags[Option[T @@ InputObjectResult]] {
type Res = Option[T]
}
implicit def ioSeqOptArgTpe[T] = new WithoutInputTypeTags[Seq[Option[T @@ InputObjectResult]]] {
type Res = Seq[Option[T]]
}
implicit def ioOptSeqArgTpe[T] = new WithoutInputTypeTags[Option[Seq[T @@ InputObjectResult]]] {
type Res = Option[Seq[T]]
}
implicit def ioOptSeqOptArgTpe[T] = new WithoutInputTypeTags[Option[Seq[Option[T @@ InputObjectResult]]]] {
type Res = Option[Seq[Option[T]]]
}
}
trait WithoutInputTypeTagsLowPrio {
implicit def defaultArgTpe[T] = new WithoutInputTypeTags[T] {
type Res = T
}
}
trait ArgumentType[T] {
type Res
}
object ArgumentType extends ArgumentTypeLowPrio {
implicit def coercedArgTpe[T] = new ArgumentType[T @@ CoercedScalaResult] {
type Res = T
}
implicit def coercedOptArgTpe[T] = new ArgumentType[Option[T @@ CoercedScalaResult]] {
type Res = T
}
implicit def coercedSeqOptArgTpe[T] = new ArgumentType[Seq[Option[T @@ CoercedScalaResult]]] {
type Res = Seq[Option[T]]
}
implicit def coercedOptSeqArgTpe[T] = new ArgumentType[Option[Seq[T @@ CoercedScalaResult]]] {
type Res = Seq[T]
}
implicit def coercedOptSeqOptArgTpe[T] = new ArgumentType[Option[Seq[Option[T @@ CoercedScalaResult]]]] {
type Res = Seq[Option[T]]
}
implicit def ioArgTpe[T] = new ArgumentType[T @@ InputObjectResult] {
type Res = T
}
implicit def ioOptArgTpe[T] = new ArgumentType[Option[T @@ InputObjectResult]] {
type Res = T
}
implicit def ioSeqOptArgTpe[T] = new ArgumentType[Seq[Option[T @@ InputObjectResult]]] {
type Res = Seq[Option[T]]
}
implicit def ioOptSeqArgTpe[T] = new ArgumentType[Option[Seq[T @@ InputObjectResult]]] {
type Res = Seq[T]
}
implicit def ioOptSeqOptArgTpe[T] = new ArgumentType[Option[Seq[Option[T @@ InputObjectResult]]]] {
type Res = Seq[Option[T]]
}
}
trait ArgumentTypeLowPrio extends ArgumentTypeLowestPrio {
implicit def optionArgTpe[T] = new ArgumentType[Option[T]] {
type Res = T
}
}
trait ArgumentTypeLowestPrio {
implicit def defaultArgTpe[T] = new ArgumentType[T] {
type Res = T
}
}
case class EnumType[T](
name: String,
description: Option[String] = None,
values: List[EnumValue[T]]) extends InputType[T @@ CoercedScalaResult] with OutputType[T] with LeafType with NullableType with UnmodifiedType with Named {
lazy val byName = values groupBy (_.name) mapValues (_.head)
lazy val byValue = values groupBy (_.value) mapValues (_.head)
def coerceUserInput(value: Any): Either[Violation, (T, Boolean)] = value match {
case name: String ⇒ byName get name map (v ⇒ Right(v.value → v.deprecationReason.isDefined)) getOrElse Left(EnumValueCoercionViolation(name))
case v if byValue exists (_._1 == v) ⇒ Right(v.asInstanceOf[T] → byValue(v.asInstanceOf[T]).deprecationReason.isDefined)
case _ ⇒ Left(EnumCoercionViolation)
}
def coerceInput(value: ast.Value): Either[Violation, (T, Boolean)] = value match {
case ast.EnumValue(name, _, _) ⇒ byName get name map (v ⇒ Right(v.value → v.deprecationReason.isDefined)) getOrElse Left(EnumValueCoercionViolation(name))
case _ ⇒ Left(EnumCoercionViolation)
}
def coerceOutput(value: T): String = byValue(value).name
}
case class EnumValue[+T](
name: String,
description: Option[String] = None,
value: T,
deprecationReason: Option[String] = None) extends Named
case class InputObjectType[T](
name: String,
description: Option[String] = None,
fieldsFn: () ⇒ List[InputField[_]]
) extends InputType[T @@ InputObjectResult] with NullableType with UnmodifiedType with Named {
lazy val fields = fieldsFn()
lazy val fieldsByName = fields groupBy(_.name) mapValues(_.head)
}
object InputObjectType {
type DefaultInput = Map[String, Any]
def apply[T](name: String, fields: List[InputField[_]])(implicit res: InputObjectDefaultResult[T]): InputObjectType[res.Res] =
InputObjectType(Named.checkName(name), None, fieldsFn = Named.checkIntFieldsFn(fields))
def apply[T](name: String, description: String, fields: List[InputField[_]])(implicit res: InputObjectDefaultResult[T]): InputObjectType[res.Res] =
InputObjectType(Named.checkName(name), Some(description), fieldsFn = Named.checkIntFieldsFn(fields))
def apply[T](name: String, fieldsFn: () ⇒ List[InputField[_]])(implicit res: InputObjectDefaultResult[T]): InputObjectType[res.Res] =
InputObjectType(Named.checkName(name), None, Named.checkIntFields(fieldsFn))
def apply[T](name: String, description: String, fieldsFn: () ⇒ List[InputField[_]])(implicit res: InputObjectDefaultResult[T]): InputObjectType[res.Res] =
InputObjectType(Named.checkName(name), Some(description), Named.checkIntFields(fieldsFn))
def createFromMacro[T](name: String, description: Option[String] = None, fieldsFn: () ⇒ List[InputField[_]]) =
InputObjectType[T](Named.checkName(name), description, Named.checkIntFields(fieldsFn))
}
trait InputObjectDefaultResult[T] {
type Res
}
object InputObjectDefaultResult extends InputObjectDefaultResultLowPrio {
implicit def nothingResult = new InputObjectDefaultResult[Nothing] {
override type Res = InputObjectType.DefaultInput
}
}
trait InputObjectDefaultResultLowPrio {
implicit def defaultResult[T] = new InputObjectDefaultResult[T] {
override type Res = T
}
}
case class InputField[T](
name: String,
fieldType: InputType[T],
description: Option[String],
defaultValue: Option[(_, ToInput[_, _])]
) extends InputValue[T] with Named {
if (!fieldType.isInstanceOf[OptionInputType[_]] && defaultValue.isDefined)
throw new IllegalArgumentException(s"Input field '$name' is has NotNull type and defines a default value, which is not allowed! You need to either make this fields nullable or remove the default value.")
def inputValueType = fieldType
}
object InputField {
def apply[T, Default](name: String, fieldType: InputType[T], description: String, defaultValue: Default)(implicit toInput: ToInput[Default, _], res: WithoutInputTypeTags[T]): InputField[res.Res] =
InputField(name, fieldType, Some(description), Some(defaultValue → toInput)).asInstanceOf[InputField[res.Res]]
def apply[T, Default](name: String, fieldType: InputType[T], defaultValue: Default)(implicit toInput: ToInput[Default, _], res: WithoutInputTypeTags[T]): InputField[res.Res] =
InputField(name, fieldType, None, Some(defaultValue → toInput)).asInstanceOf[InputField[res.Res]]
def apply[T](name: String, fieldType: InputType[T], description: String)(implicit res: WithoutInputTypeTags[T]): InputField[res.Res] =
InputField(name, fieldType, Some(description), None).asInstanceOf[InputField[res.Res]]
def apply[T](name: String, fieldType: InputType[T])(implicit res: WithoutInputTypeTags[T]): InputField[res.Res] =
InputField(name, fieldType, None, None).asInstanceOf[InputField[res.Res]]
def createFromMacroWithDefault[T, Default](
name: String, fieldType: InputType[T], description: Option[String], defaultValue: Default
)(implicit toInput: ToInput[Default, _], res: WithoutInputTypeTags[T]): InputField[res.Res] =
InputField(name, fieldType, description, Some(defaultValue → toInput)).asInstanceOf[InputField[res.Res]]
def createFromMacroWithoutDefault[T](name: String, fieldType: InputType[T], description: Option[String])(implicit res: WithoutInputTypeTags[T]): InputField[res.Res] =
InputField(name, fieldType, description, None).asInstanceOf[InputField[res.Res]]
}
case class ListType[T](ofType: OutputType[T]) extends OutputType[Seq[T]] with NullableType
case class ListInputType[T](ofType: InputType[T]) extends InputType[Seq[T]] with NullableType
case class OptionType[T](ofType: OutputType[T]) extends OutputType[Option[T]]
case class OptionInputType[T] (ofType: InputType[T]) extends InputType[Option[T]]
sealed trait HasArguments {
def arguments: List[Argument[_]]
}
object DirectiveLocation extends Enumeration {
// Operations
val Query = Value
val Mutation = Value
val Subscription = Value
val Field = Value
val FragmentDefinition = Value
val FragmentSpread = Value
val InlineFragment = Value
// Schema Definitions
val Schema = Value
val Scalar = Value
val Object = Value
val FieldDefinition = Value
val ArgumentDefinition = Value
val Interface = Value
val Union = Value
val Enum = Value
val EnumValue = Value
val InputObject = Value
val InputFieldDefinition = Value
def fromString(location: String): DirectiveLocation.Value = location match {
case "QUERY" ⇒ Query
case "MUTATION" ⇒ Mutation
case "SUBSCRIPTION" ⇒ Subscription
case "FIELD" ⇒ Field
case "FRAGMENT_DEFINITION" ⇒ FragmentDefinition
case "FRAGMENT_SPREAD" ⇒ FragmentSpread
case "INLINE_FRAGMENT" ⇒ InlineFragment
case "SCHEMA" ⇒ Schema
case "SCALAR" ⇒ Scalar
case "OBJECT" ⇒ Object
case "FIELD_DEFINITION" ⇒ FieldDefinition
case "ARGUMENT_DEFINITION" ⇒ ArgumentDefinition
case "INTERFACE" ⇒ Interface
case "UNION" ⇒ Union
case "ENUM" ⇒ Enum
case "ENUM_VALUE" ⇒ EnumValue
case "INPUT_OBJECT" ⇒ InputObject
case "INPUT_FIELD_DEFINITION" ⇒ InputFieldDefinition
}
}
case class Directive(
name: String,
description: Option[String] = None,
arguments: List[Argument[_]] = Nil,
locations: Set[DirectiveLocation.Value] = Set.empty,
shouldInclude: DirectiveContext ⇒ Boolean) extends HasArguments
case class Schema[Ctx, Val](
query: ObjectType[Ctx, Val],
mutation: Option[ObjectType[Ctx, Val]] = None,
subscription: Option[ObjectType[Ctx, Val]] = None,
additionalTypes: List[Type with Named] = Nil,
directives: List[Directive] = BuiltinDirectives,
validationRules: List[SchemaValidationRule] = SchemaValidationRule.default) {
lazy val types: Map[String, (Int, Type with Named)] = {
def sameType(t1: Type, t2: Type) =
t1.getClass.getSimpleName == t2.getClass.getSimpleName
def typeConflict(name: String, t1: Type, t2: Type, parentInfo: String) =
throw SchemaValidationException(ConflictingTypeDefinitionViolation(
name, t1.getClass.getSimpleName :: t2.getClass.getSimpleName :: Nil, parentInfo) :: Nil)
def updated(priority: Int, name: String, tpe: Type with Named, result: Map[String, (Int, Type with Named)], parentInfo: String) =
result get name match {
case Some(found) if !sameType(found._2, tpe) ⇒ typeConflict(name, found._2, tpe, parentInfo)
case Some(_) ⇒ result
case None ⇒ result.updated(name, priority → tpe)
}
def collectTypes(parentInfo: String, priority: Int, tpe: Type, result: Map[String, (Int, Type with Named)]): Map[String, (Int, Type with Named)] = {
tpe match {
case null ⇒ throw new IllegalStateException(
s"A `null` value was provided instead of type for $parentInfo.\n" +
"This can happen if you have recursive type definition or circular references within your type graph.\n" +
"Please use no-arg function to provide fields for such types.\n" +
"You can find more info in the docs: http://sangria-graphql.org/learn/#circular-references-and-recursive-types")
case t: Named if result contains t.name ⇒
result get t.name match {
case Some(found) if !sameType(found._2, t) ⇒ typeConflict(t.name, found._2, t, parentInfo)
case _ ⇒ result
}
case OptionType(ofType) ⇒ collectTypes(parentInfo, priority, ofType, result)
case OptionInputType(ofType) ⇒ collectTypes(parentInfo, priority, ofType, result)
case ListType(ofType) ⇒ collectTypes(parentInfo, priority, ofType, result)
case ListInputType(ofType) ⇒ collectTypes(parentInfo, priority, ofType, result)
case t @ ScalarType(name, _, _, _, _, _, _) ⇒ updated(priority, name, t, result, parentInfo)
case t @ EnumType(name, _, _) ⇒ updated(priority, name, t, result, parentInfo)
case t @ InputObjectType(name, _, _) ⇒
t.fields.foldLeft(updated(priority, name, t, result, parentInfo)) {
case (acc, field) ⇒
collectTypes(s"a field '${field.name}' of '$name' input object type", priority, field.fieldType, acc)
}
case t: ObjectLikeType[_, _] ⇒
val own = t.fields.foldLeft(updated(priority, t.name, t, result, parentInfo)) {
case (acc, field) ⇒
val fromArgs = field.arguments.foldLeft(collectTypes(s"a field '${field.name}' of '${t.name}' type", priority, field.fieldType, acc)) {
case (aacc, arg) ⇒ collectTypes(s"an argument '${arg.name}' defined in field '${field.name}' of '${t.name}' type", priority, arg.argumentType, aacc)
}
field.manualPossibleTypes().foldLeft(fromArgs) {
case (acc, objectType) ⇒ collectTypes(s"a manualPossibleType defined in '${t.name}' type", priority, objectType, acc)
}
}
val withPossible = t match {
case i: InterfaceType[_, _] ⇒
i.manualPossibleTypes().foldLeft(own) {
case (acc, objectType) ⇒ collectTypes(s"a manualPossibleType defined in '${i.name}' type", priority, objectType, acc)
}
case _ ⇒ own
}
t.interfaces.foldLeft(withPossible) {
case (acc, interface) ⇒ collectTypes(s"an interface defined in '${t.name}' type", priority, interface, acc)
}
case t @ UnionType(name, _, types) ⇒
types.foldLeft(updated(priority, name, t, result, parentInfo)) {case (acc, tpe) ⇒ collectTypes(s"a '$name' type", priority, tpe, acc)}
}
}
val schemaTypes = collectTypes("a '__Schema' type", 30, introspection.__Schema, Map(BuiltinScalars map (s ⇒ s.name → (40 → s)): _*))
val queryTypes = collectTypes("a query type", 20, query, schemaTypes)
val queryTypesWithAdditions = queryTypes ++ additionalTypes.map(t ⇒ t.name → (10 → t))
val queryAndSubTypes = mutation map (collectTypes("a mutation type", 10, _, queryTypesWithAdditions)) getOrElse queryTypesWithAdditions
val queryAndSubAndMutTypes = subscription map (collectTypes("a subscription type", 10, _, queryAndSubTypes)) getOrElse queryAndSubTypes
queryAndSubAndMutTypes
}
lazy val typeList = types.values.toList.sortBy(t ⇒ t._1 + t._2.name).map(_._2)
lazy val availableTypeNames = typeList map (_.name)
lazy val allTypes = types collect {case (name, (_, tpe)) ⇒ name → tpe}
lazy val inputTypes = types collect {case (name, (_, tpe: InputType[_])) ⇒ name → tpe}
lazy val outputTypes = types collect {case (name, (_, tpe: OutputType[_])) ⇒ name → tpe}
lazy val scalarTypes = types collect {case (name, (_, tpe: ScalarType[_])) ⇒ name → tpe}
lazy val unionTypes: Map[String, UnionType[_]] =
types.filter(_._2._2.isInstanceOf[UnionType[_]]).mapValues(_._2.asInstanceOf[UnionType[_]])
lazy val directivesByName = directives groupBy (_.name) mapValues (_.head)
def getInputType(tpe: ast.Type): Option[InputType[_]] = tpe match {
case ast.NamedType(name, _) ⇒ inputTypes get name map (OptionInputType(_))
case ast.NotNullType(ofType, _) ⇒ getInputType(ofType) collect {case OptionInputType(ot) ⇒ ot}
case ast.ListType(ofType, _) ⇒ getInputType(ofType) map (t ⇒ OptionInputType(ListInputType(t)))
}
def getInputType(tpe: IntrospectionTypeRef): Option[InputType[_]] = tpe match {
case IntrospectionNamedTypeRef(_, name) ⇒ inputTypes get name map (OptionInputType(_))
case IntrospectionNonNullTypeRef(ofType) ⇒ getInputType(ofType) collect {case OptionInputType(ot) ⇒ ot}
case IntrospectionListTypeRef(ofType) ⇒ getInputType(ofType) map (t ⇒ OptionInputType(ListInputType(t)))
}
def getOutputType(tpe: ast.Type, topLevel: Boolean = false): Option[OutputType[_]] = tpe match {
case ast.NamedType(name, _) ⇒ outputTypes get name map (ot ⇒ if (topLevel) ot else OptionType(ot))
case ast.NotNullType(ofType, _) ⇒ getOutputType(ofType) collect {case OptionType(ot) ⇒ ot}
case ast.ListType(ofType, _) ⇒ getOutputType(ofType) map (ListType(_))
}
lazy val directImplementations: Map[String, List[ObjectLikeType[_, _]]] = {
typeList
.collect{case objectLike: ObjectLikeType[_, _] ⇒ objectLike}
.flatMap(objectLike ⇒ objectLike.interfaces map (_.name → objectLike))
.groupBy(_._1)
.mapValues(_ map (_._2))
}
lazy val implementations: Map[String, List[ObjectType[_, _]]] = {
def findConcreteTypes(tpe: ObjectLikeType[_, _]): List[ObjectType[_, _]] = tpe match {
case obj: ObjectType[_, _] ⇒ obj :: Nil
case interface: InterfaceType[_, _] ⇒ directImplementations(interface.name) flatMap findConcreteTypes
}
directImplementations map {
case (name, directImpls) ⇒ name → directImpls.flatMap(findConcreteTypes).groupBy(_.name).map(_._2.head).toList
}
}
lazy val possibleTypes: Map[String, List[ObjectType[_, _]]] =
implementations ++ unionTypes.values.map(ut ⇒ ut.name → ut.types)
def isPossibleType(baseTypeName: String, tpe: ObjectType[_, _]) =
possibleTypes get baseTypeName exists (_ exists (_.name == tpe.name))
val validationErrors = validationRules flatMap (_.validate(this))
if (validationErrors.nonEmpty) throw SchemaValidationException(validationErrors)
}
object Schema {
def isBuiltInType(typeName: String) =
BuiltinScalarsByName.contains(typeName) || IntrospectionTypesByName.contains(typeName)
def isBuiltInDirective(directiveName: String) =
BuiltinDirectivesByName.contains(directiveName)
def isIntrospectionType(typeName: String) =
IntrospectionTypesByName.contains(typeName)
def getBuiltInType(typeName: String): Option[Type with Named] =
BuiltinScalarsByName.get(typeName) orElse IntrospectionTypesByName.get(typeName)
/**
* Build a `Schema` for use by client tools.
*
* Given the result of a client running the introspection query, creates and
* returns a `Schema` instance which can be then used with all sangria
* tools, but cannot be used to execute a query, as introspection does not
* represent the "resolver", "parse" or "serialize" functions or any other
* server-internal mechanisms.
*
* @param introspectionResult the result of introspection query
*/
def buildFromIntrospection[T : InputUnmarshaller](introspectionResult: T) =
IntrospectionSchemaMaterializer.buildSchema[T](introspectionResult)
/**
* Build a `Schema` for use by client tools.
*
* Given the result of a client running the introspection query, creates and
* returns a `Schema` instance which can be then used with all sangria
* tools, but cannot be used to execute a query, as introspection does not
* represent the "resolver", "parse" or "serialize" functions or any other
* server-internal mechanisms.
*
* @param introspectionResult the result of introspection query
*/
def buildFromIntrospection[Ctx, T : InputUnmarshaller](introspectionResult: T, builder: IntrospectionSchemaBuilder[Ctx]) =
IntrospectionSchemaMaterializer.buildSchema[Ctx, T](introspectionResult, builder)
def buildFromAst(document: ast.Document) =
AstSchemaMaterializer.buildSchema(document)
def buildFromAst[Ctx](document: ast.Document, builder: AstSchemaBuilder[Ctx]) =
AstSchemaMaterializer.buildSchema[Ctx](document, builder)
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy