Skip to main content

time_macros/
serde_format_description.rs

1use proc_macro::{Ident, TokenStream};
2
3use crate::FormatDescriptionVersion;
4
5pub(crate) fn build(
6    version: FormatDescriptionVersion,
7    visibility: TokenStream,
8    mod_name: Ident,
9    ty: TokenStream,
10    format: TokenStream,
11    format_description_display: String,
12) -> TokenStream {
13    let ty_s = &*ty.to_string();
14
15    let visitor = if cfg!(feature = "parsing") {
16        quote! {
17            pub(super) struct Visitor;
18            pub(super) struct OptionVisitor;
19
20            impl<'a> ::serde::de::Visitor<'a> for Visitor {
21                type Value = __TimeSerdeType;
22
23                fn expecting(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
24                    write!(
25                        f,
26                        concat!(
27                            "a(n) `",
28                            #(ty_s),
29                            "` in the format \"{}\"",
30                        ),
31                        #(format_description_display.as_str())
32                    )
33                }
34
35                fn visit_str<E: ::serde::de::Error>(
36                    self,
37                    value: &str
38                ) -> Result<__TimeSerdeType, E> {
39                    __TimeSerdeType::parse(value, &description()).map_err(E::custom)
40                }
41            }
42
43            impl<'a> ::serde::de::Visitor<'a> for OptionVisitor {
44                type Value = Option<__TimeSerdeType>;
45
46                fn expecting(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
47                    write!(
48                        f,
49                        concat!(
50                            "an `Option<",
51                            #(ty_s),
52                            ">` in the format \"{}\"",
53                        ),
54                        #(format_description_display.as_str())
55                    )
56                }
57
58                fn visit_some<D: ::serde::de::Deserializer<'a>>(
59                    self,
60                    deserializer: D
61                ) -> Result<Option<__TimeSerdeType>, D::Error> {
62                    deserializer
63                        .deserialize_str(Visitor)
64                        .map(Some)
65                }
66
67                fn visit_none<E: ::serde::de::Error>(
68                    self
69                ) -> Result<Option<__TimeSerdeType>, E> {
70                    Ok(None)
71                }
72
73                fn visit_unit<E: ::serde::de::Error>(
74                    self
75                ) -> Result<Option<__TimeSerdeType>, E> {
76                    Ok(None)
77                }
78            }
79        }
80    } else {
81        quote!()
82    };
83
84    let serialize_primary = if cfg!(feature = "formatting") {
85        quote! {
86            pub fn serialize<S: ::serde::Serializer>(
87                datetime: &__TimeSerdeType,
88                serializer: S,
89            ) -> Result<S::Ok, S::Error> {
90                use ::serde::Serialize;
91                datetime
92                    .format(&description())
93                    .map_err(::time::error::Format::into_invalid_serde_value::<S>)?
94                    .serialize(serializer)
95            }
96        }
97    } else {
98        quote!()
99    };
100
101    let deserialize_primary = if cfg!(feature = "parsing") {
102        quote! {
103            pub fn deserialize<'a, D: ::serde::Deserializer<'a>>(
104                deserializer: D
105            ) -> Result<__TimeSerdeType, D::Error> {
106                use ::serde::Deserialize;
107                deserializer.deserialize_str(Visitor)
108            }
109        }
110    } else {
111        quote!()
112    };
113
114    let serialize_option = if cfg!(feature = "formatting") {
115        quote! {
116            #[expect(clippy::ref_option)]
117            pub fn serialize<S: ::serde::Serializer>(
118                option: &Option<__TimeSerdeType>,
119                serializer: S,
120            ) -> Result<S::Ok, S::Error> {
121                use ::serde::Serialize;
122                option.map(|datetime| datetime.format(&description()))
123                    .transpose()
124                    .map_err(::time::error::Format::into_invalid_serde_value::<S>)?
125                    .serialize(serializer)
126            }
127        }
128    } else {
129        quote!()
130    };
131
132    let deserialize_option = if cfg!(feature = "parsing") {
133        quote! {
134            pub fn deserialize<'a, D: ::serde::Deserializer<'a>>(
135                deserializer: D
136            ) -> Result<Option<__TimeSerdeType>, D::Error> {
137                use ::serde::Deserialize;
138                deserializer.deserialize_option(OptionVisitor)
139            }
140        }
141    } else {
142        quote!()
143    };
144
145    let deserialize_option_imports = if cfg!(feature = "parsing") {
146        quote! {
147            use super::__hygiene::{OptionVisitor, Visitor};
148        }
149    } else {
150        quote!()
151    };
152
153    let fd_traits = match (cfg!(feature = "formatting"), cfg!(feature = "parsing")) {
154        (false, false) => {
155            bug!("serde_format_description::build called without formatting or parsing enabled")
156        }
157        (false, true) => quote! { ::time::parsing::Parsable },
158        (true, false) => quote! { ::time::formatting::Formattable },
159        (true, true) => quote! { ::time::formatting::Formattable + ::time::parsing::Parsable },
160    };
161
162    let hygiene_imports = match (cfg!(feature = "formatting"), cfg!(feature = "parsing")) {
163        (false, false) => bug!(
164            "`serde_format_description` cannot be enabled without either the `formatting` or \
165             `parsing` features"
166        ),
167        (false, true) => quote! { pub use self::__hygiene::deserialize; },
168        (true, false) => quote! { pub use self::__hygiene::serialize; },
169        (true, true) => quote! { pub use self::__hygiene::{serialize, deserialize}; },
170    };
171
172    let main_type_import = if version.is_at_most_v2() {
173        quote! { use ::time::#S(ty) as __TimeSerdeType; }
174    } else {
175        quote! { use #S(ty) as __TimeSerdeType; }
176    };
177
178    quote! {
179        #S(visibility) mod #(mod_name) {
180            use super::*;
181            #S(main_type_import)
182            #[expect(clippy::pub_use)]
183            #S(hygiene_imports)
184
185            const fn description() -> impl #S(fd_traits) {
186                #S(format)
187            }
188
189            mod __hygiene {
190                use super::{description, __TimeSerdeType};
191
192                #S(visitor)
193                #S(serialize_primary)
194                #S(deserialize_primary)
195            }
196
197            // While technically public, this is effectively the same visibility as the enclosing
198            // module, which has its visibility controlled by the user.
199            pub mod option {
200                use super::{description, __TimeSerdeType};
201                #S(deserialize_option_imports)
202
203                #S(serialize_option)
204                #S(deserialize_option)
205            }
206        }
207    }
208}