cosmic_macros/
lib.rs

1#![crate_type = "lib"]
2#![allow(warnings)]
3
4#[macro_use]
5extern crate lazy_static;
6#[macro_use]
7extern crate strum_macros;
8
9use proc_macro::TokenStream;
10use std::str::FromStr;
11
12use chrono::{DateTime, Utc};
13use cosmic_space::loc;
14use nom::combinator::into;
15use nom_locate::LocatedSpan;
16use proc_macro2::Ident;
17use quote::__private::ext::RepToTokensExt;
18use quote::{format_ident, quote, ToTokens, TokenStreamExt};
19use regex::Regex;
20use syn::__private::TokenStream2;
21use syn::parse::{Parse, ParseBuffer, ParseStream};
22use syn::parse_quote::ParseQuote;
23use syn::spanned::Spanned;
24use syn::token::Async;
25use syn::{
26    parse_macro_input, AngleBracketedGenericArguments, Attribute, Data, DataEnum, DataUnion,
27    DeriveInput, FieldsNamed, FieldsUnnamed, FnArg, GenericArgument, ImplItem, ItemImpl,
28    ItemStruct, PathArguments, PathSegment, ReturnType, Signature, Type, Visibility,
29};
30
31use cosmic_space::parse::route_attribute_value;
32use cosmic_space::util::log;
33use cosmic_space::wasm::Timestamp;
34
35#[no_mangle]
36extern "C" fn cosmic_uuid() -> loc::Uuid {
37    loc::Uuid::from(uuid::Uuid::new_v4().to_string()).unwrap()
38}
39
40#[no_mangle]
41extern "C" fn cosmic_timestamp() -> Timestamp {
42    Timestamp::new(Utc::now().timestamp_millis())
43}
44
45/// This macro will auto implement the `cosmic_space::wave::exchange::asynch::DirectedHandler` trait.
46/// In order to finalize the implementation a `#[handler]` attribute must also be specified
47/// above one of the impls.
48#[proc_macro_derive(DirectedHandler)]
49pub fn directed_handler(item: TokenStream) -> TokenStream {
50    TokenStream::from(quote! {})
51}
52
53/// This impl attribute creates a `fn handler` which receives directed waves and routes them to the first local method
54/// which the route selector matches.
55/// To implement:
56/// ```
57///
58/// use cosmic_space::err::SpaceErr;
59/// use cosmic_space::hyper::HyperSubstance;
60/// use cosmic_space::log::PointLogger;
61/// use cosmic_space::substance::Substance;
62/// use cosmic_space::substance::Substance::Text;
63/// use cosmic_space::wave::core::ReflectedCore;
64/// use cosmic_space::wave::exchange::asynch::InCtx;
65///
66/// #[derive(DirectedHandler)]
67/// pub struct MyHandler {
68///   logger: PointLogger
69/// }
70///
71/// #[handler]
72/// impl MyHandler {
73///     /// the route attribute captures an ExtMethod implementing a custom `MyNameIs`
74///     /// notice that the InCtx will accept any valid cosmic_space::substance::Substance
75///     #[route("Ext<MyNameIs>")]
76///     pub async fn hello(&self, ctx: InCtx<'_, Text>) -> Result<String, SpaceErr> {
77///         /// also we can return any Substance in our Reflected wave
78///         Ok(format!("Hello, {}", ctx.input.to_string()))
79///     }
80///
81///     /// if the function returns nothing then an Empty Ok Reflected will be returned unless
82///     /// the wave type is `Wave<Signal>`
83///     #[route("Ext<Bye>")]
84///     pub async fn bye(&self, ctx: InCtx<'_,()>) {
85///        self.logger.info("funny that! He left without saying a word!");
86///     }
87/// }
88#[proc_macro_attribute]
89pub fn handler(attr: TokenStream, item: TokenStream) -> TokenStream {
90    _handler(attr, item, true)
91}
92
93#[proc_macro_attribute]
94pub fn handler_sync(attr: TokenStream, item: TokenStream) -> TokenStream {
95    _handler(attr, item, false)
96}
97
98fn _handler(attr: TokenStream, item: TokenStream, _async: bool) -> TokenStream {
99    let item_cp = item.clone();
100    let mut impl_item = parse_macro_input!(item_cp as syn::ItemImpl);
101    //    let mut selectors = vec![];
102    let mut static_selectors = vec![];
103    let mut static_selector_keys = vec![];
104    let mut idents = vec![];
105    let impl_name = find_impl_type(&impl_item);
106
107    //    let mut output = vec![];
108
109    for item_impl in &impl_item.items {
110        if let ImplItem::Method(call) = item_impl {
111            if let Some(attr) = find_route_attr(&call.attrs) {
112                let internal = attr.tokens.to_token_stream().to_string();
113                idents.push(format_ident!("__{}__route", call.sig.ident.clone()));
114                let selector_ident = format_ident!("__{}_{}__", impl_name, call.sig.ident);
115                let route_selector = attr.to_token_stream().to_string();
116                static_selector_keys.push(selector_ident.clone());
117                let static_selector = quote! {
118                    static ref #selector_ident : cosmic_space::config::bind::RouteSelector = cosmic_space::parse::route_attribute(#route_selector).unwrap();
119                };
120                static_selectors.push(static_selector);
121            //println!(" ~~ ROUTE {}", attr.tokens.to_string() );
122            /*                let route = route( attr.to_token_stream().into(), call.to_token_stream().into() );
123                           let mut route = parse_macro_input!(route as syn::ImplItem );
124                           output.push(route);
125
126            */
127            } else {
128                //                output.push( ImplItem::Method(call) );
129            }
130        } else {
131            //           output.push( item_impl );
132        }
133    }
134
135    //    impl_item.items.append( & mut output );
136
137    let self_ty = impl_item.self_ty.clone();
138    let generics = impl_item.generics.clone();
139    let where_clause = impl_item.generics.where_clause.clone();
140
141    let attr: TokenStream2 = attr.into();
142
143    let rtn = if attr.is_empty() {
144        quote! {Ok(RespCore::not_found())}
145    } else {
146        let rtn = match _async {
147            true => quote! {
148            #attr.handle(request).await },
149            false => quote! {
150            #attr.handler.handle(request) },
151        };
152        rtn
153    };
154
155    let selector = match _async {
156        true => quote! {cosmic_space::wave::exchange::asynch::DirectedHandlerSelector},
157        false => quote! {cosmic_space::wave::exchange::synch::DirectedHandlerSelector},
158    };
159
160    let handler = match _async {
161        true => quote! {cosmic_space::wave::exchange::asynch::DirectedHandler},
162        false => quote! {cosmic_space::wave::exchange::synch::DirectedHandler},
163    };
164
165    let root_ctx = match _async {
166        true => quote! {cosmic_space::wave::exchange::asynch::RootInCtx},
167        false => quote! {cosmic_space::wave::exchange::synch::RootInCtx},
168    };
169
170    let _await = match _async {
171        true => quote! {.await},
172        false => quote! {},
173    };
174
175    let _async_trait = match _async {
176        true => quote! {#[async_trait]},
177        false => quote! {},
178    };
179
180    let _async = match _async {
181        true => quote! {async},
182        false => quote! {},
183    };
184
185    let rtn = quote! {
186        impl #generics #selector for #self_ty #where_clause{
187              fn select<'a>( &self, select: &'a cosmic_space::wave::RecipientSelector<'a>, ) -> Result<&dyn #handler, ()> {
188                if select.wave.core().method == cosmic_space::wave::core::Method::Cmd(cosmic_space::wave::core::cmd::CmdMethod::Bounce) {
189                    return Ok(self);
190                }
191                #(
192                    if #static_selector_keys.is_match(&select.wave).is_ok() {
193                        return Ok(self);
194                    }
195                )*
196                Err(())
197              }
198        }
199
200        #_async_trait
201        impl #generics #handler for #self_ty #where_clause{
202            #_async fn handle( &self, ctx: #root_ctx) -> cosmic_space::wave::core::CoreBounce {
203                #(
204                    if #static_selector_keys.is_match(&ctx.wave).is_ok() {
205                       return self.#idents( ctx )#_await;
206                    }
207                )*
208                if ctx.wave.core().method == cosmic_space::wave::core::Method::Cmd(cosmic_space::wave::core::cmd::CmdMethod::Bounce) {
209                    return self.bounce(ctx)#_await;
210                }
211                ctx.not_found().into()
212             }
213        }
214
215        lazy_static! {
216            #( #static_selectors )*
217        }
218
219    };
220
221    //    println!("{}", rtn.to_string());
222
223    TokenStream2::from_iter(vec![rtn, TokenStream2::from(item)]).into()
224}
225
226fn find_impl_type(item_impl: &ItemImpl) -> Ident {
227    if let Type::Path(path) = &*item_impl.self_ty {
228        path.path.segments.last().as_ref().unwrap().ident.clone()
229    } else {
230        panic!("could not get impl name")
231    }
232}
233
234fn find_route_attr(attrs: &Vec<Attribute>) -> Option<Attribute> {
235    for attr in attrs {
236        if attr
237            .path
238            .segments
239            .last()
240            .expect("segment")
241            .to_token_stream()
242            .to_string()
243            .as_str()
244            == "route"
245        {
246            return Some(attr.clone());
247        }
248    }
249    return None;
250}
251
252/*
253#[proc_macro_attribute]
254pub fn route(attr: TokenStream, item: TokenStream ) -> TokenStream {
255    item
256}
257
258 */
259
260#[proc_macro_attribute]
261pub fn route(attr: TokenStream, input: TokenStream) -> TokenStream {
262    //  let combined = TokenStream::from_iter( vec![attr,item]);
263
264    let input = parse_macro_input!(input as syn::ImplItemMethod);
265
266    log(route_attribute_value(attr.to_string().as_str())).expect("valid route selector");
267
268    //    attr.to_tokens().next();
269    // we do this just to mem for a valid selector...
270    //log(wrapped_route_selector(attr.tokens.to_string().as_str())).expect("properly formatted route selector");
271
272    let params: Vec<FnArg> = input.sig.inputs.clone().into_iter().collect();
273    let ctx = params
274        .get(1)
275        .expect("route expected InCtx<I,M> as first parameter");
276    let ctx = messsage_ctx(ctx).expect("route expected InCtx<I,M> as first parameter");
277
278    let __await = match input.sig.asyncness {
279        None => quote! {},
280        Some(_) => quote! {.await},
281    };
282
283    let root_ctx = match input.sig.asyncness {
284        None => quote! {cosmic_space::wave::exchange::synch::RootInCtx},
285        Some(_) => quote! {cosmic_space::wave::exchange::asynch::RootInCtx},
286    };
287
288    let in_ctx = match input.sig.asyncness {
289        None => quote! {cosmic_space::wave::exchange::synch::InCtx},
290        Some(_) => quote! {cosmic_space::wave::exchange::asynch::InCtx},
291    };
292
293    let __async = match input.sig.asyncness {
294        None => quote! {},
295        Some(_) => quote! {async},
296    };
297
298    let orig = input.sig.ident.clone();
299    let ident = format_ident!("__{}__route", input.sig.ident);
300    let rtn_type = rtn_type(&input.sig.output);
301    let item = ctx.item;
302
303    let expanded = quote! {
304      #__async fn #ident( &self, mut ctx: #root_ctx ) -> cosmic_space::wave::core::CoreBounce {
305          let ctx: #in_ctx<'_,#item> = match ctx.push::<#item>() {
306              Ok(ctx) => ctx,
307              Err(err) => {
308                    if ctx.wave.is_signal() {
309                      return cosmic_space::wave::core::CoreBounce::Absorbed;
310                    }
311                    else {
312                      return cosmic_space::wave::core::CoreBounce::Reflected(err.into());
313                    }
314              }
315          };
316
317          let result = self.#orig(ctx)#__await;
318          #rtn_type
319      }
320
321      #input
322
323    };
324
325    //println!("{}", expanded.to_string());
326    TokenStream::from(expanded)
327}
328
329pub(crate) enum Item {
330    Request,
331    RequestCore,
332    Payload,
333}
334
335impl FromStr for Item {
336    type Err = String;
337
338    fn from_str(s: &str) -> Result<Self, Self::Err> {
339        match s {
340            "Request" => Ok(Item::Request),
341            "RequestCore" => Ok(Item::RequestCore),
342            "Payload" => Ok(Item::Payload),
343            what => panic!("cannot convert Request to type '{}'", what),
344        }
345    }
346}
347
348pub(crate) struct RequestCtx {
349    pub item: GenericArgument,
350}
351
352fn messsage_ctx(input: &FnArg) -> Result<RequestCtx, String> {
353    if let FnArg::Typed(i) = input {
354        if let Type::Path(path) = &*i.ty {
355            if let PathArguments::AngleBracketed(generics) = &path
356                .path
357                .segments
358                .last()
359                .expect("expected last segment")
360                .arguments
361            {
362                let mut args = generics.args.clone();
363                let item = args
364                    .pop()
365                    .expect("expecting a generic for Context Item")
366                    .into_value();
367
368                let ctx = RequestCtx { item };
369
370                return Ok(ctx);
371            }
372        }
373    }
374    Err("Parameter is not a RequestCtx".to_string())
375}
376
377fn rtn_type(output: &ReturnType) -> TokenStream2 {
378    match output {
379        ReturnType::Default => {
380            quote! {cosmic_space::wave::Bounce::Absorbed}
381        }
382        ReturnType::Type(_, path) => {
383            if let Type::Path(path) = &**path {
384                let PathSegment { ident, arguments } = path.path.segments.last().unwrap();
385                match ident.to_string().as_str() {
386                    "Result" => {
387                        if let PathArguments::AngleBracketed(brackets) = arguments {
388                            let arg = brackets.args.first().unwrap();
389                            if "Substance" == arg.to_token_stream().to_string().as_str() {
390                                quote! {
391                                 use cosmic_space::err::CoreReflector;
392                                 match result {
393                                     Ok(rtn) => cosmic_space::wave::core::CoreBounce::Reflected(cosmic_space::wave::core::ReflectedCore::ok_body(rtn)),
394                                     Err(err) => cosmic_space::wave::core::CoreBounce::Reflected(err.as_reflected_core())
395                                 }
396                                }
397                            } else {
398                                quote! {
399                                 use cosmic_space::err::CoreReflector;
400                                 match result {
401                                     Ok(rtn) => cosmic_space::wave::core::CoreBounce::Reflected(rtn.into()),
402                                     Err(err) => cosmic_space::wave::core::CoreBounce::Reflected(err.as_reflected_core())
403                                 }
404                                }
405                            }
406                        } else {
407                            panic!("Result without angle brackets")
408                        }
409                    }
410                    "Bounce" => {
411                        quote! {
412                            let rtn : cosmic_space::wave::core::CoreBounce = result.to_core_bounce();
413                            rtn
414                        }
415                    }
416                    "CoreBounce" => {
417                        quote! {
418                           result
419                        }
420                    }
421                    "ReflectedCore" => {
422                        quote! {
423                           cosmic_space::wave::core::CoreBounce::Reflected(result)
424                        }
425                    }
426                    what => {
427                        panic!("unknown return type: {}", what);
428                    }
429                }
430            } else {
431                panic!("expecting a path segment")
432            }
433        }
434    }
435}
436
437struct RouteAttr {
438    attribute: Attribute,
439}
440
441impl Parse for RouteAttr {
442    fn parse(input: ParseStream) -> syn::Result<Self> {
443        let mut attribute = input.call(Attribute::parse_outer)?;
444        Ok(RouteAttr {
445            attribute: attribute.remove(0),
446        })
447    }
448}