trustfall_stubgen 0.4.1

Generate a Trustfall adapter stub for a given schema.
Documentation
use std::{collections::BTreeMap, sync::Arc};

use maplit::btreemap;
use quote::quote;
use trustfall::{Schema, SchemaAdapter, TryIntoStruct};

use super::{
    root::RustFile,
    util::{parse_import, property_resolver_fn_name},
};

pub(super) fn make_properties_file(
    querying_schema: &Schema,
    adapter: Arc<SchemaAdapter<'_>>,
    properties_file: &mut RustFile,
) {
    let query = r#"
{
    VertexType {
        name @output

        property @fold @transform(op: "count") @filter(op: ">", value: ["$zero"]) {
            properties: name @output
        }
    }
}"#;
    let variables: BTreeMap<Arc<str>, i64> = btreemap! {
        "zero".into() => 0,
    };

    #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, serde::Deserialize)]
    struct ResultRow {
        name: String,
        properties: Vec<String>,
    }

    let mut rows: Vec<_> = trustfall::execute_query(querying_schema, adapter, query, variables)
        .expect("invalid query")
        .map(|x| {
            x.try_into_struct::<ResultRow>()
                .map(|mut value| {
                    value.properties.sort_unstable();
                    value
                })
                .expect("invalid conversion")
        })
        .collect();
    rows.sort_unstable();
    for row in rows {
        let resolver = make_resolver_fn(&row.name, &row.properties);
        properties_file.top_level_items.push(resolver);
    }

    properties_file.external_imports.insert(parse_import("trustfall::provider::AsVertex"));
    properties_file.external_imports.insert(parse_import("trustfall::provider::ContextIterator"));
    properties_file
        .external_imports
        .insert(parse_import("trustfall::provider::ContextOutcomeIterator"));
    properties_file.external_imports.insert(parse_import("trustfall::provider::ResolveInfo"));
    properties_file.external_imports.insert(parse_import("trustfall::FieldValue"));

    properties_file.internal_imports.insert(parse_import("super::vertex::Vertex"));
}

fn make_resolver_fn(type_name: &str, properties: &[String]) -> proc_macro2::TokenStream {
    let resolver_name = property_resolver_fn_name(type_name);
    let ident = syn::Ident::new(&resolver_name, proc_macro2::Span::call_site());
    let mut arms: proc_macro2::TokenStream = proc_macro2::TokenStream::new();

    for property_name in properties {
        let todo_msg = format!("implement property '{property_name}' in fn `{resolver_name}()`");
        arms.extend(quote! {
            #property_name => todo!(#todo_msg),
        });
    }

    let unreachable_msg =
        format!("attempted to read unexpected property '{{property_name}}' on type '{type_name}'");
    quote! {
        pub(super) fn #ident<'a, V: AsVertex<Vertex> + 'a>(
            contexts: ContextIterator<'a, V>,
            property_name: &str,
            _resolve_info: &ResolveInfo,
        ) -> ContextOutcomeIterator<'a, V, FieldValue> {
            match property_name {
                #arms
                _ => unreachable!(#unreachable_msg),
            }
        }
    }
}