1use std::env;
2use std::path::PathBuf;
3
4use proc_macro::TokenStream;
5use quote::quote;
6use syn::{LitStr, parse_macro_input};
7use walkdir::WalkDir;
8
9#[proc_macro]
23pub fn embed_schema(input: TokenStream) -> TokenStream {
24 let schema_dir = if input.is_empty() {
25 "database/schema".to_string()
26 } else {
27 let lit = parse_macro_input!(input as LitStr);
28 lit.value()
29 };
30
31 let manifest_dir =
32 env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set during macro expansion");
33 let abs_schema_dir = PathBuf::from(&manifest_dir).join(&schema_dir);
34
35 if !abs_schema_dir.exists() {
36 panic!("embed_schema!: schema directory does not exist: {}", abs_schema_dir.display());
37 }
38
39 let mut file_paths: Vec<PathBuf> = WalkDir::new(&abs_schema_dir)
40 .follow_links(true)
41 .into_iter()
42 .filter_map(|e| e.ok())
43 .filter(|e| e.file_type().is_file())
44 .map(|e| e.into_path())
45 .filter(|p| p.extension().and_then(|s| s.to_str()) == Some("surql"))
46 .collect();
47 file_paths.sort();
48
49 let file_entries: Vec<_> = file_paths
50 .iter()
51 .map(|abs_path| {
52 let abs_str = abs_path.to_str().expect("non-UTF8 path in schema dir");
53 let rel = abs_path.strip_prefix(&abs_schema_dir).expect("path not under schema dir");
54 let rel_display =
55 format!("{}/{}", schema_dir, rel.to_str().unwrap()).replace('\\', "/");
56
57 quote! {
58 ::surrealkit::EmbeddedSchemaFile {
59 path: #rel_display,
60 sql: include_str!(#abs_str),
61 }
62 }
63 })
64 .collect();
65
66 let expanded = quote! {
67 pub mod embedded_schema {
68 pub static SCHEMA: &[::surrealkit::EmbeddedSchemaFile] = &[
69 #(#file_entries),*
70 ];
71
72 pub async fn sync(
73 db: &::surrealkit::Surreal<::surrealkit::engine::any::Any>,
74 ) -> ::surrealkit::anyhow::Result<()> {
75 ::surrealkit::Sync::embedded(SCHEMA).run(db).await
76 }
77 }
78 };
79
80 expanded.into()
81}