Skip to main content

The schema

SchemaShape

The SchemaShape consists of the roots that make up your gql schema; A query, mutation and subscription type. The SchemaShape also contains extra types that should occur in the schema but are not neccesarilly discoverable through a walk of the ast.

The SchemaShape also has derived information embedded in it. For instance, one can render the schema:

import cats.effect._
import cats.implicits._
import gql._
import gql.ast._
import gql.dsl.all._

def ss = SchemaShape.unit[IO](
fields(
"4hello" -> lift(_ => "world")
)
)

println(ss.render)
// type Query {
// 4hello: String!
// }

Validation

Validation of the shape is also derived information:

println(ss.validate)
// Chain(Invalid field name '4hello', the field name must match /[_A-Za-z][_0-9A-Za-z]*/ at root.Query.4hello)

Running validation is completely optional, but is highly recommended. Running queries against a unvalidated schema can have unforseen consequences.

For instance, here is a non-exhaustive list of things that can go wrong if not validated:

  • Unforseen runtime errors if two definitions of a type has diverging field definitions.
  • Names do not respect the graphql spec.
  • Missing interface field implementations.
  • Invalid default value structure.

Validation also reports other non-critical issues such as cases of ambiguity.

For instance, if a cyclic type is defined with def, validation cannot determine if the type is truely valid. Solving this would require an infinite amount of time. An exmaple follows:

final case class A()

def cyclicType(i: Int): Type[IO, A] = {
if (i < 10000) tpe[IO, A](
"A",
"a" -> lift((_: A) => A())(cyclicType(i + 1))
)
else tpe[IO, A](
"A",
"a" -> lift(_ => "now I'm a string :)")
)
}

implicit lazy val cyclic: Type[IO, A] = cyclicType(0)

def recursiveSchema = SchemaShape.unit[IO](fields("a" -> lift(_ => A())))

recursiveSchema.validate.toList.mkString("\n")
// res2: String = "Cyclic type `A` is not reference equal. Use lazy val or `cats.Eval` to declare this type. at root.Query.a.A.a.A"

After 10000 iterations the type is no longer unifyable.

One can also choose to simply ignore some of the validation errors:

recursiveSchema.validate.filter{
case Validation.Problem(Validation.Error.CyclicDivergingTypeReference("A"), _) => false
case _ => true
}
// res3: cats.data.Chain[Validation.Problem] = Chain()
info

Validation does not attempt structural equallity since this can have unforseen performance consequences.

For instance, if the whole graph was defined with defs, one could very easily accedentally construct a case of exponential running time.

Schema

A Schema is a collection of some components that are required to execute a query. The Schema contains a SchemaShape, a Statistics instance, a query Planner implementation and state regarding BatchResolver implementations and Directives.

tip

Check out the statistics section for more information on the Statistics object.

Also, check out the planning section for more information on how the default query planner works.

Finally, you can look in the resolver section for more information on BatchResolvers.

The most powerful Schema constructor stateful, converts a State[SchemaState[F], SchemaShape[F, Q, M, S]] to a Schema[F, Q, M, S].