mod counting;
pub use counting::{Counted, Counting, Noticed};
use crate::store::builder::Backend;
use crate::store::config::WriteLimits;
use crate::store::facts::Key;
use crate::store::{CodecFormat, StorageError, StorageResult};
use amethystate_core::path::StorePath;
use error_stack::Report;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Screening {
pub ceiling: usize,
pub key_depth: Option<usize>,
pub non_finite_floats: bool,
pub enums: bool,
pub nested_options: bool,
pub wide_integers: bool,
pub integers_of_128_bits: bool,
pub integers_past_64_bits: bool,
}
impl Screening {
pub fn resolve(limits: &WriteLimits, engine: Backend) -> Self {
Self {
ceiling: limits.ceiling(engine),
key_depth: limits.key_depth,
non_finite_floats: limits.non_finite_floats(engine),
enums: limits.enums(engine),
nested_options: limits.a_nested_option(engine),
wide_integers: limits.an_integer_past_i64(engine),
integers_of_128_bits: limits.a_128_bit_integer(engine),
integers_past_64_bits: limits.an_integer_past_64_bits(engine),
}
}
pub fn for_codec(limits: &WriteLimits, codec: CodecFormat) -> Self {
Self::resolve(limits, Backend::writing(codec))
}
pub fn check_path(&self, path: &StorePath) -> StorageResult<()> {
let levels = path.len();
if let Some(cap) = self.key_depth
&& levels > cap
{
return Err(Report::new(StorageError::Depth)
.attach(Key(path.clone()))
.attach(format!("levels: {levels}, and the limit is {cap}"))
.attach("set by: limits(|l| l.key_depth(..))")
.attach(format!(
"what is stored here spends the same budget - this store reads {} levels in all",
self.ceiling
)));
}
Ok(())
}
pub fn for_value(&self, path: &StorePath) -> Noticed {
Noticed::new(self.ceiling.saturating_sub(path.len()))
}
pub fn too_deep(&self, path: &StorePath) -> Report<StorageError> {
let levels = path.len();
let left = self.ceiling.saturating_sub(levels);
Report::new(StorageError::Codec)
.attach(Key(path.clone()))
.attach(format!(
"the path spends {levels} levels and the value goes past the {left} that are left"
))
.attach(format!("this store reads at most {} levels", self.ceiling))
.attach(
"a value deeper than the reader accepts is written without complaint and \
cannot be read back",
)
}
pub fn settle_the_enum_for_a_reader(
&self,
seen: &Noticed,
value: &dyn erased_serde::Serialize,
path: &StorePath,
) {
if self.enums || !seen.saw_an_enum() {
return;
}
let readable = self.for_value(path);
let mut sink = serde_json::Serializer::new(std::io::sink());
if serde::Serialize::serialize(&readable.count(value), &mut sink).is_ok() {
seen.take_the_enum_from(&readable);
}
}
pub fn refused(&self, seen: &Noticed, path: &StorePath) -> Option<Report<StorageError>> {
if !self.non_finite_floats && seen.saw_a_non_finite_float() {
return Some(
Report::new(StorageError::Codec)
.attach(Key(path.clone()))
.attach("a NaN or an infinity, which this store cannot read back")
.attach(
"JSON has no spelling for either, so the codec writes `null` and \
decoding it as a float fails - on json, and on sqlite, which encodes \
with the same JSON",
),
);
}
if !self.integers_of_128_bits && seen.saw_a_128_bit_integer() {
return Some(
Report::new(StorageError::Codec)
.attach(Key(path.clone()))
.attach("a `u128` or an `i128`, which this store cannot read back")
.attach(
"toml and ron have no 128-bit integer type, so they refuse the type \
whatever it holds - `42u128` as much as `u128::MAX`",
),
);
}
if !self.integers_past_64_bits && seen.saw_an_integer_past_64_bits() {
return Some(
Report::new(StorageError::Codec)
.attach(Key(path.clone()))
.attach("an integer that neither an `i64` nor a `u64` holds")
.attach(
"a json number is read back into an `i64`, a `u64` or an `f64`, so one \
past both integer types comes back as none of them",
),
);
}
if !self.wide_integers && seen.saw_an_integer_past_i64() {
return Some(
Report::new(StorageError::Codec)
.attach(Key(path.clone()))
.attach("an integer that does not fit in an `i64`")
.attach(
"TOML has one integer type and it is signed and 64 bits wide, so a \
`u64` past its top has nowhere to go",
),
);
}
if !self.nested_options && seen.saw_a_collapsing_option() {
return Some(
Report::new(StorageError::Codec)
.attach(Key(path.clone()))
.attach("a `Some` holding nothing, which reads back as nothing at all")
.attach(
"the outer `Some` has nothing of its own to write, so `Some(None)` and \
`None` reach the file as one null - `c0` under msgpack, `null` under \
either JSON - and both come back `None`. ron is the one engine that \
spells the `Option` out and keeps them apart",
),
);
}
if !self.enums && seen.saw_an_enum() {
return Some(
Report::new(StorageError::Codec)
.attach(Key(path.clone()))
.attach("an enum, which this store cannot read back")
.attach(
"ron writes one as `On(3)` and parses it back into a `ron::value::Value`, \
which has no variant to put it in - the name is dropped there and the \
next read is handed a sequence. `Value` not supporting enums is \
listed in https://github.com/ron-rs/ron/issues/122",
),
);
}
None
}
}