use crate::WireError;
use alloc::collections::BTreeMap;
use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TryConstruct {
Discard,
UseDefault,
Trim,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Narrowed<T> {
Value(T),
Default,
}
pub fn fit_string(
at: usize,
value: String,
bound: usize,
behaviour: TryConstruct,
) -> Result<Narrowed<String>, WireError> {
if value.len() <= bound {
return Ok(Narrowed::Value(value));
}
match behaviour {
TryConstruct::Discard => Err(WireError::Discarded {
at,
expected: format!("at most {bound} octets"),
found: format!("{} of them", value.len()),
}),
TryConstruct::UseDefault => Ok(Narrowed::Default),
TryConstruct::Trim => {
let mut end = bound;
while end > 0 && !value.is_char_boundary(end) {
end -= 1;
}
let mut trimmed = value;
trimmed.truncate(end);
Ok(Narrowed::Value(trimmed))
}
}
}
pub fn fit_sequence<T>(
at: usize,
value: Vec<T>,
bound: usize,
behaviour: TryConstruct,
) -> Result<Narrowed<Vec<T>>, WireError> {
if value.len() <= bound {
return Ok(Narrowed::Value(value));
}
match behaviour {
TryConstruct::Discard => Err(WireError::Discarded {
at,
expected: format!("at most {bound} elements"),
found: format!("{}", value.len()),
}),
TryConstruct::UseDefault => Ok(Narrowed::Default),
TryConstruct::Trim => {
let mut trimmed = value;
trimmed.truncate(bound);
Ok(Narrowed::Value(trimmed))
}
}
}
pub fn fit_map<K: Ord, V>(
at: usize,
value: BTreeMap<K, V>,
bound: usize,
behaviour: TryConstruct,
) -> Result<Narrowed<BTreeMap<K, V>>, WireError> {
if value.len() <= bound {
return Ok(Narrowed::Value(value));
}
match behaviour {
TryConstruct::Discard => Err(WireError::Discarded {
at,
expected: format!("at most {bound} entries"),
found: format!("{}", value.len()),
}),
TryConstruct::UseDefault => Ok(Narrowed::Default),
TryConstruct::Trim => Ok(Narrowed::Value(
value.into_iter().take(bound).collect::<BTreeMap<K, V>>(),
)),
}
}
pub fn fit_map_entries<K, V>(
at: usize,
mut value: alloc::vec::Vec<crate::MapEntry<K, V>>,
bound: usize,
behaviour: TryConstruct,
) -> Result<Narrowed<alloc::vec::Vec<crate::MapEntry<K, V>>>, WireError> {
if value.len() <= bound {
return Ok(Narrowed::Value(value));
}
match behaviour {
TryConstruct::Discard => Err(WireError::Discarded {
at,
expected: format!("at most {bound} entries"),
found: format!("{}", value.len()),
}),
TryConstruct::UseDefault => Ok(Narrowed::Default),
TryConstruct::Trim => {
value.truncate(bound);
Ok(Narrowed::Value(value))
}
}
}
pub fn fit_wstring(
at: usize,
value: String,
bound: usize,
behaviour: TryConstruct,
) -> Result<Narrowed<String>, WireError> {
let units = value.encode_utf16().count();
if units <= bound {
return Ok(Narrowed::Value(value));
}
match behaviour {
TryConstruct::Discard => Err(WireError::Discarded {
at,
expected: format!("at most {bound} UTF-16 code units"),
found: format!("{units} of them"),
}),
TryConstruct::UseDefault => Ok(Narrowed::Default),
TryConstruct::Trim => {
let mut kept: Vec<u16> = value.encode_utf16().take(bound).collect();
if kept
.last()
.is_some_and(|unit| (0xD800..0xDC00).contains(unit))
{
kept.pop();
}
let trimmed = String::from_utf16(&kept).map_err(|_| WireError::OutOfRange {
at,
expected: String::from("well-formed UTF-16 after a trim to the receiver's bound"),
})?;
Ok(Narrowed::Value(trimmed))
}
}
}