use derive_more::IsVariant;
use crate::domain::{vo::IndexProgress, Uuid7};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Subtitle<Id = Uuid7> {
id: Id,
media_id: Id,
tracks: std::vec::Vec<Id>,
track_progress: IndexProgress,
}
impl Subtitle<Uuid7> {
pub fn try_new(id: Uuid7, media_id: Uuid7) -> Result<Self, SubtitleError> {
if id.is_nil() {
return Err(SubtitleError::NilId);
}
if media_id.is_nil() {
return Err(SubtitleError::NilMediaId);
}
Ok(Self {
id,
media_id,
tracks: std::vec::Vec::new(),
track_progress: IndexProgress::new(),
})
}
}
impl<Id> Subtitle<Id> {
#[inline(always)]
pub const fn id_ref(&self) -> &Id {
&self.id
}
#[inline(always)]
pub const fn media_id_ref(&self) -> &Id {
&self.media_id
}
#[inline(always)]
pub const fn tracks_slice(&self) -> &[Id] {
self.tracks.as_slice()
}
#[inline(always)]
pub const fn track_progress_ref(&self) -> &IndexProgress {
&self.track_progress
}
#[must_use]
#[inline(always)]
pub fn with_tracks(mut self, tracks: impl Into<std::vec::Vec<Id>>) -> Self {
self.tracks = tracks.into();
self
}
#[must_use]
#[inline(always)]
pub const fn with_track_progress(mut self, progress: IndexProgress) -> Self {
self.track_progress = progress;
self
}
#[inline(always)]
pub fn set_tracks(&mut self, tracks: impl Into<std::vec::Vec<Id>>) -> &mut Self {
self.tracks = tracks.into();
self
}
#[inline(always)]
pub const fn set_track_progress(&mut self, progress: IndexProgress) -> &mut Self {
self.track_progress = progress;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, IsVariant, thiserror::Error)]
#[non_exhaustive]
pub enum SubtitleError {
#[error("Subtitle id must not be the nil UUID")]
NilId,
#[error("Subtitle `media_id` (FK → Media) must not be the nil UUID")]
NilMediaId,
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
#[test]
fn try_new_happy_path() {
let media_id = Uuid7::new();
let s = Subtitle::try_new(Uuid7::new(), media_id).expect("valid construction must succeed");
assert_eq!(s.media_id_ref(), &media_id);
assert!(s.tracks_slice().is_empty());
assert_eq!(s.track_progress_ref(), &IndexProgress::new());
}
#[test]
fn try_new_rejects_nil_id() {
let r = Subtitle::try_new(Uuid7::nil(), Uuid7::new());
assert_eq!(r.err(), Some(SubtitleError::NilId));
assert!(SubtitleError::NilId.is_nil_id());
}
#[test]
fn try_new_rejects_nil_media_id() {
let r = Subtitle::try_new(Uuid7::new(), Uuid7::nil());
assert_eq!(r.err(), Some(SubtitleError::NilMediaId));
assert!(SubtitleError::NilMediaId.is_nil_media_id());
}
#[test]
fn builders_and_setters_chain() {
let t1 = Uuid7::new();
let t2 = Uuid7::new();
let s = Subtitle::try_new(Uuid7::new(), Uuid7::new())
.unwrap()
.with_tracks(std::vec![t1, t2])
.with_track_progress(IndexProgress::from_parts(2, 1, 0));
assert_eq!(s.tracks_slice().len(), 2);
assert!(s.tracks_slice().contains(&t1));
assert!(s.tracks_slice().contains(&t2));
assert_eq!(s.track_progress_ref().total(), 2);
assert_eq!(s.track_progress_ref().indexed(), 1);
assert_eq!(s.track_progress_ref().failed(), 0);
}
#[test]
fn setters_mutate_in_place() {
let mut s = Subtitle::try_new(Uuid7::new(), Uuid7::new()).unwrap();
s.set_tracks(std::vec![Uuid7::new()]);
s.set_track_progress(IndexProgress::from_parts(1, 0, 1));
assert_eq!(s.tracks_slice().len(), 1);
assert_eq!(s.track_progress_ref().failed(), 1);
}
#[test]
fn index_progress_builders_and_setters() {
let p = IndexProgress::new()
.with_total(5)
.with_indexed(3)
.with_failed(1);
assert_eq!(p.total(), 5);
assert_eq!(p.indexed(), 3);
assert_eq!(p.failed(), 1);
let mut p = p;
p.set_total(10);
p.set_indexed(7);
p.set_failed(2);
assert_eq!(p.total(), 10);
assert_eq!(p.indexed(), 7);
assert_eq!(p.failed(), 2);
}
}