use std::io::{self, Read};
use std::path::Path;
pub(crate) const MAX_ID_FILE_BYTES: u64 = 64 * 1024;
pub(crate) fn read_capped(path: &Path) -> io::Result<String> {
let file = std::fs::File::open(path)?;
let mut buf = String::new();
file.take(MAX_ID_FILE_BYTES).read_to_string(&mut buf)?;
Ok(buf)
}
#[cfg(feature = "_transport")]
pub(crate) fn trim_trailing_slashes(base_url: impl Into<String>) -> String {
let mut base_url = base_url.into();
let trimmed_len = base_url.trim_end_matches('/').len();
base_url.truncate(trimmed_len);
base_url
}
pub const UNINITIALIZED_SENTINEL: &str = "uninitialized";
#[must_use]
pub fn normalize(raw: &str) -> Option<&str> {
match classify(raw) {
NormalizeOutcome::Usable(v) => Some(v),
_ => None,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum NormalizeOutcome<'a> {
Usable(&'a str),
Empty,
Sentinel,
}
pub(crate) fn classify(raw: &str) -> NormalizeOutcome<'_> {
let trimmed = raw.trim();
if trimmed.is_empty() {
return NormalizeOutcome::Empty;
}
if trimmed.eq_ignore_ascii_case(UNINITIALIZED_SENTINEL) {
return NormalizeOutcome::Sentinel;
}
NormalizeOutcome::Usable(trimmed)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn normalize_rejects_sentinels() {
assert_eq!(normalize(""), None);
assert_eq!(normalize(" "), None);
assert_eq!(normalize("uninitialized"), None);
assert_eq!(normalize("UNINITIALIZED\n"), None);
assert_eq!(normalize(" abc123\n"), Some("abc123"));
}
#[test]
fn classify_distinguishes_empty_from_sentinel() {
assert_eq!(classify(""), NormalizeOutcome::Empty);
assert_eq!(classify(" \n\t"), NormalizeOutcome::Empty);
assert_eq!(classify("uninitialized"), NormalizeOutcome::Sentinel);
assert_eq!(classify("UNINITIALIZED\n"), NormalizeOutcome::Sentinel);
assert_eq!(classify(" abc123\n"), NormalizeOutcome::Usable("abc123"));
}
}