use std::sync::LazyLock;
use apollo_compiler::ExecutableDocument;
use apollo_compiler::Schema;
use apollo_compiler::validation::Valid;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::LitStr;
const SCHEMA_DIAGNOSTIC_LABEL: &str = "<sui schema>";
const QUERY_DIAGNOSTIC_LABEL: &str = "<graphql! input>";
static VALIDATED_SCHEMA: LazyLock<Result<Valid<Schema>, String>> = LazyLock::new(|| {
Schema::parse_and_validate(crate::schema::SCHEMA_SDL, SCHEMA_DIAGNOSTIC_LABEL)
.map_err(|e| format!("Failed to parse Sui GraphQL schema: {e}"))
});
pub fn expand(input: TokenStream) -> TokenStream {
match expand_impl(input) {
Ok(tokens) => tokens.into(),
Err(err) => {
let compile_error = err.to_compile_error();
quote!({ #compile_error "" }).into()
}
}
}
fn expand_impl(input: TokenStream) -> Result<TokenStream2, syn::Error> {
let lit: LitStr = syn::parse(input)?;
let source = lit.value();
let schema = VALIDATED_SCHEMA
.as_ref()
.map_err(|e| syn::Error::new(proc_macro2::Span::call_site(), e.clone()))?;
match ExecutableDocument::parse_and_validate(schema, source.as_str(), QUERY_DIAGNOSTIC_LABEL) {
Ok(valid) => {
let formatted = valid.to_string();
Ok(quote!(#formatted))
}
Err(with_errors) => {
let mut combined: Option<syn::Error> = None;
for diag in with_errors.errors.iter() {
let msg = match diag.line_column_range() {
Some(range) => format!(
"GraphQL [line {}, col {}]: {}",
range.start.line, range.start.column, diag.error
),
None => format!("GraphQL: {}", diag.error),
};
let err = syn::Error::new(proc_macro2::Span::call_site(), msg);
match &mut combined {
Some(existing) => existing.combine(err),
None => combined = Some(err),
}
}
Err(combined.unwrap_or_else(|| {
syn::Error::new(
proc_macro2::Span::call_site(),
"GraphQL validation failed with no diagnostics",
)
}))
}
}
}