miette_derive/
url.rs

1use proc_macro2::TokenStream;
2use quote::quote;
3use syn::{
4    parenthesized,
5    parse::{Parse, ParseStream},
6    Fields, Token,
7};
8
9use crate::{
10    diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
11    utils::{display_pat_members, gen_all_variants_with, gen_unused_pat},
12};
13use crate::{
14    fmt::{self, Display},
15    forward::WhichFn,
16};
17
18pub enum Url {
19    Display(Display),
20    DocsRs,
21}
22
23impl Parse for Url {
24    fn parse(input: ParseStream) -> syn::Result<Self> {
25        let ident = input.parse::<syn::Ident>()?;
26        if ident == "url" {
27            let la = input.lookahead1();
28            if la.peek(syn::token::Paren) {
29                let content;
30                parenthesized!(content in input);
31                if content.peek(syn::LitStr) {
32                    let fmt = content.parse()?;
33                    let args = if content.is_empty() {
34                        TokenStream::new()
35                    } else {
36                        fmt::parse_token_expr(&content, false)?
37                    };
38                    let display = Display {
39                        fmt,
40                        args,
41                        has_bonus_display: false,
42                    };
43                    Ok(Url::Display(display))
44                } else {
45                    let option = content.parse::<syn::Ident>()?;
46                    if option == "docsrs" {
47                        Ok(Url::DocsRs)
48                    } else {
49                        Err(syn::Error::new(option.span(), "Invalid argument to url() sub-attribute. It must be either a string or a plain `docsrs` identifier"))
50                    }
51                }
52            } else {
53                input.parse::<Token![=]>()?;
54                Ok(Url::Display(Display {
55                    fmt: input.parse()?,
56                    args: TokenStream::new(),
57                    has_bonus_display: false,
58                }))
59            }
60        } else {
61            Err(syn::Error::new(ident.span(), "not a url"))
62        }
63    }
64}
65
66impl Url {
67    pub(crate) fn gen_enum(
68        enum_name: &syn::Ident,
69        variants: &[DiagnosticDef],
70    ) -> Option<TokenStream> {
71        gen_all_variants_with(
72            variants,
73            WhichFn::Url,
74            |ident, fields, DiagnosticConcreteArgs { url, .. }| {
75                let (pat, fmt, args) = match url.as_ref()? {
76                    // fall through to `_ => None` below
77                    Url::Display(display) => {
78                        let (display_pat, display_members) = display_pat_members(fields);
79                        let (fmt, args) = display.expand_shorthand_cloned(&display_members);
80                        (display_pat, fmt.value(), args)
81                    }
82                    Url::DocsRs => {
83                        let pat = gen_unused_pat(fields);
84                        let fmt =
85                            "https://docs.rs/{crate_name}/{crate_version}/{mod_name}/{item_path}"
86                                .into();
87                        let item_path = format!("enum.{}.html#variant.{}", enum_name, ident);
88                        let args = quote! {
89                            ,
90                            crate_name=env!("CARGO_PKG_NAME"),
91                            crate_version=env!("CARGO_PKG_VERSION"),
92                            mod_name=env!("CARGO_PKG_NAME").replace('-', "_"),
93                            item_path=#item_path
94                        };
95                        (pat, fmt, args)
96                    }
97                };
98                Some(quote! {
99                    Self::#ident #pat => std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args))),
100                })
101            },
102        )
103    }
104
105    pub(crate) fn gen_struct(
106        &self,
107        struct_name: &syn::Ident,
108        fields: &Fields,
109    ) -> Option<TokenStream> {
110        let (pat, fmt, args) = match self {
111            Url::Display(display) => {
112                let (display_pat, display_members) = display_pat_members(fields);
113                let (fmt, args) = display.expand_shorthand_cloned(&display_members);
114                (display_pat, fmt.value(), args)
115            }
116            Url::DocsRs => {
117                let pat = gen_unused_pat(fields);
118                let fmt =
119                    "https://docs.rs/{crate_name}/{crate_version}/{mod_name}/{item_path}".into();
120                let item_path = format!("struct.{}.html", struct_name);
121                let args = quote! {
122                    ,
123                    crate_name=env!("CARGO_PKG_NAME"),
124                    crate_version=env!("CARGO_PKG_VERSION"),
125                    mod_name=env!("CARGO_PKG_NAME").replace('-', "_"),
126                    item_path=#item_path
127                };
128                (pat, fmt, args)
129            }
130        };
131        Some(quote! {
132            fn url(&self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + '_>> {
133                #[allow(unused_variables, deprecated)]
134                let Self #pat = self;
135                std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args)))
136            }
137        })
138    }
139}