sark-gen 0.28.1

Sark proc-macro generators
Documentation
use crate::{macros::tokens::local, runtime::paths::Paths, types};

pub(super) mod task;

pub(super) enum Source {
    Discarded,
    Raw(Raw),
    Json(Json),
}

pub(super) struct Json {
    ident: syn::Ident,
    ty: syn::Type,
}

pub(super) struct Raw {
    ident: syn::Ident,
    borrowed_ty: syn::Type,
    segmented: bool,
}

impl Raw {
    fn new(paths: &Paths, ident: syn::Ident, ty: &syn::Type) -> syn::Result<Self> {
        let ns_http = &paths.http;
        let view = types::View::new(ty);
        let segments = view.is_named("Segments");
        let valid = segments || view.is_named("Shared");
        if !valid {
            return Err(syn::Error::new_spanned(
                ty,
                "#[raw_body] field type must be owned::Segments or Shared",
            ));
        }
        let borrowed = if segments {
            syn::parse_quote!(#ns_http::request::body::Ref<'req, 'd>)
        } else {
            syn::parse_quote!(&'req [::core::primitive::u8])
        };
        Ok(Self {
            ident,
            borrowed_ty: borrowed,
            segmented: segments,
        })
    }
}

pub(super) struct Plan {
    source: Source,
    length: Option<syn::Ident>,
}

impl Plan {
    pub(super) fn scope(
        &mut self,
        request: &syn::Ident,
    ) -> Option<(syn::Ident, proc_macro2::TokenStream)> {
        let Source::Json(json) = &mut self.source else {
            return None;
        };
        let name = quote::format_ident!("__{}Json", request);
        let ty = &json.ty;
        let declaration = quote::quote!(type #name = #ty;);
        json.ty = syn::parse_quote!(#name);
        Some((name, declaration))
    }

    pub(super) fn mode_impls(
        &self,
        paths: &Paths,
        vis: &syn::Visibility,
        mode: &syn::Ident,
    ) -> proc_macro2::TokenStream {
        let ns_core = &paths.core;
        let ns_http = &paths.http;
        let ns_json = &paths.json;
        let (borrowed, owned) = match &self.source {
            Source::Discarded => (quote::quote!(()), quote::quote!(())),
            Source::Raw(raw) => {
                let borrowed = &raw.borrowed_ty;
                let owned = if raw.segmented {
                    quote::quote!(#ns_http::request::Segments<'d>)
                } else {
                    quote::quote!(#ns_http::request::Bytes<'d>)
                };
                (quote::quote!(#borrowed), owned)
            }
            Source::Json(json) => {
                let ty = &json.ty;
                (
                    quote::quote!(
                        <#ty as #ns_json::parsing::Request>::View<'req>
                    ),
                    quote::quote!(
                        <#ty as #ns_json::segment::IntoRequest>::Request<'d>
                    ),
                )
            }
        };
        quote::quote! {
            #[doc(hidden)]
            #vis trait #mode<'d>: #ns_core::request::mode::Mode<'d> {
                type Body;
            }

            impl<'req, 'd: 'req> #mode<'d> for #ns_core::request::mode::Ref<'req> {
                type Body = #borrowed;
            }

            impl<'d> #mode<'d> for #ns_core::request::mode::Own {
                type Body = #owned;
            }
        }
    }

    pub(super) fn rewrite(&self, field: &mut syn::Field, mode: &syn::Ident, param: &syn::Ident) {
        let body = match &self.source {
            Source::Raw(raw) => field.ident.as_ref() == Some(&raw.ident),
            Source::Json(json) => field.ident.as_ref() == Some(&json.ident),
            Source::Discarded => false,
        };
        if body {
            field.ty = syn::parse_quote!(<#param as #mode<'d>>::Body);
        }
    }

    pub(super) fn from_attrs(attrs: &[syn::Attribute]) -> syn::Result<Self> {
        let mut json = None;
        for attr in attrs {
            if attr.path().is_ident("json_body") {
                if let Some(first) = &json {
                    let mut error = syn::Error::new_spanned(attr, "duplicate #[json_body(...)]");
                    error.combine(syn::Error::new_spanned(
                        first,
                        "first #[json_body(...)] declared here",
                    ));
                    return Err(error);
                }
                json = Some(attr.parse_args::<syn::Type>()?);
            }
        }
        Ok(Self {
            source: match json {
                Some(ty) => Source::Json(Json {
                    ident: syn::Ident::new("body", proc_macro2::Span::call_site()),
                    ty,
                }),
                None => Source::Discarded,
            },
            length: None,
        })
    }

    pub(super) fn try_raw(
        self,
        paths: &Paths,
        ident: syn::Ident,
        ty: &syn::Type,
    ) -> syn::Result<Self> {
        let source = match self.source {
            Source::Discarded => Source::Raw(Raw::new(paths, ident, ty)?),
            Source::Raw(raw) => {
                let mut error = syn::Error::new_spanned(ty, "duplicate #[raw_body]");
                error.combine(syn::Error::new_spanned(
                    raw.ident,
                    "first #[raw_body] declared here",
                ));
                return Err(error);
            }
            Source::Json(json) => {
                let mut error = syn::Error::new_spanned(
                    ty,
                    "#[json_body(...)] and #[raw_body] are mutually exclusive",
                );
                error.combine(syn::Error::new_spanned(
                    json.ty,
                    "#[json_body(...)] declared here",
                ));
                return Err(error);
            }
        };
        Ok(Self {
            source,
            length: self.length,
        })
    }

    pub(super) fn try_len(self, ident: syn::Ident, ty: &syn::Type) -> syn::Result<Self> {
        if let Some(first) = self.length {
            let mut error = syn::Error::new_spanned(ty, "duplicate #[body_len]");
            error.combine(syn::Error::new_spanned(
                first,
                "first #[body_len] declared here",
            ));
            return Err(error);
        }
        if !types::View::new(ty).is_named("BodyLen") {
            return Err(syn::Error::new_spanned(
                ty,
                "#[body_len] field type must be request::BodyLen",
            ));
        }
        Ok(Self {
            source: self.source,
            length: Some(ident),
        })
    }

    pub(super) fn append_json(&self, named: &mut syn::FieldsNamed, vis: &syn::Visibility) {
        let Source::Json(json) = &self.source else {
            return;
        };
        let ident = &json.ident;
        let ty = &json.ty;
        named.named.push(syn::parse_quote!(#vis #ident: #ty));
    }

    pub(super) fn fields(
        &self,
        paths: &Paths,
        body: syn::Ident,
        len: proc_macro2::TokenStream,
    ) -> Vec<proc_macro2::TokenStream> {
        let ns_core = &paths.core;
        let mut fields = Vec::with_capacity(2);
        match &self.source {
            Source::Raw(raw) => {
                let ident = &raw.ident;
                fields.push(quote::quote!(#ident: #body));
            }
            Source::Json(json) => {
                let ident = &json.ident;
                fields.push(quote::quote!(#ident: #body));
            }
            Source::Discarded => {}
        }
        if let Some(ident) = &self.length {
            fields.push(quote::quote!(#ident: #ns_core::request::BodyLen::new(#len)));
        }
        fields
    }

    pub(super) fn task(&self, paths: &Paths) -> (proc_macro2::TokenStream, &Source) {
        let ns_core = &paths.core;
        let body = match &self.source {
            Source::Json(_) => quote::quote!(#ns_core::body::Contiguous),
            Source::Raw(raw) => {
                if raw.segmented {
                    quote::quote!(#ns_core::body::Segmented)
                } else {
                    quote::quote!(#ns_core::body::Contiguous)
                }
            }
            Source::Discarded => quote::quote!(#ns_core::body::Discarded),
        };
        (body, &self.source)
    }

    pub(super) fn parsing(
        &self,
        paths: &Paths,
        path: &proc_macro2::TokenStream,
    ) -> (
        proc_macro2::TokenStream,
        proc_macro2::TokenStream,
        proc_macro2::TokenStream,
    ) {
        let ns_core = &paths.core;
        let ns_json = &paths.json;
        let body = local("body");
        let (cause, param, value) = match &self.source {
            Source::Json(json) => {
                let ty = &json.ty;
                (
                    quote::quote!(#ns_json::parsing::Error<'req>),
                    quote::quote!(<#ty as #ns_json::parsing::Request>::View<'req>),
                    quote::quote! {
                        <#ty as #ns_json::parsing::Request>::decode(#body.as_slice())
                            .map_err(#ns_core::route::Input::Body)
                    },
                )
            }
            Source::Raw(raw) => {
                let ty = &raw.borrowed_ty;
                let value = if raw.segmented {
                    quote::quote!(::core::result::Result::Ok(#body))
                } else {
                    quote::quote!(::core::result::Result::Ok(#body.as_slice()))
                };
                (
                    quote::quote!(::core::convert::Infallible),
                    quote::quote!(#ty),
                    value,
                )
            }
            Source::Discarded => (
                quote::quote!(::core::convert::Infallible),
                quote::quote!(()),
                quote::quote!(::core::result::Result::Ok(())),
            ),
        };
        let input = match &self.source {
            Source::Discarded => quote::quote!(_),
            Source::Raw(_) | Source::Json(_) => quote::quote!(#body),
        };
        let error = quote::quote!(#ns_core::route::Input<#path, #cause,>);
        let parsed = quote::quote! {
            type ParsedBody<'req, 'd> = #param
            where
                'd: 'req;

            fn parse_body<'req, 'd>(
                #input: <Self::Body as #ns_core::body::Project>::View<'req, 'd>,
            ) -> ::core::result::Result<
                Self::ParsedBody<'req, 'd>,
                #error,
            >
            where
                'd: 'req,
            {
                #value
            }
        };
        (error, parsed, param)
    }

    pub(super) fn request(&self) -> proc_macro2::TokenStream {
        let req = local("req");
        match &self.length {
            Some(_) => quote::quote!(#req),
            None => quote::quote!(_),
        }
    }

    pub(super) fn input(&self) -> proc_macro2::TokenStream {
        let body = local("parsed_body");
        match &self.source {
            Source::Raw(_) | Source::Json(_) => quote::quote!(#body),
            Source::Discarded => quote::quote!(_),
        }
    }
}