Skip to main content

xsd_parser/pipeline/renderer/
mod.rs

1//! Code rendering infrastructure for Rust type generation.
2//!
3//! This module defines the [`Renderer`] and supporting components responsible
4//! for converting fully resolved [`DataTypes`] into structured Rust code modules.
5//! It provides a flexible, composable rendering pipeline through the
6//! [`RenderStep`] trait, allowing each rendering step to be added, removed, or
7//! customized as needed.
8//!
9//! The [`Renderer`] can be extended with custom [`RenderStep`] implementations
10//! or modified using configuration methods such as [`flags()`](Renderer::flags),
11//! [`derive()`](Renderer::derive), and [`dyn_type_traits()`](Renderer::dyn_type_traits).
12//!
13//! Example usage:
14//! ```rust,ignore
15//! let module = Renderer::new(&types)
16//!     .with_default_steps()
17//!     .derive(["Debug", "Clone"])
18//!     .finish();
19//! ```
20
21mod context;
22mod custom;
23mod error;
24mod meta;
25mod steps;
26
27use std::fmt::{Debug, Display};
28use std::str::FromStr;
29
30use inflector::Inflector;
31use proc_macro2::TokenStream;
32use quote::{format_ident, quote};
33
34use crate::config::{DynTypeTraits, RendererFlags};
35use crate::models::{
36    code::{IdentPath, Module},
37    data::{DataTypeVariant, DataTypes, Occurs, PathData},
38    meta::ModuleMeta,
39};
40
41pub use self::context::{Context, ValueKey, Values};
42pub use self::custom::{ValueRenderer, ValueRendererBox};
43pub use self::error::Error;
44pub use self::meta::MetaData;
45pub use self::steps::{
46    DefaultsRenderStep, EnumConstantsRenderStep, NamespaceConstantsRenderStep,
47    NamespaceSerialization, PrefixConstantsRenderStep, QuickXmlCollectNamespacesRenderStep,
48    QuickXmlDeserializeRenderStep, QuickXmlSerializeRenderStep, SerdeQuickXmlTypesRenderStep,
49    SerdeXmlRsV7TypesRenderStep, SerdeXmlRsV8TypesRenderStep, TypesRenderStep,
50    WithNamespaceTraitRenderStep,
51};
52
53/// The [`Renderer`] is the central orchestrator for Rust code generation from
54/// resolved schema types.
55///
56/// It allows the user to define a rendering pipeline using modular [`RenderStep`]s.
57/// Each step contributes part of the code output - such as type definitions,
58/// trait impls, constants, or serialization logic.
59///
60/// The [`Renderer`] holds configuration, shared metadata, and controls the execution
61/// of rendering steps over the input [`DataTypes`].
62///
63/// You can chain configuration methods to adjust derive traits, dynamic trait
64/// injection, and serialization support, and then call [`finish`](Renderer::finish)
65/// to produce a [`Module`] ready for rendering as Rust source.
66#[must_use]
67#[derive(Debug)]
68pub struct Renderer<'types> {
69    meta: MetaData<'types>,
70    steps: Vec<Box<dyn RenderStep + 'types>>,
71    dyn_type_traits: DynTypeTraits,
72}
73
74impl<'types> Renderer<'types> {
75    /// Create a new [`Renderer`] instance from the passed `types`.
76    pub fn new(types: &'types DataTypes<'types>) -> Self {
77        let meta = MetaData {
78            types,
79            flags: RendererFlags::empty(),
80            derive: vec![IdentPath::from_ident(format_ident!("Debug"))],
81            dyn_type_traits: Vec::new(),
82            alloc_crate: format_ident!("std"),
83            xsd_parser_types: format_ident!("xsd_parser_types"),
84        };
85
86        Self {
87            meta,
88            steps: Vec::new(),
89            dyn_type_traits: DynTypeTraits::Auto,
90        }
91    }
92
93    /// Add a [`RenderStep`] to the renderer.
94    pub fn with_step<X>(self, step: X) -> Self
95    where
96        X: RenderStep + 'types,
97    {
98        self.with_step_boxed(Box::new(step))
99    }
100
101    /// Add an already boxed [`RenderStep`] to the renderer.
102    pub fn with_step_boxed(mut self, step: Box<dyn RenderStep + 'types>) -> Self {
103        self.steps.push(step);
104
105        self
106    }
107
108    /// Add the default renderers to the generator.
109    pub fn with_default_steps(self) -> Self {
110        self.with_step(TypesRenderStep)
111    }
112
113    /// Remove all [`Renderer`]s from the generator.
114    pub fn clear_steps(mut self) -> Self {
115        self.steps.clear();
116
117        self
118    }
119
120    /// Set the [`RendererFlags`] flags the renderer should use for rendering the code.
121    pub fn flags(mut self, value: RendererFlags) -> Self {
122        self.meta.flags = value;
123
124        self
125    }
126
127    /// Set the traits the generated types should derive from.
128    ///
129    /// Default is `Debug`.
130    ///
131    /// If you want to set the derive for a single value, please have a look to
132    /// [`DataType::derive`](crate::models::data::DataType::derive).
133    ///
134    /// # Examples
135    ///
136    /// ```ignore
137    /// let renderer = Renderer::new(types)
138    ///     .derive(["Debug", "Clone", "Eq", "PartialEq", "Ord", "PartialOrd"]);
139    /// ```
140    pub fn derive<I>(mut self, value: I) -> Self
141    where
142        I: IntoIterator,
143        I::Item: Display,
144    {
145        self.meta.derive = value
146            .into_iter()
147            .map(|x| IdentPath::from_str(&x.to_string()).expect("Invalid identifier path"))
148            .collect();
149
150        self
151    }
152
153    /// Set the traits that should be implemented by dynamic types.
154    ///
155    /// The passed values must be valid type paths.
156    ///
157    /// # Errors
158    ///
159    /// Will raise a [`InvalidIdentifier`](Error::InvalidIdentifier) error
160    /// if the passed strings does not represent a valid identifier.
161    ///
162    /// # Examples
163    ///
164    /// ```ignore
165    /// let generator = Generator::new(types)
166    ///     .dyn_type_traits(["::core::fmt::Debug", "::core::any::Any"]);
167    /// ```
168    pub fn dyn_type_traits<I>(mut self, value: I) -> Result<Self, Error>
169    where
170        I: IntoIterator,
171        I::Item: AsRef<str>,
172    {
173        let traits = value
174            .into_iter()
175            .map(|x| {
176                let s = x.as_ref();
177                IdentPath::from_str(s)
178            })
179            .collect::<Result<Vec<_>, _>>()?;
180
181        self.dyn_type_traits = DynTypeTraits::Custom(traits);
182
183        Ok(self)
184    }
185
186    /// Set the name of the `alloc` create that the generator should use for
187    /// generating the code.
188    ///
189    /// This is useful if the `alloc` create can not be resolved by the default
190    /// name in your environment. You can just set a name that suites your needs.
191    ///
192    /// By default `std` is used as `alloc` crate.
193    pub fn alloc_crate<S: Display>(mut self, value: S) -> Self {
194        self.meta.alloc_crate = format_ident!("{value}");
195
196        self
197    }
198
199    /// Set the name of the `xsd-parser-types` create that the generator should use for
200    /// generating the code.
201    ///
202    /// This is useful if the `xsd-parser-types` create can not be resolved by the default
203    /// name in your environment. You can just set a name that suites your needs.
204    pub fn xsd_parser_types<S: Display>(mut self, value: S) -> Self {
205        self.meta.xsd_parser_types = format_ident!("{value}");
206
207        self
208    }
209
210    /// Finish the rendering process and return the resulting [`Module`].
211    #[must_use]
212    pub fn finish(self) -> Module {
213        let mut module = Module::default();
214        let Self {
215            mut meta,
216            mut steps,
217            dyn_type_traits,
218        } = self;
219
220        meta.dyn_type_traits = match dyn_type_traits {
221            DynTypeTraits::Auto => {
222                let traits = meta.derive.iter().map(|x| match x.to_string().as_ref() {
223                    "Debug" => IdentPath::from_str("::core::fmt::Debug").unwrap(),
224                    "Hash" => IdentPath::from_str("::core::hash::Hash").unwrap(),
225                    _ => x.clone(),
226                });
227
228                let as_any =
229                    IdentPath::from_parts([meta.xsd_parser_types.clone()], format_ident!("AsAny"));
230
231                traits.chain(Some(as_any)).collect()
232            }
233            DynTypeTraits::Custom(x) => x,
234        };
235
236        for step in &mut steps {
237            step.initialize(&mut meta);
238        }
239
240        let mut values = Values::new();
241        for (ident, data) in &meta.types.items {
242            let mut ctx = Context::new(&meta, data, ident, &mut module, values);
243
244            for step in &mut steps {
245                step.render_type(&mut ctx);
246            }
247
248            values = ctx.values;
249        }
250
251        for step in &mut steps {
252            step.finish(&meta, &mut module);
253        }
254
255        module
256    }
257}
258
259/// Trait that is used to define a renderer.
260///
261/// A render step is used to generate the actual code of a specific
262/// [`DataType`](crate::models::data::DataType).
263/// The idea is that different render steps generate different code. This can be
264/// used by the user to compose different render steps depending on his needs, or
265/// he could even implement customized steps on his own.
266pub trait RenderStep: Debug {
267    /// Returns the type of the render step.
268    fn render_step_type(&self) -> RenderStepType;
269
270    /// Initialized the renderer.
271    ///
272    /// This is called once for each renderer when the generator is initialized.
273    fn initialize(&mut self, meta: &mut MetaData<'_>) {
274        let _meta = meta;
275    }
276
277    /// Renders the code for the given type.
278    ///
279    /// This is called once for each type that needs to be rendered.
280    fn render_type(&mut self, ctx: &mut Context<'_, '_>) {
281        let _cxt = ctx;
282    }
283
284    /// Finishes the rendering process.
285    ///
286    /// This is called once for each renderer after the actual rendering of the
287    /// types is finished and the code generation process is being finished.
288    fn finish(&mut self, meta: &MetaData<'_>, module: &mut Module) {
289        let _meta = meta;
290        let _module = module;
291    }
292}
293
294/// Defines the type of the render step.
295///
296/// This defines the type of a render steps and is used to manage mutually
297/// exclusive render steps. For example the renderer pipeline should only
298/// contain one single render step of type [`Types`](RenderStepType::Types).
299#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
300pub enum RenderStepType {
301    /// Render step that renders the actual types defined in the schema.
302    /// This type of render step should only exists once in the whole renderer pipeline.
303    Types,
304
305    /// Render step that renders additional type definitions they are not conflicting
306    /// with the actual types defined in the schema.
307    ExtraTypes,
308
309    /// Render step that renders additional implementation blocks or trait implementations
310    /// for types defined in a different render step.
311    ExtraImpls,
312
313    /// The type of this render step is undefined.
314    /// If you are implementing a custom render step and you are not sure what type
315    /// to use, then use this one.
316    #[default]
317    Undefined,
318}
319
320impl RenderStepType {
321    /// Returns `true` if the two types are mutual exclusive to each other, `false` otherwise.
322    #[must_use]
323    pub fn is_mutual_exclusive_to(&self, other: Self) -> bool {
324        matches!((self, other), (Self::Types, Self::Types))
325    }
326}
327
328impl ModuleMeta {
329    pub(super) fn make_ns_const(&self) -> Option<PathData> {
330        self.namespace.as_ref()?;
331        let name = self.name().map_or_else(
332            || format!("UNNAMED_{}", self.namespace_id.0),
333            |name| name.as_str().to_screaming_snake_case(),
334        );
335        let ident = format_ident!("NS_{name}");
336        let path = IdentPath::from_parts([], ident);
337
338        Some(PathData::from_path(path))
339    }
340
341    pub(super) fn make_prefix_const(&self) -> Option<PathData> {
342        self.namespace.as_ref()?;
343        let name = self.name()?;
344        let name = name.as_str().to_screaming_snake_case();
345        let ident = format_ident!("PREFIX_{name}");
346        let path = IdentPath::from_parts([], ident);
347
348        Some(PathData::from_path(path))
349    }
350}
351
352impl Occurs {
353    /// Wrapped the passed type `ident` into a suitable rust type depending on
354    /// the occurrence and the need of indirection (boxing).
355    ///
356    /// # Examples
357    /// - `Occurs::Single` will return the type as is, or as `Box<T>`
358    /// - `Occurs::Optional` will return the type as `Option<T>`
359    /// - `Occurs::DynamicList` will return the type as `Vec<T>`
360    /// - `Occurs::StaticList` will return the type as array `[T; SIZE]`
361    #[must_use]
362    pub fn make_type(
363        self,
364        ctx: &Context<'_, '_>,
365        ident: &TokenStream,
366        need_indirection: bool,
367    ) -> Option<TokenStream> {
368        match self {
369            Self::None => None,
370            Self::Single if need_indirection => {
371                let box_ = ctx.resolve_build_in("::alloc::boxed::Box");
372
373                Some(quote! { #box_<#ident> })
374            }
375            Self::Single => Some(quote! { #ident }),
376            Self::Optional if need_indirection => {
377                let box_ = ctx.resolve_build_in("::alloc::boxed::Box");
378                let option = ctx.resolve_build_in("::core::option::Option");
379
380                Some(quote! { #option<#box_<#ident>> })
381            }
382            Self::Optional => {
383                let option = ctx.resolve_build_in("::core::option::Option");
384
385                Some(quote! { #option<#ident> })
386            }
387            Self::DynamicList => {
388                let vec = ctx.resolve_build_in("::alloc::vec::Vec");
389
390                Some(quote! { #vec<#ident> })
391            }
392            Self::StaticList(sz) if need_indirection => {
393                let box_ = ctx.resolve_build_in("::alloc::boxed::Box");
394
395                Some(quote! { [#box_<#ident>; #sz] })
396            }
397            Self::StaticList(sz) => Some(quote! { [#ident; #sz] }),
398        }
399    }
400}