Skip to main content

xsd_parser/
lib.rs

1#![recursion_limit = "256"]
2#![allow(clippy::doc_markdown)]
3#![doc = include_str!(concat!(env!("OUT_DIR"), "/README.md"))]
4
5pub mod config;
6pub mod misc;
7pub mod models;
8pub mod pipeline;
9pub mod traits;
10
11mod macros;
12mod meta_types_printer;
13
14/// Type alias for [`pipeline::renderer::Error`].
15pub type RendererError = self::pipeline::renderer::Error;
16
17/// Type alias for [`pipeline::generator::Error`].
18pub type GeneratorError = self::pipeline::generator::Error;
19
20/// Type alias for [`pipeline::interpreter::Error`].
21pub type InterpreterError = self::pipeline::interpreter::Error;
22
23/// Type alias for [`pipeline::parser::Error`].
24pub type ParserError<E> = self::pipeline::parser::Error<E>;
25
26use std::fmt::Debug;
27use std::fs::write;
28use std::io::Error as IoError;
29
30pub use proc_macro2::Ident as Ident2;
31
32pub use self::config::Config;
33pub use self::meta_types_printer::MetaTypesPrinter;
34pub use self::models::{
35    code::{Module, SubModules},
36    data::DataTypes,
37    meta::MetaTypes,
38    schema::Schemas,
39    IdentCache, IdentType, Name, TypeIdent,
40};
41pub use self::pipeline::{
42    generator::Generator,
43    interpreter::Interpreter,
44    optimizer::Optimizer,
45    parser::Parser,
46    renderer::{
47        DefaultsRenderStep, NamespaceConstantsRenderStep, QuickXmlDeserializeRenderStep,
48        QuickXmlSerializeRenderStep, RenderStep, Renderer, SerdeQuickXmlTypesRenderStep,
49        SerdeXmlRsV7TypesRenderStep, SerdeXmlRsV8TypesRenderStep, TypesRenderStep,
50        WithNamespaceTraitRenderStep,
51    },
52};
53pub use self::traits::{VecHelper, WithIdent};
54
55use anyhow::Error as AnyError;
56use proc_macro2::TokenStream;
57use quote::ToTokens;
58use thiserror::Error as ThisError;
59use tracing::instrument;
60
61use self::config::{
62    Generate, GeneratorConfig, InterpreterConfig, InterpreterFlags, OptimizerConfig,
63    OptimizerFlags, ParserConfig, ParserFlags, RendererConfig, Resolver, Schema,
64};
65use self::macros::{assert, unreachable};
66use self::pipeline::{
67    optimizer::UnrestrictedBaseFlags,
68    parser::resolver::{FileResolver, ManyResolver},
69};
70
71/// Generates rust code from a XML schema using the passed `config`.
72///
73/// This is the most easiest way to use the `xsd-parser` crate. The `generate`
74/// function provides a simple way to generate rust code from XML schemas using
75/// the passed configuration.
76///
77/// If you need more detailed control over the generation process or only a part
78/// of it, use the [`Parser`], [`Interpreter`], [`Optimizer`] or [`Generator`]
79/// directly.
80///
81/// # Errors
82///
83/// Returns a suitable [`Error`] type if the process was not successful.
84#[instrument(err, level = "trace")]
85pub fn generate(config: Config) -> Result<TokenStream, Error> {
86    let module = generate_modules(config)?;
87    let code = module.to_token_stream();
88
89    Ok(code)
90}
91
92/// Generates rust code split into different modules from a XML schema using the
93/// passed `config`.
94///
95/// Like [`generate`] but instead of returning the whole code as token stream it
96/// returns a [`Module`], holding the code for itself and his sub-modules.
97/// Call [`Module::write_to_files()`] or [`Module::write_to_files_with()`] to
98/// actually create the source code files recursively.
99///
100/// # Errors
101///
102/// Returns a suitable [`Error`] type if the process was not successful.
103#[instrument(err, level = "trace")]
104pub fn generate_modules(config: Config) -> Result<Module, Error> {
105    let schemas = exec_parser(config.parser)?;
106    let (meta_types, ident_cache) =
107        exec_interpreter_with_ident_cache(config.interpreter, &schemas)?;
108    let meta_types = exec_optimizer(config.optimizer, meta_types)?;
109    let data_types = exec_generator_with_ident_cache(
110        config.generator,
111        &schemas,
112        Some(&ident_cache),
113        &meta_types,
114    )?;
115    let module = exec_render(config.renderer, &data_types)?;
116
117    Ok(module)
118}
119
120/// Executes the [`Parser`] with the passed `config`.
121///
122/// # Errors
123///
124/// Returns a suitable [`Error`] type if the process was not successful.
125#[instrument(err, level = "trace")]
126pub fn exec_parser(config: ParserConfig) -> Result<Schemas, Error> {
127    tracing::info!("Parse Schemas");
128
129    let mut resolver = ManyResolver::new();
130    for r in config.resolver {
131        match r {
132            #[cfg(feature = "web-resolver")]
133            Resolver::Web => {
134                let web_resolver = self::pipeline::parser::resolver::WebResolver::new();
135
136                resolver = resolver.add_resolver(web_resolver);
137            }
138            Resolver::File => {
139                let file_resolver = FileResolver::new();
140
141                resolver = resolver.add_resolver(file_resolver);
142            }
143        }
144    }
145
146    let mut parser = Parser::new()
147        .with_resolver(resolver)
148        .resolve_includes(config.flags.contains(ParserFlags::RESOLVE_INCLUDES))
149        .generate_prefixes(config.flags.contains(ParserFlags::GENERATE_PREFIXES))
150        .alternative_prefixes(config.flags.contains(ParserFlags::ALTERNATIVE_PREFIXES));
151
152    if config.flags.contains(ParserFlags::DEFAULT_NAMESPACES) {
153        parser = parser.with_default_namespaces();
154    }
155
156    for (prefix, namespace) in config.namespaces {
157        parser = parser.with_namespace(prefix, namespace);
158    }
159
160    for schema in config.schemas {
161        match schema {
162            Schema::Url(url) => parser = parser.add_schema_from_url(url)?,
163            Schema::File(path) => parser = parser.add_schema_from_file(path)?,
164            Schema::Schema(schema) => parser = parser.add_schema_from_str(&schema)?,
165            Schema::NamedSchema(name, schema) => {
166                parser = parser.add_named_schema_from_str(name, &schema)?;
167            }
168        }
169    }
170
171    let schemas = parser.finish();
172
173    if let Some(output) = config.debug_output {
174        let debug = format!("{schemas:#?}");
175
176        write(output, debug)?;
177    }
178
179    Ok(schemas)
180}
181
182/// Executes the [`Interpreter`] with the passed `config` and `schema`.
183///
184/// # Errors
185///
186/// Returns a suitable [`Error`] type if the process was not successful.
187#[instrument(err, level = "trace", skip(schemas))]
188pub fn exec_interpreter(config: InterpreterConfig, schemas: &Schemas) -> Result<MetaTypes, Error> {
189    exec_interpreter_with_ident_cache(config, schemas).map(|(types, _)| types)
190}
191
192/// Executes the [`Interpreter`] with the passed `config` and `schema`.
193///
194/// Returns the interpreted [`MetaTypes`] structure, as well as the [`IdentCache`]
195/// that can be used to lookup [`TypeIdent`]ifiers.
196///
197/// # Errors
198///
199/// Returns a suitable [`Error`] type if the process was not successful.
200#[instrument(err, level = "trace", skip(schemas))]
201pub fn exec_interpreter_with_ident_cache(
202    config: InterpreterConfig,
203    schemas: &Schemas,
204) -> Result<(MetaTypes, IdentCache), Error> {
205    tracing::info!("Interpret Schema");
206
207    let mut interpreter = Interpreter::new(schemas);
208
209    macro_rules! eval_flag {
210        ($flag:ident, $fn:ident) => {
211            if config.flags.contains(InterpreterFlags::$flag) {
212                interpreter = interpreter.$fn()?;
213            }
214        };
215    }
216
217    if let Some(naming) = config.naming {
218        interpreter = interpreter.with_naming_boxed(naming);
219    }
220
221    eval_flag!(BUILDIN_TYPES, with_buildin_types);
222    eval_flag!(DEFAULT_TYPEDEFS, with_default_typedefs);
223    eval_flag!(WITH_XS_ANY_TYPE, with_xs_any_type);
224    eval_flag!(WITH_XS_ANY_SIMPLE_TYPE, with_xs_any_simple_type);
225    eval_flag!(WITH_NUM_BIG_INT, with_num_big_int);
226    eval_flag!(NONZERO_TYPEDEFS, with_nonzero_typedefs);
227
228    for (ident, ty) in config.types {
229        let ident = ident.resolve(schemas)?;
230        interpreter = interpreter.with_type(ident, ty)?;
231    }
232
233    let (types, ident_cache) = interpreter.finish()?;
234
235    if let Some(output) = config.debug_output {
236        let printer = MetaTypesPrinter::new(&types);
237        let debug = format!("{printer}");
238
239        write(output, debug)?;
240    }
241
242    Ok((types, ident_cache))
243}
244
245/// Executes the [`Optimizer`] with the passed `config` and `types`.
246///
247/// # Errors
248///
249/// Returns a suitable [`Error`] type if the process was not successful.
250#[instrument(err, level = "trace", skip(types))]
251pub fn exec_optimizer(config: OptimizerConfig, types: MetaTypes) -> Result<MetaTypes, Error> {
252    tracing::info!("Optimize Types");
253
254    let mut optimizer = Optimizer::new(types);
255    let mut unrestricted_base = UnrestrictedBaseFlags::empty();
256
257    macro_rules! exec {
258        ($flag:ident, $method:ident) => {
259            if config.flags.contains(OptimizerFlags::$flag) {
260                optimizer = optimizer.$method();
261            }
262        };
263    }
264
265    macro_rules! unrestricted_base {
266        ($a:ident, $b:ident) => {
267            if config.flags.contains(OptimizerFlags::$a) {
268                unrestricted_base |= UnrestrictedBaseFlags::$b;
269            }
270        };
271    }
272
273    unrestricted_base!(USE_UNRESTRICTED_BASE_TYPE_COMPLEX, COMPLEX);
274    unrestricted_base!(USE_UNRESTRICTED_BASE_TYPE_SIMPLE, SIMPLE);
275    unrestricted_base!(USE_UNRESTRICTED_BASE_TYPE_ENUM, ENUM);
276    unrestricted_base!(USE_UNRESTRICTED_BASE_TYPE_UNION, UNION);
277
278    if !unrestricted_base.is_empty() {
279        optimizer = optimizer.use_unrestricted_base_type(unrestricted_base);
280    }
281
282    exec!(
283        REPLACE_XS_ANY_TYPE_WITH_ANY_ELEMENT,
284        replace_xs_any_type_with_any_element
285    );
286    exec!(REMOVE_EMPTY_ENUM_VARIANTS, remove_empty_enum_variants);
287    exec!(REMOVE_EMPTY_ENUMS, remove_empty_enums);
288    exec!(CONVERT_DYNAMIC_TO_CHOICE, convert_dynamic_to_choice);
289    exec!(FLATTEN_COMPLEX_TYPES, flatten_complex_types);
290    exec!(FLATTEN_UNIONS, flatten_unions);
291    exec!(MERGE_ENUM_UNIONS, merge_enum_unions);
292    exec!(RESOLVE_TYPEDEFS, resolve_typedefs);
293    exec!(
294        REMOVE_DUPLICATE_UNION_VARIANTS,
295        remove_duplicate_union_variants
296    );
297    exec!(REMOVE_EMPTY_UNIONS, remove_empty_unions);
298    exec!(REMOVE_DUPLICATES, remove_duplicates);
299    exec!(RESOLVE_TYPEDEFS, resolve_typedefs);
300    exec!(REMOVE_EMPTY_ENUM_VARIANTS, remove_empty_enum_variants);
301    exec!(REMOVE_EMPTY_ENUMS, remove_empty_enums);
302    exec!(
303        REMOVE_DUPLICATE_UNION_VARIANTS,
304        remove_duplicate_union_variants
305    );
306    exec!(REMOVE_EMPTY_UNIONS, remove_empty_unions);
307    exec!(MERGE_CHOICE_CARDINALITIES, merge_choice_cardinalities);
308    exec!(SIMPLIFY_MIXED_TYPES, simplify_mixed_types);
309
310    let types = optimizer.finish();
311
312    if let Some(output) = config.debug_output {
313        let printer = MetaTypesPrinter::new(&types);
314        let debug = format!("{printer}");
315
316        write(output, debug)?;
317    }
318
319    Ok(types)
320}
321
322/// Executes the [`Generator`] with the passed `config`, `schema` and `types` to
323/// generate a [`DataTypes`] for further processing.
324///
325/// # Errors
326///
327/// Returns a suitable [`Error`] type if the process was not successful.
328#[instrument(err, level = "trace", skip(schemas, types))]
329pub fn exec_generator<'types>(
330    config: GeneratorConfig,
331    schemas: &Schemas,
332    types: &'types MetaTypes,
333) -> Result<DataTypes<'types>, Error> {
334    exec_generator_with_ident_cache(config, schemas, None, types)
335}
336
337/// Executes the [`Generator`] with the passed `config`, `schema`, `ident_cache`
338/// and `types` to generate a [`DataTypes`] for further processing.
339///
340/// The [`IdentCache`] is needed to resolve [`TypeIdent`]ifiers that should be
341/// rendered. If it is not passed, resolving the types by only using the [`Schemas`]
342/// structure may fail.
343///
344/// # Errors
345///
346/// Returns a suitable [`Error`] type if the process was not successful.
347#[instrument(err, level = "trace", skip(schemas, types))]
348pub fn exec_generator_with_ident_cache<'types>(
349    config: GeneratorConfig,
350    schemas: &Schemas,
351    ident_cache: Option<&IdentCache>,
352    types: &'types MetaTypes,
353) -> Result<DataTypes<'types>, Error> {
354    tracing::info!("Generate Module");
355
356    let mut generator = Generator::new(types)
357        .flags(config.flags)
358        .box_flags(config.box_flags)
359        .typedef_mode(config.typedef_mode);
360
361    generator = generator
362        .text_type(config.text_type)
363        .map_err(GeneratorError::from)?;
364    generator = generator
365        .mixed_type(config.mixed_type)
366        .map_err(GeneratorError::from)?;
367    generator = generator
368        .nillable_type(config.nillable_type)
369        .map_err(GeneratorError::from)?;
370    generator = generator
371        .any_type(config.any_type)
372        .map_err(GeneratorError::from)?;
373    generator = generator
374        .any_attributes_type(config.any_attributes_type)
375        .map_err(GeneratorError::from)?;
376
377    generator = generator.with_type_postfix(IdentType::Type, config.type_postfix.type_);
378    generator = generator.with_type_postfix(IdentType::Element, config.type_postfix.element);
379    generator =
380        generator.with_type_postfix(IdentType::ElementType, config.type_postfix.element_type);
381    generator = generator.with_type_postfix(
382        IdentType::NillableContent,
383        config.type_postfix.nillable_content,
384    );
385    generator = generator.with_type_postfix(
386        IdentType::DynamicElement,
387        config.type_postfix.dynamic_element,
388    );
389
390    for triple in config.types {
391        let mut ident = triple.resolve(schemas)?;
392        if let Some(ident_cache) = &ident_cache {
393            ident = ident_cache.resolve(ident)?;
394        }
395
396        generator = generator.with_type(ident)?;
397    }
398
399    let mut generator = generator.into_fixed();
400    match config.generate {
401        Generate::All => generator = generator.generate_all_types()?,
402        Generate::Named => generator = generator.generate_named_types()?,
403        Generate::Types(idents) => {
404            for triple in idents {
405                let ident = triple.resolve(schemas)?;
406
407                generator = generator.generate_type(ident)?;
408            }
409        }
410    }
411
412    let data_types = generator.finish();
413
414    Ok(data_types)
415}
416
417/// Executes the rendering process using the passed `config` and the `types`
418/// created by the [`Generator`].
419///
420/// # Errors
421///
422/// Returns a suitable [`Error`] type if the process was not successful.
423pub fn exec_render(config: RendererConfig, types: &DataTypes<'_>) -> Result<Module, RendererError> {
424    tracing::info!("Render Module");
425
426    let mut renderer = Renderer::new(types)
427        .flags(config.flags)
428        .alloc_crate(config.alloc)
429        .xsd_parser_types(config.xsd_parser_types);
430
431    if let Some(derive) = config.derive {
432        renderer = renderer.derive(derive);
433    }
434
435    if let Some(traits) = config.dyn_type_traits {
436        renderer = renderer.dyn_type_traits(traits)?;
437    }
438
439    for step in config.steps {
440        renderer = renderer.with_step_boxed(step.into_render_step());
441    }
442
443    let module = renderer.finish();
444
445    Ok(module)
446}
447
448/// Error emitted by the [`generate`] function.
449#[derive(Debug, ThisError)]
450pub enum Error {
451    /// IO Error.
452    #[error("IO Error: {0}")]
453    IoError(
454        #[from]
455        #[source]
456        IoError,
457    ),
458
459    /// Parser error.
460    #[error("Parser error: {0}")]
461    ParserError(#[source] ParserError<AnyError>),
462
463    /// Interpreter error.
464    #[error("Interpreter error: {0}")]
465    InterpreterError(
466        #[from]
467        #[source]
468        InterpreterError,
469    ),
470
471    /// Generator error.
472    #[error("Generator error: {0}")]
473    GeneratorError(
474        #[from]
475        #[source]
476        GeneratorError,
477    ),
478
479    /// Renderer error.
480    #[error("Renderer error: {0}")]
481    RendererError(
482        #[from]
483        #[source]
484        RendererError,
485    ),
486}
487
488impl<E> From<ParserError<E>> for Error
489where
490    AnyError: From<E>,
491{
492    fn from(value: ParserError<E>) -> Self {
493        match value {
494            ParserError::IoError(err) => Self::ParserError(ParserError::IoError(err)),
495            ParserError::XmlError(err) => Self::ParserError(ParserError::XmlError(err)),
496            ParserError::UrlParseError(err) => Self::ParserError(ParserError::UrlParseError(err)),
497            ParserError::UnableToResolve(url) => {
498                Self::ParserError(ParserError::UnableToResolve(url))
499            }
500            ParserError::Resolver(err) => {
501                Self::ParserError(ParserError::Resolver(AnyError::from(err)))
502            }
503            ParserError::InvalidFilePath(path) => {
504                Self::ParserError(ParserError::InvalidFilePath(path))
505            }
506            ParserError::MismatchingTargetNamespace {
507                location,
508                found,
509                expected,
510            } => Self::ParserError(ParserError::MismatchingTargetNamespace {
511                location,
512                found,
513                expected,
514            }),
515        }
516    }
517}