use std::fmt::Display;
use std::str::{self, FromStr};
use crate::{Error, Result, CHUNK_SIZE};
#[derive(PartialEq, Eq, Debug, Clone, Copy, Hash)]
pub struct ChunkType(
[u8; CHUNK_SIZE],
);
impl ChunkType {
pub fn bytes(&self) -> [u8; CHUNK_SIZE] {
self.0
}
fn at_char(&self, index: usize) -> char {
(self.0)[index] as char
}
pub fn is_critical(&self) -> bool {
self.at_char(0).is_uppercase()
}
pub fn is_public(&self) -> bool {
self.at_char(1).is_uppercase()
}
pub fn is_reserved_bit_valid(&self) -> bool {
self.at_char(2).is_uppercase()
}
pub fn is_safe_to_copy(&self) -> bool {
self.at_char(3).is_lowercase()
}
pub fn is_valid(&self) -> bool {
!self.is_public() && self.is_reserved_bit_valid()
}
}
impl TryFrom<[u8; CHUNK_SIZE]> for ChunkType {
type Error = Error;
fn try_from(bytes: [u8; CHUNK_SIZE]) -> Result<Self> {
if bytes.iter().all(|&c| (c as char).is_ascii_alphabetic()) {
Ok(Self(bytes))
} else {
Err(Error::Custom(
"Invalid ascii character, must be alphabetic".to_owned(),
))
}
}
}
impl FromStr for ChunkType {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
if s.len() != CHUNK_SIZE {
Err(Error::Custom("Invalid length of chunk".to_owned()))
} else if !s.chars().all(|c| c.is_ascii_alphabetic()) {
Err(Error::Custom(
"Invalid ascii character, must be alphabetic".to_owned(),
))
} else {
let mut chunk_bytes = [0; CHUNK_SIZE];
chunk_bytes.clone_from_slice(s.as_bytes());
Ok(ChunkType(chunk_bytes))
}
}
}
impl Display for ChunkType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
str::from_utf8(&self.bytes()).map_err(|e| Error::from(e))?
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::convert::TryFrom;
use std::str::FromStr;
#[test]
pub fn test_chunk_type_from_bytes() {
let expected = [82, 117, 83, 116];
let actual = ChunkType::try_from([82, 117, 83, 116]).unwrap();
assert_eq!(expected, actual.bytes());
}
#[test]
pub fn test_chunk_type_from_str() {
let expected = ChunkType::try_from([82, 117, 83, 116]).unwrap();
let actual = ChunkType::from_str("RuSt").unwrap();
assert_eq!(expected, actual);
}
#[test]
pub fn test_chunk_type_is_critical() {
let chunk = ChunkType::from_str("RuSt").unwrap();
assert!(chunk.is_critical());
}
#[test]
pub fn test_chunk_type_is_not_critical() {
let chunk = ChunkType::from_str("ruSt").unwrap();
assert!(!chunk.is_critical());
}
#[test]
pub fn test_chunk_type_is_public() {
let chunk = ChunkType::from_str("RUSt").unwrap();
assert!(chunk.is_public());
}
#[test]
pub fn test_chunk_type_is_not_public() {
let chunk = ChunkType::from_str("RuSt").unwrap();
assert!(!chunk.is_public());
}
#[test]
pub fn test_chunk_type_is_reserved_bit_valid() {
let chunk = ChunkType::from_str("RuSt").unwrap();
assert!(chunk.is_reserved_bit_valid());
}
#[test]
pub fn test_chunk_type_is_reserved_bit_invalid() {
let chunk = ChunkType::from_str("Rust").unwrap();
assert!(!chunk.is_reserved_bit_valid());
}
#[test]
pub fn test_chunk_type_is_safe_to_copy() {
let chunk = ChunkType::from_str("RuSt").unwrap();
assert!(chunk.is_safe_to_copy());
}
#[test]
pub fn test_chunk_type_is_unsafe_to_copy() {
let chunk = ChunkType::from_str("RuST").unwrap();
assert!(!chunk.is_safe_to_copy());
}
#[test]
pub fn test_valid_chunk_is_valid() {
let chunk = ChunkType::from_str("RuSt").unwrap();
assert!(chunk.is_valid());
}
#[test]
pub fn test_invalid_chunk_is_valid() {
let chunk = ChunkType::from_str("Rust").unwrap();
assert!(!chunk.is_valid());
let chunk = ChunkType::from_str("Ru1t");
assert!(chunk.is_err());
}
#[test]
pub fn test_chunk_type_string() {
let chunk = ChunkType::from_str("RuSt").unwrap();
assert_eq!(&chunk.to_string(), "RuSt");
}
#[test]
pub fn test_chunk_type_trait_impls() {
let chunk_type_1: ChunkType = TryFrom::try_from([82, 117, 83, 116]).unwrap();
let chunk_type_2: ChunkType = FromStr::from_str("RuSt").unwrap();
let _chunk_string = format!("{}", chunk_type_1);
let _are_chunks_equal = chunk_type_1 == chunk_type_2;
}
}