1#![doc(html_logo_url = "https://raw.githubusercontent.com/ifiokjr/gelx/main/setup/assets/logo.png")]
2
3use gelx_core::GelxMetadata;
4use gelx_core::generate_query_token_stream;
5use gelx_core::get_descriptor_sync;
6use gelx_core::resolve_path;
7use proc_macro::TokenStream;
8use proc_macro2::Span;
9use quote::ToTokens;
10use syn::Token;
11use syn::parse::Parse;
12use syn::parse_macro_input;
13
14#[proc_macro]
32pub fn gelx_raw(input: TokenStream) -> TokenStream {
33 parse_macro_input!(input as GelQueryInput)
34 .to_token_stream()
35 .into()
36}
37
38pub(crate) struct GelQueryInput {
39 pub(crate) module: syn::Ident,
40 pub(crate) query: String,
41}
42
43impl Parse for GelQueryInput {
44 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
45 let module: syn::Ident = input.parse()?;
46
47 let query_content = if input.peek(Token![,]) {
48 input.parse::<Token![,]>()?;
49
50 let marker: syn::Ident = input.parse()?;
51 input.parse::<Token![:]>()?;
52 let raw_content: syn::LitStr = input.parse()?;
53
54 if marker == "file" {
55 QueryContent::File(raw_content.value(), raw_content.span())
56 } else if marker == "query" {
57 QueryContent::Query(raw_content.value())
58 } else {
59 let message = format!("unexpected marker token: {marker}");
60 return Err(syn::Error::new_spanned(marker, message));
61 }
62 } else {
63 QueryContent::File(format!("queries/{module}.edgeql"), module.span())
64 };
65
66 let query = query_content.resolve()?;
67 Ok(Self { module, query })
68 }
69}
70
71impl ToTokens for GelQueryInput {
72 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
73 let metadata: GelxMetadata = std::env::var("GELX_METADATA_BASE64")
74 .ok()
75 .and_then(|value| GelxMetadata::try_from_base64(value).ok())
76 .unwrap_or_default();
77 let module_name = self.module.to_string();
78 let token_stream = get_descriptor_sync(&self.query, &metadata)
79 .and_then(|descriptor| {
80 generate_query_token_stream(&descriptor, &module_name, &self.query, &metadata, true)
81 })
82 .unwrap_or_else(|error| syn::Error::from(error).to_compile_error());
83
84 tokens.extend(token_stream);
85 }
86}
87
88#[derive(Debug)]
89pub(crate) enum QueryContent {
90 Query(String),
91 File(String, Span),
92}
93
94impl QueryContent {
95 pub fn resolve(self) -> syn::Result<String> {
96 match self {
97 QueryContent::Query(query) => Ok(query),
98 QueryContent::File(relative_path, span) => {
99 let path = resolve_path(relative_path, span)?;
100
101 std::fs::read_to_string(&path)
102 .map_err(|error| {
103 syn::Error::new(
104 span,
105 format!("failed to read query file at {}: {}", path.display(), error),
106 )
107 })
108 .map(|value| value.trim().to_string())
109 }
110 }
111 }
112}