xsd_parser/
lib.rs

1#![doc = include_str!(concat!(env!("OUT_DIR"), "/README.md"))]
2
3pub mod config;
4pub mod generator;
5pub mod interpreter;
6pub mod optimizer;
7pub mod parser;
8pub mod quick_xml;
9pub mod schema;
10pub mod types;
11
12mod macros;
13mod misc;
14
15/// Type alias for [`generator::Error`].
16pub type GeneratorError = generator::Error;
17
18/// Type alias for [`interpreter::Error`].
19pub type InterpreterError = interpreter::Error;
20
21/// Type alias for [`parser::Error`].
22pub type ParserError<E> = parser::Error<E>;
23
24use std::fs::write;
25
26pub use config::Config;
27pub use generator::Generator;
28pub use interpreter::Interpreter;
29pub use misc::{AsAny, Error, WithNamespace};
30pub use optimizer::Optimizer;
31pub use parser::Parser;
32
33use macros::{assert_eq, unreachable};
34use proc_macro2::TokenStream;
35use tracing::instrument;
36
37use self::config::{
38    Generate, GeneratorConfig, InterpreterConfig, InterpreterFlags, OptimizerConfig,
39    OptimizerFlags, ParserConfig, ParserFlags, Resolver, Schema,
40};
41use self::misc::TypesPrinter;
42use self::parser::resolver::{FileResolver, ManyResolver};
43use self::schema::Schemas;
44use self::types::{IdentType, Types};
45
46/// Generates rust code from a XML schema using the passed `config`.
47///
48/// This is the most easiest way to use the `xsd-parser` crate. The `generate`
49/// function provides a simple way to generate rust code from XML schemas using
50/// the passed configuration.
51///
52/// If you need more detailed control over the generation process or only a part
53/// of it, use the [`Parser`], [`Interpreter`], [`Optimizer`] or [`Generator`]
54/// directly.
55///
56/// # Errors
57///
58/// Returns a suitable [`Error`] type if the process was not successful.
59#[instrument(err, level = "trace")]
60pub fn generate(config: Config) -> Result<TokenStream, Error> {
61    let schemas = exec_parser(config.parser)?;
62    let types = exec_interpreter(config.interpreter, &schemas)?;
63    let types = exec_optimizer(config.optimizer, types)?;
64    let code = exec_generator(config.generator, &schemas, &types)?;
65
66    Ok(code)
67}
68
69/// Executes the [`Parser`] with the passed `config`.
70///
71/// # Errors
72///
73/// Returns a suitable [`Error`] type if the process was not successful.
74#[instrument(err, level = "trace")]
75pub fn exec_parser(config: ParserConfig) -> Result<Schemas, Error> {
76    tracing::info!("Parse Schemas");
77
78    let mut resolver = ManyResolver::new();
79    for r in config.resolver {
80        match r {
81            #[cfg(feature = "web-resolver")]
82            Resolver::Web => {
83                let web_resolver = self::parser::resolver::WebResolver::new();
84
85                resolver = resolver.add_resolver(web_resolver);
86            }
87            Resolver::File => {
88                let file_resolver = FileResolver::new();
89
90                resolver = resolver.add_resolver(file_resolver);
91            }
92        }
93    }
94
95    let mut parser = Parser::new()
96        .with_resolver(resolver)
97        .resolve_includes(config.flags.contains(ParserFlags::RESOLVE_INCLUDES));
98
99    if config.flags.contains(ParserFlags::DEFAULT_NAMESPACES) {
100        parser = parser.with_default_namespaces();
101    }
102
103    for (prefix, namespace) in config.namespaces {
104        parser = parser.with_namespace(prefix, namespace);
105    }
106
107    for schema in config.schemas {
108        match schema {
109            Schema::Url(url) => parser = parser.add_schema_from_url(url)?,
110            Schema::File(path) => parser = parser.add_schema_from_file(path)?,
111            Schema::Schema(schema) => parser = parser.add_schema_from_str(&schema)?,
112        }
113    }
114
115    let schemas = parser.finish();
116
117    if let Some(output) = config.debug_output {
118        let debug = format!("{schemas:#?}");
119
120        write(output, debug)?;
121    }
122
123    Ok(schemas)
124}
125
126/// Executes the [`Interpreter`] with the passed `config` and `schema`.
127///
128/// # Errors
129///
130/// Returns a suitable [`Error`] type if the process was not successful.
131#[instrument(err, level = "trace", skip(schemas))]
132pub fn exec_interpreter(config: InterpreterConfig, schemas: &Schemas) -> Result<Types, Error> {
133    tracing::info!("Interpret Schema");
134
135    let mut interpreter = Interpreter::new(schemas);
136
137    if config.flags.contains(InterpreterFlags::BUILDIN_TYPES) {
138        interpreter = interpreter.with_buildin_types()?;
139    }
140
141    if config.flags.contains(InterpreterFlags::DEFAULT_TYPEDEFS) {
142        interpreter = interpreter.with_default_typedefs()?;
143    }
144
145    if config.flags.contains(InterpreterFlags::WITH_XS_ANY_TYPE) {
146        interpreter = interpreter.with_xs_any_type()?;
147    }
148
149    for (ident, ty) in config.types {
150        let ident = ident.resolve(schemas)?;
151        interpreter = interpreter.with_type(ident, ty)?;
152    }
153
154    let types = interpreter.finish()?;
155
156    if let Some(output) = config.debug_output {
157        let printer = TypesPrinter::new(&types);
158        let debug = format!("{printer}");
159
160        write(output, debug)?;
161    }
162
163    Ok(types)
164}
165
166/// Executes the [`Optimizer`] with the passed `config` and `types`.
167///
168/// # Errors
169///
170/// Returns a suitable [`Error`] type if the process was not successful.
171#[instrument(err, level = "trace", skip(types))]
172pub fn exec_optimizer(config: OptimizerConfig, types: Types) -> Result<Types, Error> {
173    tracing::info!("Optimize Types");
174
175    let mut optimizer = Optimizer::new(types);
176
177    macro_rules! exec {
178        ($flag:ident, $method:ident) => {
179            if config.flags.contains(OptimizerFlags::$flag) {
180                optimizer = optimizer.$method();
181            }
182        };
183    }
184
185    exec!(REMOVE_EMPTY_ENUM_VARIANTS, remove_empty_enum_variants);
186    exec!(REMOVE_EMPTY_ENUMS, remove_empty_enums);
187    exec!(
188        REMOVE_DUPLICATE_UNION_VARIANTS,
189        remove_duplicate_union_variants
190    );
191    exec!(REMOVE_EMPTY_UNIONS, remove_empty_unions);
192    exec!(USE_UNRESTRICTED_BASE_TYPE, use_unrestricted_base_type);
193    exec!(CONVERT_DYNAMIC_TO_CHOICE, convert_dynamic_to_choice);
194    exec!(FLATTEN_COMPLEX_TYPES, flatten_complex_types);
195    exec!(FLATTEN_UNIONS, flatten_unions);
196    exec!(MERGE_ENUM_UNIONS, merge_enum_unions);
197    exec!(RESOLVE_TYPEDEFS, resolve_typedefs);
198    exec!(REMOVE_DUPLICATES, remove_duplicates);
199    exec!(RESOLVE_TYPEDEFS, resolve_typedefs);
200    exec!(MERGE_CHOICE_CARDINALITIES, merge_choice_cardinalities);
201
202    let types = optimizer.finish();
203
204    if let Some(output) = config.debug_output {
205        let printer = TypesPrinter::new(&types);
206        let debug = format!("{printer}");
207
208        write(output, debug)?;
209    }
210
211    Ok(types)
212}
213
214/// Executes the [`Generator`] with the passed `config`, `schema` and `types`.
215///
216/// # Errors
217///
218/// Returns a suitable [`Error`] type if the process was not successful.
219#[instrument(err, level = "trace", skip(schemas, types))]
220pub fn exec_generator(
221    config: GeneratorConfig,
222    schemas: &Schemas,
223    types: &Types,
224) -> Result<TokenStream, Error> {
225    tracing::info!("Generate Code");
226
227    let mut generator = Generator::new(types)
228        .flags(config.flags)
229        .box_flags(config.box_flags)
230        .typedef_mode(config.typedef_mode)
231        .serde_support(config.serde_support)
232        .xsd_parser_crate(config.xsd_parser);
233
234    if let Some(derive) = config.derive {
235        generator = generator.derive(derive);
236    }
237
238    if let Some(traits) = config.dyn_type_traits {
239        generator = generator.dyn_type_traits(traits);
240    }
241
242    generator = generator.with_type_postfix(IdentType::Type, config.type_postfix.type_);
243    generator = generator.with_type_postfix(IdentType::Element, config.type_postfix.element);
244    generator =
245        generator.with_type_postfix(IdentType::ElementType, config.type_postfix.element_type);
246
247    for triple in config.types {
248        let ident = triple.resolve(schemas)?;
249
250        generator = generator.with_type(ident)?;
251    }
252
253    let mut generator = generator.into_fixed();
254    match config.generate {
255        Generate::All => generator = generator.generate_all_types()?,
256        Generate::Types(idents) => {
257            for triple in idents {
258                let ident = triple.resolve(schemas)?;
259
260                generator = generator.generate_type(ident)?;
261            }
262        }
263    }
264
265    let code = generator.finish();
266
267    Ok(code)
268}