use jiff::Timestamp as JiffTimestamp;
use crate::{
buffa::error::BuffaError,
domain::{Location, MediaFile, Uuid7},
generated::media::v1 as wire,
};
impl TryFrom<&wire::MediaFile> for MediaFile<Uuid7> {
type Error = BuffaError;
fn try_from(w: &wire::MediaFile) -> Result<Self, Self::Error> {
let id = id_from_bytes(&w.id)?;
let media_id = id_from_bytes(&w.media_id)?;
let watched_location_id = id_from_bytes(&w.watched_location_id)?;
let watch_volume = id_from_bytes(&w.watch_volume)?;
let loc_wire = w
.location
.as_option()
.ok_or(BuffaError::MissingRequiredField("MediaFile.location"))?;
let location: Location<Uuid7> = match &loc_wire.kind {
Some(wire::location::Kind::Local(local)) => Location::try_from(local.as_ref())?,
None => return Err(BuffaError::MissingRequiredField("MediaFile.location")),
#[allow(unreachable_patterns)]
Some(_) => return Err(BuffaError::UnsupportedLocationKind),
};
let created_at = if w.created_at == 0 {
None
} else {
Some(
JiffTimestamp::from_millisecond(w.created_at)
.map_err(|_| BuffaError::TimestampOutOfRange(w.created_at))?,
)
};
Ok(MediaFile::from_parts(
id,
media_id,
created_at,
location,
watched_location_id,
watch_volume,
))
}
}
impl From<&MediaFile<Uuid7>> for wire::MediaFile {
fn from(d: &MediaFile<Uuid7>) -> Self {
wire::MediaFile {
id: ::buffa::bytes::Bytes::copy_from_slice(d.id_ref().as_bytes()),
media_id: ::buffa::bytes::Bytes::copy_from_slice(d.media_id_ref().as_bytes()),
created_at: d.created_at_ref().map(|t| t.as_millisecond()).unwrap_or(0),
location: ::buffa::MessageField::some(wire::Location::from(d.location_ref())),
watched_location_id: ::buffa::bytes::Bytes::copy_from_slice(
d.watched_location_id_ref().as_bytes(),
),
watch_volume: ::buffa::bytes::Bytes::copy_from_slice(d.watch_volume_ref().as_bytes()),
__buffa_unknown_fields: Default::default(),
}
}
}
fn id_from_bytes(b: &::buffa::bytes::Bytes) -> Result<Uuid7, BuffaError> {
let arr: [u8; 16] = b
.as_ref()
.try_into()
.map_err(|_| BuffaError::IdWrongLength(b.len()))?;
Uuid7::try_from_bytes(arr).map_err(BuffaError::from)
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
use crate::domain::WatchedLocation;
fn ts() -> JiffTimestamp {
JiffTimestamp::from_millisecond(1_700_000_000_000).expect("valid timestamp")
}
fn watch(volume: Uuid7) -> WatchedLocation<Uuid7> {
WatchedLocation::try_new(Uuid7::new(), volume, JiffTimestamp::default()).expect("valid watch")
}
fn loc(volume: Uuid7, name: &str) -> Location<Uuid7> {
Location::try_local_uuid7(volume, ["Movies", name]).expect("valid location")
}
fn build_domain(media_id: Uuid7, vol: Uuid7, name: &str) -> MediaFile<Uuid7> {
let wl = watch(vol);
MediaFile::try_new(Uuid7::new(), media_id, Some(ts()), loc(vol, name), &wl)
.expect("valid construction")
}
#[test]
fn modelled_fields_round_trip() {
let d = build_domain(Uuid7::new(), Uuid7::new(), "clip.mp4");
let w: wire::MediaFile = (&d).into();
let d2: MediaFile<Uuid7> = MediaFile::try_from(&w).expect("roundtrip");
assert_eq!(d, d2);
}
#[test]
fn created_at_none_roundtrips_via_zero_sentinel() {
let vol = Uuid7::new();
let wl = watch(vol);
let d = MediaFile::try_new(Uuid7::new(), Uuid7::new(), None, loc(vol, "x.mp4"), &wl).unwrap();
let w: wire::MediaFile = (&d).into();
assert_eq!(w.created_at, 0);
let d2 = MediaFile::try_from(&w).expect("roundtrip");
assert_eq!(d, d2);
assert!(d2.created_at_ref().is_none());
}
#[test]
fn two_media_files_share_one_media_round_trip() {
let media_id = Uuid7::new();
let vol_a = Uuid7::new();
let vol_b = Uuid7::new();
let f_a = build_domain(media_id, vol_a, "copy-a.mp4");
let f_b = build_domain(media_id, vol_b, "copy-b.mp4");
assert_eq!(f_a.media_id_ref(), f_b.media_id_ref());
assert_ne!(f_a.id_ref(), f_b.id_ref());
let w_a: wire::MediaFile = (&f_a).into();
let w_b: wire::MediaFile = (&f_b).into();
let d_a: MediaFile<Uuid7> = MediaFile::try_from(&w_a).expect("roundtrip a");
let d_b: MediaFile<Uuid7> = MediaFile::try_from(&w_b).expect("roundtrip b");
assert_eq!(d_a, f_a);
assert_eq!(d_b, f_b);
assert_eq!(d_a.media_id_ref(), d_b.media_id_ref());
}
#[test]
fn wire_missing_location_errors() {
let d = build_domain(Uuid7::new(), Uuid7::new(), "clip.mp4");
let mut w = wire::MediaFile::from(&d);
w.location = ::buffa::MessageField::none();
let err = MediaFile::try_from(&w).unwrap_err();
assert!(err.is_missing_required_field());
}
#[test]
fn wire_invalid_id_errors() {
let d = build_domain(Uuid7::new(), Uuid7::new(), "clip.mp4");
let mut w = wire::MediaFile::from(&d);
w.id = ::buffa::bytes::Bytes::copy_from_slice(&[0u8; 8]);
let err = MediaFile::try_from(&w).unwrap_err();
assert!(err.is_id_wrong_length());
}
#[test]
fn wire_out_of_range_created_at_errors() {
let d = build_domain(Uuid7::new(), Uuid7::new(), "clip.mp4");
let mut w = wire::MediaFile::from(&d);
w.created_at = i64::MAX;
let err = MediaFile::try_from(&w).unwrap_err();
assert!(err.is_timestamp_out_of_range());
}
}