Skip to content

Commit 137683d

Browse files
committed
Implement support for custom generator code
Types defined by the user using `CustomMeta` type variant may want to execute additional code, once the type is executed by the generator. This commits adds a new `CustomGenerator` trait that can be added to the `CustomMeta` and is executed by the generator when the type is executed. This allows users to instruct the generator to generate other dependent types, use the generator context to resolve type paths or to just execute any kind of custom code.
1 parent 70ffa0b commit 137683d

12 files changed

Lines changed: 206 additions & 80 deletions

File tree

xsd-parser/src/config/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,17 @@ impl IdentQuadruple {
841841
}
842842
}
843843

844+
impl From<TypeIdent> for IdentQuadruple {
845+
fn from(value: TypeIdent) -> Self {
846+
Self {
847+
ns: Some(NamespaceIdent::Id(value.ns)),
848+
schema: Some(SchemaIdent::Id(value.schema)),
849+
name: value.name.into(),
850+
type_: value.type_,
851+
}
852+
}
853+
}
854+
844855
impl<X> From<(IdentType, X)> for IdentQuadruple
845856
where
846857
X: AsRef<str>,

xsd-parser/src/models/code/ident_path.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,12 @@ impl ToTokens for IdentPath {
273273
}
274274

275275
impl ModulePath {
276+
/// Creates a new [`ModulePath`] instance that represents the root module.
277+
#[must_use]
278+
pub fn root() -> Self {
279+
Self::default()
280+
}
281+
276282
/// Create a new [`ModulePath`] instance from the passed namespace id `ns` and the
277283
/// `types` information.
278284
///

xsd-parser/src/models/meta/custom.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ use std::hash::{Hash, Hasher};
66
use xsd_parser_types::misc::Namespace;
77

88
use crate::config::NamespaceId;
9-
use crate::pipeline::generator::{ValueGenerator, ValueGeneratorBox};
9+
use crate::pipeline::generator::{
10+
CustomGenerator, CustomGeneratorBox, ValueGenerator, ValueGeneratorBox,
11+
};
1012

1113
/// Type information for a custom defined type.
1214
pub struct CustomMeta {
@@ -24,6 +26,11 @@ pub struct CustomMeta {
2426
/// to suitable rust code.
2527
pub default: Option<ValueGeneratorBox>,
2628

29+
/// The generator step that is execute if a custom type is processed by the generator.
30+
///
31+
/// This can be used to add custom code to the generated code for a custom type.
32+
pub generator: Option<CustomGeneratorBox>,
33+
2734
/// The namespaces needed by this custom type.
2835
pub namespaces: Vec<CustomMetaNamespace>,
2936

@@ -42,6 +49,7 @@ impl CustomMeta {
4249
name: name.into(),
4350
include: None,
4451
default: None,
52+
generator: None,
4553
namespaces: Vec::new(),
4654
allow_any: false,
4755
}
@@ -80,6 +88,14 @@ impl CustomMeta {
8088
self
8189
}
8290

91+
/// Set the generator step that is execute if a custom type is processed by the generator.
92+
#[must_use]
93+
pub fn with_generator<X: CustomGenerator>(mut self, x: X) -> Self {
94+
self.generator = Some(Box::new(x));
95+
96+
self
97+
}
98+
8399
/// Add a namespace that is needed by this custom type.
84100
///
85101
/// The namespace may be added to the root element during serialization.
@@ -120,6 +136,10 @@ impl Clone for CustomMeta {
120136
name: self.name.clone(),
121137
include: self.include.clone(),
122138
default: self.default.as_ref().map(|x| ValueGenerator::clone(&**x)),
139+
generator: self
140+
.generator
141+
.as_ref()
142+
.map(|x| CustomGenerator::clone(&**x)),
123143
namespaces: self.namespaces.clone(),
124144
allow_any: self.allow_any,
125145
}
@@ -132,6 +152,7 @@ impl Debug for CustomMeta {
132152
.field("name", &self.name)
133153
.field("include", &self.include)
134154
.field("default", &self.default.is_some())
155+
.field("generator", &self.generator.is_some())
135156
.field("namespaces", &self.namespaces)
136157
.field("allow_any", &self.allow_any)
137158
.finish()

xsd-parser/src/models/meta/types.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
use std::collections::BTreeMap;
22

3+
use inflector::Inflector;
4+
use quote::format_ident;
35
use xsd_parser_types::misc::Namespace;
46

57
use crate::models::{
8+
code::IdentPath,
9+
data::PathData,
610
schema::{xs::FormChoiceType, NamespaceId, SchemaId},
711
Name, Naming, TypeIdent,
812
};
@@ -156,6 +160,32 @@ impl ModuleMeta {
156160
pub fn prefix(&self) -> Option<&Name> {
157161
self.prefix.as_ref().or(self.name.as_ref())
158162
}
163+
164+
/// Create a path to the contstant that represents the namespace of this module.
165+
#[must_use]
166+
pub fn make_ns_const(&self) -> Option<PathData> {
167+
self.namespace.as_ref()?;
168+
let name = self.name().map_or_else(
169+
|| format!("UNNAMED_{}", self.namespace_id.0),
170+
|name| name.as_str().to_screaming_snake_case(),
171+
);
172+
let ident = format_ident!("NS_{name}");
173+
let path = IdentPath::from_parts([], ident);
174+
175+
Some(PathData::from_path(path))
176+
}
177+
178+
/// Create a path to the contstant that represents the prefix of this module.
179+
#[must_use]
180+
pub fn make_prefix_const(&self) -> Option<PathData> {
181+
self.namespace.as_ref()?;
182+
let name = self.name()?;
183+
let name = name.as_str().to_screaming_snake_case();
184+
let ident = format_ident!("PREFIX_{name}");
185+
let path = IdentPath::from_parts([], ident);
186+
187+
Some(PathData::from_path(path))
188+
}
159189
}
160190

161191
fn get_resolved_impl<'a>(

xsd-parser/src/pipeline/generator/context.rs

Lines changed: 54 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -40,46 +40,25 @@ impl<'a, 'types> Context<'a, 'types> {
4040
self.namespaces.last()
4141
}
4242

43-
pub(super) fn new(
44-
meta: &'a MetaData<'types>,
45-
ident: &'a TypeIdent,
46-
state: &'a mut State<'types>,
47-
) -> Self {
48-
let reachable = state.loop_detection.get_reachable(&state.cache, ident);
49-
50-
Self {
51-
meta,
52-
ident,
53-
state,
54-
reachable,
55-
namespaces: Vec::new(),
56-
}
57-
}
58-
59-
pub(super) fn current_module(&self) -> ModuleIdent {
60-
ModuleIdent::new(
61-
self.meta.types,
62-
self.ident,
63-
self.check_generator_flags(GeneratorFlags::USE_NAMESPACE_MODULES),
64-
self.check_generator_flags(GeneratorFlags::USE_SCHEMA_MODULES),
65-
)
66-
}
67-
68-
pub(super) fn current_type_ref(&self) -> &TypeRef {
69-
self.state.cache.get(self.ident).unwrap()
70-
}
71-
72-
pub(super) fn get_trait_infos(&mut self) -> &TraitInfos {
73-
&self.state.trait_infos
74-
}
75-
76-
pub(super) fn get_or_create_type_ref(&mut self, ident: &TypeIdent) -> Result<&TypeRef, Error> {
43+
/// Get the [`TypeRef`] for the passed `ident`, creating it (and scheduling
44+
/// the type for generation) if it does not exist yet.
45+
///
46+
/// # Errors
47+
///
48+
/// Forwards the errors raised while creating the type reference.
49+
pub fn get_or_create_type_ref(&mut self, ident: &TypeIdent) -> Result<&TypeRef, Error> {
7750
let type_ref = self.state.get_or_create_type_ref_mut(self.meta, ident)?;
7851

7952
Ok(type_ref)
8053
}
8154

82-
pub(super) fn get_or_create_type_ref_for_value(
55+
/// Like [`get_or_create_type_ref`](Self::get_or_create_type_ref), but also
56+
/// marks the type as reachable by value if `by_value` is set.
57+
///
58+
/// # Errors
59+
///
60+
/// Forwards the errors raised while creating the type reference.
61+
pub fn get_or_create_type_ref_for_value(
8362
&mut self,
8463
ident: &TypeIdent,
8564
by_value: bool,
@@ -93,7 +72,14 @@ impl<'a, 'types> Context<'a, 'types> {
9372
Ok(type_ref)
9473
}
9574

96-
pub(super) fn get_or_create_type_ref_for_element(
75+
/// Like [`get_or_create_type_ref`](Self::get_or_create_type_ref), but for an
76+
/// element. Returns the [`TypeRef`] together with a flag that indicates if
77+
/// the type needs to be boxed.
78+
///
79+
/// # Errors
80+
///
81+
/// Forwards the errors raised while creating the type reference.
82+
pub fn get_or_create_type_ref_for_element(
9783
&mut self,
9884
ident: &TypeIdent,
9985
by_value: bool,
@@ -108,6 +94,38 @@ impl<'a, 'types> Context<'a, 'types> {
10894
Ok((type_ref, boxed))
10995
}
11096

97+
pub(super) fn new(
98+
meta: &'a MetaData<'types>,
99+
ident: &'a TypeIdent,
100+
state: &'a mut State<'types>,
101+
) -> Self {
102+
let reachable = state.loop_detection.get_reachable(&state.cache, ident);
103+
104+
Self {
105+
meta,
106+
ident,
107+
state,
108+
reachable,
109+
namespaces: Vec::new(),
110+
}
111+
}
112+
113+
pub(super) fn current_module(&self) -> ModuleIdent {
114+
ModuleIdent::new(
115+
self.meta.types,
116+
self.ident,
117+
self.check_generator_flags(GeneratorFlags::USE_NAMESPACE_MODULES),
118+
self.check_generator_flags(GeneratorFlags::USE_SCHEMA_MODULES),
119+
)
120+
}
121+
122+
pub(super) fn current_type_ref(&self) -> &TypeRef {
123+
self.state.cache.get(self.ident).unwrap()
124+
}
125+
126+
pub(super) fn get_trait_infos(&mut self) -> &TraitInfos {
127+
&self.state.trait_infos
128+
}
111129
pub(super) fn make_trait_impls(&mut self) -> Result<Vec<TokenStream>, Error> {
112130
let ident = self.ident.clone();
113131
let current_module = self.current_module();

xsd-parser/src/pipeline/generator/custom.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,52 @@
11
use std::fmt::{Debug, Formatter, Result as FmtResult};
22

3+
use crate::models::meta::CustomMeta;
34
use crate::pipeline::renderer::ValueRenderer;
45

56
use super::{Context, Error};
67

8+
/// Boxed version of [`CustomGenerator`].
9+
pub type CustomGeneratorBox = Box<dyn CustomGenerator>;
10+
11+
impl Debug for CustomGeneratorBox {
12+
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
13+
f.debug_struct("CustomGeneratorBox").finish()
14+
}
15+
}
16+
17+
/// Implements a custom generator step that is execute if a custom type is
18+
/// processed by the generator.
19+
pub trait CustomGenerator: 'static {
20+
/// Execute this generator step.
21+
fn exec<'types>(
22+
&self,
23+
ctx: &mut Context<'_, 'types>,
24+
meta: &'types CustomMeta,
25+
) -> Result<(), Error>;
26+
27+
/// Clone this instance and return it as a box.
28+
fn clone(&self) -> Box<dyn CustomGenerator>;
29+
}
30+
31+
impl<X> CustomGenerator for X
32+
where
33+
X: for<'types> Fn(&mut Context<'_, 'types>, &'types CustomMeta) -> Result<(), Error>
34+
+ Clone
35+
+ 'static,
36+
{
37+
fn exec<'types>(
38+
&self,
39+
ctx: &mut Context<'_, 'types>,
40+
meta: &'types CustomMeta,
41+
) -> Result<(), Error> {
42+
(*self)(ctx, meta)
43+
}
44+
45+
fn clone(&self) -> Box<dyn CustomGenerator> {
46+
Box::new(self.clone())
47+
}
48+
}
49+
750
/// Boxed version of [`ValueGenerator`].
851
pub type ValueGeneratorBox = Box<dyn ValueGenerator>;
952

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use std::borrow::Cow;
2+
3+
use crate::models::{data::CustomData, meta::CustomMeta};
4+
5+
use super::super::{Context, Error};
6+
7+
impl<'types> CustomData<'types> {
8+
pub(super) fn new(
9+
meta: &'types CustomMeta,
10+
ctx: &mut Context<'_, 'types>,
11+
) -> Result<Self, Error> {
12+
if let Some(generator) = &meta.generator {
13+
generator.exec(ctx, meta)?;
14+
}
15+
16+
Ok(Self {
17+
meta: Cow::Borrowed(meta),
18+
})
19+
}
20+
}

xsd-parser/src/pipeline/generator/data/mod.rs

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
mod complex;
22
mod constrains;
3+
mod custom;
34
mod dynamic;
45
mod enumeration;
56
mod reference;
@@ -13,8 +14,8 @@ use std::mem::swap;
1314
use crate::config::GeneratorFlags;
1415
use crate::models::{
1516
code::IdentPath,
16-
data::{BuildInData, CustomData, PathData},
17-
meta::{BuildInMeta, CustomMeta},
17+
data::{BuildInData, PathData},
18+
meta::BuildInMeta,
1819
};
1920

2021
use super::Context;
@@ -27,14 +28,6 @@ impl<'types> BuildInData<'types> {
2728
}
2829
}
2930

30-
impl<'types> CustomData<'types> {
31-
fn new(meta: &'types CustomMeta) -> Self {
32-
Self {
33-
meta: Cow::Borrowed(meta),
34-
}
35-
}
36-
}
37-
3831
impl Context<'_, '_> {
3932
fn path_data_nillable(&self, is_mixed: bool, mut path: PathData) -> PathData {
4033
if !is_mixed {

xsd-parser/src/pipeline/generator/data/type_.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ impl<'types> DataType<'types> {
2121
let derive = ConfigValue::Default;
2222
let variant = match &meta.variant {
2323
M::BuildIn(x) => D::BuildIn(BuildInData::new(x)),
24-
M::Custom(x) => D::Custom(CustomData::new(x)),
24+
M::Custom(x) => D::Custom(CustomData::new(x, ctx)?),
2525
M::Union(x) => D::Union(UnionData::new(x, ctx)?),
2626
M::Dynamic(x) => D::Dynamic(DynamicData::new(x, ctx)?),
2727
M::Reference(x) => D::Reference(ReferenceData::new(x, ctx)?),

xsd-parser/src/pipeline/generator/mod.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,14 @@ use crate::models::{
4444
use crate::traits::Naming;
4545

4646
pub use self::context::Context;
47-
pub use self::custom::{ValueGenerator, ValueGeneratorBox, ValueGeneratorMode};
47+
pub use self::custom::{
48+
CustomGenerator, CustomGeneratorBox, ValueGenerator, ValueGeneratorBox, ValueGeneratorMode,
49+
};
4850
pub use self::error::Error;
4951
pub use self::meta::MetaData;
52+
pub use self::state::TypeRef;
5053

51-
use self::state::{LoopDetection, PendingType, State, TraitInfos, TypeRef};
54+
use self::state::{LoopDetection, PendingType, State, TraitInfos};
5255

5356
/// Configurable Rust code generator for schema-derived type information.
5457
///

0 commit comments

Comments
 (0)