xaeroflux_macros/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::Ident;
3use quote::quote;
4use syn::{DeriveInput, LitStr, parse_macro_input};
5
6#[proc_macro_derive(PipeKind, attributes(pipe_kind))]
7pub fn derive_pipe_kind(input: TokenStream) -> TokenStream {
8    // Parse the struct we're deriving for.
9    let input = parse_macro_input!(input as DeriveInput);
10    let struct_name = input.ident;
11
12    // Look for exactly one #[pipe_kind(...)] attribute.
13    let mut found: Option<Ident> = None;
14    for attr in input.attrs.into_iter() {
15        if attr.path().is_ident("pipe_kind") {
16            if found.is_some() {
17                panic!("Multiple `#[pipe_kind]` attributes found. Only one is allowed.");
18            }
19            // Parse the contents as a single Ident (e.g. Control or Data).
20            let kind_ident = attr.parse_args::<Ident>().expect(
21                "`#[pipe_kind(...)]` must contain exactly one bare identifier, e.g. `Control` or \
22                 `Data`",
23            );
24            found = Some(kind_ident);
25        }
26    }
27
28    let kind_ident =
29        found.expect("Missing `#[pipe_kind(Control)]` or `#[pipe_kind(Data)]` attribute.");
30
31    // Map that identifier into the corresponding BusKind variant.
32    let bus_variant = match kind_ident.to_string().as_str() {
33        "Control" => quote! { BusKind::Control },
34        "Data" => quote! { BusKind::Data },
35        other => panic!(
36            "`#[pipe_kind(...)]` must be either `Control` or `Data`, got `{}`",
37            other
38        ),
39    };
40
41    // Generate `impl StructName { pub fn new(bounds: Option<usize>) -> Self {
42    // StructName(Pipe::new(BusKind::X, bounds)) } }`.
43    let expanded = quote! {
44        impl #struct_name {
45            pub fn new(bounds: Option<usize>) -> Self {
46                use crossbeam::channel::Sender;
47                use crossbeam::channel::Receiver;
48                use crossbeam::channel::bounded;
49                let (source_tx, source_rx) =  crossbeam::channel::bounded(bounds.unwrap_or(100));
50                let (sink_tx, sink_rx) =  crossbeam::channel::bounded(bounds.unwrap_or(100));
51                let source = NetworkSource{rx: source_rx, tx: source_tx};
52                let sink = NetworkSink{tx: sink_tx, rx: sink_rx};
53                #struct_name(Arc::new(crate::networking::p2p::NetworkPipe{source,
54                    sink, bus_kind: #bus_variant,
55                    bounds}))
56            }
57        }
58    };
59    TokenStream::from(expanded)
60}
61
62#[proc_macro]
63pub fn subject(input: TokenStream) -> TokenStream {
64    // 1) Parse exactly one string literal: subject!("workspace/MyWS/object/MyObj")
65    let subject_name_tokens = parse_macro_input!(input as LitStr);
66    let span = subject_name_tokens.span();
67    let literal_str = subject_name_tokens.value();
68
69    // 2) Split on '/' and validate that we get exactly 4 parts:
70    let parts: Vec<_> = literal_str.split('/').collect();
71    if parts.len() != 4 {
72        return syn::Error::new_spanned(
73            &subject_name_tokens,
74            "Subject must look like \"workspace/<workspace_id>/object/<object_id>\"",
75        )
76        .to_compile_error()
77        .into();
78    }
79
80    // 3) Check the "workspace" / "object" prefixes:
81    if parts[0] != "workspace" || parts[2] != "object" {
82        return syn::Error::new_spanned(
83            &subject_name_tokens,
84            "Subject must look like \"workspace/<workspace_id>/object/<object_id>\"",
85        )
86        .to_compile_error()
87        .into();
88    }
89
90    // 4) Extract the actual IDs:
91    let ws_id_str = parts[1]; // e.g. "MyWorkspace"
92    let obj_id_str = parts[3]; // e.g. "MyObject"
93
94    if ws_id_str.is_empty() || obj_id_str.is_empty() {
95        return syn::Error::new_spanned(
96            &subject_name_tokens,
97            "workspace_id and object_id cannot be empty",
98        )
99        .to_compile_error()
100        .into();
101    }
102
103    // 5) Compute three separate blake3 hashes:
104    //
105    //    a. hash of workspace_id
106    //    b. hash of object_id
107    //    c. hash of (workspace_hash || object_hash)
108    //
109    let mut h = blake3::Hasher::new();
110    h.update(ws_id_str.as_bytes());
111    let ws_hash = h.finalize();
112    let ws_bytes = ws_hash.as_bytes();
113
114    let mut h = blake3::Hasher::new();
115    h.update(obj_id_str.as_bytes());
116    let obj_hash = h.finalize();
117    let obj_bytes = obj_hash.as_bytes();
118
119    let mut h = blake3::Hasher::new();
120    h.update(ws_bytes);
121    h.update(obj_bytes);
122    let subject_hash = h.finalize();
123    let subject_bytes = subject_hash.as_bytes();
124
125    // 6) Turn each 32‐byte array into a Vec<TokenStream> of byte‐literals:
126    let subject_bytes_tokens = subject_bytes
127        .iter()
128        .map(|b| quote! { #b })
129        .collect::<Vec<_>>();
130
131    let ws_bytes_tokens = ws_bytes.iter().map(|b| quote! { #b }).collect::<Vec<_>>();
132
133    let obj_bytes_tokens = obj_bytes.iter().map(|b| quote! { #b }).collect::<Vec<_>>();
134
135    // 7) Build LitStrs for workspace_id and object_id (reusing the same span):
136    let ws_id_lit = LitStr::new(ws_id_str, span);
137    let obj_id_lit = LitStr::new(obj_id_str, span);
138
139    // 8) Assemble the final token‐stream with PooledEventPtr architecture support.
140    let expanded = quote! {
141        {
142            // Bring everything into scope from the current crate:
143            use crate::subject::SubjectHash;
144            use xaeroflux_core::event::{ScanWindow, XaeroEvent};
145            use crate::subject::Subject;
146            use xaeroflux_core::event::{EventType, SystemEventKind};
147            use xaeroflux_core::date_time::emit_secs;
148            use xaeroflux_core::pool::XaeroPoolManager;
149            // Initialize ring buffer pools if not already initialized
150            XaeroPoolManager::init();
151
152            // 1) Construct the Subject itself, calling new_with_workspace(...)
153            let subject = Subject::new_with_workspace(
154                #subject_name_tokens.to_string(),         // name: String
155                [ #(#subject_bytes_tokens),* ],           // hash: [u8; 32]
156                #ws_id_lit.to_string(),                   // workspace_id: String
157                #obj_id_lit.to_string(),                  // object_id: String
158            );
159
160            // 2) Create "WorkspaceCreated" system event using XaeroPoolManager
161            let workspace_data = vec![ #(#ws_bytes_tokens),* ];
162            let wc_evt = XaeroPoolManager::create_xaero_event(
163                &workspace_data,                          // data slice
164                EventType::SystemEvent(SystemEventKind::WorkspaceCreated).to_u8(),
165                None,                                     // author_id
166                None,                                     // merkle_proof
167                None,                                     // vector_clock
168                emit_secs(),                              // timestamp
169            ).unwrap_or_else(|e| {
170                tracing::error!("Critical: Failed to create WorkspaceCreated event: {:?}", e);
171                panic!("Cannot bootstrap subject without WorkspaceCreated event - ring buffer pool exhausted");
172            });
173
174            // Send WorkspaceCreated event
175            subject.control.sink.tx.send(wc_evt)
176                .expect("failed to bootstrap: WorkspaceCreated");
177
178            // 3) Create "ObjectCreated" system event using XaeroPoolManager
179            let object_data = vec![ #(#obj_bytes_tokens),* ];
180            let oc_evt = XaeroPoolManager::create_xaero_event(
181                &object_data,                             // data slice
182                EventType::SystemEvent(SystemEventKind::ObjectCreated).to_u8(),
183                None,                                     // author_id
184                None,                                     // merkle_proof
185                None,                                     // vector_clock
186                emit_secs(),                              // timestamp
187            ).unwrap_or_else(|e| {
188                tracing::error!("Critical: Failed to create ObjectCreated event: {:?}", e);
189                panic!("Cannot bootstrap subject without ObjectCreated event - ring buffer pool exhausted");
190            });
191
192            // Send ObjectCreated event
193            subject.control.sink.tx.send(oc_evt)
194                .expect("failed to bootstrap: ObjectCreated");
195
196            // 4) Return the newly‐constructed Arc<Subject>
197            subject
198        }
199    };
200
201    // 9) Convert into a TokenStream
202    expanded.into()
203}