// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// A MoonBit value as a GraphQL result: async-graphql's `OutputType`. Implemented
/// for the built-in scalars, `Int64` (written as a string, since GraphQL's `Int`
/// is 32-bit), `Json`, `T?` and `Array[T]`; the `#gql` generator writes it for
/// each annotated type.
pub(open) trait ToGql {
fn to_gql(Self) -> Json
}
///|
/// A MoonBit value read from an argument or a parent value: async-graphql's
/// `InputType`. A refusal is a `ResolverError` saying what was expected.
pub(open) trait FromGql {
fn from_gql(Json) -> Self raise ResolverError
}
///|
/// The refusal of a value that is not a `want`, as every [`FromGql`] here writes
/// it: `expected Int, got "x"`.
pub fn ResolverError::expected(want : String, got : Json) -> ResolverError {
ResolverError("expected \{want}, got \{got.stringify()}")
}
///|
fn refuse(got : Json, want : String) -> ResolverError {
ResolverError::expected(want, got)
}
///|
pub impl ToGql for Json with fn to_gql(self) {
self
}
///|
pub impl FromGql for Json with fn from_gql(v) {
v
}
///|
pub impl ToGql for String with fn to_gql(self) {
Json::string(self)
}
///|
pub impl FromGql for String with fn from_gql(v) {
match v {
String(s) => s
other => raise refuse(other, "String")
}
}
///|
pub impl ToGql for Bool with fn to_gql(self) {
Json::boolean(self)
}
///|
pub impl FromGql for Bool with fn from_gql(v) {
match v {
True => true
False => false
other => raise refuse(other, "Boolean")
}
}
///|
pub impl ToGql for Int32 with fn to_gql(self) {
Json::number(self.to_double())
}
///|
/// Refuses a fraction or a number outside 32 bits, as the spec's `Int` does.
pub impl FromGql for Int32 with fn from_gql(v) {
match v {
Number(n, ..) if n == n.floor() && n >= -2147483648.0 && n <= 2147483647.0 =>
n.to_int()
other => raise refuse(other, "Int")
}
}
///|
pub impl ToGql for Double with fn to_gql(self) {
Json::number(self)
}
///|
pub impl FromGql for Double with fn from_gql(v) {
match v {
Number(n, ..) => n
other => raise refuse(other, "Float")
}
}
///|
pub impl ToGql for Int64 with fn to_gql(self) {
Json::string(self.to_string())
}
///|
/// Reads the string `Int64` is written as, or a whole number.
pub impl FromGql for Int64 with fn from_gql(v) {
match v {
String(s) => @string.parse_int64(s) catch { _ => raise refuse(v, "Int64") }
Number(n, ..) if n == n.floor() => n.to_int64()
other => raise refuse(other, "Int64")
}
}
///|
pub impl[T : ToGql] ToGql for T? with fn to_gql(self) {
match self {
Some(x) => x.to_gql()
None => Json::null()
}
}
///|
pub impl[T : FromGql] FromGql for T? with fn from_gql(v) {
match v {
Null => None
v => Some(T::from_gql(v))
}
}
///|
pub impl[T : ToGql] ToGql for Array[T] with fn to_gql(self) {
Json::array(self.map(x => x.to_gql()))
}
///|
pub impl[T : FromGql] FromGql for Array[T] with fn from_gql(v) {
match v {
Array(xs) => xs.map(x => T::from_gql(x))
other => raise refuse(other, "a list")
}
}
///|
/// The argument `name` as any [`FromGql`] type; a missing argument reads as
/// `null`, so it is `None` for a `T?`. A refusal names the argument.
pub fn[T : FromGql] ResolveInfo::read(
self : ResolveInfo,
name : String,
) -> T raise ResolverError {
T::from_gql(self.arg(name)) catch {
ResolverError(m) => raise ResolverError("argument `\{name}`: \{m}")
e => raise e
}
}
///|
/// The member `key` of an object value as any [`FromGql`] type, the way a
/// generated input type reads its fields; a missing member reads as `null`. A
/// refusal names the member.
pub fn[T : FromGql] read(value : Json, key : String) -> T raise ResolverError {
guard value is Object(m) else { raise refuse(value, "an object") }
T::from_gql(m.get(key).unwrap_or(Json::null())) catch {
ResolverError(msg) => raise ResolverError("field `\{key}`: \{msg}")
e => raise e
}
}
///|
/// Any error a resolver raised, as the field error it becomes; a
/// `ResolverError` passes through unchanged.
pub fn field_error(e : Error) -> ResolverError {
match e {
ResolverError(m) => ResolverError(m)
ResolverErrorExt(m, ext) => ResolverErrorExt(m, ext)
e => ResolverError(e.to_string())
}
}