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()
Validation does not attempt structural equallity since this can have unforseen performance consequences.
For instance, if the whole graph was defined with def
s, 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 Directive
s.
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 BatchResolver
s.
The most powerful Schema
constructor stateful
, converts a State[SchemaState[F], SchemaShape[F, Q, M, S]]
to a Schema[F, Q, M, S]
.