1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
use std::{
    error::Error,
    fmt::{self, Display, Formatter},
};
/// [ChunkType] validation error.
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum ChunkTypeError {
    /// Contains non-ascii alphabet error.
    NonAsciiAlphabetic,
    /// The second character is not lowercase error.
    NonPrivateChunkType,
    /// The third character is not uppercase error.
    Reserved,
}
impl Display for ChunkTypeError {
    #[inline]
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        Display::fmt(
            match self {
                Self::NonAsciiAlphabetic => "All characters are must be ASCII alphabet",
                Self::NonPrivateChunkType => "The second character is must be lowercase",
                Self::Reserved => "The third character is must be uppercase",
            },
            f,
        )
    }
}
impl Error for ChunkTypeError {}
/// A 4-byte chunk type code.
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct ChunkType(pub(crate) [u8; 4]);
impl ChunkType {
    // -- Critical chunks --
    /// Archive header
    pub const AHED: ChunkType = ChunkType(*b"AHED");
    /// Archive end marker
    pub const AEND: ChunkType = ChunkType(*b"AEND");
    /// Archive next part marker
    pub const ANXT: ChunkType = ChunkType(*b"ANXT");
    /// Entry header
    pub const FHED: ChunkType = ChunkType(*b"FHED");
    /// Password hash string format
    pub const PHSF: ChunkType = ChunkType(*b"PHSF");
    /// Entry data stream
    pub const FDAT: ChunkType = ChunkType(*b"FDAT");
    /// Entry data stream end marker
    pub const FEND: ChunkType = ChunkType(*b"FEND");
    /// Solid mode data header
    pub const SHED: ChunkType = ChunkType(*b"SHED");
    /// Solid mode data stream
    pub const SDAT: ChunkType = ChunkType(*b"SDAT");
    /// Solid mode data stream end marker
    pub const SEND: ChunkType = ChunkType(*b"SEND");
    // -- Auxiliary chunks --
    /// Raw file size
    #[allow(non_upper_case_globals)]
    pub const fSIZ: ChunkType = ChunkType(*b"fSIZ");
    /// Creation datetime
    #[allow(non_upper_case_globals)]
    pub const cTIM: ChunkType = ChunkType(*b"cTIM");
    /// Last modified datetime
    #[allow(non_upper_case_globals)]
    pub const mTIM: ChunkType = ChunkType(*b"mTIM");
    /// Last accessed datetime
    #[allow(non_upper_case_globals)]
    pub const aTIM: ChunkType = ChunkType(*b"aTIM");
    /// Entry permissions
    #[allow(non_upper_case_globals)]
    pub const fPRM: ChunkType = ChunkType(*b"fPRM");
    /// Extended attribute
    #[allow(non_upper_case_globals)]
    pub const xATR: ChunkType = ChunkType(*b"xATR");
    /// Returns the length of the chunk type code.
    ///
    /// # Returns
    ///
    /// An integer value representing the length of the chunk type code.
    ///
    /// # Example
    ///
    /// ```
    /// use libpna::ChunkType;
    ///
    /// let chunk_type = ChunkType::AHED;
    ///
    /// assert_eq!(chunk_type.len(), 4);
    /// ```
    #[allow(clippy::len_without_is_empty)]
    #[inline]
    pub const fn len(&self) -> usize {
        self.0.len()
    }
    /// Creates private [ChunkType].
    ///
    /// # Errors
    ///
    /// This function will return an error in the following cases:
    /// - Value contains non-ASCII alphabet characters
    /// - The second character is not lowercase
    /// - The third character is not uppercase
    ///
    /// # Examples
    ///
    /// ```
    /// # use libpna::{ChunkType, ChunkTypeError};
    /// assert!(ChunkType::private(*b"myTy").is_ok());
    /// assert_eq!(
    ///     ChunkType::private(*b"zeR\0").unwrap_err(),
    ///     ChunkTypeError::NonAsciiAlphabetic
    /// );
    /// assert_eq!(
    ///     ChunkType::private(*b"pRIv").unwrap_err(),
    ///     ChunkTypeError::NonPrivateChunkType
    /// );
    /// assert_eq!(
    ///     ChunkType::private(*b"rese").unwrap_err(),
    ///     ChunkTypeError::Reserved
    /// );
    /// ```
    #[inline]
    pub fn private(ty: [u8; 4]) -> Result<Self, ChunkTypeError> {
        for c in ty {
            if !c.is_ascii_alphabetic() {
                return Err(ChunkTypeError::NonAsciiAlphabetic);
            }
        }
        if !ty[1].is_ascii_lowercase() {
            return Err(ChunkTypeError::NonPrivateChunkType);
        }
        if !ty[2].is_ascii_uppercase() {
            return Err(ChunkTypeError::Reserved);
        }
        Ok(Self(ty))
    }
    /// Creates custom [ChunkType] without any check.
    ///
    /// # Panic
    /// Printing ChunkType that contains non-utf8 characters will be panicked.
    /// ```no_run
    /// # use libpna::ChunkType;
    ///
    /// let custom_chunk_type = unsafe { ChunkType::from_unchecked([0xe3, 0x81, 0x82, 0xe3]) };
    /// format!("{}", custom_chunk_type);
    /// ```
    ///
    /// # Safety
    /// Safe when value consists only of ascii alphabetic characters ('a'...'z' and 'A'...'Z').
    /// ```
    /// # use libpna::ChunkType;
    ///
    /// let custom_chunk_type = unsafe { ChunkType::from_unchecked(*b"myTy") };
    /// format!("{}", custom_chunk_type);
    /// ```
    #[inline]
    pub const unsafe fn from_unchecked(ty: [u8; 4]) -> Self {
        Self(ty)
    }
}
impl Display for ChunkType {
    #[inline]
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        Display::fmt(unsafe { std::str::from_utf8_unchecked(&self.0) }, f)
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn to_string() {
        assert_eq!("AHED", ChunkType::AHED.to_string());
    }
}