use std::io;
use crate::{
property::Kind,
stream::{layout::Numbering, owned},
traits::{PropertyExt, PropertyValueExt, ReadProperty, WriteProperty, WriterExt},
Error, PropertyValueEnum, ValueSlot,
};
use byteorder::{WriteBytesExt, LE};
use ltk_hash::HashWidth;
use ltk_io_ext::{measure, window_at};
mod item;
pub use item::ContainerItem;
fn hash_width_of(value: &PropertyValueEnum) -> Option<HashWidth> {
match value {
PropertyValueEnum::Hash(hash) => Some(hash.width()),
_ => None,
}
}
pub(crate) fn track_hash_width(
expected: &mut Option<HashWidth>,
value: &PropertyValueEnum,
) -> Result<(), Error> {
let Some(got) = hash_width_of(value) else {
return Ok(());
};
match *expected {
None => *expected = Some(got),
Some(expected) if expected != got => {
return Err(Error::MismatchedHashWidths { expected, got })
}
Some(_) => {}
}
Ok(())
}
pub(crate) fn check_hash_width(
expected: Option<HashWidth>,
value: &PropertyValueEnum,
) -> Result<(), Error> {
match (expected, hash_width_of(value)) {
(Some(expected), Some(got)) if expected != got => {
Err(Error::MismatchedHashWidths { expected, got })
}
_ => Ok(()),
}
}
pub(crate) fn mixed_widths(error: Error) -> io::Error {
io::Error::new(io::ErrorKind::InvalidInput, error.to_string())
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct Container {
item_kind: Kind,
items: Vec<PropertyValueEnum>,
}
impl Container {
pub fn empty(item_kind: Kind) -> Result<Self, Error> {
Self::new(item_kind, Vec::new())
}
pub fn new(item_kind: Kind, items: Vec<PropertyValueEnum>) -> Result<Self, Error> {
if item_kind.is_container() {
return Err(Error::InvalidNesting(item_kind));
}
let mut hash_width = None;
for item in &items {
if item.kind() != item_kind {
return Err(Error::MismatchedContainerTypes {
expected: item_kind,
got: item.kind(),
});
}
track_hash_width(&mut hash_width, item)?;
}
Ok(Self { item_kind, items })
}
#[must_use]
pub fn hash_width(&self) -> Option<HashWidth> {
self.items.first().and_then(hash_width_of)
}
#[inline(always)]
#[must_use]
pub fn item_kind(&self) -> Kind {
self.item_kind
}
#[inline(always)]
#[must_use]
pub fn items(&self) -> &[PropertyValueEnum] {
&self.items
}
#[inline(always)]
#[must_use]
pub fn get(&self, index: usize) -> Option<&PropertyValueEnum> {
self.items.get(index)
}
#[inline(always)]
#[must_use]
pub fn slot(&mut self, index: usize) -> Option<ValueSlot<'_>> {
let item_kind = self.item_kind;
Some(ValueSlot::pinned(item_kind, self.items.get_mut(index)?))
}
pub(crate) fn items_mut(&mut self) -> std::slice::IterMut<'_, PropertyValueEnum> {
self.items.iter_mut()
}
#[inline(always)]
#[must_use]
pub fn len(&self) -> usize {
self.items.len()
}
#[inline(always)]
#[must_use]
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
pub fn push(&mut self, value: PropertyValueEnum) -> Result<(), Error> {
if value.kind() != self.item_kind {
return Err(Error::MismatchedContainerTypes {
expected: self.item_kind,
got: value.kind(),
});
}
check_hash_width(self.hash_width(), &value)?;
self.items.push(value);
Ok(())
}
#[inline(always)]
#[must_use]
pub fn into_items(self) -> Vec<PropertyValueEnum> {
self.items
}
}
impl Default for Container {
fn default() -> Self {
Self {
item_kind: Kind::None,
items: Vec::new(),
}
}
}
impl<T: ContainerItem + Into<PropertyValueEnum>> From<Vec<T>> for Container {
fn from(items: Vec<T>) -> Self {
items.into_iter().collect()
}
}
impl<T: ContainerItem + Into<PropertyValueEnum>> FromIterator<T> for Container {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
Self {
item_kind: T::KIND,
items: iter.into_iter().map(Into::into).collect(),
}
}
}
impl TryFrom<Vec<PropertyValueEnum>> for Container {
type Error = Error;
fn try_from(items: Vec<PropertyValueEnum>) -> Result<Self, Self::Error> {
let item_kind = items.first().ok_or(Error::EmptyContainer)?.kind();
Self::new(item_kind, items)
}
}
impl PropertyExt for Container {
fn size_no_header(&self) -> usize {
9 + self.items.iter().map(|i| i.size_no_header()).sum::<usize>()
}
}
impl PropertyValueExt for Container {
const KIND: Kind = Kind::Container;
}
impl ReadProperty for Container {
fn from_reader<R: io::Read + io::Seek + ?Sized>(
reader: &mut R,
legacy: bool,
) -> Result<Self, Error> {
owned::read_from(
reader,
Kind::Container,
Numbering::from_legacy(legacy),
owned::read_container,
)
}
}
impl WriteProperty for Container {
fn to_writer<R: io::Write + io::Seek + ?Sized>(
&self,
writer: &mut R,
legacy: bool,
) -> Result<(), io::Error> {
if legacy {
unimplemented!("legacy container writing");
}
writer.write_property_kind(self.item_kind)?;
let size_pos = writer.stream_position()?;
writer.write_u32::<LE>(0)?;
let (size, _) = measure(writer, |writer| {
writer.write_u32::<LE>(self.items.len() as _)?;
let mut hash_width = None;
for item in &self.items {
track_hash_width(&mut hash_width, item).map_err(mixed_widths)?;
item.to_writer(writer)?;
}
Ok::<_, io::Error>(())
})?;
window_at(writer, size_pos, |writer| writer.write_u32::<LE>(size as _))?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::property::values;
#[test]
fn takes_its_item_kind_from_the_type() {
let list = Container::from(vec![values::I32::new(1), values::I32::new(2)]);
assert_eq!(list.item_kind(), Kind::I32);
assert_eq!(list.len(), 2);
let list: Container = std::iter::empty::<values::String>().collect();
assert_eq!(list.item_kind(), Kind::String);
assert!(list.is_empty());
}
#[test]
fn infers_its_item_kind_from_the_first_item() {
let items: Vec<PropertyValueEnum> =
vec![values::U8::new(1).into(), values::U8::new(2).into()];
let list: Container = Container::try_from(items).unwrap();
assert_eq!(list.item_kind(), Kind::U8);
let empty: Result<Container, _> = Container::try_from(Vec::<PropertyValueEnum>::new());
assert!(matches!(empty, Err(Error::EmptyContainer)));
}
#[test]
fn rejects_an_item_that_is_not_its_item_kind() {
assert!(matches!(
Container::new(Kind::I32, vec![values::String::from("no").into()]),
Err(Error::MismatchedContainerTypes {
expected: Kind::I32,
got: Kind::String
})
));
let mut list = Container::empty(Kind::I32).unwrap();
assert!(matches!(
list.push(values::String::from("no").into()),
Err(Error::MismatchedContainerTypes {
expected: Kind::I32,
got: Kind::String
})
));
assert!(list.push(values::I32::new(1).into()).is_ok());
assert_eq!(list.len(), 1);
}
#[test]
fn rejects_a_nested_container() {
for kind in [
Kind::Container,
Kind::UnorderedContainer,
Kind::Optional,
Kind::Map,
] {
assert!(matches!(
Container::empty(kind),
Err(Error::InvalidNesting(k)) if k == kind
));
}
}
#[test]
fn borrows_its_items() {
let list = Container::from(vec![values::I32::new(1), values::I32::new(2)]);
assert_eq!(list.get(0), Some(&values::I32::new(1).into()));
assert_eq!(list.get(2), None);
assert_eq!(list.items().len(), 2);
assert_eq!(list.into_items().len(), 2);
}
#[test]
fn pins_its_item_kind_to_the_slots_it_hands_out() {
use crate::property::ValueMut;
let mut list = Container::from(vec![values::I32::new(1), values::I32::new(2)]);
assert!(list.slot(2).is_none());
let mut slot = list.slot(1).unwrap();
assert_eq!(slot.pinned_kind(), Some(Kind::I32));
assert!(matches!(
slot.set(values::String::from("no").into()),
Err(Error::MismatchedContainerTypes {
expected: Kind::I32,
got: Kind::String
})
));
assert_eq!(
slot.set(values::I32::new(7).into()).unwrap(),
values::I32::new(2).into()
);
let ValueMut::I32(item) = slot.as_mut() else {
panic!("the slot holds an i32");
};
item.value += 1;
assert_eq!(list.item_kind(), Kind::I32);
assert_eq!(
list.items(),
[values::I32::new(1).into(), values::I32::new(8).into()]
);
}
}