sangria.schema.Schema.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sangria-core_2.13 Show documentation
Show all versions of sangria-core_2.13 Show documentation
Scala GraphQL implementation
The newest version!
package sangria.schema
import sangria.ast.{AstNode, Document}
import language.implicitConversions
import sangria.execution.{FieldTag, SubscriptionField}
import sangria.marshalling.FromInput.{CoercedScalaResult, InputObjectResult}
import sangria.marshalling._
import sangria.{ast, introspection}
import sangria.validation._
import sangria.introspection._
import sangria.renderer.{QueryRenderer, SchemaFilter, SchemaRenderer}
import sangria.schema.InputObjectType.DefaultInput
import sangria.streaming.SubscriptionStreamLike
import scala.annotation.{implicitNotFound, tailrec}
import scala.reflect.ClassTag
import sangria.util.tag._
import scala.util.matching.Regex
sealed trait Type {
def namedType: Type with Named = {
@tailrec
def getNamedType(tpe: Type): Type with Named =
tpe match {
case OptionInputType(ofType) => getNamedType(ofType)
case OptionType(ofType) => getNamedType(ofType)
case ListInputType(ofType) => getNamedType(ofType)
case ListType(ofType) => getNamedType(ofType)
case n: Named => n
case t => throw new IllegalStateException("Expected named type, but got: " + t)
}
getNamedType(this)
}
}
sealed trait InputType[+T] extends Type {
lazy val isOptional: Boolean = this match {
case _: OptionInputType[_] => true
case _ => false
}
lazy val isList: Boolean = this match {
case _: ListInputType[_] => true
case _ => false
}
lazy val isNamed: Boolean = !(isOptional && isList)
lazy val nonOptionalType: InputType[_] = this match {
case tpe: OptionInputType[_] => tpe.ofType
case tpe => tpe
}
def namedInputType: InputType[_] = namedType.asInstanceOf[InputType[_]]
}
sealed trait OutputType[+T] extends Type
sealed trait LeafType extends Type with Named with HasAstInfo
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 MappedAbstractType[T] extends Type with AbstractType {
def contraMap(value: T): Any
}
sealed trait NullableType
sealed trait UnmodifiedType
sealed trait HasDescription {
/** A description of this schema element that can be presented to clients of the GraphQL service.
*/
def description: Option[String]
}
sealed trait Named extends HasDescription {
def name: String
def rename(newName: String): this.type
}
sealed trait HasDeprecation {
def deprecationReason: Option[String]
}
sealed trait HasAstInfo {
def astDirectives: Vector[ast.Directive]
def astNodes: Vector[ast.AstNode]
}
object Named {
val NameRegexp: Regex = """^[_a-zA-Z][_a-zA-Z0-9]*$""".r
def isValidName(name: String): Boolean = NameRegexp.pattern.matcher(name).matches()
}
/** 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`.
*
* @param description
* A description of this schema element that can be presented to clients of the GraphQL service.
*/
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,
astDirectives: Vector[ast.Directive] = Vector.empty,
astNodes: Vector[ast.AstNode] = Vector.empty
) extends InputType[T @@ CoercedScalaResult]
with OutputType[T]
with LeafType
with NullableType
with UnmodifiedType
with Named {
def withDirective(directive: ast.Directive): ScalarType[T] =
copy(astDirectives = astDirectives :+ directive)
def withDirectives(directives: ast.Directive*): ScalarType[T] =
copy(astDirectives = astDirectives ++ directives)
def rename(newName: String): this.type = copy(name = newName).asInstanceOf[this.type]
def toAst: ast.TypeDefinition = SchemaRenderer.renderType(this)
}
case class ScalarAlias[T, ST](
aliasFor: ScalarType[ST],
toScalar: T => ST,
fromScalar: ST => Either[Violation, T]
) extends InputType[T @@ CoercedScalaResult]
with OutputType[T]
with LeafType
with NullableType
with UnmodifiedType
with Named {
def name: String = aliasFor.name
def description: Option[String] = aliasFor.description
def rename(newName: String): this.type =
copy(aliasFor = aliasFor.rename(newName)).asInstanceOf[this.type]
override def astDirectives: Vector[ast.Directive] = aliasFor.astDirectives
override def astNodes: Vector[AstNode] = aliasFor.astNodes
def toAst: ast.TypeDefinition = SchemaRenderer.renderType(this)
}
sealed trait ObjectLikeType[Ctx, Val]
extends OutputType[Val]
with CompositeType[Val]
with NullableType
with UnmodifiedType
with Named
with HasAstInfo {
def interfaces: List[InterfaceType[Ctx, _]]
def fieldsFn: () => List[Field[Ctx, Val]]
lazy val ownFields: Vector[Field[Ctx, Val]] = fieldsFn().toVector
private def removeDuplicates[T, E](list: Vector[T], valueFn: T => E) =
list
.foldLeft((Vector.empty, Vector.empty): (Vector[E], Vector[T])) {
case (a @ (visited, _), 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)
lazy val uniqueFields: Vector[Field[Ctx, _]] =
removeDuplicates(fields, (e: Field[Ctx, _]) => e.name)
lazy val fieldsByName: Map[String, Vector[Field[Ctx, _]]] = 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)
def toAst: ast.TypeDefinition = SchemaRenderer.renderType(this)
}
/** GraphQL schema object description.
*
* Describes a type of object in a GraphQL schema that is presented by a Sangria server. Objects of
* the type contain [[fieldsFn fields]] and can be viewed as simply a container of fields—internal
* nodes in the tree of data that a GraphQL request returns. The data store operations take place
* at the level of the [[Field fields]] that are leaf nodes in that tree.
*
* Constructing the internal nodes of a [[Schema schema]] consists mostly of constructing instances
* of this class.
*
* @param description
* A description of this schema element that can be presented to clients of the GraphQL service.
* @see
* [[sangria.ast.ObjectTypeDefinition]]
*/
case class ObjectType[Ctx, Val: ClassTag](
name: String,
description: Option[String],
fieldsFn: () => List[Field[Ctx, Val]],
interfaces: List[InterfaceType[Ctx, _]],
instanceCheck: (Any, Class[_], ObjectType[Ctx, Val]) => Boolean,
astDirectives: Vector[ast.Directive],
astNodes: Vector[ast.AstNode]
) extends ObjectLikeType[Ctx, Val] {
lazy val valClass: Class[_] = implicitly[ClassTag[Val]].runtimeClass
def withDirective(directive: ast.Directive): ObjectType[Ctx, Val] =
copy(astDirectives = astDirectives :+ directive)
def withDirectives(directives: ast.Directive*): ObjectType[Ctx, Val] =
copy(astDirectives = astDirectives ++ directives)
def withInstanceCheck(
fn: (Any, Class[_], ObjectType[Ctx, Val]) => Boolean): ObjectType[Ctx, Val] =
copy(instanceCheck = fn)
def isInstanceOf(value: Any): Boolean = instanceCheck(value, valClass, this)
def rename(newName: String): this.type = copy(name = newName).asInstanceOf[this.type]
}
object ObjectType {
def apply[Ctx, Val: ClassTag](name: String, fields: List[Field[Ctx, Val]]): ObjectType[Ctx, Val] =
ObjectType(
name,
None,
fieldsFn = () => fields,
Nil,
instanceCheck = defaultInstanceCheck,
Vector.empty,
Vector.empty)
def apply[Ctx, Val: ClassTag](
name: String,
description: String,
fields: List[Field[Ctx, Val]]): ObjectType[Ctx, Val] =
ObjectType(
name,
Some(description),
fieldsFn = () => fields,
Nil,
instanceCheck = defaultInstanceCheck,
Vector.empty,
Vector.empty)
def apply[Ctx, Val: ClassTag](
name: String,
interfaces: List[PossibleInterface[Ctx, Val]],
fields: List[Field[Ctx, Val]]): ObjectType[Ctx, Val] =
ObjectType(
name,
None,
fieldsFn = () => fields,
interfaces.map(_.interfaceType),
instanceCheck = defaultInstanceCheck,
Vector.empty,
Vector.empty)
def apply[Ctx, Val: ClassTag](
name: String,
description: String,
interfaces: List[PossibleInterface[Ctx, Val]],
fields: List[Field[Ctx, Val]]): ObjectType[Ctx, Val] =
ObjectType(
name,
Some(description),
fieldsFn = () => fields,
interfaces.map(_.interfaceType),
instanceCheck = defaultInstanceCheck,
Vector.empty,
Vector.empty)
def apply[Ctx, Val: ClassTag](
name: String,
fieldsFn: () => List[Field[Ctx, Val]]): ObjectType[Ctx, Val] =
ObjectType(
name,
None,
fieldsFn,
Nil,
instanceCheck = defaultInstanceCheck,
Vector.empty,
Vector.empty)
def apply[Ctx, Val: ClassTag](
name: String,
description: String,
fieldsFn: () => List[Field[Ctx, Val]]): ObjectType[Ctx, Val] =
ObjectType(
name,
Some(description),
fieldsFn,
Nil,
instanceCheck = defaultInstanceCheck,
Vector.empty,
Vector.empty)
def apply[Ctx, Val: ClassTag](
name: String,
interfaces: List[PossibleInterface[Ctx, Val]],
fieldsFn: () => List[Field[Ctx, Val]]): ObjectType[Ctx, Val] =
ObjectType(
name,
None,
fieldsFn,
interfaces.map(_.interfaceType),
instanceCheck = defaultInstanceCheck,
Vector.empty,
Vector.empty)
def apply[Ctx, Val: ClassTag](
name: String,
description: String,
interfaces: List[PossibleInterface[Ctx, Val]],
fieldsFn: () => List[Field[Ctx, Val]]): ObjectType[Ctx, Val] =
ObjectType(
name,
Some(description),
fieldsFn,
interfaces.map(_.interfaceType),
instanceCheck = defaultInstanceCheck,
Vector.empty,
Vector.empty)
def createFromMacro[Ctx, Val: ClassTag](
name: String,
description: Option[String],
interfaces: List[InterfaceType[Ctx, _]],
fieldsFn: () => List[Field[Ctx, Val]]) =
ObjectType(
name,
description,
fieldsFn,
interfaces,
instanceCheck = defaultInstanceCheck,
Vector.empty,
Vector.empty)
implicit def acceptUnitCtx[Ctx, Val](objectType: ObjectType[Unit, Val]): ObjectType[Ctx, Val] =
objectType.asInstanceOf[ObjectType[Ctx, Val]]
def defaultInstanceCheck[Ctx, Val]: (Any, Class[_], ObjectType[Ctx, Val]) => Boolean =
(value, valClass, tpe) => valClass.isAssignableFrom(value.getClass)
}
/** @param description
* A description of this schema element that can be presented to clients of the GraphQL service.
*/
case class InterfaceType[Ctx, Val](
name: String,
description: Option[String] = None,
fieldsFn: () => List[Field[Ctx, Val]],
interfaces: List[InterfaceType[Ctx, _]],
manualPossibleTypes: () => List[ObjectType[_, _]],
astDirectives: Vector[ast.Directive],
astNodes: Vector[ast.AstNode] = Vector.empty
) extends ObjectLikeType[Ctx, Val]
with AbstractType {
def withDirective(directive: ast.Directive): InterfaceType[Ctx, Val] =
copy(astDirectives = astDirectives :+ directive)
def withDirectives(directives: ast.Directive*): InterfaceType[Ctx, Val] =
copy(astDirectives = astDirectives ++ directives)
def withPossibleTypes(possible: PossibleObject[Ctx, Val]*): InterfaceType[Ctx, Val] =
copy(manualPossibleTypes = () => possible.toList.map(_.objectType))
def withPossibleTypes(possible: () => List[PossibleObject[Ctx, Val]]): InterfaceType[Ctx, Val] =
copy(manualPossibleTypes = () => possible().map(_.objectType))
def rename(newName: String): this.type = copy(name = newName).asInstanceOf[this.type]
}
object InterfaceType {
val emptyPossibleTypes: () => List[ObjectType[_, _]] = () => Nil
def apply[Ctx, Val](name: String, fields: List[Field[Ctx, Val]]): InterfaceType[Ctx, Val] =
InterfaceType(
name,
None,
fieldsFn = () => fields,
Nil,
emptyPossibleTypes,
Vector.empty,
Vector.empty)
def apply[Ctx, Val](
name: String,
description: String,
fields: List[Field[Ctx, Val]]): InterfaceType[Ctx, Val] =
InterfaceType(
name,
Some(description),
fieldsFn = () => fields,
Nil,
emptyPossibleTypes,
Vector.empty,
Vector.empty)
def apply[Ctx, Val](
name: String,
fields: List[Field[Ctx, Val]],
interfaces: List[PossibleInterface[Ctx, Val]]): InterfaceType[Ctx, Val] =
InterfaceType(
name,
None,
fieldsFn = () => fields,
interfaces.map(_.interfaceType),
emptyPossibleTypes,
Vector.empty,
Vector.empty)
def apply[Ctx, Val](
name: String,
description: String,
fields: List[Field[Ctx, Val]],
interfaces: List[PossibleInterface[Ctx, Val]]): InterfaceType[Ctx, Val] =
InterfaceType(
name,
Some(description),
fieldsFn = () => fields,
interfaces.map(_.interfaceType),
emptyPossibleTypes,
Vector.empty,
Vector.empty)
def apply[Ctx, Val](
name: String,
fieldsFn: () => List[Field[Ctx, Val]]): InterfaceType[Ctx, Val] =
InterfaceType(name, None, fieldsFn, Nil, emptyPossibleTypes, Vector.empty, Vector.empty)
def apply[Ctx, Val](
name: String,
description: String,
fieldsFn: () => List[Field[Ctx, Val]]): InterfaceType[Ctx, Val] =
InterfaceType(
name,
Some(description),
fieldsFn,
Nil,
emptyPossibleTypes,
Vector.empty,
Vector.empty)
def apply[Ctx, Val](
name: String,
fieldsFn: () => List[Field[Ctx, Val]],
interfaces: List[PossibleInterface[Ctx, Val]]): InterfaceType[Ctx, Val] =
InterfaceType(
name,
None,
fieldsFn,
interfaces.map(_.interfaceType),
emptyPossibleTypes,
Vector.empty,
Vector.empty)
def apply[Ctx, Val](
name: String,
description: String,
fieldsFn: () => List[Field[Ctx, Val]],
interfaces: List[PossibleInterface[Ctx, Val]]): InterfaceType[Ctx, Val] =
InterfaceType(
name,
Some(description),
fieldsFn,
interfaces.map(_.interfaceType),
emptyPossibleTypes,
Vector.empty,
Vector.empty)
}
case class PossibleInterface[Ctx, Concrete](interfaceType: InterfaceType[Ctx, _])
object PossibleInterface extends PossibleInterfaceLowPrioImplicits {
def apply[Ctx, Abstract, Concrete](interface: InterfaceType[Ctx, Abstract])(implicit
ev: PossibleType[Abstract, Concrete]): PossibleInterface[Ctx, Concrete] =
PossibleInterface[Ctx, Concrete](interface)
implicit def convert[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]: PossibleType[AbstrType, ConcreteType] =
SingletonPossibleType.asInstanceOf[PossibleType[AbstrType, ConcreteType]]
implicit def InheritanceBasedPossibleType[Abstract, Concrete](implicit
ev: Concrete <:< Abstract): PossibleType[Abstract, Concrete] =
create[Abstract, Concrete]
}
/** @param description
* A description of this schema element that can be presented to clients of the GraphQL service.
*/
case class UnionType[Ctx](
name: String,
description: Option[String] = None,
typesFn: () => List[ObjectType[Ctx, _]],
astDirectives: Vector[ast.Directive] = Vector.empty,
astNodes: Vector[ast.AstNode] = Vector.empty)
extends OutputType[Any]
with CompositeType[Any]
with AbstractType
with NullableType
with UnmodifiedType
with HasAstInfo {
def withDirective(directive: ast.Directive): UnionType[Ctx] =
copy(astDirectives = astDirectives :+ directive)
def withDirectives(directives: ast.Directive*): UnionType[Ctx] =
copy(astDirectives = astDirectives ++ directives)
def rename(newName: String): this.type = copy(name = newName).asInstanceOf[this.type]
def toAst: ast.TypeDefinition = SchemaRenderer.renderType(this)
/** Creates a type-safe version of union type which might be useful in cases where the value is
* wrapped in a type like `Either`.
*/
def mapValue[T](func: T => Any): OutputType[T] =
new UnionType[Ctx](name, description, typesFn, astDirectives, astNodes)
with MappedAbstractType[T] {
override def contraMap(value: T): Any = func(value)
}.asInstanceOf[OutputType[T]]
lazy val types: List[ObjectType[Ctx, _]] = typesFn()
}
object UnionType {
def apply[Ctx](name: String, types: List[ObjectType[Ctx, _]]): UnionType[Ctx] =
UnionType(name, None, () => types)
def apply[Ctx](
name: String,
description: Option[String],
types: List[ObjectType[Ctx, _]]): UnionType[Ctx] =
UnionType(name, description, () => types)
def apply[Ctx](
name: String,
description: Option[String],
types: List[ObjectType[Ctx, _]],
astDirectives: Vector[ast.Directive]): UnionType[Ctx] =
UnionType(name, description, () => types, astDirectives)
def apply[Ctx](
name: String,
description: Option[String],
types: List[ObjectType[Ctx, _]],
astDirectives: Vector[ast.Directive],
astNodes: Vector[ast.AstNode]): UnionType[Ctx] =
UnionType[Ctx](name, description, () => types, astDirectives, astNodes)
}
/** @param description
* A description of this schema element that can be presented to clients of the GraphQL service.
* @param resolve
* A function that maps the context of this field definition to an action that retrieves the
* field's data.
*/
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[_, _]],
astDirectives: Vector[ast.Directive],
astNodes: Vector[ast.AstNode])
extends Named
with HasArguments
with HasDeprecation
with HasAstInfo {
def withDirective(directive: ast.Directive): Field[Ctx, Val] =
copy(astDirectives = astDirectives :+ directive)
def withDirectives(directives: ast.Directive*): Field[Ctx, Val] =
copy(astDirectives = astDirectives ++ directives)
def withPossibleTypes(possible: PossibleObject[Ctx, Val]*): Field[Ctx, Val] =
copy(manualPossibleTypes = () => possible.toList.map(_.objectType))
def withPossibleTypes(possible: () => List[PossibleObject[Ctx, Val]]): Field[Ctx, Val] =
copy(manualPossibleTypes = () => possible().map(_.objectType))
def rename(newName: String): this.type = copy(name = newName).asInstanceOf[this.type]
def toAst: ast.FieldDefinition = SchemaRenderer.renderField(this)
}
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,
astDirectives: Vector[ast.Directive] = Vector.empty
)(implicit ev: ValidOutType[Res, Out]): Field[Ctx, Val] =
Field[Ctx, Val](
name,
fieldType,
description,
arguments,
resolve,
deprecationReason,
tags,
complexity,
() => possibleTypes.map(_.objectType),
astDirectives,
Vector.empty)
def subs[Ctx, Val, StreamSource, Res, Out](
name: String,
fieldType: OutputType[Out],
description: Option[String] = None,
arguments: List[Argument[_]] = Nil,
resolve: Context[Ctx, Val] => StreamSource,
possibleTypes: => List[PossibleObject[_, _]] = Nil,
tags: List[FieldTag] = Nil,
complexity: Option[(Ctx, Args, Double) => Double] = None,
deprecationReason: Option[String] = None,
astDirectives: Vector[ast.Directive] = Vector.empty
)(implicit
stream: SubscriptionStreamLike[StreamSource, Action, Ctx, Res, Out]): Field[Ctx, Val] = {
val s = stream.subscriptionStream
Field[Ctx, Val](
name,
fieldType,
description,
arguments,
ctx =>
SubscriptionValue[Ctx, StreamSource, stream.StreamSource](
resolve(ctx).asInstanceOf[stream.StreamSource[Any]],
s),
deprecationReason,
SubscriptionField[stream.StreamSource](s) +: tags,
complexity,
() => possibleTypes.map(_.objectType),
astDirectives,
Vector.empty
)
}
}
@implicitNotFound(msg = "${Res} is invalid type for the resulting GraphQL type ${Out}.")
trait ValidOutType[-Res, +Out]
object ValidOutType {
private val valid = new ValidOutType[Any, Any] {}
implicit def validSubclass[Res, Out](implicit ev: Res <:< Out): ValidOutType[Res, Out] =
valid.asInstanceOf[ValidOutType[Res, Out]]
implicit def validNothing[Out]: ValidOutType[Nothing, Out] =
valid.asInstanceOf[ValidOutType[Nothing, Out]]
implicit def validOption[Res, Out](implicit ev: Res <:< Out): ValidOutType[Res, Option[Out]] =
valid.asInstanceOf[ValidOutType[Res, Option[Out]]]
}
trait InputValue[T] {
def name: String
def inputValueType: InputType[_]
def description: Option[String]
def defaultValue: Option[(_, ToInput[_, _])]
def deprecationReason: Option[String]
}
/** @param description
* A description of this schema element that can be presented to clients of the GraphQL service.
*/
case class Argument[T](
name: String,
argumentType: InputType[_],
description: Option[String],
defaultValue: Option[(_, ToInput[_, _])],
fromInput: FromInput[_],
deprecationReason: Option[String],
astDirectives: Vector[ast.Directive],
astNodes: Vector[ast.AstNode])
extends InputValue[T]
with Named
with HasAstInfo
with HasDeprecation {
def withDirective(directive: ast.Directive): Argument[T] =
copy(astDirectives = astDirectives :+ directive)
def withDirectives(directives: ast.Directive*): Argument[T] =
copy(astDirectives = astDirectives ++ directives)
def withDeprecationReason(deprecationReason: String): Argument[T] =
copy(deprecationReason = Some(deprecationReason))
override def inputValueType: InputType[_] = argumentType
override def rename(newName: String): this.type = copy(name = newName).asInstanceOf[this.type]
def toAst: ast.InputValueDefinition = SchemaRenderer.renderArg(this)
}
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(
name,
argumentType,
Some(description),
Some(defaultValue -> toInput),
fromInput,
None,
Vector.empty,
Vector.empty)
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(
name,
argumentType,
None,
Some(defaultValue -> toInput),
fromInput,
None,
Vector.empty,
Vector.empty)
def apply[T](
name: String,
argumentType: InputType[T],
description: String
)(implicit fromInput: FromInput[T], res: WithoutInputTypeTags[T]): Argument[res.Res] =
Argument(
name,
argumentType,
Some(description),
None,
fromInput,
None,
Vector.empty,
Vector.empty)
def apply[T](name: String, argumentType: InputType[T])(implicit
fromInput: FromInput[T],
res: WithoutInputTypeTags[T]): Argument[res.Res] =
Argument(name, argumentType, None, None, fromInput, None, Vector.empty, Vector.empty)
def createWithoutDefault[T](
name: String,
argumentType: InputType[T],
description: Option[String])(implicit
fromInput: FromInput[T],
res: ArgumentType[T]): Argument[res.Res] =
Argument(name, argumentType, description, None, fromInput, None, Vector.empty, Vector.empty)
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(
name,
argumentType,
description,
Some(defaultValue -> toInput),
fromInput,
None,
Vector.empty,
Vector.empty)
}
trait WithoutInputTypeTags[T] {
type Res
}
object WithoutInputTypeTags extends WithoutInputTypeTagsLowPrio {
implicit def coercedArgTpe[T]: WithoutInputTypeTags[T @@ CoercedScalaResult] { type Res = T } =
new WithoutInputTypeTags[T @@ CoercedScalaResult] {
type Res = T
}
implicit def coercedOptArgTpe[T]
: WithoutInputTypeTags[Option[T @@ CoercedScalaResult]] { type Res = Option[T] } =
new WithoutInputTypeTags[Option[T @@ CoercedScalaResult]] {
type Res = Option[T]
}
implicit def coercedSeqOptArgTpe[T]
: WithoutInputTypeTags[Seq[Option[T @@ CoercedScalaResult]]] { type Res = Seq[Option[T]] } =
new WithoutInputTypeTags[Seq[Option[T @@ CoercedScalaResult]]] {
type Res = Seq[Option[T]]
}
implicit def coercedOptSeqArgTpe[T]
: WithoutInputTypeTags[Option[Seq[T @@ CoercedScalaResult]]] { type Res = Option[Seq[T]] } =
new WithoutInputTypeTags[Option[Seq[T @@ CoercedScalaResult]]] {
type Res = Option[Seq[T]]
}
implicit def coercedOptSeqOptArgTpe[T]
: WithoutInputTypeTags[Option[Seq[Option[T @@ CoercedScalaResult]]]] {
type Res = Option[Seq[Option[T]]]
} = new WithoutInputTypeTags[Option[Seq[Option[T @@ CoercedScalaResult]]]] {
type Res = Option[Seq[Option[T]]]
}
implicit def ioArgTpe[T]: WithoutInputTypeTags[T @@ InputObjectResult] { type Res = T } =
new WithoutInputTypeTags[T @@ InputObjectResult] {
type Res = T
}
implicit def ioOptArgTpe[T]
: WithoutInputTypeTags[Option[T @@ InputObjectResult]] { type Res = Option[T] } =
new WithoutInputTypeTags[Option[T @@ InputObjectResult]] {
type Res = Option[T]
}
implicit def ioSeqOptArgTpe[T]
: WithoutInputTypeTags[Seq[Option[T @@ InputObjectResult]]] { type Res = Seq[Option[T]] } =
new WithoutInputTypeTags[Seq[Option[T @@ InputObjectResult]]] {
type Res = Seq[Option[T]]
}
implicit def ioOptSeqArgTpe[T]
: WithoutInputTypeTags[Option[Seq[T @@ InputObjectResult]]] { type Res = Option[Seq[T]] } =
new WithoutInputTypeTags[Option[Seq[T @@ InputObjectResult]]] {
type Res = Option[Seq[T]]
}
implicit def ioOptSeqOptArgTpe[T]
: WithoutInputTypeTags[Option[Seq[Option[T @@ InputObjectResult]]]] {
type Res = Option[Seq[Option[T]]]
} = new WithoutInputTypeTags[Option[Seq[Option[T @@ InputObjectResult]]]] {
type Res = Option[Seq[Option[T]]]
}
}
trait WithoutInputTypeTagsLowPrio {
implicit def defaultArgTpe[T]: WithoutInputTypeTags[T] { type Res = T } =
new WithoutInputTypeTags[T] {
type Res = T
}
}
trait ArgumentType[T] {
type Res
}
object ArgumentType extends ArgumentTypeLowPrio {
implicit def coercedArgTpe[T]: ArgumentType[T @@ CoercedScalaResult] { type Res = T } =
new ArgumentType[T @@ CoercedScalaResult] {
type Res = T
}
implicit def coercedOptArgTpe[T]: ArgumentType[Option[T @@ CoercedScalaResult]] { type Res = T } =
new ArgumentType[Option[T @@ CoercedScalaResult]] {
type Res = T
}
implicit def coercedSeqOptArgTpe[T]
: ArgumentType[Seq[Option[T @@ CoercedScalaResult]]] { type Res = Seq[Option[T]] } =
new ArgumentType[Seq[Option[T @@ CoercedScalaResult]]] {
type Res = Seq[Option[T]]
}
implicit def coercedOptSeqArgTpe[T]
: ArgumentType[Option[Seq[T @@ CoercedScalaResult]]] { type Res = Seq[T] } =
new ArgumentType[Option[Seq[T @@ CoercedScalaResult]]] {
type Res = Seq[T]
}
implicit def coercedOptSeqOptArgTpe[T]
: ArgumentType[Option[Seq[Option[T @@ CoercedScalaResult]]]] { type Res = Seq[Option[T]] } =
new ArgumentType[Option[Seq[Option[T @@ CoercedScalaResult]]]] {
type Res = Seq[Option[T]]
}
implicit def ioArgTpe[T]: ArgumentType[T @@ InputObjectResult] { type Res = T } =
new ArgumentType[T @@ InputObjectResult] {
type Res = T
}
implicit def ioOptArgTpe[T]: ArgumentType[Option[T @@ InputObjectResult]] { type Res = T } =
new ArgumentType[Option[T @@ InputObjectResult]] {
type Res = T
}
implicit def ioSeqOptArgTpe[T]
: ArgumentType[Seq[Option[T @@ InputObjectResult]]] { type Res = Seq[Option[T]] } =
new ArgumentType[Seq[Option[T @@ InputObjectResult]]] {
type Res = Seq[Option[T]]
}
implicit def ioOptSeqArgTpe[T]
: ArgumentType[Option[Seq[T @@ InputObjectResult]]] { type Res = Seq[T] } =
new ArgumentType[Option[Seq[T @@ InputObjectResult]]] {
type Res = Seq[T]
}
implicit def ioOptSeqOptArgTpe[T]
: ArgumentType[Option[Seq[Option[T @@ InputObjectResult]]]] { type Res = Seq[Option[T]] } =
new ArgumentType[Option[Seq[Option[T @@ InputObjectResult]]]] {
type Res = Seq[Option[T]]
}
}
trait ArgumentTypeLowPrio extends ArgumentTypeLowestPrio {
implicit def optionArgTpe[T]: ArgumentType[Option[T]] { type Res = T } =
new ArgumentType[Option[T]] {
type Res = T
}
}
trait ArgumentTypeLowestPrio {
implicit def defaultArgTpe[T]: ArgumentType[T] { type Res = T } = new ArgumentType[T] {
type Res = T
}
}
/** @param description
* A description of this schema element that can be presented to clients of the GraphQL service.
*/
case class EnumType[T](
name: String,
description: Option[String] = None,
values: List[EnumValue[T]],
astDirectives: Vector[ast.Directive] = Vector.empty,
astNodes: Vector[ast.AstNode] = Vector.empty)
extends InputType[T @@ CoercedScalaResult]
with OutputType[T]
with LeafType
with NullableType
with UnmodifiedType
with Named
with HasAstInfo {
lazy val byName: Map[String, EnumValue[T]] =
values.groupBy(_.name).map { case (k, v) => (k, v.head) }
lazy val byValue: Map[T, EnumValue[T]] =
values.groupBy(_.value).map { case (k, v) => (k, v.head) }
def withDirective(directive: ast.Directive): EnumType[T] =
copy(astDirectives = astDirectives :+ directive)
def withDirectives(directives: ast.Directive*): EnumType[T] =
copy(astDirectives = astDirectives ++ directives)
def coerceUserInput(value: Any): Either[Violation, (T, Boolean)] = value match {
case valueName: String =>
byName
.get(valueName)
.map(v => Right(v.value -> v.deprecationReason.isDefined))
.getOrElse(Left(EnumValueCoercionViolation(valueName, name, values.map(_.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(valueName, _, _) =>
byName
.get(valueName)
.map(v => Right(v.value -> v.deprecationReason.isDefined))
.getOrElse(Left(EnumValueCoercionViolation(valueName, name, values.map(_.name))))
case _ => Left(EnumCoercionViolation)
}
def coerceOutput(value: T): String = byValue(value).name
def rename(newName: String): this.type = copy(name = newName).asInstanceOf[this.type]
def toAst: ast.TypeDefinition = SchemaRenderer.renderType(this)
}
/** @param description
* A description of this schema element that can be presented to clients of the GraphQL service.
*/
case class EnumValue[+T](
name: String,
description: Option[String] = None,
value: T,
deprecationReason: Option[String] = None,
astDirectives: Vector[ast.Directive] = Vector.empty,
astNodes: Vector[ast.AstNode] = Vector.empty)
extends Named
with HasDeprecation
with HasAstInfo {
def withDirective(directive: ast.Directive): EnumValue[T] =
copy(astDirectives = astDirectives :+ directive)
def withDirectives(directives: ast.Directive*): EnumValue[T] =
copy(astDirectives = astDirectives ++ directives)
def rename(newName: String): this.type = copy(name = newName).asInstanceOf[this.type]
def toAst: ast.EnumValueDefinition = SchemaRenderer.renderEnumValue(this)
}
/** @param description
* A description of this schema element that can be presented to clients of the GraphQL service.
*/
case class InputObjectType[T](
name: String,
description: Option[String] = None,
fieldsFn: () => List[InputField[_]],
astDirectives: Vector[ast.Directive],
astNodes: Vector[ast.AstNode]
) extends InputType[T @@ InputObjectResult]
with NullableType
with UnmodifiedType
with Named
with HasAstInfo {
lazy val fields: List[InputField[_]] = fieldsFn()
// noinspection RedundantCollectionConversion
lazy val fieldsByName: Map[String, InputField[_]] =
fields.groupBy(_.name).map { case (k, v) => (k, v.head) }.toMap // required for 2.12
def withDirective(directive: ast.Directive): InputObjectType[T] =
copy(astDirectives = astDirectives :+ directive)
def withDirectives(directives: ast.Directive*): InputObjectType[T] =
copy(astDirectives = astDirectives ++ directives)
def rename(newName: String): this.type = copy(name = newName).asInstanceOf[this.type]
def toAst: ast.TypeDefinition = SchemaRenderer.renderType(this)
}
object InputObjectType {
type DefaultInput = Map[String, Any]
def apply[T](name: String, fields: List[InputField[_]])(implicit
res: InputObjectDefaultResult[T]): InputObjectType[res.Res] =
InputObjectType(name, None, fieldsFn = () => fields, Vector.empty, Vector.empty)
def apply[T](name: String, description: String, fields: List[InputField[_]])(implicit
res: InputObjectDefaultResult[T]): InputObjectType[res.Res] =
InputObjectType(name, Some(description), fieldsFn = () => fields, Vector.empty, Vector.empty)
def apply[T](name: String, fieldsFn: () => List[InputField[_]])(implicit
res: InputObjectDefaultResult[T]): InputObjectType[res.Res] =
InputObjectType(name, None, fieldsFn, Vector.empty, Vector.empty)
def apply[T](name: String, description: String, fieldsFn: () => List[InputField[_]])(implicit
res: InputObjectDefaultResult[T]): InputObjectType[res.Res] =
InputObjectType(name, Some(description), fieldsFn, Vector.empty, Vector.empty)
def createFromMacro[T](
name: String,
description: Option[String] = None,
fieldsFn: () => List[InputField[_]]): InputObjectType[T] =
InputObjectType[T](name, description, fieldsFn, Vector.empty, Vector.empty)
}
trait InputObjectDefaultResult[T] {
type Res
}
object InputObjectDefaultResult extends InputObjectDefaultResultLowPrio {
implicit def nothingResult: InputObjectDefaultResult[Nothing] { type Res = DefaultInput } =
new InputObjectDefaultResult[Nothing] {
override type Res = InputObjectType.DefaultInput
}
}
trait InputObjectDefaultResultLowPrio {
implicit def defaultResult[T]: InputObjectDefaultResult[T] { type Res = T } =
new InputObjectDefaultResult[T] {
override type Res = T
}
}
/** @param description
* A description of this schema element that can be presented to clients of the GraphQL service.
*/
case class InputField[T](
name: String,
fieldType: InputType[T],
description: Option[String],
defaultValue: Option[(_, ToInput[_, _])],
deprecationReason: Option[String],
astDirectives: Vector[ast.Directive],
astNodes: Vector[ast.AstNode]
) extends InputValue[T]
with Named
with HasAstInfo
with HasDeprecation {
def withDirective(directive: ast.Directive): InputField[T] =
copy(astDirectives = astDirectives :+ directive)
def withDirectives(directives: ast.Directive*): InputField[T] =
copy(astDirectives = astDirectives ++ directives)
def withDeprecationReason(deprecationReason: String): InputField[T] =
copy(deprecationReason = Some(deprecationReason))
def inputValueType: InputType[T] = fieldType
def rename(newName: String): InputField.this.type = copy(name = newName).asInstanceOf[this.type]
def toAst: ast.InputValueDefinition = SchemaRenderer.renderInputField(this)
}
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),
None,
Vector.empty,
Vector.empty).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),
None,
Vector.empty,
Vector.empty)
.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, None, Vector.empty, Vector.empty)
.asInstanceOf[InputField[res.Res]]
def apply[T](name: String, fieldType: InputType[T])(implicit
res: WithoutInputTypeTags[T]): InputField[res.Res] =
InputField(name, fieldType, None, None, None, Vector.empty, Vector.empty)
.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),
None,
Vector.empty,
Vector.empty).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, None, Vector.empty, Vector.empty)
.asInstanceOf[InputField[res.Res]]
}
case class ListType[T](ofType: OutputType[T]) extends OutputType[Iterable[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 {
val ArgumentDefinition: Value = Value
val Enum: Value = Value
val EnumValue: Value = Value
val Field: Value = Value
/** Indicates that a directive can be used on a [[sangria.ast.FieldDefinition field definition]].
*/
val FieldDefinition: Value = Value
val FragmentDefinition: Value = Value
val FragmentSpread: Value = Value
val InlineFragment: Value = Value
val InputFieldDefinition: Value = Value
val InputObject: Value = Value
val Interface: Value = Value
val Mutation: Value = Value
val Object: Value = Value
val Query: Value = Value
val Scalar: Value = Value
val Schema: Value = Value
val Subscription: Value = Value
val Union: Value = Value
val VariableDefinition: Value = 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 "VARIABLE_DEFINITION" => VariableDefinition
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
}
def toSpecString(location: DirectiveLocation.Value): String = location match {
case Query => "QUERY"
case Mutation => "MUTATION"
case Subscription => "SUBSCRIPTION"
case Field => "FIELD"
case FragmentDefinition => "FRAGMENT_DEFINITION"
case FragmentSpread => "FRAGMENT_SPREAD"
case InlineFragment => "INLINE_FRAGMENT"
case VariableDefinition => "VARIABLE_DEFINITION"
case Schema => "SCHEMA"
case Scalar => "SCALAR"
case Object => "OBJECT"
case FieldDefinition => "FIELD_DEFINITION"
case ArgumentDefinition => "ARGUMENT_DEFINITION"
case Interface => "INTERFACE"
case Union => "UNION"
case Enum => "ENUM"
case EnumValue => "ENUM_VALUE"
case InputObject => "INPUT_OBJECT"
case InputFieldDefinition => "INPUT_FIELD_DEFINITION"
}
}
/** @param description
* A description of this schema element that can be presented to clients of the GraphQL service.
*/
case class Directive(
name: String,
description: Option[String] = None,
arguments: List[Argument[_]] = Nil,
locations: Set[DirectiveLocation.Value] = Set.empty,
repeatable: Boolean = false,
shouldInclude: DirectiveContext => Boolean = _ => true)
extends HasArguments
with Named {
def rename(newName: String): this.type = copy(name = newName).asInstanceOf[this.type]
def toAst: ast.DirectiveDefinition = SchemaRenderer.renderDirective(this)
}
/** GraphQL schema description.
*
* Describes the schema that is presented by a Sangria server. An instance of this type needs to be
* presented to Sangria's execution method, so that it knows what to execute in response to a
* GraphQL request that conforms to this schema.
*
* The [[ObjectType types]] contained in the schema have associated [[Action actions]] that
* Sangria's execution uses to convert a parsed GraphQL request to its data store operations.
*
* @param query
* The query
* @param description
* A description of this schema element that can be presented to clients of the GraphQL service.
* @tparam Ctx
* Type of a context object that will be passed to each Sangria execution of a GraphQL query
* against this schema.
* @see
* [[ast.SchemaDefinition]]
*/
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,
override val description: Option[String] = None,
directives: List[Directive] = BuiltinDirectives,
validationRules: List[SchemaValidationRule] = SchemaValidationRule.default,
override val astDirectives: Vector[ast.Directive] = Vector.empty,
override val astNodes: Vector[ast.AstNode] = Vector.empty)
extends HasAstInfo
with HasDescription {
def withDirective(directive: ast.Directive): Schema[Ctx, Val] =
copy(astDirectives = astDirectives :+ directive)
def withDirectives(directives: ast.Directive*): Schema[Ctx, Val] =
copy(astDirectives = astDirectives ++ directives)
def extend(
document: ast.Document,
builder: AstSchemaBuilder[Ctx] = AstSchemaBuilder.default[Ctx]): Schema[Ctx, Val] =
AstSchemaMaterializer.extendSchema(this, document, builder)
def compare(oldSchema: Schema[_, _]): Vector[SchemaChange] =
SchemaComparator.compare(oldSchema, this)
lazy val toAst: Document = SchemaRenderer.schemaAst(this)
def toAst(filter: SchemaFilter): Document = SchemaRenderer.schemaAst(this, filter)
def renderPretty: String = QueryRenderer.renderPretty(toAst)
def renderPretty(filter: SchemaFilter): String = QueryRenderer.renderPretty(toAst(filter))
def renderCompact: String = QueryRenderer.renderCompact(toAst)
def renderCompact(filter: SchemaFilter): String = QueryRenderer.renderCompact(toAst(filter))
lazy val types: Map[String, (Int, Type with Named)] = {
def sameType(t1: Type, t2: Type): Boolean = {
val sameSangriaType = t1.getClass.getName == t2.getClass.getName
(t1, t2) match {
case (ot1: ObjectType[_, _], ot2: ObjectType[_, _]) =>
sameSangriaType && (ot1.valClass == ot2.valClass) && (ot1.fieldsByName.keySet == ot2.fieldsByName.keySet)
case (ot1: InputObjectType[_], ot2: InputObjectType[_]) =>
sameSangriaType && (ot1.fieldsByName.keySet == ot2.fieldsByName.keySet)
case _ => sameSangriaType
}
}
def typeConflict(name: String, t1: Type, t2: Type, parentInfo: String) =
(t1, t2) match {
case (_: ObjectType[_, _], _: ObjectType[_, _]) =>
throw SchemaValidationException(
Vector(ConflictingObjectTypeCaseClassViolation(name, parentInfo)))
case (_: InputObjectType[_], _: InputObjectType[_]) =>
throw SchemaValidationException(
Vector(ConflictingInputObjectTypeCaseClassViolation(name, parentInfo)))
case _ =>
val conflictingTypes = List(t1, t2).map(_.getClass.getSimpleName)
throw SchemaValidationException(
Vector(ConflictingTypeDefinitionViolation(name, conflictingTypes, parentInfo)))
}
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: https://sangria-graphql.github.io/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) =>
if (t.isInstanceOf[ScalarAlias[_, _]] && found._2.isInstanceOf[ScalarType[_]])
result
else 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, _, _, _, _, _, _, _, _) if BuiltinScalars.contains(t) =>
updated(40, name, t, result, parentInfo)
case t @ ScalarType(name, _, _, _, _, _, _, _, _) =>
updated(priority, name, t, result, parentInfo)
case ScalarAlias(aliasFor, _, _) =>
updated(priority, aliasFor.name, aliasFor, 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, _, _, _, _) =>
t.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.empty)
val queryTypes = collectTypes("a query type", 20, query, schemaTypes)
val queryTypesWithAdditions = additionalTypes.foldLeft(queryTypes) { case (acc, tpe) =>
collectTypes("additional type", 10, tpe, acc)
}
val queryAndSubTypes = mutation.fold(queryTypesWithAdditions)(
collectTypes("a mutation type", 10, _, queryTypesWithAdditions))
val queryAndSubAndMutTypes = subscription.fold(queryAndSubTypes)(
collectTypes("a subscription type", 10, _, queryAndSubTypes))
val queryAndSubAndMutAndDirArgTypes = directives.foldLeft(queryAndSubAndMutTypes) {
case (acc, dir) =>
val argumentTypes = dir.arguments.map(_.argumentType)
argumentTypes.foldLeft(acc) { case (acc, arg) =>
collectTypes("an argument type", 10, arg, acc)
}
}
queryAndSubAndMutAndDirArgTypes
}
lazy val typeList: Vector[Type with Named] =
types.values.toVector.sortBy(t => t._1.toString + t._2.name).map(_._2)
lazy val availableTypeNames: Vector[String] = typeList.map(_.name)
lazy val allTypes: Map[String, Type with Named] = 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.iterator
.filter(_._2._2.isInstanceOf[UnionType[_]])
.map { case (k, v) => (k, v._2.asInstanceOf[UnionType[_]]) }
.toMap
lazy val directivesByName: Map[String, Directive] =
directives.groupBy(_.name).iterator.map { case (k, v) => (k, v.head) }.toMap
def getInputType(tpe: ast.Type): Option[InputType[_]] = tpe match {
case ast.NamedType(name, _) =>
inputTypes.get(name).map(OptionInputType(_)) match {
// this.types does not include builtin scalar types,
// if the schema does not use a scalar in fields or arguments at least once
// https://github.com/sangria-graphql/sangria/issues/965
case None => BuiltinScalars.find(_.name == name).map(OptionInputType(_))
case output => output
}
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, Vector[ObjectLikeType[_, _]]] =
typeList
.collect { case objectLike: ObjectLikeType[_, _] => objectLike }
.flatMap(objectLike => objectLike.interfaces.map(_.name -> objectLike))
.groupBy(_._1)
.map { case (k, v) => (k, v.map(_._2)) }
lazy val allImplementations: Map[String, Vector[ObjectLikeType[_, _]]] = {
def findDescendants(tpe: ObjectLikeType[_, _]): Vector[ObjectLikeType[_, _]] = tpe match {
case obj: ObjectType[_, _] => Vector(obj)
case interface: InterfaceType[_, _] =>
Vector(interface) ++ directImplementations(interface.name).flatMap(findDescendants)
}
directImplementations.map { case (name, directImpls) =>
name -> directImpls.flatMap(findDescendants).groupBy(_.name).map(_._2.head).toVector
}
}
def isPossibleImplementation(baseTypeName: String, tpe: ObjectLikeType[_, _]): Boolean =
tpe.name == baseTypeName || allImplementations
.get(baseTypeName)
.exists(_.exists(_.name == tpe.name))
@deprecated("Use concreteImplementations instead", "4.0.0")
lazy val implementations: Map[String, Vector[ObjectType[_, _]]] = concreteImplementations
lazy val concreteImplementations: Map[String, Vector[ObjectType[_, _]]] = allImplementations
.map { case (k, xs) =>
(
k,
xs.collect { case obj: ObjectType[_, _] =>
obj
})
}
.filter { case (_, v) =>
v.nonEmpty
}
/** This contains the map of all the concrete types by supertype.
*
* The supertype can be either a union or interface.
*
* According to the spec, even if an interface can implement another interface, this must only
* contains concrete types
*
* @see
* https://spec.graphql.org/June2018/#sec-Union
* @see
* https://spec.graphql.org/June2018/#sec-Interface
*
* @return
* Map of subtype by supertype name
*/
lazy val possibleTypes: Map[String, Vector[ObjectType[_, _]]] =
concreteImplementations ++ unionTypes.values.map(ut => ut.name -> ut.types.toVector)
def isPossibleType(baseTypeName: String, tpe: ObjectType[_, _]): Boolean =
possibleTypes.get(baseTypeName).exists(_.exists(_.name == tpe.name))
def analyzer(query: Document): SchemaBasedDocumentAnalyzer =
SchemaBasedDocumentAnalyzer(this, query)
SchemaValidationRule.validateWithException(this, validationRules)
}
object Schema {
def isBuiltInType(typeName: String): Boolean =
BuiltinScalarsByName.contains(typeName) || IntrospectionTypesByName.contains(typeName)
def isBuiltInGraphQLType(typeName: String): Boolean =
BuiltinGraphQLScalarsByName.contains(typeName) || IntrospectionTypesByName.contains(typeName)
def isBuiltInSangriaType(typeName: String): Boolean =
BuiltinSangriaScalarsByName.contains(typeName) || IntrospectionTypesByName.contains(typeName)
def isBuiltInDirective(directiveName: String): Boolean =
BuiltinDirectivesByName.contains(directiveName)
def isIntrospectionType(typeName: String): Boolean =
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): Schema[Any, Any] =
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]): Schema[Ctx, Any] =
IntrospectionSchemaMaterializer.buildSchema[Ctx, T](introspectionResult, builder)
def buildFromAst(document: ast.Document): Schema[Any, Any] =
AstSchemaMaterializer.buildSchema(document)
def buildFromAst[Ctx](document: ast.Document, builder: AstSchemaBuilder[Ctx]): Schema[Ctx, Any] =
AstSchemaMaterializer.buildSchema[Ctx](document, builder)
def buildStubFromAst(document: ast.Document): Schema[Any, Any] =
AstSchemaMaterializer.buildSchema(Document.emptyStub + document)
def buildStubFromAst[Ctx](
document: ast.Document,
builder: AstSchemaBuilder[Ctx]): Schema[Ctx, Any] =
AstSchemaMaterializer.buildSchema[Ctx](Document.emptyStub + document, builder)
def buildDefinitions(document: ast.Document): Vector[Named] =
AstSchemaMaterializer.definitions(document)
def buildDefinitions[Ctx](document: ast.Document, builder: AstSchemaBuilder[Ctx]): Vector[Named] =
AstSchemaMaterializer.definitions[Ctx](document, builder)
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy