teaql-core 5.0.2

TeaQL core, SQL, runtime, dialect, and macro crates for model-driven data access
Documentation
use std::error::Error;
use std::fmt::{Display, Formatter};

use chrono::NaiveDate;

pub const DEFAULT_BUSINESS_ID_PROFILE: &str = "daily-sequence";
pub const DEFAULT_BUSINESS_ID_DIGITS: u8 = 8;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BusinessIdErrorCode {
    InvalidDefinition,
    InvalidFormat,
    Immutable,
    Exhausted,
    Allocation,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BusinessIdError {
    pub code: BusinessIdErrorCode,
    pub message: String,
}

impl BusinessIdError {
    pub fn new(code: BusinessIdErrorCode, message: impl Into<String>) -> Self {
        Self {
            code,
            message: message.into(),
        }
    }
}

impl Display for BusinessIdError {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
        write!(formatter, "{:?}: {}", self.code, self.message)
    }
}

impl Error for BusinessIdError {}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BusinessIdDefinition {
    pub field_name: String,
    pub profile: String,
    pub prefix: String,
    pub namespace: String,
    pub split_by_date: bool,
    pub preserve_digits: u8,
    pub separator: String,
}

impl BusinessIdDefinition {
    pub fn daily_sequence(
        field_name: impl Into<String>,
        prefix: impl Into<String>,
        namespace: impl Into<String>,
    ) -> Self {
        Self {
            field_name: field_name.into(),
            profile: DEFAULT_BUSINESS_ID_PROFILE.to_owned(),
            prefix: prefix.into(),
            namespace: namespace.into(),
            split_by_date: true,
            preserve_digits: DEFAULT_BUSINESS_ID_DIGITS,
            separator: "-".to_owned(),
        }
    }

    pub fn max_sequence(&self) -> Result<u64, BusinessIdError> {
        if self.preserve_digits == 0 || self.preserve_digits > 19 {
            return Err(BusinessIdError::new(
                BusinessIdErrorCode::InvalidDefinition,
                "preserve_digits must be between 1 and 19",
            ));
        }
        10_u64
            .checked_pow(u32::from(self.preserve_digits))
            .and_then(|value| value.checked_sub(1))
            .ok_or_else(|| {
                BusinessIdError::new(
                    BusinessIdErrorCode::InvalidDefinition,
                    "preserve_digits exceeds the supported u64 range",
                )
            })
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BusinessIdScope {
    pub tenant: String,
    pub namespace: String,
    pub date_partition: Option<String>,
}

impl BusinessIdScope {
    pub fn canonical_key(&self) -> String {
        [
            escape(&self.tenant),
            escape(&self.namespace),
            escape(self.date_partition.as_deref().unwrap_or("")),
        ]
        .join("|")
    }
}

fn escape(value: &str) -> String {
    value.replace('%', "%25").replace('|', "%7C")
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BusinessIdGenerationRequest<'a> {
    pub definition: &'a BusinessIdDefinition,
    pub tenant: &'a str,
    pub aggregate_type: &'a str,
    pub business_date: NaiveDate,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BusinessIdPlan {
    pub scope: BusinessIdScope,
    pub prefix: String,
    pub date_text: Option<String>,
    pub preserve_digits: u8,
    pub separator: String,
    pub max_sequence: u64,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BusinessIdAllocation {
    pub scope: BusinessIdScope,
    pub sequence: u64,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BusinessIdValue(pub String);

pub trait BusinessIdAllocator: Send + Sync {
    fn allocate(&self, plan: &BusinessIdPlan) -> Result<BusinessIdAllocation, BusinessIdError>;
}

pub trait BusinessIdProfile: Send + Sync {
    fn plan(
        &self,
        request: &BusinessIdGenerationRequest<'_>,
    ) -> Result<BusinessIdPlan, BusinessIdError>;

    fn format(
        &self,
        plan: &BusinessIdPlan,
        allocation: &BusinessIdAllocation,
    ) -> Result<BusinessIdValue, BusinessIdError>;

    fn validate(
        &self,
        definition: &BusinessIdDefinition,
        value: &str,
    ) -> Result<BusinessIdValue, BusinessIdError>;
}

pub trait BusinessIdSlot {
    fn current_business_id(&self) -> Option<&str>;
    fn is_new_aggregate(&self) -> bool;
    fn assign_business_id(&mut self, value: BusinessIdValue);
}