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        }
74    } else {
75        quote_!()
76    };
77
78    let serialize_primary = if cfg!(feature = "formatting") {
79        quote_! {
80            pub fn serialize<S: ::serde::Serializer>(
81                datetime: &__TimeSerdeType,
82                serializer: S,
83            ) -> Result<S::Ok, S::Error> {
84                use ::serde::Serialize;
85                datetime
86                    .format(&description())
87                    .map_err(::time::error::Format::into_invalid_serde_value::<S>)?
88                    .serialize(serializer)
89            }
90        }
91    } else {
92        quote_!()
93    };
94
95    let deserialize_primary = if cfg!(feature = "parsing") {
96        quote_! {
97            pub fn deserialize<'a, D: ::serde::Deserializer<'a>>(
98                deserializer: D
99            ) -> Result<__TimeSerdeType, D::Error> {
100                use ::serde::Deserialize;
101                deserializer.deserialize_str(Visitor)
102            }
103        }
104    } else {
105        quote_!()
106    };
107
108    let serialize_option = if cfg!(feature = "formatting") {
109        quote_! {
110            #[expect(clippy::ref_option)]
111            pub fn serialize<S: ::serde::Serializer>(
112                option: &Option<__TimeSerdeType>,
113                serializer: S,
114            ) -> Result<S::Ok, S::Error> {
115                use ::serde::Serialize;
116                option.map(|datetime| datetime.format(&description()))
117                    .transpose()
118                    .map_err(::time::error::Format::into_invalid_serde_value::<S>)?
119                    .serialize(serializer)
120            }
121        }
122    } else {
123        quote_!()
124    };
125
126    let deserialize_option = if cfg!(feature = "parsing") {
127        quote_! {
128            pub fn deserialize<'a, D: ::serde::Deserializer<'a>>(
129                deserializer: D
130            ) -> Result<Option<__TimeSerdeType>, D::Error> {
131                use ::serde::Deserialize;
132                deserializer.deserialize_option(OptionVisitor)
133            }
134        }
135    } else {
136        quote_!()
137    };
138
139    let deserialize_option_imports = if cfg!(feature = "parsing") {
140        quote_! {
141            use super::__hygiene::{OptionVisitor, Visitor};
142        }
143    } else {
144        quote_!()
145    };
146
147    let fd_traits = match (cfg!(feature = "formatting"), cfg!(feature = "parsing")) {
148        (false, false) => {
149            bug!("serde_format_description::build called without formatting or parsing enabled")
150        }
151        (false, true) => quote_! { ::time::parsing::Parsable },
152        (true, false) => quote_! { ::time::formatting::Formattable },
153        (true, true) => quote_! { ::time::formatting::Formattable + ::time::parsing::Parsable },
154    };
155
156    let hygiene_imports = match (cfg!(feature = "formatting"), cfg!(feature = "parsing")) {
157        (false, false) => bug!(
158            "`serde_format_description` cannot be enabled without either the `formatting` or \
159             `parsing` features"
160        ),
161        (false, true) => quote_! { pub use self::__hygiene::deserialize; },
162        (true, false) => quote_! { pub use self::__hygiene::serialize; },
163        (true, true) => quote_! { pub use self::__hygiene::{serialize, deserialize}; },
164    };
165
166    let main_type_import = if version.is_at_most_v2() {
167        quote_! { use ::time::#S(ty) as __TimeSerdeType; }
168    } else {
169        quote_! { use #S(ty) as __TimeSerdeType; }
170    };
171
172    quote_! {
173        #S(visibility) mod #(mod_name) {
174            use super::*;
175            #S(main_type_import)
176            #[expect(clippy::pub_use)]
177            #S(hygiene_imports)
178
179            const fn description() -> impl #S(fd_traits) {
180                #S(format)
181            }
182
183            mod __hygiene {
184                use super::{description, __TimeSerdeType};
185
186                #S(visitor)
187                #S(serialize_primary)
188                #S(deserialize_primary)
189            }
190
191            // While technically public, this is effectively the same visibility as the enclosing
192            // module, which has its visibility controlled by the user.
193            pub mod option {
194                use super::{description, __TimeSerdeType};
195                #S(deserialize_option_imports)
196
197                #S(serialize_option)
198                #S(deserialize_option)
199            }
200        }
201    }
202}