///|
/// A struct's fields become the type's fields, named in camelCase. A skipped
/// field stays out of the schema but rides along, so a field method sees it.
#gql.type(description="Someone who writes books")
pub struct Author {
id : Int64
name : String
#gql.skip
email : String
}
///|
/// Constructors without payload are the enum's values.
#derive(Eq)
#gql.enum(description="Where a book stands")
pub enum Status {
Draft
Published
}
///|
#gql.type
pub struct Book {
id : Int64
title : String
status : Status
author_id : Int64
#gql.field(name="isbn", deprecation_reason="ISBNs are going away")
code : String?
}
///|
/// An input type: what a mutation takes.
#gql.input
pub struct NewBook {
title : String
author_id : Int64
status : Status?
}
///|
#gql.type
pub struct Magazine {
id : Int64
title : String
issue : Int32
}
///|
/// An enum whose constructors each wrap one `#gql.type` struct is an interface
/// those structs implement, with the fields `field=` names; each member must
/// have them, written the same way.
#gql.interface(field="id", field="title", description="Anything on the shelf")
pub enum Work {
Printed(Book)
Periodical(Magazine)
}
///|
/// Without fields of its own, the same enum is a union.
#gql.union
pub enum Hit {
Writer(Author)
Title(Book)
}
///|
/// A method marked `#gql.field` is a field of its type, resolved from the value
/// it is asked on; its parameters are the field's arguments.
#gql.field(description="Their books, at most `first`")
fn Author::books(self : Author, first? : Int32) -> Array[Book] {
let mine = shelf.filter(b => b.author_id == self.id)
match first {
Some(n) if n < mine.length() => mine[:n].to_owned()
_ => mine
}
}
///|
#gql.field
fn Author::contact(self : Author) -> String {
self.email
}
///|
#gql.field
fn Book::author(self : Book) -> Author? {
writers.search_by(a => a.id == self.author_id).map(i => writers[i])
}
///|
/// Free functions marked `#gql.query` and `#gql.mutation` are root fields.
#gql.query
fn author(id : Int64) -> Author? {
writers.search_by(a => a.id == id).map(i => writers[i])
}
///|
#gql.query(description="Every book, or those in one state")
fn books(status? : Status) -> Array[Book] {
match status {
Some(s) => shelf.filter(b => b.status == s)
None => shelf
}
}
///|
#gql.query
fn works() -> Array[Work] {
shelf.map(b => Printed(b)) + issues.map(m => Periodical(m))
}
///|
#gql.query(description="Authors and titles that contain `text`")
fn search(text : String) -> Array[Hit] {
writers.filter(a => a.name.contains(text)).map(a => Writer(a)) +
shelf.filter(b => b.title.contains(text)).map(b => Title(b))
}
///|
/// A resolver that raises: any error becomes a field error, and a
/// `ResolverError` keeps its message as written.
#gql.mutation
fn add_book(input : NewBook) -> Book raise {
if input.title == "" {
raise @exec.ResolverError("a book needs a title")
}
let book : Book = {
id: (shelf.length() + 1).to_int64(),
title: input.title,
status: input.status.unwrap_or(Draft),
author_id: input.author_id,
code: None,
}
shelf.push(book)
book
}
///|
let writers : Array[Author] = []
///|
let shelf : Array[Book] = []
///|
let issues : Array[Magazine] = [{ id: 10, title: "Locus", issue: 754, }]
///|
/// The same two authors and three books before every run.
fn reset() -> Unit {
writers.clear()
writers.push({ id: 1, name: "Ursula", email: "ursula@example.com", })
writers.push({ id: 2, name: "Ted", email: "ted@example.com", })
shelf.clear()
shelf.push({
id: 1,
title: "The Dispossessed",
status: Published,
author_id: 1,
code: Some("978-0060512750"),
})
shelf.push({
id: 2,
title: "Lathe",
status: Published,
author_id: 1,
code: None,
})
shelf.push({
id: 3,
title: "Exhalation",
status: Draft,
author_id: 2,
code: None,
})
}