Ctrl K
ComunidadGuías…

src/types.rs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
use serde::{Deserialize, Serialize};

/// Whether an asset is an image or a video.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AssetItemType {
    Image,
    Movie,
}

/// Version size key for asset versions.
///
/// Uses `#[repr(u8)]` to guarantee 1-byte size for better struct packing.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum AssetVersionSize {
    Original = 0,
    Alternative = 1,
    Medium = 2,
    Thumb = 3,
    Adjusted = 4,
    LiveOriginal = 5,
    LiveMedium = 6,
    LiveThumb = 7,
    LiveAdjusted = 8,
}

impl From<VersionSize> for AssetVersionSize {
    fn from(v: VersionSize) -> Self {
        match v {
            VersionSize::Original => Self::Original,
            VersionSize::Medium => Self::Medium,
            VersionSize::Thumb => Self::Thumb,
            VersionSize::Adjusted => Self::Adjusted,
            VersionSize::Alternative => Self::Alternative,
        }
    }
}

/// Reason for a record change in a delta/incremental sync response.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChangeReason {
    /// New or modified record (state DB determines which).
    Created,
    /// Soft-deleted (moved to trash / recently deleted).
    SoftDeleted,
    /// Permanently purged from the source.
    HardDeleted,
    /// Hidden from the main library view.
    Hidden,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, clap::ValueEnum, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum VersionSize {
    Original,
    Medium,
    Thumb,
    Adjusted,
    Alternative,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, clap::ValueEnum, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum LivePhotoSize {
    Original,
    Medium,
    Thumb,
    Adjusted,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, clap::ValueEnum, Serialize, Deserialize)]
#[repr(u8)]
#[serde(rename_all = "lowercase")]
pub enum PhotoResolution {
    None = 0,
    Original = 1,
    Medium = 2,
    Thumb = 3,
}

impl PhotoResolution {
    pub const fn to_asset_version_size(self) -> Option<AssetVersionSize> {
        match self {
            Self::None => None,
            Self::Original => Some(AssetVersionSize::Original),
            Self::Medium => Some(AssetVersionSize::Medium),
            Self::Thumb => Some(AssetVersionSize::Thumb),
        }
    }
}

