pico_pngme/
chunk_type.rs

1// Implementation of Chunk Type section of PNG spec[http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html]
2use std::convert::TryInto;
3use std::error::Error;
4use std::fmt::{self, Display};
5use std::str::FromStr;
6
7#[derive(PartialEq, Debug, Clone)]
8pub struct ChunkType {
9    bytes: [u8; 4],
10}
11
12#[derive(Debug)]
13pub enum ChunkTypeErr {
14    InvalidArgs,
15}
16
17impl Display for ChunkTypeErr {
18    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19        write!(f, "Invalid arguments")
20    }
21}
22
23impl Error for ChunkTypeErr {}
24
25impl ChunkType {
26    // @notice ChunkType constructor
27    // @param _bytes Bytes of ChunkType
28    pub fn new(_bytes: &[u8; 4]) -> ChunkType {
29        ChunkType { bytes: *_bytes }
30    }
31
32    // @notice Returns Type Bytes
33    pub fn bytes(&self) -> [u8; 4] {
34        self.bytes
35    }
36
37    // @notice Checks if reserved bit is valid
38    pub fn is_valid(&self) -> bool {
39        self.is_reserved_bit_valid()
40    }
41
42    // @notice Check if chunk type is critical
43    pub fn is_critical(&self) -> bool {
44        self.bytes[0].is_ascii_uppercase()
45    }
46
47    // @notice: Checks if chunk is public
48    pub fn is_public(&self) -> bool {
49        self.bytes[1].is_ascii_uppercase()
50    }
51
52    // @notice: Checks if chunk's reserved bits are valid
53    pub fn is_reserved_bit_valid(&self) -> bool {
54        self.bytes[2].is_ascii_uppercase()
55    }
56
57    // @notice: Checks if chunck is safe to copy in case of modification
58    pub fn is_safe_to_copy(&self) -> bool {
59        self.bytes[3].is_ascii_lowercase()
60    }
61
62}
63
64// @notice: Constructs a ChunkType instance from a 4 byte Array
65impl TryFrom<[u8; 4]> for ChunkType {
66    type Error = ChunkTypeErr;
67
68    fn try_from(bytes: [u8; 4]) -> Result<Self, ChunkTypeErr> {
69        Ok(ChunkType { bytes })
70    }
71}
72
73// @notice: Constructs a ChunkType instance from a &str
74impl FromStr for ChunkType {
75    type Err = ChunkTypeErr;
76
77    fn from_str(s: &str) -> Result<Self, ChunkTypeErr> {
78
79        if s.chars().count() != 4 {
80            Err(ChunkTypeErr::InvalidArgs)
81
82        } else {
83
84            let temp = ChunkType {
85                bytes: s.as_bytes().try_into().unwrap(),
86            };
87
88            match temp.bytes.iter().all(|c| c.is_ascii_alphabetic()) {
89                true => Ok(temp),
90                _ => Err(ChunkTypeErr::InvalidArgs),
91            }
92        }
93    }
94}
95
96impl fmt::Display for ChunkType {
97    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98        write!(
99            f,
100            "{} {} {} {}",
101            self.bytes[0], self.bytes[1], self.bytes[2], self.bytes[3]
102        )
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109    use std::convert::TryFrom;
110    use std::str::FromStr;
111
112    #[test]
113    pub fn test_chunk_type_from_bytes() {
114        let expected = [82, 117, 83, 116];
115        let actual = ChunkType::try_from([82, 117, 83, 116]).unwrap();
116
117        assert_eq!(expected, actual.bytes());
118    }
119
120    #[test]
121    pub fn test_chunk_type_from_str() {
122        let expected = ChunkType::try_from([82, 117, 83, 116]).unwrap();
123        let actual = ChunkType::from_str("RuSt").unwrap();
124        assert_eq!(expected, actual);
125    }
126
127    #[test]
128    pub fn test_chunk_type_is_critical() {
129        let chunk = ChunkType::from_str("RuSt").unwrap();
130        assert!(chunk.is_critical());
131    }
132
133    #[test]
134    pub fn test_chunk_type_is_not_critical() {
135        let chunk = ChunkType::from_str("ruSt").unwrap();
136        assert!(!chunk.is_critical());
137    }
138
139    #[test]
140    pub fn test_chunk_type_is_public() {
141        let chunk = ChunkType::from_str("RUSt").unwrap();
142        assert!(chunk.is_public());
143    }
144
145    #[test]
146    pub fn test_chunk_type_is_not_public() {
147        let chunk = ChunkType::from_str("RuSt").unwrap();
148        assert!(!chunk.is_public());
149    }
150
151    #[test]
152    pub fn test_chunk_type_is_reserved_bit_valid() {
153        let chunk = ChunkType::from_str("RuSt").unwrap();
154        assert!(chunk.is_reserved_bit_valid());
155    }
156
157    #[test]
158    pub fn test_chunk_type_is_reserved_bit_invalid() {
159        let chunk = ChunkType::from_str("Rust").unwrap();
160        assert!(!chunk.is_reserved_bit_valid());
161    }
162
163    #[test]
164    pub fn test_chunk_type_is_safe_to_copy() {
165        let chunk = ChunkType::from_str("RuSt").unwrap();
166        assert!(chunk.is_safe_to_copy());
167    }
168
169    #[test]
170    pub fn test_chunk_type_is_unsafe_to_copy() {
171        let chunk = ChunkType::from_str("RuST").unwrap();
172        assert!(!chunk.is_safe_to_copy());
173    }
174
175    #[test]
176    pub fn test_valid_chunk_is_valid() {
177        let chunk = ChunkType::from_str("RuSt").unwrap();
178        assert!(chunk.is_valid());
179    }
180
181    #[test]
182    pub fn test_invalid_chunk_is_valid() {
183        let chunk = ChunkType::from_str("Rust").unwrap();
184        assert!(!chunk.is_valid());
185
186        let chunk = ChunkType::from_str("Ru1t");
187        assert!(chunk.is_err());
188    }
189
190    #[test]
191    pub fn test_chunk_type_string() {
192        let chunk = ChunkType::from_str("RuSt").unwrap();
193        assert_eq!(&chunk.to_string(), "RuSt");
194    }
195
196    #[test]
197    pub fn test_chunk_type_trait_impls() {
198        let chunk_type_1: ChunkType = TryFrom::try_from([82, 117, 83, 116]).unwrap();
199        let chunk_type_2: ChunkType = FromStr::from_str("RuSt").unwrap();
200        let _chunk_string = format!("{}", chunk_type_1);
201        let _are_chunks_equal = chunk_type_1 == chunk_type_2;
202    }
203}