1use 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 pub fn new(_bytes: &[u8; 4]) -> ChunkType {
29 ChunkType { bytes: *_bytes }
30 }
31
32 pub fn bytes(&self) -> [u8; 4] {
34 self.bytes
35 }
36
37 pub fn is_valid(&self) -> bool {
39 self.is_reserved_bit_valid()
40 }
41
42 pub fn is_critical(&self) -> bool {
44 self.bytes[0].is_ascii_uppercase()
45 }
46
47 pub fn is_public(&self) -> bool {
49 self.bytes[1].is_ascii_uppercase()
50 }
51
52 pub fn is_reserved_bit_valid(&self) -> bool {
54 self.bytes[2].is_ascii_uppercase()
55 }
56
57 pub fn is_safe_to_copy(&self) -> bool {
59 self.bytes[3].is_ascii_lowercase()
60 }
61
62}
63
64impl 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
73impl 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}