use crate::{
Associativity, BaseObject, CategorySpec, DiagonalSpec, StructuralFeatures,
TensorSpec, TerminalSpec, ValidationError,
};
#[derive(Clone, Debug)]
pub struct CategoryBuilder {
name: String,
base_objects: Vec<BaseObject>,
structure: StructuralFeatures,
}
impl CategoryBuilder {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
base_objects: Vec::new(),
structure: StructuralFeatures::default(),
}
}
pub fn with_base(mut self, name: impl Into<String>) -> Self {
self.base_objects.push(BaseObject {
name: name.into(),
description: None,
});
self
}
pub fn with_base_described(mut self, name: impl Into<String>, description: impl Into<String>) -> Self {
self.base_objects.push(BaseObject {
name: name.into(),
description: Some(description.into()),
});
self
}
pub fn with_terminal(mut self) -> Self {
self.structure.terminal = true;
self
}
pub fn with_products(mut self) -> Self {
self.structure.products = true;
self
}
pub fn with_initial(mut self) -> Self {
self.structure.initial = true;
self
}
pub fn with_coproducts(mut self) -> Self {
self.structure.coproducts = true;
self
}
pub fn with_exponentials(mut self) -> Self {
self.structure.exponentials = true;
self.structure.products = true; self
}
pub fn with_tensor(mut self) -> Self {
self.structure.tensor = Some(TensorSpec::default());
self
}
pub fn with_tensor_custom(
mut self,
symbol: impl Into<String>,
unit_symbol: impl Into<String>,
associativity: Associativity,
) -> Self {
self.structure.tensor = Some(TensorSpec {
symbol: symbol.into(),
unit_symbol: unit_symbol.into(),
associativity,
});
self
}
pub fn with_linear_hom(mut self) -> Self {
self.structure.linear_hom = true;
if self.structure.tensor.is_none() {
self.structure.tensor = Some(TensorSpec::default());
}
self
}
pub fn with_diagonal(mut self) -> Self {
self.structure.diagonal = DiagonalSpec::Universal;
self
}
pub fn with_diagonal_restricted(mut self, types: Vec<String>) -> Self {
self.structure.diagonal = DiagonalSpec::Restricted(types);
self
}
pub fn with_weakening(mut self) -> Self {
self.structure.terminal_morphism = TerminalSpec::Universal;
self
}
pub fn with_weakening_restricted(mut self, types: Vec<String>) -> Self {
self.structure.terminal_morphism = TerminalSpec::Restricted(types);
self
}
pub fn with_symmetry(mut self) -> Self {
self.structure.symmetry = true;
self
}
pub fn cartesian(mut self) -> Self {
self.structure.diagonal = DiagonalSpec::Universal;
self.structure.terminal_morphism = TerminalSpec::Universal;
self.structure.symmetry = true;
self
}
pub fn linear(mut self) -> Self {
self.structure.diagonal = DiagonalSpec::None;
self.structure.terminal_morphism = TerminalSpec::None;
self
}
pub fn affine(mut self) -> Self {
self.structure.diagonal = DiagonalSpec::None;
self.structure.terminal_morphism = TerminalSpec::Universal;
self
}
pub fn relevant(mut self) -> Self {
self.structure.diagonal = DiagonalSpec::Universal;
self.structure.terminal_morphism = TerminalSpec::None;
self
}
pub fn build(self) -> Result<CategorySpec, ValidationError> {
self.validate()?;
Ok(CategorySpec {
name: self.name,
base_objects: self.base_objects,
structure: self.structure,
})
}
fn validate(&self) -> Result<(), ValidationError> {
if self.name.is_empty() {
return Err(ValidationError::EmptyName);
}
let mut seen = std::collections::HashSet::new();
for obj in &self.base_objects {
if !seen.insert(&obj.name) {
return Err(ValidationError::DuplicateBaseType(obj.name.clone()));
}
}
if self.structure.exponentials && !self.structure.products {
return Err(ValidationError::ExponentialsRequireProducts);
}
if self.structure.linear_hom && self.structure.tensor.is_none() {
return Err(ValidationError::LinearHomRequiresTensor);
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_build_ccc() {
let spec = CategoryBuilder::new("CCC")
.with_terminal()
.with_products()
.with_exponentials()
.cartesian()
.build()
.unwrap();
assert_eq!(spec.name, "CCC");
assert!(spec.structure.terminal);
assert!(spec.structure.products);
assert!(spec.structure.exponentials);
assert!(spec.is_cartesian());
}
#[test]
fn test_build_smcc() {
let spec = CategoryBuilder::new("SMCC")
.with_tensor()
.with_linear_hom()
.with_symmetry()
.linear()
.build()
.unwrap();
assert_eq!(spec.name, "SMCC");
assert!(spec.structure.tensor.is_some());
assert!(spec.structure.linear_hom);
assert!(spec.structure.symmetry);
assert!(spec.is_linear());
}
#[test]
fn test_build_affine() {
let spec = CategoryBuilder::new("Affine")
.with_tensor()
.with_linear_hom()
.affine()
.build()
.unwrap();
assert!(spec.is_affine());
}
#[test]
fn test_empty_name_fails() {
let result = CategoryBuilder::new("").build();
assert!(matches!(result, Err(ValidationError::EmptyName)));
}
#[test]
fn test_duplicate_base_type_fails() {
let result = CategoryBuilder::new("Test")
.with_base("Int")
.with_base("Int")
.build();
assert!(matches!(result, Err(ValidationError::DuplicateBaseType(_))));
}
}