///|
/// `#gql.*` on structs, enums, fields, methods and free functions, turned into
/// `register(schema, resolvers)` and the `ToGql`/`FromGql` of each annotated
/// type. An enum whose constructors each wrap one `#gql.type` struct is a union
/// or an interface over those structs. `#gql.subscription` adds
/// `register_subscriptions`, and a type with a `key` adds `register_federation`.
priv struct Gql {}
///|
impl @gen.Generator for Gql with fn name(_) {
"moongql"
}
///|
fn type_spec(name : String) -> @attr.Spec {
let spec = @attr.Spec::new(name)
.optional("name", Str)
.optional("description", Str)
if name == "gql.type" {
spec.optional("key", Str).optional("extended", Bool).optional("tag", Str)
} else {
spec
}
}
///|
let field_spec : @attr.Spec = @attr.Spec::new("gql.field")
.optional("name", Str)
.optional("description", Str)
.optional("deprecation_reason", Str)
.optional("kind", Str)
.many("permission", Str)
.optional("shareable", Bool)
.optional("inaccessible", Bool)
.optional("external", Bool)
.optional("override_from", Str)
.optional("requires", Str)
.optional("provides", Str)
.optional("tag", Str)
///|
fn root_spec(name : String) -> @attr.Spec {
@attr.Spec::new(name)
.optional("name", Str)
.optional("description", Str)
.optional("deprecation_reason", Str)
.many("permission", Str)
}
///|
let reference_spec : @attr.Spec = @attr.Spec::new("gql.reference")
///|
/// `#gql.enum`, `#gql.union`, and `#gql.interface` with the fields it names.
fn enum_spec(name : String) -> @attr.Spec {
if name == "gql.interface" {
type_spec(name).many("field", Str)
} else {
type_spec(name)
}
}
///|
let skip_spec : @attr.Spec = @attr.Spec::new("gql.skip")
///|
fn at(attr : @attribute.Attribute, file : String) -> String {
"\{file}:\{attr.loc.start.lnum}:\{attr.loc.start.column()}"
}
///|
/// The `#gql.*` attributes among `attrs`, refusing one that does not parse.
fn gql_attrs(
attrs : @list.List[@attribute.Attribute],
file : String,
) -> Array[@attribute.Attribute] raise {
for attr in attrs {
if @attr.name_of(attr) is None && attr.raw.has_prefix("#gql.") {
fail("\{at(attr, file)}: `\{attr.raw}` does not parse as an annotation")
}
}
@attr.under(attrs, "gql").filter(a => @attr.name_of(a) != Some("gql.sdl"))
}
///|
/// The one attribute among `found`, which must be one of `names`.
fn single(
found : Array[@attribute.Attribute],
names : Array[String],
what : String,
file : String,
) -> @attribute.Attribute? raise {
for attr in found {
guard @attr.name_of(attr) is Some(n) && names.contains(n) else {
let n = @attr.name_of(attr).unwrap_or("?")
fail("\{at(attr, file)}: `#\{n}` does not go on \{what}")
}
}
match found {
[] => None
[one] => Some(one)
[_, second, ..] => fail("\{at(second, file)}: one `#gql.*` per declaration")
}
}
///|
/// The optional `name=`, `description=` and `deprecation_reason=` as labelled
/// arguments of a schema call.
fn labels(args : @attr.Args, keys : Array[String]) -> Array[String] {
let out = []
for key in keys {
if args.str(key) is Some(v) {
out.push("\{key}=\{quoted(v)}")
}
}
out
}
///|
/// The generated file, built up declaration by declaration.
priv struct Out {
known : Map[String, Known]
// Each object's fields by GraphQL name, with the arguments and type they are
// written with, for the interfaces built over them.
field_types : Map[String, Map[String, (Array[String], String)]]
scalars : Map[String, Unit]
types : Array[String]
// Unions and interfaces, registered once every object and its fields are.
abstracts : Array[String]
fields : Array[String]
impls : Array[String]
mut query : Bool
mut mutation : Bool
// What `register_subscriptions` runs.
subscriptions : Array[String]
// Field directives and entities, for `register_federation`.
directives : Array[String]
entities : Map[String, Entity]
}
///|
/// A type with a federation key, and how a representation becomes one.
priv struct Entity {
name : String
key : String
extended : Bool
external : Array[String]
mut resolve : String?
}
///|
/// The federation directives written on a field, as calls.
fn federate(
out : Out,
owner : String,
type_name : String,
field : String,
args : @attr.Args,
where_ : String,
) -> Unit raise {
let place = "\{quoted(type_name)}, \{quoted(field)}"
if args.bool("shareable") is Some(true) {
out.directives.push("federation.shareable(\{place})")
}
if args.bool("inaccessible") is Some(true) {
out.directives.push("federation.inaccessible(\{place})")
}
if args.str("override_from") is Some(from) {
out.directives.push("federation.override_(\{place}, from=\{quoted(from)})")
}
if args.str("tag") is Some(name) {
out.directives.push("federation.tag_field(\{place}, name=\{quoted(name)})")
}
for key in ["requires", "provides"] {
if args.str(key) is Some(fields) {
out.directives.push(
"federation.\{key}(\{place}, fields=\{quoted(fields)})",
)
}
}
if args.bool("external") is Some(true) {
match out.entities.get(owner) {
Some(entity) => entity.external.push(field)
None => fail("\{where_}: `external` is for a type with a `key`")
}
}
}
///|
fn handle(name : String) -> String {
"gql_" + camel(name).to_lower()
}
///|
/// A struct's fields: schema calls on `obj`, and the two conversions.
fn record(
out : Out,
name : String,
known : Known,
fields : Array[@gen.Field],
public : Bool,
where_ : String,
file : String,
) -> Unit raise {
let obj = handle(name)
let writes = []
let reads = []
let keys : Map[String, Unit] = Map([])
if known.shape == Object {
writes.push("\"__typename\": \{quoted(known.name)}")
}
for field in fields {
let attr = single(
gql_attrs(field.attrs, file),
["gql.field", "gql.skip"],
"a field",
file,
)
let at_field = match attr {
Some(a) => at(a, file)
None => where_
}
let key = match attr {
Some(a) if @attr.name_of(a) is Some("gql.skip") => {
ignore(skip_spec.bind(a))
field.name
}
_ => {
let args = match attr {
Some(a) => Some(field_spec.bind(a))
None => None
}
let gql_name = match args {
Some(a) => a.str("name").unwrap_or(camel(field.name))
None => camel(field.name)
}
let kind = match args {
Some(a) => a.str("kind")
None => None
}
guard graphql_type(field.ty, out.known, kind, out.scalars) is Some(t) else {
fail(
"\{at_field}: field `\{field.name}`: `\{field.ty}` has no GraphQL type; give `#gql.field(kind=...)`",
)
}
if known.shape == Input && output_core(field.ty, out.known) is Some(o) {
fail(
"\{at_field}: field `\{field.name}`: `\{o}` is an output type; an input field takes a `#gql.input` struct, an enum or a scalar",
)
}
let extra = match args {
Some(a) => labels(a, ["deprecation_reason", "description"])
None => []
}
out.types.push(
"\{obj}.field(\{[quoted(gql_name), t, ..extra].join(", ")})",
)
field_type(out, name, gql_name, [], t)
if args is Some(a) {
if !a.strs("permission").is_empty() {
fail(
"\{at_field}: a plain field has no resolver to guard; put `permission` on a method",
)
}
federate(out, name, known.name, gql_name, a, at_field)
}
gql_name
}
}
if keys.contains(key) || key == "__typename" {
fail("\{at_field}: `\{name}` has two fields called `\{key}`")
}
keys.set(key, ())
writes.push("\{quoted(key)}: @moongql.ToGql::to_gql(self.\{field.name})")
reads.push("\{field.name}: @moongql.read(v, \{quoted(key)})")
}
let vis = if public { "pub " } else { "" }
out.impls.push(
(
$|\{vis}impl @moongql.ToGql for \{name} with fn to_gql(self) {
$| { \{writes.join(", ")} }
$|}
$|
$|\{vis}impl @moongql.FromGql for \{name} with fn from_gql(v) {
$| \{name}::{ \{reads.join(", ")} }
$|}
),
)
if public {
out.impls.push(extensions(name))
}
}
///|
/// An enum of constructors without payload, as a GraphQL enum.
fn enumeration(
out : Out,
name : String,
known : Known,
raw : @syntax.Impl,
args : @attr.Args,
public : Bool,
where_ : String,
) -> Unit raise {
guard raw is TopTypeDef({ components: Variant(constrs), .. }) else {
fail("\{where_}: `#gql.enum` goes on an enum")
}
let names = []
for c in constrs {
if c.args is Some(_) {
fail(
"\{where_}: `\{name}::\{c.name.name}` carries a payload; a GraphQL enum value does not",
)
}
names.push(c.name.name)
}
let extra = labels(args, ["description"])
out.types.push(
"schema.enum_(\{[quoted(known.name), "[\{names.map(quoted).join(", ")}]", ..extra].join(", ")})",
)
let writes = names.map(n => "\{n} => \{quoted(n)}")
let reads = names.map(n => "String(\{quoted(n)}) => \{n}")
let vis = if public { "pub " } else { "" }
out.impls.push(
(
$|\{vis}impl @moongql.ToGql for \{name} with fn to_gql(self) {
$| match self {
$| \{writes.join("\n")}
$| }
$|}
$|
$|\{vis}impl @moongql.FromGql for \{name} with fn from_gql(v) {
$| match v {
$| \{reads.join("\n")}
$| other => raise @moongql.ResolverError::expected(\{quoted(known.name)}, other)
$| }
$|}
),
)
if public {
out.impls.push(extensions(name))
}
}
///|
fn field_type(
out : Out,
owner : String,
gql_name : String,
args : Array[String],
t : String,
) -> Unit {
match out.field_types.get(owner) {
Some(m) => m[gql_name] = (args, t)
None => out.field_types[owner] = Map([(gql_name, (args, t))])
}
}
///|
/// The struct a constructor wraps, when it wraps exactly one, by plain name.
fn payload(c : @syntax.ConstrDecl) -> String? {
guard c.args is Some(args) && args.to_array() is [param] else { return None }
match param.ty {
Name(constr_id={ id: Ident(name~), .. }, tys~, ..) if tys.is_empty() =>
Some(name)
_ => None
}
}
///|
/// An enum whose constructors each wrap one `#gql.type` struct, as a union of
/// those object types, or as an interface they implement with the fields
/// `field=` names. A value converts by its constructor, and back by the
/// `__typename` its object carries.
fn abstract_type(
out : Out,
name : String,
known : Known,
raw : @syntax.Impl,
args : @attr.Args,
public : Bool,
where_ : String,
) -> Unit raise {
let what = if known.shape == Union { "union" } else { "interface" }
guard raw is TopTypeDef({ components: Variant(constrs), .. }) else {
fail("\{where_}: `#gql.\{what}` goes on an enum")
}
let members = []
for c in constrs {
guard payload(c) is Some(ty) &&
out.known.get(ty) is Some({ shape: Object, name: gql_name, }) else {
fail(
"\{where_}: `\{name}::\{c.name.name}` does not wrap one `#gql.type` struct",
)
}
members.push((c.name.name, ty, gql_name))
}
if members.is_empty() {
fail("\{where_}: `\{name}` has no members")
}
let extra = labels(args, ["description"])
if known.shape == Union {
out.abstracts.push(
"schema.union(\{[quoted(known.name), "[\{members.map(m => quoted(m.2)).join(", ")}]", ..extra].join(", ")})",
)
} else {
let fields = args.strs("field")
if fields.is_empty() {
fail("\{where_}: `#gql.interface` names at least one `field`")
}
let iface = handle(name)
out.abstracts.push(
"let \{iface} = schema.interface(\{[quoted(known.name), ..extra].join(", ")})",
)
for field in fields {
let mut written = None
for m in members {
guard out.field_types.get(m.1).bind(f => f.get(field)) is Some(sig) else {
fail("\{where_}: `\{m.1}` has no field `\{field}`")
}
match written {
Some(w) if w != sig =>
fail(
"\{where_}: field `\{field}` is not the same in every member of `\{name}`",
)
_ => written = Some(sig)
}
}
let (args, t) = written.unwrap()
out.abstracts.push(
if args.is_empty() {
"\{iface}.field(\{quoted(field)}, \{t})"
} else {
"\{iface}.field_args(\{quoted(field)}, [\{args.join(", ")}], \{t})"
},
)
}
for m in members {
out.abstracts.push("\{handle(m.1)}.implements(\{quoted(known.name)})")
}
}
let writes = members.map(m => "\{m.0}(v) => @moongql.ToGql::to_gql(v)")
let reads = members.map(m => {
"{ \"__typename\": String(\{quoted(m.2)}), .. } => \{m.0}(@moongql.FromGql::from_gql(v))"
})
let vis = if public { "pub " } else { "" }
out.impls.push(
(
$|\{vis}impl @moongql.ToGql for \{name} with fn to_gql(self) {
$| match self {
$| \{writes.join("\n")}
$| }
$|}
$|
$|\{vis}impl @moongql.FromGql for \{name} with fn from_gql(v) {
$| match v {
$| \{reads.join("\n")}
$| other => raise @moongql.ResolverError::expected(\{quoted(known.name)}, other)
$| }
$|}
),
)
if public {
out.impls.push(extensions(name))
}
}
///|
fn is_info(ty : String) -> Bool {
ty is ("ResolveInfo" | "@moongql.ResolveInfo" | "@exec.ResolveInfo")
}
///|
/// A function's parameters as GraphQL arguments and the call reading each
/// from `info`. `self` is passed in as `self_`.
fn call_of(
out : Out,
params : ArrayView[@gen.Param],
where_ : String,
) -> (Array[String], Array[String]) raise {
let gql_args = []
let call = []
for param in params {
guard param.name is Some(name) && param.ty is Some(ty) else {
fail("\{where_}: every parameter needs a name and a type")
}
if is_info(ty) {
call.push(
match param.pass {
Positional => "info"
Labelled => "\{name}=info"
Optional | Defaulted => "\{name}?=Some(info)"
},
)
continue
}
let gql_name = camel(name)
let optional = param.pass is (Optional | Defaulted)
let arg_ty = if optional && inner_option(ty) is None {
ty + "?"
} else {
ty
}
guard graphql_type(arg_ty, out.known, None, out.scalars) is Some(t) else {
fail("\{where_}: parameter `\{name}`: `\{ty}` has no GraphQL type")
}
if output_core(ty, out.known) is Some(o) {
fail(
"\{where_}: parameter `\{name}`: `\{o}` is an output type; an argument takes a `#gql.input` struct, an enum or a scalar",
)
}
gql_args.push("(\{quoted(gql_name)}, \{t})")
let read = "info.read(\{quoted(gql_name)})"
call.push(
match param.pass {
Positional => read
Labelled => "\{name}=\{read}"
Optional | Defaulted => "\{name}?=\{read}"
},
)
}
(gql_args, call)
}
///|
/// Whether a function declares that it raises.
fn raises(raw : @syntax.Impl) -> Bool {
match raw {
TopFuncDef(fun_decl={ error_type: NoErrorType | Noraise(..), .. }, ..) =>
false
_ => true
}
}
///|
/// The schema call and the resolver of one function field on `obj`, whose
/// type the resolver map knows as `owner`.
fn function_field(
out : Out,
obj : String,
owner : String,
target : String,
attr : @attribute.Attribute,
args : @attr.Args,
name : String,
params : ArrayView[@gen.Param],
returns : String?,
raw : @syntax.Impl,
file : String,
self_type : String?,
) -> Unit raise {
let where_ = at(attr, file)
let gql_name = args.str("name").unwrap_or(camel(name))
guard returns is Some(r) && r != "Unit" else {
fail("\{where_}: a GraphQL field returns a value")
}
guard graphql_type(r, out.known, args.str("kind"), out.scalars) is Some(t) else {
fail("\{where_}: `\{r}` has no GraphQL type; give `kind=`")
}
let (gql_args, call) = call_of(out, params, where_)
if self_type is Some(ty) {
field_type(out, ty, gql_name, gql_args, t)
}
let extra = labels(args, ["deprecation_reason", "description"])
out.fields.push(
if gql_args.is_empty() {
"\{obj}.field(\{[quoted(gql_name), t, ..extra].join(", ")})"
} else {
"\{obj}.field_args(\{[quoted(gql_name), "[\{gql_args.join(", ")}]", t, ..extra].join(", ")})"
},
)
let call = match self_type {
Some(_) => "\{target}(\{["self_", ..call].join(", ")})"
None => "\{target}(\{call.join(", ")})"
}
let value = "@moongql.ToGql::to_gql(\{call})"
let value = if raises(raw) {
"\{value} catch { e => raise @moongql.field_error(e) }"
} else {
value
}
let guarded = guarded(args, value)
let body = match self_type {
Some(ty) =>
"{ let self_ : \{ty} = @moongql.FromGql::from_gql(info.parent); \{guarded} }"
None => guarded
}
let info = if self_type is Some(_) ||
params.length() > 0 ||
!args.strs("permission").is_empty() {
"info"
} else {
"_"
}
out.fields.push(
"resolvers.field(\{owner}, \{quoted(gql_name)}, \{info} => \{body})",
)
}
///|
/// `value` behind the checks `permission` names, each a function of `ResolveInfo`.
fn guarded(args : @attr.Args, value : String) -> String {
let checks = args.strs("permission").map(p => "\{p}(info)")
if checks.is_empty() {
value
} else {
"{ if !(\{checks.join(" && ")}) { raise @moongql.not_permitted() }; \{value} }"
}
}
///|
/// A subscription root field: `Array[T]` is the events, each a `T`.
fn subscription_field(
out : Out,
attr : @attribute.Attribute,
args : @attr.Args,
name : String,
params : ArrayView[@gen.Param],
returns : String?,
raw : @syntax.Impl,
file : String,
) -> Unit raise {
let where_ = at(attr, file)
guard returns is Some(r) && inner_array(r) is Some(item) else {
fail("\{where_}: a subscription returns its events, an `Array[T]`")
}
guard graphql_type(item, out.known, None, out.scalars) is Some(t) else {
fail("\{where_}: `\{item}` has no GraphQL type")
}
let gql_name = args.str("name").unwrap_or(camel(name))
let (gql_args, call) = call_of(out, params, where_)
let extra = labels(args, ["deprecation_reason", "description"])
let root = "gql_subscription"
out.subscriptions.push(
if gql_args.is_empty() {
"\{root}.field(\{[quoted(gql_name), t, ..extra].join(", ")})"
} else {
"\{root}.field_args(\{[quoted(gql_name), "[\{gql_args.join(", ")}]", t, ..extra].join(", ")})"
},
)
let events = "\{name}(\{call.join(", ")})"
let events = if raises(raw) {
"\{events} catch { e => raise @moongql.field_error(e) }"
} else {
events
}
let source = guarded(args, "(\{events}).map(e => @moongql.ToGql::to_gql(e))")
let used = params.length() > 0 || !args.strs("permission").is_empty()
let info = if used { "info" } else { "_" }
out.subscriptions.push(
"subscribers.field(\{root}.name, \{quoted(gql_name)}, \{info} => \{source})",
)
out.subscriptions.push(
"resolvers.field(\{root}.name, \{quoted(gql_name)}, info => info.parent)",
)
}
///|
/// The function that turns a representation back into an entity. Its parameters
/// are read from the representation by their GraphQL names, and what it returns
/// says which entity it is for.
fn reference(
out : Out,
attr : @attribute.Attribute,
name : String,
params : Array[@gen.Param],
returns : String?,
raw : @syntax.Impl,
file : String,
) -> Unit raise {
let where_ = at(attr, file)
ignore(reference_spec.bind(attr))
let ty = returns.map(r => inner_option(r).unwrap_or(r)).unwrap_or("")
guard out.entities.get(ty) is Some(entity) else {
fail("\{where_}: `#gql.reference` returns a type with a `key`, not `\{ty}`")
}
if entity.resolve is Some(_) {
fail("\{where_}: `\{ty}` has a reference function already")
}
let reads = []
for param in params {
guard param.name is Some(p) && param.pass is Positional else {
fail("\{where_}: a reference function takes plain parameters")
}
reads.push("@moongql.read(rep, \{quoted(camel(p))})")
}
let call = "\{name}(\{reads.join(", ")})"
let call = if raises(raw) {
"\{call} catch { e => raise @moongql.field_error(e) }"
} else {
call
}
entity.resolve = Some("@moongql.ToGql::to_gql(\{call})")
}
///|
/// First pass: the annotated types, so that a field may name one declared
/// further down.
fn collect(decls : Array[@gen.Decl], out : Out) -> Unit raise {
for decl in decls {
match decl {
Struct(name~, attrs~, file~, ..) =>
match
single(
gql_attrs(attrs, file),
["gql.type", "gql.input"],
"a struct",
file,
) {
Some(attr) => {
let full = @attr.name_of(attr).unwrap_or("")
let args = type_spec(full).bind(attr)
let shape = if full == "gql.type" { Object } else { Input }
out.known.set(name, {
name: args.str("name").unwrap_or(name),
shape,
})
}
None => ()
}
Enum(name~, attrs~, file~, ..) =>
match
single(
gql_attrs(attrs, file),
["gql.enum", "gql.union", "gql.interface"],
"an enum",
file,
) {
Some(attr) => {
let full = @attr.name_of(attr).unwrap_or("")
let args = enum_spec(full).bind(attr)
out.known.set(name, {
name: args.str("name").unwrap_or(name),
shape: match full {
"gql.union" => Union
"gql.interface" => Interface
_ => Enum
},
})
}
None => ()
}
_ => ()
}
}
}
///|
impl @gen.Generator for Gql with fn generate(_, decls) {
let out : Out = {
known: Map([]),
field_types: Map([]),
abstracts: [],
scalars: Map([]),
types: [],
fields: [],
impls: [],
query: false,
mutation: false,
subscriptions: [],
directives: [],
entities: Map([]),
}
collect(decls, out)
let abstracts = []
let references = []
for decl in decls {
match decl {
Struct(name~, fields~, attrs~, public~, file~, ..) => {
guard out.known.get(name) is Some(known) else {
for field in fields {
if gql_attrs(field.attrs, file) is [first, ..] {
fail(
"\{at(first, file)}: `\{name}` has no `#gql.type` or `#gql.input`, so its fields take no `#gql.*`",
)
}
}
continue
}
let attr = gql_attrs(attrs, file)[0]
let full = @attr.name_of(attr).unwrap_or("")
let args = type_spec(full).bind(attr)
let call = if known.shape == Object { "object" } else { "input" }
let extra = labels(args, ["description"])
if full == "gql.type" && args.str("key") is Some(key) {
out.entities[name] = {
name: known.name,
key,
extended: args.bool("extended") is Some(true),
external: [],
resolve: None,
}
}
if args.str("tag") is Some(tag) {
out.directives.push(
"federation.tag(\{quoted(known.name)}, name=\{quoted(tag)})",
)
}
out.types.push(
"let \{handle(name)} = schema.\{call}(\{[quoted(known.name), ..extra].join(", ")})",
)
record(out, name, known, fields, public, at(attr, file), file)
}
Enum(name~, public~, file~, raw~, ..) => {
guard out.known.get(name) is Some(known) else { continue }
let attr = gql_attrs(decl.attrs(), file)[0]
if known.shape == Enum {
let args = type_spec("gql.enum").bind(attr)
enumeration(out, name, known, raw, args, public, at(attr, file))
} else {
abstracts.push((name, known, raw, attr, public, file))
}
}
Func(owner~, name~, params~, returns~, is_async~, attrs~, file~, raw~, ..) => {
let attr = match owner {
Some(_) =>
single(gql_attrs(attrs, file), ["gql.field"], "a method", file)
None =>
single(
gql_attrs(attrs, file),
["gql.query", "gql.mutation", "gql.subscription", "gql.reference"],
"a free function",
file,
)
}
guard attr is Some(attr) else { continue }
if is_async {
fail("\{at(attr, file)}: a moongql resolver is synchronous")
}
match owner {
Some(ty) => {
guard out.known.get(ty) is Some({ name: type_name, shape: Object, }) else {
fail(
"\{at(attr, file)}: `\{ty}` has no `#gql.type`, so its methods are no fields",
)
}
guard params is [{ name: Some("self"), .. }, .. rest] else {
fail("\{at(attr, file)}: a field method takes `self` first")
}
let args = field_spec.bind(attr)
function_field(
out,
handle(ty),
quoted(type_name),
"\{ty}::\{name}",
attr,
args,
name,
rest,
returns,
raw,
file,
Some(ty),
)
}
None => {
let full = @attr.name_of(attr).unwrap_or("")
if full == "gql.reference" {
references.push((attr, name, params, returns, raw, file))
continue
}
let args = root_spec(full).bind(attr)
if full == "gql.subscription" {
subscription_field(
out, attr, args, name, params, returns, raw, file,
)
continue
}
let obj = if full == "gql.query" {
out.query = true
"gql_query"
} else {
out.mutation = true
"gql_mutation"
}
function_field(
out,
obj,
"\{obj}.name",
name,
attr,
args,
name,
params,
returns,
raw,
file,
None,
)
}
}
}
Other(..) => ()
}
}
for found in references {
let (attr, name, params, returns, raw, file) = found
reference(out, attr, name, params, returns, raw, file)
}
for abs in abstracts {
let (name, known, raw, attr, public, file) = abs
let full = @attr.name_of(attr).unwrap_or("")
abstract_type(
out,
name,
known,
raw,
enum_spec(full).bind(attr),
public,
at(attr, file),
)
}
schema_first(decls, out)
if out.types.is_empty() &&
out.fields.is_empty() &&
out.subscriptions.is_empty() {
return @quote.join([])
}
let setup = []
for scalar in out.scalars.keys() {
setup.push(
"if schema.scalar_by_name(\{quoted(scalar)}) is None { schema.scalar(\{quoted(scalar)}) }",
)
}
setup.append(out.types)
setup.append(out.abstracts)
if out.query {
setup.push(
"let gql_query = match schema.type_by_name(schema.query) { Some(t) => t; None => schema.object(schema.query) }",
)
}
if out.mutation {
setup.push(
(
$|let gql_mutation = match schema.mutation {
$| Some(n) => match schema.type_by_name(n) { Some(t) => t; None => schema.object(n) }
$| None => { schema.set_mutation("Mutation"); schema.object("Mutation") }
$|}
),
)
}
setup.append(out.fields)
let sections = [
(
$|pub fn register(schema : @moongql.Schema, resolvers : @moongql.Resolvers) -> Unit {
$| \{setup.join("\n")}
$|}
),
]
if !out.subscriptions.is_empty() {
sections.push(
(
$|pub fn register_subscriptions(schema : @moongql.Schema, resolvers : @moongql.Resolvers, subscribers : @moongql.Subscribers) -> Unit {
$| let gql_subscription = match schema.subscription {
$| Some(n) => match schema.type_by_name(n) { Some(t) => t; None => schema.object(n) }
$| None => { schema.set_subscription("Subscription"); schema.object("Subscription") }
$| }
$| \{out.subscriptions.join("\n")}
$|}
),
)
}
if !out.entities.is_empty() || !out.directives.is_empty() {
let calls = []
for _, entity in out.entities {
let parts = [
"name=\{quoted(entity.name)}",
"key=\{quoted(entity.key)}",
"resolve=(rep, _) => \{entity.resolve.unwrap_or("rep")}",
]
if entity.extended {
parts.push("extends=true")
}
if !entity.external.is_empty() {
parts.push("external=[\{entity.external.map(quoted).join(", ")}]")
}
calls.push("federation.entity(\{parts.join(", ")})")
}
calls.append(out.directives)
sections.push(
(
$|pub fn register_federation(federation : @moongql.Federation) -> Unit {
$| \{calls.join("\n")}
$|}
),
)
}
sections.append(out.impls)
@quote.items(sections.join("\n\n")).render()
}