#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub enum ConversionSelector {
FieldPath(String),
SemanticType(String),
NamedType(String),
}
impl ConversionSelector {
#[must_use]
pub fn semantic_type(name: impl Into<String>) -> Self {
Self::SemanticType(name.into())
}
#[must_use]
pub fn named_type(name: impl Into<String>) -> Self {
Self::NamedType(name.into())
}
#[must_use]
pub fn field_path(path: impl Into<String>) -> Self {
Self::FieldPath(path.into())
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash)]
pub enum DomainVarData {
#[default]
Bytes,
LossyStrings,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GenerationConfig {
pub(crate) module_name: String,
pub(crate) shared_module: Option<String>,
pub(crate) domain_objects: bool,
pub(crate) domain_var_data: DomainVarData,
pub(crate) conversions: Vec<ConversionSelector>,
pub(crate) domain_types: Vec<(ConversionSelector, String)>,
pub(crate) external_sbe_rt_path: Option<String>,
pub(crate) error_from_path: Option<String>,
pub(crate) unchecked_companions: bool,
pub(crate) keyword_append_token: String,
pub(crate) deprecated_attrs: bool,
}
impl GenerationConfig {
#[must_use]
pub fn new(module_name: impl Into<String>) -> Self {
Self {
module_name: module_name.into(),
shared_module: None,
domain_objects: false,
domain_var_data: DomainVarData::Bytes,
conversions: Vec::new(),
domain_types: Vec::new(),
external_sbe_rt_path: None,
error_from_path: None,
unchecked_companions: false,
keyword_append_token: "_".into(),
deprecated_attrs: false,
}
}
#[must_use]
pub(crate) fn module_name(&self) -> &str {
&self.module_name
}
#[must_use]
pub(crate) fn domain_objects_enabled(&self) -> bool {
self.domain_objects
}
pub(crate) fn has_conversions(&self) -> bool {
!self.conversions.is_empty() || !self.domain_types.is_empty()
}
#[must_use]
pub(crate) fn external_sbe_rt_path(&self) -> Option<&str> {
self.external_sbe_rt_path.as_deref()
}
#[must_use]
pub fn with_external_sbe_rt(mut self, path: impl Into<String>) -> Self {
self.external_sbe_rt_path = Some(path.into());
self
}
#[must_use]
pub fn with_conversion(mut self, selector: ConversionSelector) -> Self {
if !self.conversions.contains(&selector) {
self.conversions.push(selector);
}
self
}
#[must_use]
pub fn with_domain_type(
mut self,
selector: ConversionSelector,
rust_type: impl Into<String>,
) -> Self {
let sel = selector;
let ty = rust_type.into();
if !self.conversions.contains(&sel) {
self.conversions.push(sel.clone());
}
if !self.domain_types.iter().any(|(s, _)| s == &sel) {
self.domain_types.push((sel, ty));
}
self
}
#[must_use]
pub fn enable_error_from_impls(mut self, path: impl Into<String>) -> Self {
self.error_from_path = Some(path.into());
self
}
#[must_use]
pub fn enable_domain_objects(mut self, var_data: DomainVarData) -> Self {
self.domain_objects = true;
self.domain_var_data = var_data;
self
}
#[must_use]
pub fn with_shared_module(mut self, name: impl Into<String>) -> Self {
self.shared_module = Some(name.into());
self
}
#[must_use]
pub fn with_unchecked_companions(mut self) -> Self {
self.unchecked_companions = true;
self
}
#[must_use]
pub fn with_keyword_append_token(mut self, token: impl Into<String>) -> Self {
self.keyword_append_token = token.into();
self
}
#[must_use]
pub fn with_deprecated_attrs(mut self) -> Self {
self.deprecated_attrs = true;
self
}
}
impl Default for GenerationConfig {
fn default() -> Self {
Self::new("messages")
}
}
#[cfg(test)]
mod tests {
use super::{ConversionSelector, DomainVarData, GenerationConfig};
#[test]
fn default_config_is_clean() -> Result<(), Box<dyn std::error::Error>> {
let config = GenerationConfig::default();
assert_eq!(config.module_name(), "messages");
assert!(!config.domain_objects_enabled());
assert!(!config.has_conversions());
Ok(())
}
#[test]
fn with_conversion_adds_selector() -> Result<(), Box<dyn std::error::Error>> {
let config = GenerationConfig::new("test")
.with_conversion(ConversionSelector::named_type("Decimal"));
assert!(config.has_conversions());
assert_eq!(config.conversions.len(), 1);
Ok(())
}
#[test]
fn with_conversion_dedup() -> Result<(), Box<dyn std::error::Error>> {
let config = GenerationConfig::new("test")
.with_conversion(ConversionSelector::named_type("Decimal"))
.with_conversion(ConversionSelector::named_type("Decimal"));
assert_eq!(config.conversions.len(), 1);
Ok(())
}
#[test]
fn with_domain_type_adds_conversion_and_type() -> Result<(), Box<dyn std::error::Error>> {
let config = GenerationConfig::new("test").with_domain_type(
ConversionSelector::named_type("Decimal"),
"rust_decimal::Decimal",
);
assert!(config.has_conversions());
assert_eq!(config.conversions.len(), 1);
assert_eq!(config.domain_types.len(), 1);
Ok(())
}
#[test]
fn with_domain_type_dedup() -> Result<(), Box<dyn std::error::Error>> {
let config = GenerationConfig::new("test")
.with_domain_type(
ConversionSelector::named_type("Decimal"),
"rust_decimal::Decimal",
)
.with_domain_type(
ConversionSelector::named_type("Decimal"),
"rust_decimal::Decimal",
);
assert_eq!(config.domain_types.len(), 1);
Ok(())
}
#[test]
fn with_external_sbe_rt_sets_path() -> Result<(), Box<dyn std::error::Error>> {
let config = GenerationConfig::new("m").with_external_sbe_rt("crate::rt::sbe_rt");
assert_eq!(config.external_sbe_rt_path(), Some("crate::rt::sbe_rt"));
Ok(())
}
#[test]
fn new_config_has_correct_defaults() -> Result<(), Box<dyn std::error::Error>> {
let config = GenerationConfig::new("mymod");
assert_eq!(config.module_name(), "mymod");
assert!(!config.domain_objects_enabled());
assert!(config.conversions.is_empty());
assert!(config.domain_types.is_empty());
assert_eq!(config.domain_var_data, DomainVarData::Bytes);
Ok(())
}
#[test]
fn enable_domain_objects_var_data_modes() -> Result<(), Box<dyn std::error::Error>> {
let text = GenerationConfig::new("m").enable_domain_objects(DomainVarData::LossyStrings);
assert!(text.domain_objects_enabled());
assert_eq!(text.domain_var_data, DomainVarData::LossyStrings);
let bytes = GenerationConfig::new("m").enable_domain_objects(DomainVarData::Bytes);
assert!(bytes.domain_objects_enabled());
assert_eq!(bytes.domain_var_data, DomainVarData::Bytes);
Ok(())
}
}