impl From<VersionSize> for PhotoResolution {
    fn from(value: VersionSize) -> Self {
        match value {
            VersionSize::Original | VersionSize::Adjusted | VersionSize::Alternative => {
                Self::Original
            }
            VersionSize::Medium => Self::Medium,
            VersionSize::Thumb => Self::Thumb,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, clap::ValueEnum, Serialize, Deserialize)]
#[repr(u8)]
#[serde(rename_all = "lowercase")]
pub enum LivePhotoResolution {
    Original = 0,
    Medium = 1,
    Thumb = 2,
}

impl LivePhotoResolution {
    pub const fn to_asset_version_size(self) -> AssetVersionSize {
        match self {
            Self::Original => AssetVersionSize::LiveOriginal,
            Self::Medium => AssetVersionSize::LiveMedium,
            Self::Thumb => AssetVersionSize::LiveThumb,
        }
    }
}

impl From<LivePhotoSize> for LivePhotoResolution {
    fn from(value: LivePhotoSize) -> Self {
        match value.to_asset_version_size() {
            AssetVersionSize::LiveOriginal | AssetVersionSize::LiveAdjusted => Self::Original,
            AssetVersionSize::LiveMedium => Self::Medium,
            AssetVersionSize::LiveThumb => Self::Thumb,
            _ => Self::Original,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Domain {
    Com,
    Cn,
}

impl Domain {
    pub const fn as_str(&self) -> &str {
        match self {
            Self::Com => "com",
            Self::Cn => "cn",
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum LogLevel {
    Debug,
    Info,
    Warn,
    Error,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum, Serialize, Deserialize)]
#[repr(u8)]
pub enum FileMatchPolicy {
    #[value(name = "name-size-dedup-with-suffix")]
    #[serde(rename = "name-size-dedup-with-suffix")]
    NameSizeDedupWithSuffix = 0,
    #[value(name = "name-id7")]
    #[serde(rename = "name-id7")]
    NameId7 = 1,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum, Serialize, Deserialize)]
#[repr(u8)]
pub enum RawTreatmentPolicy {
    #[value(name = "as-is")]
    #[serde(rename = "as-is")]
    Unchanged = 0,
    #[value(name = "original")]
    #[serde(rename = "original")]
    PreferOriginal = 1,
    #[value(name = "alternative")]
    #[serde(rename = "alternative")]
    PreferAlternative = 2,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, clap::ValueEnum, Serialize, Deserialize)]
#[repr(u8)]
pub enum RawPolicy {
    #[value(name = "as-is")]
    #[serde(rename = "as-is")]
    AsIs = 0,
    #[value(name = "prefer-raw")]
    #[serde(rename = "prefer-raw")]
    PreferRaw = 1,
    #[value(name = "prefer-jpeg")]
    #[serde(rename = "prefer-jpeg")]
    PreferJpeg = 2,
}

impl From<RawTreatmentPolicy> for RawPolicy {
    fn from(value: RawTreatmentPolicy) -> Self {
        match value {
            RawTreatmentPolicy::Unchanged => Self::AsIs,
            RawTreatmentPolicy::PreferOriginal => Self::PreferRaw,
            RawTreatmentPolicy::PreferAlternative => Self::PreferJpeg,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum, Serialize, Deserialize)]
#[repr(u8)]
#[serde(rename_all = "lowercase")]
pub enum LivePhotoMovFilenamePolicy {
    Suffix = 0,
    Original = 1,
}

/// Controls which components of live photos are downloaded.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, clap::ValueEnum, Serialize, Deserialize)]
#[repr(u8)]
#[serde(rename_all = "kebab-case")]
pub enum LivePhotoMode {
    /// Download both the still image and the MOV video
    #[default]
    Both,
    /// Download only the still image, skip the MOV
    #[value(name = "image-only")]
    ImageOnly,
    /// Download only the MOV video, skip the still image
    #[value(name = "video-only")]
    VideoOnly,
    /// Skip live photos entirely (both image and MOV)
    Skip,
}

impl LivePhotoSize {
    pub fn to_asset_version_size(self) -> AssetVersionSize {
        match self {
            Self::Original => AssetVersionSize::LiveOriginal,
            Self::Medium => AssetVersionSize::LiveMedium,
            Self::Thumb => AssetVersionSize::LiveThumb,
            Self::Adjusted => AssetVersionSize::LiveAdjusted,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde::de::DeserializeOwned;
    use std::fmt::Debug;

    #[test]
    fn test_live_photo_size_to_asset_version_size() {
        assert_eq!(
            LivePhotoSize::Original.to_asset_version_size(),
            AssetVersionSize::LiveOriginal
        );
        assert_eq!(
            LivePhotoSize::Medium.to_asset_version_size(),
            AssetVersionSize::LiveMedium
        );
        assert_eq!(
            LivePhotoSize::Thumb.to_asset_version_size(),
            AssetVersionSize::LiveThumb
        );
        assert_eq!(
            LivePhotoSize::Adjusted.to_asset_version_size(),
            AssetVersionSize::LiveAdjusted
        );
    }

    #[test]
    fn test_domain_as_str() {
        assert_eq!(Domain::Com.as_str(), "com");
        assert_eq!(Domain::Cn.as_str(), "cn");
    }

    #[test]
    fn version_size_serde_round_trip() {
        for (variant, expected) in [
            (VersionSize::Original, "\"original\""),
            (VersionSize::Medium, "\"medium\""),
            (VersionSize::Thumb, "\"thumb\""),
            (VersionSize::Adjusted, "\"adjusted\""),
            (VersionSize::Alternative, "\"alternative\""),
        ] {
            let json = serde_json::to_string(&variant).expect("serialize VersionSize");
            assert_eq!(json, expected);
            let parsed: VersionSize = serde_json::from_str(&json).expect("deserialize VersionSize");
            assert_eq!(parsed, variant);
        }
    }

    #[test]
    fn live_photo_size_serde_round_trip() {
        for (variant, expected) in [
            (LivePhotoSize::Original, "\"original\""),
            (LivePhotoSize::Medium, "\"medium\""),
            (LivePhotoSize::Thumb, "\"thumb\""),
            (LivePhotoSize::Adjusted, "\"adjusted\""),
        ] {
            let json = serde_json::to_string(&variant).expect("serialize LivePhotoSize");
            assert_eq!(json, expected);
            let parsed: LivePhotoSize =
                serde_json::from_str(&json).expect("deserialize LivePhotoSize");
            assert_eq!(parsed, variant);
        }
    }

    #[test]
    fn domain_serde_round_trip() {
        for (variant, expected) in [(Domain::Com, "\"com\""), (Domain::Cn, "\"cn\"")] {
            let json = serde_json::to_string(&variant).expect("serialize Domain");
            assert_eq!(json, expected);
            let parsed: Domain = serde_json::from_str(&json).expect("deserialize Domain");
            assert_eq!(parsed, variant);
        }
    }

    #[test]
    fn log_level_serde_round_trip() {
        for (variant, expected) in [
            (LogLevel::Debug, "\"debug\""),
            (LogLevel::Info, "\"info\""),
            (LogLevel::Warn, "\"warn\""),
            (LogLevel::Error, "\"error\""),
        ] {
            let json = serde_json::to_string(&variant).expect("serialize LogLevel");
            assert_eq!(json, expected);
            let parsed: LogLevel = serde_json::from_str(&json).expect("deserialize LogLevel");
            assert_eq!(parsed, variant);
        }
    }

    #[test]
    fn file_match_policy_serde_round_trip() {
        for (variant, expected) in [
            (
                FileMatchPolicy::NameSizeDedupWithSuffix,
                "\"name-size-dedup-with-suffix\"",
            ),
            (FileMatchPolicy::NameId7, "\"name-id7\""),
        ] {
            let json = serde_json::to_string(&variant).expect("serialize FileMatchPolicy");
            assert_eq!(json, expected);
            let parsed: FileMatchPolicy =
                serde_json::from_str(&json).expect("deserialize FileMatchPolicy");
            assert_eq!(parsed, variant);
        }
    }

    #[test]
    fn raw_treatment_policy_serde_round_trip() {
        for (variant, expected) in [
            (RawTreatmentPolicy::Unchanged, "\"as-is\""),
            (RawTreatmentPolicy::PreferOriginal, "\"original\""),
            (RawTreatmentPolicy::PreferAlternative, "\"alternative\""),
        ] {
            let json = serde_json::to_string(&variant).expect("serialize RawTreatmentPolicy");
            assert_eq!(json, expected);
            let parsed: RawTreatmentPolicy =
                serde_json::from_str(&json).expect("deserialize RawTreatmentPolicy");
            assert_eq!(parsed, variant);
        }
    }

    #[test]
    fn live_photo_mode_serde_round_trip() {
        for (variant, expected) in [
            (LivePhotoMode::Both, "\"both\""),
            (LivePhotoMode::ImageOnly, "\"image-only\""),
            (LivePhotoMode::VideoOnly, "\"video-only\""),
            (LivePhotoMode::Skip, "\"skip\""),
        ] {
            let json = serde_json::to_string(&variant).expect("serialize LivePhotoMode");
            assert_eq!(json, expected);
            let parsed: LivePhotoMode =
                serde_json::from_str(&json).expect("deserialize LivePhotoMode");
            assert_eq!(parsed, variant);
        }
    }

    #[test]
    fn live_photo_mov_filename_policy_serde_round_trip() {
        for (variant, expected) in [
            (LivePhotoMovFilenamePolicy::Suffix, "\"suffix\""),
            (LivePhotoMovFilenamePolicy::Original, "\"original\""),
        ] {
            let json =
                serde_json::to_string(&variant).expect("serialize LivePhotoMovFilenamePolicy");
            assert_eq!(json, expected);
            let parsed: LivePhotoMovFilenamePolicy =
                serde_json::from_str(&json).expect("deserialize LivePhotoMovFilenamePolicy");
            assert_eq!(parsed, variant);
        }
    }

    fn assert_serde_string_rejected<T>(value: &str)
    where
        T: Debug + DeserializeOwned,
    {
        let json = serde_json::to_string(value).expect("serialize test string");
        serde_json::from_str::<T>(&json).expect_err("invalid enum value should be rejected");
    }

    #[test]
    fn durable_config_enums_reject_unknown_serde_strings() {
        assert_serde_string_rejected::<VersionSize>("full");
        assert_serde_string_rejected::<LivePhotoSize>("live-original");
        assert_serde_string_rejected::<PhotoResolution>("adjusted");
        assert_serde_string_rejected::<LivePhotoResolution>("adjusted");
        assert_serde_string_rejected::<Domain>("icloud.com");
        assert_serde_string_rejected::<LogLevel>("warning");
        assert_serde_string_rejected::<FileMatchPolicy>("name-size");
        assert_serde_string_rejected::<RawTreatmentPolicy>("prefer-raw");
        assert_serde_string_rejected::<RawPolicy>("original");
        assert_serde_string_rejected::<LivePhotoMovFilenamePolicy>("mov");
        assert_serde_string_rejected::<LivePhotoMode>("video_only");
    }

    #[test]
    fn from_version_size_all_variants() {
        for (input, expected) in [
            (VersionSize::Original, AssetVersionSize::Original),
            (VersionSize::Medium, AssetVersionSize::Medium),
            (VersionSize::Thumb, AssetVersionSize::Thumb),
            (VersionSize::Adjusted, AssetVersionSize::Adjusted),
            (VersionSize::Alternative, AssetVersionSize::Alternative),
        ] {
            assert_eq!(AssetVersionSize::from(input), expected, "from {input:?}");
        }
    }

    #[test]
    fn asset_version_size_is_one_byte() {
        assert_eq!(std::mem::size_of::<AssetVersionSize>(), 1);
    }

    #[test]
    fn asset_version_size_variants_have_distinct_repr_values() {
        let variants = [
            AssetVersionSize::Original as u8,
            AssetVersionSize::Alternative as u8,
            AssetVersionSize::Medium as u8,
            AssetVersionSize::Thumb as u8,
            AssetVersionSize::Adjusted as u8,
            AssetVersionSize::LiveOriginal as u8,
            AssetVersionSize::LiveMedium as u8,
            AssetVersionSize::LiveThumb as u8,
            AssetVersionSize::LiveAdjusted as u8,
        ];
        let unique: std::collections::HashSet<u8> = variants.iter().copied().collect();
        assert_eq!(
            unique.len(),
            variants.len(),
            "all repr(u8) values must be distinct"
        );
    }
}

/// Placement of selected adjusted renditions. Primary mode opts into
/// preservation-backed management of kei-owned current paths.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub enum EditedNaming {
    #[default]
    Suffix,
    Primary,
}