Defining the Schema - Scala
In most cases, we can use Schema.derived[A]
to automatically derive the schema, but if we want to define it manually, we can use the builder class in symphony.schema.builder.*
.
Creating a schema manually
This is a Scala-defined SymphonyQL's API:
enum Origin {
case EARTH, MARS, BELT
}
case class Character(name: String, origin: Origin)
case class FilterArgs(origin: Option[Origin])
case class Queries(characters: FilterArgs => Source[Character, NotUsed])
Automatically derive a schema:
Schema.derived[Queries]
// summon[Schema[Queries]]
If we want to customize it, we just need to define a new implicit.
EnumBuilder
Defining SymphonyQL Enum Type, for example:
type JavaFunction[T, R] = java.util.function.Function[T, R]
implicit val enumSchema: Schema[Origin] = EnumBuilder
.newEnum[Origin]()
.name("Origin")
.value(builder => builder.name("EARTH").isDeprecated(false).build())
.value(builder => builder.name("MARS").isDeprecated(false).build())
.value(builder => builder.name("BELT").isDeprecated(false).build())
.serialize(new JavaFunction[Origin, String]() {
override def apply(t: Origin): String = t.toString
})
.build()
InputObjectBuilder
Defining SymphonyQL Input Object Type, for example:
implicit val inputSchema: Schema[FilterArgs] = InputObjectBuilder
.newObject[FilterArgs]()
.name("FilterArgs")
.fields(builder => builder.name("name").schema(summon[Schema[Option[Origin]]]).build())
.build()
FilterArgs
will be tiled, so the input parameter is origin
and Option<Origin>
is the default supported type, no need for anything extra. For more types, please refer to the Schema Specification.
ObjectBuilder
Defining simple SymphonyQL Object Type, for example:
implicit val outputSchema: Schema[Character] = ObjectBuilder
.newObject[Character]()
.name("Character")
.field[String](
builder => builder.name("name").schema(summon[Schema[String]]).build(),
c => c.name
)
.field[Origin](
builder => builder.name("origin").schema(summon[Schema[Origin]]).build(),
c => c.origin
)
.build()
Defining complex SymphonyQL Object Type for resolver, for example:
implicit val queriesSchema: Schema[Queries] = ObjectBuilder
.newObject[Queries]()
.name("Queries")
.fieldWithArg(
builder =>
builder
.name("characters")
.schema(summon[Schema[FilterArgs => scaladsl.Source[Character, NotUsed]]])
.build(),
a => a.characters
)
.build()
Each resolver can contain multiple fields, each of which is a Query/Mutation/Subscription API. For more types, please refer to the Schema Specification.
InterfaceBuilder
Defining SymphonyQL Interface Type, for example:
implicit val newInterface = UnionBuilder.newObject[NestedInterface]
.description("NestedInterface")
.origin("symphony.apt.tests.NestedInterface")
.name("NestedInterface")
.subSchema("Mid1", summon[Schema[Mid1]])
.subSchema("Mid2", summon[Schema[Mid2]])
.build()
Mid1
and Mid2
are direct subclasses of the trait.
UnionBuilder
Defining SymphonyQL Union Type, for example:
implicit val newUnion = UnionBuilder.newObject[SearchResult]
.description("SearchResult")
.origin("symphony.apt.tests.SearchResult")
.name("SearchResult")
.subSchema("Book", summon[Schema[Book]])
.subSchema("Author", summon[Schema[Author]])
.build()
Book
and Author
are direct subclasses of the trait.
Enums, unions, interfaces
If you don't want to manually define enumerations, interfaces and union schemas, please read here.
A sealed trait will be converted to a different GraphQL type depending on its content:
- a sealed trait with only case objects will be converted to an
ENUM
- a sealed trait with only case classes will be converted to a
UNION
GraphQL does not support empty objects, so in case a sealed trait mixes case classes and case objects, a union type will be created and the case objects will have a "fake" field named _
which is not queryable:
sealed trait ORIGIN
object ORIGIN {
case object EARTH extends ORIGIN
case object MARS extends ORIGIN
case object BELT extends ORIGIN
}
The snippet above will produce the following GraphQL type:
enum Origin {
BELT
EARTH
MARS
}
Here's an example of union:
sealed trait Role
object Role {
case class Captain(shipName: String) extends Role
case class Engineer(specialty: String) extends Role
case object Mechanic extends Role
}
The snippet above will produce the following GraphQL type:
union Role = Captain | Engineer | Mechanic
type Captain {
shipName: String!
}
type Engineer {
specialty: String!
}
type Mechanic {
_: Boolean!
}
Tool annotations
@GQLDefault
Annotation to specify the default value of an input field.
@GQLDeprecated
Annotation used to indicate a type or a field is deprecated.
@GQLDescription
Annotation used to provide a description to a field or a type.
@GQLExcluded
Annotation used to exclude a field from a type.
@GQLInputName
Annotation used to customize the name of an input type.
@GQLInterface
Annotation to make a sealed trait an interface instead of a union type or an enum.
@GQLName
Annotation used to provide an alternative name to a field or a type.
@GQLUnion
Annotation to make a sealed trait a union instead of an enum.