aboutsummaryrefslogtreecommitdiff
path: root/src/domain/value_objects.rs
diff options
context:
space:
mode:
authorMikkel Thestrup <mikkel@mithe.dk>2026-01-26 21:27:59 +0100
committerMikkel Thestrup <mikkel@mithe.dk>2026-01-27 15:37:03 +0100
commit4e78fd83349c95711cdee5acc56f248f81ebd25c (patch)
tree6a380da4c3651f73a806cafc9222012af9564f56 /src/domain/value_objects.rs
parent4b1074193991a510fd2129513d5fcb7c6da933d2 (diff)
downloadkal-4e78fd83349c95711cdee5acc56f248f81ebd25c.tar.gz
kal-4e78fd83349c95711cdee5acc56f248f81ebd25c.zip
feat(domain): add core domain models for calendar application
Implement domain-driven design layer with entities, value objects, and repository traits for calendar management system. Domain Entities: - Calendar: manages calendar lifecycle and archival state - Event: handles single occurrence events with cancellation support - RecurringEvent: supports recurring events with exception handling - RecurrenceException: manages modifications to specific occurrences Value Objects: - TimeRange: enforces valid start/end time constraints - EventColor: type-safe color representation - Frequency: recurrence frequency enumeration (daily/weekly/monthly/yearly) - RecurrenceRule: encapsulates recurrence pattern logic - CalendarId & EventId: essentially just a wrapper Repository Traits: - CalendarRepository: calendar persistence interface - EventRepository: event querying and persistence with overlap detection - RecurrenceRepository: recurring event management Key Design Decisions: - Use Uuid for entity IDs (type safety, performance) - Encapsulate business logic within entities - Immutable value objects with validation - Repository pattern for infrastructure abstraction - Clear separation of concerns between domain and persistence layers All entities include timestamp tracking and follow builder pattern for construction with validation at domain boundaries.
Diffstat (limited to 'src/domain/value_objects.rs')
-rwxr-xr-xsrc/domain/value_objects.rs147
1 files changed, 147 insertions, 0 deletions
diff --git a/src/domain/value_objects.rs b/src/domain/value_objects.rs
new file mode 100755
index 0000000..f38d351
--- /dev/null
+++ b/src/domain/value_objects.rs
@@ -0,0 +1,147 @@
+use core::fmt;
+
+use chrono::{DateTime, Utc};
+use getset::{Getters};
+use uuid::Uuid;
+
+use crate::domain::error::DomainError;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct EventColor(u8);
+
+impl From<u8> for EventColor {
+ fn from(value: u8) -> Self {
+ Self(value)
+ }
+}
+
+impl From<EventColor> for u8 {
+ fn from(color: EventColor) -> Self {
+ color.0
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Getters)]
+pub struct TimeRange {
+ #[getset(get = "pub")]
+ starts_at: DateTime<Utc>,
+ #[getset(get = "pub")]
+ ends_at: DateTime<Utc>,
+}
+
+impl TimeRange {
+ pub fn new(
+ starts_at: DateTime<Utc>,
+ ends_at: DateTime<Utc>,
+ ) -> Result<Self, DomainError> {
+ if starts_at >= ends_at {
+ Err(DomainError::InvalidTimeRange)
+ } else {
+ Ok(Self { starts_at, ends_at })
+ }
+ }
+
+ pub fn overlaps(&self, other: &Self) -> bool {
+ self.starts_at < other.ends_at
+ && other.starts_at < self.ends_at
+ }
+
+ pub fn duration(&self) -> chrono::Duration {
+ self.ends_at - self.starts_at
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Frequency {
+ Daily,
+ Weekly,
+ Monthly,
+ Yearly,
+}
+
+impl fmt::Display for Frequency {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Frequency::Daily => write!(f, "DAILY"),
+ Frequency::Weekly => write!(f, "WEEKLY"),
+ Frequency::Monthly => write!(f, "MONTHLY"),
+ Frequency::Yearly => write!(f, "YEARLY"),
+ }
+ }
+}
+
+impl std::str::FromStr for Frequency {
+ type Err = DomainError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s.to_uppercase().as_str() {
+ "DAILY" => Ok(Frequency::Daily),
+ "WEEKLY" => Ok(Frequency::Weekly),
+ "MONTHLY" => Ok(Frequency::Monthly),
+ "YEARLY" => Ok(Frequency::Yearly),
+ _ => Err(DomainError::InvalidFrequency),
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct CalendarId(Uuid);
+
+impl CalendarId {
+ pub fn new() -> Self {
+ Self(Uuid::new_v4())
+ }
+
+ pub fn from_uuid(uuid: Uuid) -> Self {
+ Self(uuid)
+ }
+
+ pub fn as_uuid(&self) -> Uuid {
+ self.0
+ }
+}
+
+impl std::fmt::Display for CalendarId {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+
+impl std::str::FromStr for CalendarId {
+ type Err = uuid::Error;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ Ok(Self(Uuid::parse_str(s)?))
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct EventId(Uuid);
+
+impl EventId {
+ pub fn new() -> Self {
+ Self(Uuid::new_v4())
+ }
+
+ pub fn from_uuid(uuid: Uuid) -> Self {
+ Self(uuid)
+ }
+
+ pub fn as_uuid(&self) -> Uuid {
+ self.0
+ }
+}
+
+impl std::fmt::Display for EventId {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+
+impl std::str::FromStr for EventId {
+ type Err = uuid::Error;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ Ok(Self(Uuid::parse_str(s)?))
+ }
+}