use std::fmt;
#[cfg(test)]
mod tests;
#[derive(Debug)]
pub enum NormalizationError {
InputLimit {
actual: usize,
limit: usize,
},
Utf8(std::str::Utf8Error),
OutputLimit {
limit: usize,
},
Allocation(std::collections::TryReserveError),
}
impl fmt::Display for NormalizationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InputLimit { actual, limit } => {
write!(f, "Candid output has {actual} bytes, exceeding {limit}")
}
Self::Utf8(_) => f.write_str("Candid extractor output is not UTF-8"),
Self::OutputLimit { limit } => write!(f, "normalized Candid exceeds {limit} bytes"),
Self::Allocation(_) => f.write_str("Candid normalization allocation failed"),
}
}
}
impl std::error::Error for NormalizationError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Utf8(source) => Some(source),
Self::Allocation(source) => Some(source),
_ => None,
}
}
}
pub fn normalize(bytes: &[u8], max_bytes: usize) -> Result<String, NormalizationError> {
if bytes.len() > max_bytes {
return Err(NormalizationError::InputLimit {
actual: bytes.len(),
limit: max_bytes,
});
}
let text = std::str::from_utf8(bytes).map_err(NormalizationError::Utf8)?;
let mut length = 0_usize;
for line in text.lines() {
length = length
.checked_add(line.trim_end().len())
.and_then(|length| length.checked_add(1))
.filter(|&length| length <= max_bytes)
.ok_or(NormalizationError::OutputLimit { limit: max_bytes })?;
}
let mut normalized = String::new();
normalized
.try_reserve_exact(length)
.map_err(NormalizationError::Allocation)?;
for line in text.lines() {
normalized.push_str(line.trim_end());
normalized.push('\n');
}
Ok(normalized)
}