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