use crate::domain::entity::source::Source;
use crate::domain::entity::{persona_id::PersonaId, projection::ProjectionName, slot::Slot};
use crate::domain::error::WireResult;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Wiring {
persona_id: PersonaId,
slot: Slot,
source: Source,
projection_ref: Option<ProjectionName>,
}
impl Wiring {
pub fn new(
persona_id: PersonaId,
slot: Slot,
source: Source,
projection_ref: Option<ProjectionName>,
) -> Self {
Self {
persona_id,
slot,
source,
projection_ref,
}
}
pub fn from_parts(
persona_id: impl Into<String>,
slot: impl Into<String>,
source_uri: impl Into<String>,
projection_ref: Option<String>,
) -> WireResult<Self> {
let projection_ref = projection_ref.map(ProjectionName::new).transpose()?;
Ok(Self::new(
PersonaId::new(persona_id)?,
Slot::new(slot)?,
Source::new(source_uri)?,
projection_ref,
))
}
pub fn persona_id(&self) -> &PersonaId {
&self.persona_id
}
pub fn slot(&self) -> &Slot {
&self.slot
}
pub fn source(&self) -> &Source {
&self.source
}
pub fn projection_ref(&self) -> Option<&ProjectionName> {
self.projection_ref.as_ref()
}
pub fn storage_node_id(&self) -> String {
format!("{}.{}", self.persona_id.as_str(), self.slot.as_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::domain::error::{DomainError, WireError};
fn sample() -> Wiring {
Wiring::from_parts(
"test_persona_a",
"mailbox",
"mini-app://mailbox?alias=for_test_persona_a",
Some("test_persona_a.section.mailbox".to_string()),
)
.expect("valid wiring")
}
#[test]
fn from_parts_accepts_valid() {
let w = sample();
assert_eq!(w.persona_id().as_str(), "test_persona_a");
assert_eq!(w.slot().as_str(), "mailbox");
assert_eq!(
w.source().as_str(),
"mini-app://mailbox?alias=for_test_persona_a"
);
assert_eq!(
w.projection_ref().map(|p| p.as_str()),
Some("test_persona_a.section.mailbox")
);
}
#[test]
fn from_parts_allows_missing_projection_ref() {
let w = Wiring::from_parts(
"test_persona_a",
"mail",
"mini-app://mail?alias=for_test_persona_a",
None,
)
.unwrap();
assert!(w.projection_ref().is_none());
}
#[test]
fn from_parts_rejects_empty_persona_id() {
let err = Wiring::from_parts("", "mailbox", "mini-app://x", None)
.expect_err("empty persona must reject");
assert!(matches!(
err,
WireError::Domain(DomainError::InvalidPersonaId(_))
));
}
#[test]
fn from_parts_rejects_slot_with_dot() {
let err = Wiring::from_parts("test_persona_a", "a.b", "mini-app://x", None)
.expect_err("dot slot must reject");
assert!(matches!(
err,
WireError::Domain(DomainError::InvalidMetadata(_))
));
}
#[test]
fn from_parts_rejects_invalid_source() {
let err = Wiring::from_parts("test_persona_a", "mailbox", "no_scheme", None)
.expect_err("scheme-less source must reject");
assert!(matches!(
err,
WireError::Domain(DomainError::InvalidSource(_))
));
}
#[test]
fn from_parts_rejects_empty_projection_ref() {
let err = Wiring::from_parts(
"test_persona_a",
"mailbox",
"mini-app://x",
Some(String::new()),
)
.expect_err("empty projection ref must reject");
assert!(matches!(
err,
WireError::Domain(DomainError::InvalidProjection(_))
));
}
#[test]
fn storage_node_id_concatenates_natural_key() {
let w = sample();
assert_eq!(w.storage_node_id(), "test_persona_a.mailbox");
}
#[test]
fn immutable_equality() {
let a = sample();
let b = sample();
assert_eq!(a, b);
}
#[test]
fn new_typed_vo_path_assembles() {
let w = Wiring::new(
PersonaId::new("test_persona_a").unwrap(),
Slot::new("mailbox").unwrap(),
Source::new("mini-app://mailbox?alias=for_test_persona_a").unwrap(),
Some(ProjectionName::new("test_persona_a.section.mailbox").unwrap()),
);
assert_eq!(w.persona_id().as_str(), "test_persona_a");
assert_eq!(w.slot().as_str(), "mailbox");
assert_eq!(
w.source().as_str(),
"mini-app://mailbox?alias=for_test_persona_a"
);
assert_eq!(
w.projection_ref().map(|p| p.as_str()),
Some("test_persona_a.section.mailbox")
);
}
#[test]
fn from_parts_rejects_empty_slot() {
let err = Wiring::from_parts("test_persona_a", "", "mini-app://x", None)
.expect_err("empty slot must reject");
assert!(matches!(
err,
WireError::Domain(DomainError::InvalidMetadata(_))
));
}
}