mas_templates/
macros.rs

1// Copyright 2024 New Vector Ltd.
2// Copyright 2021-2024 The Matrix.org Foundation C.I.C.
3//
4// SPDX-License-Identifier: AGPL-3.0-only
5// Please see LICENSE in the repository root for full details.
6
7/// Count the number of tokens. Used to have a fixed-sized array for the
8/// templates list.
9macro_rules! count {
10    () => (0_usize);
11    ( $x:tt $($xs:tt)* ) => (1_usize + count!($($xs)*));
12}
13
14/// Macro that helps generating helper function that renders a specific template
15/// with a strongly-typed context. It also register the template in a static
16/// array to help detecting missing templates at startup time.
17///
18/// The syntax looks almost like a function to confuse syntax highlighter as
19/// little as possible.
20#[macro_export]
21macro_rules! register_templates {
22    {
23        $(
24            extra = { $( $extra_template:expr ),* $(,)? };
25        )?
26
27        $(
28            // Match any attribute on the function, such as #[doc], #[allow(dead_code)], etc.
29            $( #[ $attr:meta ] )*
30            // The function name
31            pub fn $name:ident
32                // Optional list of generics. Taken from
33                // https://newbedev.com/rust-macro-accepting-type-with-generic-parameters
34                $(< $( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+ >)?
35                // Type of context taken by the template
36                ( $param:ty )
37            {
38                // The name of the template file
39                $template:expr
40            }
41        )*
42    } => {
43        /// List of registered templates
44        static TEMPLATES: [&'static str; count!( $( $template )* )] = [ $( $template, )* ];
45
46        impl Templates {
47            $(
48                $(#[$attr])?
49                ///
50                /// # Errors
51                ///
52                /// Returns an error if the template fails to render.
53                pub fn $name
54                    $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)?
55                    (&self, context: &$param)
56                -> Result<String, TemplateError> {
57                    let ctx = ::minijinja::value::Value::from_serialize(context);
58
59                    let env = self.environment.load();
60                    let tmpl = env.get_template($template)
61                        .map_err(|source| TemplateError::Missing { template: $template, source })?;
62                    tmpl.render(ctx)
63                        .map_err(|source| TemplateError::Render { template: $template, source })
64                }
65            )*
66        }
67
68        /// Helps rendering each template with sample data
69        pub mod check {
70            use super::*;
71
72            $(
73                #[doc = concat!("Render the `", $template, "` template with sample contexts")]
74                ///
75                /// # Errors
76                ///
77                /// Returns an error if the template fails to render with any of the sample.
78                pub fn $name
79                    $(< $( $lt $( : $clt $(+ $dlt )* + TemplateContext )? ),+ >)?
80                    (templates: &Templates, now: chrono::DateTime<chrono::Utc>, rng: &mut impl rand::Rng)
81                -> anyhow::Result<()> {
82                    let samples: Vec< $param > = TemplateContext::sample(now, rng);
83
84                    let name = $template;
85                    for sample in samples {
86                        let context = serde_json::to_value(&sample)?;
87                        ::tracing::info!(name, %context, "Rendering template");
88                        templates. $name (&sample)
89                            .with_context(|| format!("Failed to render template {:?} with context {}", name, context))?;
90                    }
91
92                    Ok(())
93                }
94            )*
95        }
96    };
97}