fix(codec): replace codec and map codec structs with Encode and Decode traits and their field versions (#2051)

* removed codecs in favor of the `Encode` and `Decode` traits and their derivatives

* placed more exports in the `pumpkin-codecs` lib

* split primitives into their own folder and added some tests

* added `Either` support and added a simple test for it

* fixed linting for tests
This commit is contained in:
SomeYellowGuy
2026-04-21 20:08:25 +05:30
committed by GitHub
parent deab79bb00
commit 513477d1cf
39 changed files with 1272 additions and 5312 deletions

1
Cargo.lock generated
View File

@@ -2892,7 +2892,6 @@ dependencies = [
name = "pumpkin-codecs"
version = "0.1.0-dev+26.1"
dependencies = [
"dashmap",
"either",
"serde_json",
"tracing",

View File

@@ -7,7 +7,6 @@ license.workspace = true
[dependencies]
serde_json.workspace = true
dashmap.workspace = true
tracing.workspace = true
either.workspace = true

View File

@@ -1,85 +0,0 @@
use crate::codec::Codec;
use crate::coders::{Decoder, Encoder};
use crate::data_result::DataResult;
use crate::dynamic_ops::DynamicOps;
use crate::lifecycle::Lifecycle;
use crate::map_like::MapLike;
use crate::struct_builder::StructBuilder;
use std::collections::HashMap;
use std::fmt::Display;
use std::hash::Hash;
/// A trait to provide basic functionality for an implementation of a *map* [`Codec`] or of a [`MapCodec`].
pub trait BaseMapCodec {
/// The key type of this map codec.
type Key: Display + Eq + Hash;
type KeyCodec: Codec<Value = Self::Key> + 'static;
/// The value (element) type of this map codec.
type Element;
type ElementCodec: Codec<Value = Self::Element> + 'static;
fn key_codec(&self) -> &'static Self::KeyCodec;
fn element_codec(&self) -> &'static Self::ElementCodec;
fn encode<T: Display + PartialEq + Clone>(
&self,
input: &HashMap<Self::Key, Self::Element>,
ops: &'static impl DynamicOps<Value = T>,
mut prefix: impl StructBuilder<Value = T>,
) -> impl StructBuilder<Value = T> {
for (key, element) in input {
prefix = prefix.add_key_result_value_result(
self.key_codec().encode_start(key, ops),
self.element_codec().encode_start(element, ops),
);
}
prefix
}
fn decode<T: Display + PartialEq + Clone>(
&self,
input: &impl MapLike<Value = T>,
ops: &'static impl DynamicOps<Value = T>,
) -> DataResult<HashMap<Self::Key, Self::Element>> {
let mut read_map: HashMap<Self::Key, Self::Element> = HashMap::new();
let mut failed: Vec<(T, T)> = vec![];
let result = input.iter().fold(
DataResult::new_success_with_lifecycle((), Lifecycle::Stable),
|r, (k, e)| {
// First, we try to parse the key and value.
let key_result = self.key_codec().parse(k.clone(), ops);
let element_result = self.element_codec().parse(e.clone(), ops);
let entry_result =
key_result.apply_2_and_make_stable(|kr, er| (kr, er), element_result);
let accumulated = r.add_message(&entry_result);
let entry = entry_result.into_result_or_partial();
if let Some((key, element)) = entry {
// If this parses successfully, we try adding it to our map.
if read_map.contains_key(&key) {
// There was already a value for this key.
failed.push((k, e.clone()));
return accumulated.add_message::<()>(&DataResult::new_error(format!(
"Duplicate entry for key: {key}"
)));
}
read_map.insert(key, element);
} else {
// Could not parse.
failed.push((k, e.clone()));
}
accumulated
},
);
let errors = ops.create_map(failed);
result
.with_complete_or_partial(read_map)
.map_error(|e| format!("{e} (Missed inputs: {errors})"))
}
}

View File

@@ -1,595 +0,0 @@
use crate::HasValue;
use crate::codecs::either::{EitherCodec, new_either_codec};
use crate::codecs::lazy::{LazyCodec, new_lazy_codec};
use crate::codecs::list::{ListCodec, new_list_codec};
use crate::codecs::primitive::{
BoolCodec, ByteBufferCodec, ByteCodec, DoubleCodec, FloatCodec, IntCodec, IntStreamCodec,
LongCodec, LongStreamCodec, ShortCodec, StringCodec,
};
use crate::codecs::range::RangeCodec;
use crate::codecs::range::new_range_codec;
use crate::codecs::unbounded_map::{UnboundedMapCodec, new_unbounded_map_codec};
use crate::codecs::validated::{ValidatedCodec, new_validated_codec};
use crate::coders::{
ComappedEncoderImpl, Decoder, Encoder, FlatComappedEncoderImpl, FlatMappedDecoderImpl,
MappedDecoderImpl, comap, decoder_field, encoder_field, flat_comap, flat_map, map,
};
use crate::data_result::DataResult;
use crate::dynamic_ops::DynamicOps;
use crate::keyable::Keyable;
use crate::map_codec::ComposedMapCodec;
use crate::map_codecs::field_coders::{FieldDecoder, FieldEncoder};
use crate::map_codecs::optional_field::{
DefaultValueProviderMapCodec, OptionalFieldMapCodec, new_default_value_provider_map_codec,
new_optional_field_map_codec,
};
use crate::map_codecs::simple::{SimpleMapCodec, new_simple_map_codec};
use std::fmt::Display;
use std::hash::Hash;
/// A type of *codec* describing the way to **encode from and decode to** something of a type `Value` (`Value` -> `?` and `?` -> `Value`).
///
/// # Usage
/// This trait is the main way serialization/deserialization can be handled easily.
/// - To encode something, use [`Codec::encode_start`]
/// - To decode something, use [`Codec::parse`].
///
/// To use these methods, use a [`DynamicOps`] instance to tell the intermediate format to encode to/decode from:
///
/// # Primitive Codecs
/// This trait's module (`codec`) provides many common codecs that can be used for more complex codec types:
/// - [`BYTE_CODEC`], [`SHORT_CODEC`], [`INT_CODEC`], [`LONG_CODEC`], [`BOOL_CODEC`], [`FLOAT_CODEC`] and [`DOUBLE_CODEC`] for Java primitive types.
/// - [`STRING_CODEC`] for `String`s.
/// - [`BYTE_CODEC`], [`USHORT_CODEC`], [`UINT_CODEC`] and [`ULONG_CODEC`] for unsigned versions of Java primitive number types (`u8`, `u16`, `u32` and `u64`).
/// - [`BYTE_BUFFER_CODEC`] for byte buffers (equivalent to `Box<[u8]>`).
/// - [`INT_STREAM_CODEC`] and [`LONG_STREAM_CODEC`] for Java's `int` and `long` stream codecs (equivalent to `Vec<i32>` and `Vec<i64>`).
///
/// # Creating a Codec
/// There are a few codec types that can be created for custom types. **Keep in mind that codecs are meant
/// to be static instances, and they should not be created at runtime. Codecs are also immutable,
/// which means they cannot be modified after they are created.** Usually, codecs are declared
/// using `pub static`.
///
/// ## Lists
/// Use one of the following with the required arguments:
/// - [`list`]: Creates a list codec of a given codec with the provided minimum and maximum size limits.
/// - [`limited_list`]: Creates a list codec of a given codec with the provided maximum size limit.
/// - [`unbounded_list`]: Creates a list codec of a given codec with no size limit.
///
/// ## Ranges
/// A codec can also only accept a range of values of some number type. You can use one of the following for that:
/// - [`int_range`]: For `int`s.
/// - [`float_range`]: For `float`s.
/// - [`double_range`]: For `double`s.
///
/// ## Structs
/// Use the [`crate::struct_codec!`] macro to generate a codec implementation for a struct.
/// A struct codec can work with up to 16 [`Field`]s, which each take a [`MapCodec`]
/// and a getter. A `MapCodec` is simply an object that works with one or more keys of a provided map.
/// Most of them used will be [`FieldMapCodec`]s, which only work with one singular key.
///
/// A field `FieldMapCodec` can be created with one of the following:
/// - [`field`]: Provides a *required* field with the provided codec and name.
/// - [`optional_field`]: Provides an *optional* field with the provided codec and name. Since this type of `MapCodec`
/// has **no default value**, it encodes into an [`Option`].
/// - [`optional_field_with_default`]: Provides an *optional* field with the provided codec and name, along with a default value factory
/// for when the value does not exist while decoding.
/// - [`lenient_optional_field`] and [`lenient_optional_field_with_default`] for lenient versions of the above two optional field methods.
///
/// To create a `Field` object using a `MapCodec`, use [`for_getter`] (which takes a `MapCodec` to own)
/// or, in more specific cases, [`for_getter_ref`] (which takes a static `MapCodec` pointer) to include a getter method
/// to tell the codec how to get some value (for encoding) from a struct instance.
/// These `Field`s can then be placed in the `struct_codec` body, one for each pair, along with a constructor function at the end
/// to tell the codec how to create an instance (for decoding) with the provided values. See the documentation
/// of the `struct_codec!` macro for a basic example for defining a struct codec.
///
/// ## Unbounded Maps
/// Use the [`unbounded_map`] function to create a codec encoding/decoding a `HashMap` of any arbitrary key.
/// **Unbounded map codecs only support keys that can encode from/decode to strings.**
///
/// ## Either
/// Use the [`either`] function to create a codec that can use one of two provided codecs to serialize/deserialize
/// an [`Either`].
///
/// # Transformers
/// A map codec of a type `B` can be implemented by *transforming* another codec of type `A` to work with type `B`.
/// The following methods can be used depending on the equivalence relation between the two types:
/// - [`xmap`]
/// - [`comap_flat_map`]
/// - [`flat_map_comap`]
/// - [`flat_xmap`]
///
/// For example, the unsigned types use `flat_xmap` to convert between the `i_` and `u_` types.
///
/// # Validator Codecs
/// The [`validate`] function returns a codec wrapper that validates a value before encoding and after decoding.
/// A validated codec takes a function that can either return an [`Ok`] for a success,
/// or an [`Err`] with the provided message to place in a `DataResult`.
///
/// [`MapCodec`]: super::map_codec::MapCodec
/// [`for_getter`]: super::map_codec::for_getter
/// [`for_getter_ref`]: super::map_codec::for_getter_ref
/// [`Field`]: super::struct_codecs::Field
///
/// [`Either`]: crate::util::either::Either
pub trait Codec: Encoder + Decoder {}
// Any struct implementing Encoder<Value = A> and Decoder<Value = A> will also implement Codec<Value = A>.
impl<T> Codec for T where T: Encoder + Decoder {}
/// A codec allowing an arbitrary encoder and decoder.
pub struct ComposedCodec<E: Encoder + 'static, D: Decoder<Value = E::Value> + 'static> {
encoder: E,
decoder: D,
}
impl<E: Encoder, D: Decoder<Value = E::Value>> HasValue for ComposedCodec<E, D> {
type Value = E::Value;
}
impl<E: Encoder, D: Decoder<Value = E::Value>> Encoder for ComposedCodec<E, D> {
fn encode<T: Display + PartialEq + Clone>(
&self,
input: &Self::Value,
ops: &'static impl DynamicOps<Value = T>,
prefix: T,
) -> DataResult<T> {
self.encoder.encode(input, ops, prefix)
}
}
impl<E: Encoder, D: Decoder<Value = E::Value>> Decoder for ComposedCodec<E, D> {
fn decode<T: Display + PartialEq + Clone>(
&self,
input: T,
ops: &'static impl DynamicOps<Value = T>,
) -> DataResult<(Self::Value, T)> {
self.decoder.decode(input, ops)
}
}
// Primitive codecs
macro_rules! define_const_codec {
($name:ident, $codec_ty:ident, $ty:ident, $java_ty:ident) => {
#[doc = concat!("A primitive codec for Java's `", stringify!($java_ty), "` (`", stringify!($ty), "` in Rust).")]
pub const $name: $codec_ty = $codec_ty;
};
(box $name:ident, $codec_ty:ident, $vec_ty:ident, $java_ty:ident) => {
#[doc = concat!("A primitive codec for Java's `", stringify!($java_ty), "`.")]
///
#[doc = concat!("This actually stores a [`Box<[", stringify!($vec_ty), "]>`].")]
#[doc = concat!("This is useful for *packed* `", stringify!($vec_ty), "`s in a single array.")]
pub const $name: $codec_ty = $codec_ty;
};
(vec $name:ident, $codec_ty:ident, $vec_ty:ident, $java_ty:ident) => {
#[doc = concat!("A primitive codec for Java's `", stringify!($java_ty), "`.")]
///
#[doc = concat!("This actually stores a [`Vec<", stringify!($vec_ty), ">`].")]
#[doc = concat!("This is useful for *packed* `", stringify!($vec_ty), "`s in a single array.")]
pub const $name: $codec_ty = $codec_ty;
};
}
define_const_codec!(BOOL_CODEC, BoolCodec, bool, boolean);
define_const_codec!(BYTE_CODEC, ByteCodec, i8, byte);
define_const_codec!(SHORT_CODEC, ShortCodec, i16, short);
define_const_codec!(INT_CODEC, IntCodec, i32, int);
define_const_codec!(LONG_CODEC, LongCodec, i64, long);
define_const_codec!(FLOAT_CODEC, FloatCodec, f32, float);
define_const_codec!(DOUBLE_CODEC, DoubleCodec, f64, double);
define_const_codec!(STRING_CODEC, StringCodec, String, String);
define_const_codec!(box BYTE_BUFFER_CODEC, ByteBufferCodec, i8, ByteBuffer);
define_const_codec!(vec INT_STREAM_CODEC, IntStreamCodec, i32, IntStream);
define_const_codec!(vec LONG_STREAM_CODEC, LongStreamCodec, i64, LongStream);
// Unsigned types
/// Helper macro to generate a [`Codec`] of unsigned number types using `flat_xmap` of their signed counterparts.
macro_rules! impl_unsigned_transformer_codec {
($name:ident, $signed_codec_type:ident, $unsigned_codec_type:ident, $unsigned_prim:ident, $signed_prim:ident, $transformed_codec:ident) => {
#[doc = concat!("The codec type for the [`", stringify!($unsigned_prim), "`] data type.")]
pub type $unsigned_codec_type = FlatXmapCodec<$unsigned_prim, $signed_codec_type>;
#[doc = concat!("A [`Codec`] for `", stringify!($unsigned_prim), "`, which is a transformer codec of [`", stringify!($transformed_codec), "`].")]
///
/// Be wary that
#[doc = concat!("if any encoded value exceeds [`", stringify!($signed_prim), "::MAX`], or if any decoded value is negative, this codec will return an error [`DataResult`].")]
pub static $name: $unsigned_codec_type = flat_xmap(
&$transformed_codec,
|i| <$unsigned_prim>::try_from(i)
.map_or_else(|_| DataResult::new_error(concat!("Could not fit ", stringify!($signed_prim), " into ", stringify!($unsigned_prim))), DataResult::new_success),
|u| <$signed_prim>::try_from(*u)
.map_or_else(|_| DataResult::new_error(concat!("Could not fit ", stringify!($unsigned_prim), " into ", stringify!($signed_prim))), DataResult::new_success),
);
};
}
impl_unsigned_transformer_codec!(UBYTE_CODEC, ByteCodec, UbyteCodec, u8, i8, BYTE_CODEC);
impl_unsigned_transformer_codec!(USHORT_CODEC, ShortCodec, UshortCodec, u16, i16, SHORT_CODEC);
impl_unsigned_transformer_codec!(UINT_CODEC, IntCodec, UintCodec, u32, i32, INT_CODEC);
impl_unsigned_transformer_codec!(ULONG_CODEC, LongCodec, UlongCodec, u64, i64, LONG_CODEC);
// Modifier methods
/// Creates a [`LazyCodec`] with a *function pointer* that returns a new [`Codec`], which will be called on first use.
pub const fn lazy<C: Codec>(f: fn() -> C) -> LazyCodec<C> {
new_lazy_codec(f)
}
/// Creates a [`ListCodec`] of another [`Codec`] with the provided minimum and maximum size.
pub const fn list<C: Codec>(codec: &'static C, min_size: usize, max_size: usize) -> ListCodec<C> {
new_list_codec(codec, min_size, max_size)
}
/// Creates a [`ListCodec`] of another [`Codec`] with the provided maximum size.
pub const fn limited_list<C: Codec>(codec: &'static C, max_size: usize) -> ListCodec<C> {
new_list_codec(codec, 0, max_size)
}
/// Creates a [`ListCodec`] of another [`Codec`], which allows any size.
pub const fn unbounded_list<C: Codec>(codec: &'static C) -> ListCodec<C> {
new_list_codec(codec, 0, usize::MAX)
}
/// Helper macro to generate the shorthand types and functions of the transformer [`Codec`] methods.
macro_rules! make_codec_transformation_function {
($name:ident, $short_type:ident, $encoder_type:ident, $decoder_type:ident, $encoder_func:ident, $decoder_func:ident, $to_func_result:ty, $from_func_result:ty, $a_equivalency:literal, $s_equivalency:literal) => {
pub type $short_type<S, C> = ComposedCodec<$encoder_type<S, C>, $decoder_type<S, C>>;
#[doc = "Transforms a [`Codec`] of type `A` to another [`Codec`] of type `S`."]
///
/// - `to` is the function called on `A` after decoding to convert it to `S`.
/// - `from` is the function called on `S` before encoding to convert it to `A`.
///
/// Use this if:
#[doc = concat!("- `A` is **", $a_equivalency, "** to `S`.")]
#[doc = concat!("- `S` is **", $s_equivalency, "** to `A`.")]
#[doc = ""]
#[doc = "A type `A` is *fully equivalent* to `B` if *A can always successfully be converted to B*."]
pub const fn $name<A, C: Codec<Value = A>, S>(codec: &'static C, to: fn(A) -> $to_func_result, from: fn(&S) -> $from_func_result) -> $short_type<S, C> {
ComposedCodec {
encoder: $encoder_func(codec, from),
decoder: $decoder_func(codec, to)
}
}
};
}
// Transformer functions
make_codec_transformation_function!(
xmap,
XmapCodec,
ComappedEncoderImpl,
MappedDecoderImpl,
comap,
map,
S,
A,
"equivalent",
"equivalent"
);
make_codec_transformation_function!(
comap_flat_map,
ComapFlatMapCodec,
ComappedEncoderImpl,
FlatMappedDecoderImpl,
comap,
flat_map,
DataResult<S>,
A,
"partially equivalent",
"equivalent"
);
make_codec_transformation_function!(
flat_map_comap,
FlatMapComapCodec,
FlatComappedEncoderImpl,
MappedDecoderImpl,
flat_comap,
map,
S,
DataResult<A>,
"equivalent",
"partially equivalent"
);
make_codec_transformation_function!(
flat_xmap,
FlatXmapCodec,
FlatComappedEncoderImpl,
FlatMappedDecoderImpl,
flat_comap,
flat_map,
DataResult<S>,
DataResult<A>,
"partially equivalent",
"partially equivalent"
);
/// Returns a transformer codec that validates a value before encoding and after decoding by calling a function,
/// which provides a [`DataResult`] depending on that value's validity.
///
/// `validator` is a function that takes the pointer of a value and returns a [`Result`].
/// - If the returned result is an [`Ok`], the codec works as normal.
/// - Otherwise, it always returns a non-result with the message [`String`].
pub const fn validate<C: Codec>(
codec: &'static C,
validator: fn(&C::Value) -> Result<(), String>,
) -> ValidatedCodec<C> {
new_validated_codec(codec, validator)
}
// Range codec functions
macro_rules! make_codec_range_function {
($func_name:ident, $shorthand_name:ident, $ty:ty, $codec:ident, $singleton_codec:ident, $java_type:ident) => {
pub type $shorthand_name = RangeCodec<$codec>;
#[doc = concat!("Returns a version of [`", stringify!($singleton_codec), "`] for `", stringify!($ty), "`s (or `", stringify!($java_type), "`s in Java) constrained to a minimum *(inclusive)* and maximum *(inclusive)* value.")]
pub const fn $func_name(min: $ty, max: $ty) -> $shorthand_name {
new_range_codec(&$singleton_codec, min, max)
}
};
}
make_codec_range_function!(int_range, IntRangeCodec, i32, IntCodec, INT_CODEC, int);
make_codec_range_function!(
float_range,
FloatRangeCodec,
f32,
FloatCodec,
FLOAT_CODEC,
float
);
make_codec_range_function!(
double_range,
DoubleRangeCodec,
f64,
DoubleCodec,
DOUBLE_CODEC,
double
);
// Map codec functions
/// Creates a [`SimpleMapCodec`] with the provided key codec, value (element) codec and the possible key values.
pub const fn simple_map<K: Codec, V: Codec, Key: Keyable>(
key_codec: &'static K,
element_codec: &'static V,
keyable: Key,
) -> SimpleMapCodec<K, V, Key>
where
<K as HasValue>::Value: Display + Eq + Hash,
{
new_simple_map_codec(key_codec, element_codec, keyable)
}
/// Creates an [`UnboundedMapCodec`] with the provided key and value (element) codec.
pub const fn unbounded_map<K: Codec, V: Codec>(
key_codec: &'static K,
element_codec: &'static V,
) -> UnboundedMapCodec<K, V>
where
<K as HasValue>::Value: Display + Eq + Hash,
{
new_unbounded_map_codec(key_codec, element_codec)
}
/// Creates an [`EitherCodec`] with the provided left and right codecs to tell the way to serialize/deserialize
/// their respective types.
pub const fn either<L: Codec, R: Codec>(
left_codec: &'static L,
right_codec: &'static R,
) -> EitherCodec<L, R> {
new_either_codec(left_codec, right_codec)
}
// Struct codec functions
/// Creates a structure [`Codec`]. This macro supports up to *16* [`Field`]s.
///
/// Struct codec types are usually pretty large. To combat this, use `pub type ... = ...` to
/// only store the complicated type once and never use it again. Rust can easily infer the type
/// for you after you define your codec.
///
/// # Example
/// ```rust
/// use pumpkin_codecs::codec::*;
/// use pumpkin_codecs::map_codec::*;
/// use pumpkin_codecs::codecs::primitive::*;
/// use pumpkin_codecs::struct_codecs::*;
/// use pumpkin_codecs::struct_codec;
///
/// // An example struct to make a codec for.
/// pub struct Person {
/// name: String,
/// age: u32
/// }
///
/// // Type to avoid writing this struct codec's type again.
/// pub type PersonCodec = StructCodec2<Person, FieldMapCodec<StringCodec>, FieldMapCodec<UintCodec>>;
///
/// // The actual codec.
/// pub static PERSON_CODEC: PersonCodec = struct_codec!(
/// for_getter(field(&STRING_CODEC, "name"), |person: &Person| &person.name),
/// for_getter(field(&UINT_CODEC, "age"), |person: &Person| &person.age),
/// |name, age| Person {name, age}
/// );
/// ```
#[macro_export]
macro_rules! struct_codec {
($f1:expr, $f:expr $(,)?) => {
$crate::struct_codecs::struct_1($f1, $f)
};
($f1:expr, $f2:expr, $f:expr $(,)?) => {
$crate::struct_codecs::struct_2($f1, $f2, $f)
};
($f1:expr, $f2:expr, $f3:expr, $f:expr $(,)?) => {
$crate::struct_codecs::struct_3($f1, $f2, $f3, $f)
};
($f1:expr, $f2:expr, $f3:expr, $f4:expr, $f:expr $(,)?) => {
$crate::struct_codecs::struct_4($f1, $f2, $f3, $f4, $f)
};
($f1:expr, $f2:expr, $f3:expr, $f4:expr, $f5:expr, $f:expr $(,)?) => {
$crate::struct_codecs::struct_5($f1, $f2, $f3, $f4, $f5, $f)
};
($f1:expr, $f2:expr, $f3:expr, $f4:expr, $f5:expr, $f6:expr, $f:expr $(,)?) => {
$crate::struct_codecs::struct_6($f1, $f2, $f3, $f4, $f5, $f6, $f)
};
($f1:expr, $f2:expr, $f3:expr, $f4:expr, $f5:expr, $f6:expr, $f7:expr, $f:expr $(,)?) => {
$crate::struct_codecs::struct_7($f1, $f2, $f3, $f4, $f5, $f6, $f7, $f)
};
($f1:expr, $f2:expr, $f3:expr, $f4:expr, $f5:expr, $f6:expr, $f7:expr, $f8:expr, $f:expr $(,)?) => {
$crate::struct_codecs::struct_8($f1, $f2, $f3, $f4, $f5, $f6, $f7, $f8, $f)
};
($f1:expr, $f2:expr, $f3:expr, $f4:expr, $f5:expr, $f6:expr, $f7:expr, $f8:expr, $f9:expr, $f:expr $(,)?) => {
$crate::struct_codecs::struct_9($f1, $f2, $f3, $f4, $f5, $f6, $f7, $f8, $f9, $f)
};
($f1:expr, $f2:expr, $f3:expr, $f4:expr, $f5:expr, $f6:expr, $f7:expr, $f8:expr, $f9:expr, $f10:expr, $f:expr $(,)?) => {
$crate::struct_codecs::struct_10($f1, $f2, $f3, $f4, $f5, $f6, $f7, $f8, $f9, $f10, $f)
};
($f1:expr, $f2:expr, $f3:expr, $f4:expr, $f5:expr, $f6:expr, $f7:expr, $f8:expr, $f9:expr, $f10:expr, $f11:expr, $f:expr $(,)?) => {
$crate::struct_codecs::struct_11(
$f1, $f2, $f3, $f4, $f5, $f6, $f7, $f8, $f9, $f10, $f11, $f,
)
};
($f1:expr, $f2:expr, $f3:expr, $f4:expr, $f5:expr, $f6:expr, $f7:expr, $f8:expr, $f9:expr, $f10:expr, $f11:expr, $f12:expr, $f:expr $(,)?) => {
$crate::struct_codecs::struct_12(
$f1, $f2, $f3, $f4, $f5, $f6, $f7, $f8, $f9, $f10, $f11, $f12, $f,
)
};
($f1:expr, $f2:expr, $f3:expr, $f4:expr, $f5:expr, $f6:expr, $f7:expr, $f8:expr, $f9:expr, $f10:expr, $f11:expr, $f12:expr, $f13:expr, $f:expr $(,)?) => {
$crate::struct_codecs::struct_13(
$f1, $f2, $f3, $f4, $f5, $f6, $f7, $f8, $f9, $f10, $f11, $f12, $f13, $f,
)
};
($f1:expr, $f2:expr, $f3:expr, $f4:expr, $f5:expr, $f6:expr, $f7:expr, $f8:expr, $f9:expr, $f10:expr, $f11:expr, $f12:expr, $f13:expr, $f14:expr, $f:expr $(,)?) => {
$crate::struct_codecs::struct_14(
$f1, $f2, $f3, $f4, $f5, $f6, $f7, $f8, $f9, $f10, $f11, $f12, $f13, $f14, $f,
)
};
($f1:expr, $f2:expr, $f3:expr, $f4:expr, $f5:expr, $f6:expr, $f7:expr, $f8:expr, $f9:expr, $f10:expr, $f11:expr, $f12:expr, $f13:expr, $f14:expr, $f15:expr, $f:expr $(,)?) => {
$crate::struct_codecs::struct_15(
$f1, $f2, $f3, $f4, $f5, $f6, $f7, $f8, $f9, $f10, $f11, $f12, $f13, $f14, $f15, $f,
)
};
($f1:expr, $f2:expr, $f3:expr, $f4:expr, $f5:expr, $f6:expr, $f7:expr, $f8:expr, $f9:expr, $f10:expr, $f11:expr, $f12:expr, $f13:expr, $f14:expr, $f15:expr, $f16:expr, $f:expr $(,)?) => {
$crate::struct_codecs::struct_16(
$f1, $f2, $f3, $f4, $f5, $f6, $f7, $f8, $f9, $f10, $f11, $f12, $f13, $f14, $f15, $f16,
$f,
)
};
}
// Field functions
/// A type of [`MapCodec`] to encode/decode for a single field of a map with the help of a [`Codec`].
pub type FieldMapCodec<C> = ComposedMapCodec<
FieldEncoder<<C as HasValue>::Value, C>,
FieldDecoder<<C as HasValue>::Value, C>,
>;
/// Creates a [`MapCodec`] for a field which relies on the provided [`Codec`] for serialization/deserialization.
pub const fn field<C: Codec>(codec: &'static C, name: &'static str) -> FieldMapCodec<C> {
ComposedMapCodec {
encoder: encoder_field(name, codec),
decoder: decoder_field(name, codec),
}
}
/// Creates a [`MapCodec`] for an optional field which relies on the provided [`Codec`] for serialization/deserialization.
///
/// Since this `MapCodec` has no 'default value', this is equivalent to encoding an [`Option`].
/// The returned `MapCodec` is also *not lenient*, meaning that it will not give a complete (successful) result
/// if the decoded field value is an error [`DataResult`] (partial or no result). Most of the time, you will
/// want a *non-lenient* field.
pub const fn optional_field<C: Codec>(
codec: &'static C,
name: &'static str,
) -> OptionalFieldMapCodec<C> {
new_optional_field_map_codec(codec, name, false)
}
/// Creates a [`MapCodec`] for an optional field which relies on the provided [`Codec`] for serialization/deserialization.
///
/// Since this `MapCodec` has no 'default value', this is equivalent to encoding an [`Option`].
/// The returned `MapCodec` is also *lenient*, meaning that it will still give a complete (successful) result
/// if the decoded field value is an error [`DataResult`] (partial or no result). Most of the time, you will
/// want a *non-lenient* field.
pub const fn lenient_optional_field<C: Codec>(
codec: &'static C,
name: &'static str,
) -> OptionalFieldMapCodec<C> {
new_optional_field_map_codec(codec, name, true)
}
pub type DefaultedFieldCodec<C> =
DefaultValueProviderMapCodec<<C as HasValue>::Value, OptionalFieldMapCodec<C>>;
/// Creates a [`MapCodec`] for an optional field which relies on the provided [`Codec`] for serialization/deserialization, along with a default value factory.
///
/// The factory provided is used for equality checks and for creating a new default value
/// for when no value is found. *If the encoded value is equal to the default value (provided via the factory), it is omitted.*
///
/// The returned `MapCodec` is also *not lenient*, meaning that it will not give a complete (successful) result
/// if the decoded field value is an error [`DataResult`] (partial or no result). Most of the time, you will
/// want a *non-lenient* field.
pub const fn optional_field_with_default<C: Codec>(
codec: &'static C,
name: &'static str,
factory: fn() -> C::Value,
) -> DefaultedFieldCodec<C>
where
<C as HasValue>::Value: PartialEq + Clone,
{
new_default_value_provider_map_codec(new_optional_field_map_codec(codec, name, false), factory)
}
/// Creates a [`MapCodec`] for an optional field which relies on the provided [`Codec`] for serialization/deserialization, along with a default value factory.
///
/// The factory provided is used for equality checks and for creating a new default value
/// for when no value is found. *If the encoded value is equal to the default value (provided via the factory), it is omitted.*
///
/// The returned `MapCodec` is also *lenient*, meaning that it will still give a complete (successful) result
/// if the decoded field value is an error [`DataResult`] (partial or no result). Most of the time, you will
/// want a *non-lenient* field.
pub const fn lenient_optional_field_with_default<C: Codec>(
codec: &'static C,
name: &'static str,
factory: fn() -> C::Value,
) -> DefaultedFieldCodec<C>
where
<C as HasValue>::Value: PartialEq + Clone,
{
new_default_value_provider_map_codec(new_optional_field_map_codec(codec, name, true), factory)
}
// Assertion functions
/// Asserts that the decoding of some value by a [`DynamicOps`] via a [`Codec`] is a success/error.
/// # Example
/// ```
/// # use pumpkin_codecs::assert_decode;
/// # use serde_json::json;
/// # use pumpkin_codecs::json_ops;
/// # use pumpkin_codecs::codec;
/// # use pumpkin_codecs::coders::Decoder;
///
/// assert_decode!(codec::INT_CODEC, json!(2), &json_ops::INSTANCE, is_success);
/// assert_decode!(codec::STRING_CODEC, json!("hello"), &json_ops::INSTANCE, is_success);
/// assert_decode!(codec::FLOAT_CODEC, json!(true), &json_ops::INSTANCE, is_error);
/// ```
#[macro_export]
macro_rules! assert_decode {
($codec:expr, $value:expr, $ops:expr, $assertion:ident) => {{
assert!($codec.decode($value, $ops).$assertion());
}};
}

View File

@@ -0,0 +1,91 @@
use crate::{DataResult, Decode, DynamicOps, Encode};
use either::Either;
impl<L: Encode, R: Encode> Encode for Either<L, R> {
fn encode<O: DynamicOps>(&self, ops: &'static O, prefix: O::Value) -> DataResult<O::Value> {
match self {
Self::Left(l) => l.encode(ops, prefix),
Self::Right(r) => r.encode(ops, prefix),
}
}
}
impl<L: Decode, R: Decode> Decode for Either<L, R> {
fn decode<O: DynamicOps>(input: O::Value, ops: &'static O) -> DataResult<(Self, O::Value)> {
let left = L::decode(input.clone(), ops).map(|(l, t)| (Self::Left(l), t));
// If the left result is a success, return that.
if left.is_success() {
return left;
}
let right = R::decode(input, ops).map(|(r, t)| (Self::Right(r), t));
// If the right result is a success, return that.
if right.is_success() {
return right;
}
// Since no result is a complete success by this point, we look for partial results.
if left.has_result_or_partial() {
return left;
}
if right.has_result_or_partial() {
return right;
}
DataResult::new_error(format!(
"Failed to parse either. First: {}; Second: {}",
left.get_message().unwrap(),
right.get_message().unwrap()
))
}
}
#[cfg(test)]
mod test {
use crate::json_ops::JsonOps;
use crate::{assert_decode, assert_encode_success};
use either::Either;
use serde_json::json;
#[test]
fn simple() {
assert_encode_success!(Either::<i32, String>::Left(5), JsonOps, json!(5));
assert_encode_success!(
Either::<i32, String>::Right("I am some text.".to_string()),
JsonOps,
json!("I am some text.")
);
// Decoding
assert_decode!(
Either<i32, String>,
json!(-238),
JsonOps,
is_success
);
assert_decode!(
Either<u32, String>,
json!(-238),
JsonOps,
is_error
);
assert_decode!(
Either<u32, String>,
json!("hello"),
JsonOps,
is_success
);
assert_decode!(
Either<u32, String>,
json!(true),
JsonOps,
is_error
);
}
}

View File

@@ -0,0 +1,208 @@
use crate::list_builder::ListBuilder;
use crate::{DataResult, Decode, DynamicOps, Encode, FlatTryFrom, Lifecycle};
/// A wrapped [`Vec`] that can only contain a size of elements between `MIN` and `MAX` (inclusive).
pub struct BoundedVec<T, const MIN: usize, const MAX: usize>(Vec<T>);
impl<T, const MIN: usize, const MAX: usize> From<BoundedVec<T, MIN, MAX>> for Vec<T> {
fn from(value: BoundedVec<T, MIN, MAX>) -> Self {
value.0
}
}
impl<T, const MIN: usize, const MAX: usize> FlatTryFrom<Vec<T>> for BoundedVec<T, MIN, MAX> {
fn flat_try_from(value: Vec<T>) -> DataResult<Self> {
let size = value.len();
if size < MIN {
create_too_short_error(MIN, MAX, size)
} else if size > MAX {
create_too_long_error(MIN, MAX, size)
} else {
DataResult::new_success(Self(value))
}
}
}
fn create_too_short_error<T>(min: usize, max: usize, size: usize) -> DataResult<T> {
DataResult::new_error(format!(
"List is too short: {size}, expected range [{min}-{max}]"
))
}
fn create_too_long_error<T>(min: usize, max: usize, size: usize) -> DataResult<T> {
DataResult::new_error(format!(
"List is too long: {size}, expected range [{min}-{max}]"
))
}
impl<T, const MIN: usize, const MAX: usize> Encode for BoundedVec<T, MIN, MAX>
where
T: Encode,
{
fn encode<O: DynamicOps>(&self, ops: &'static O, prefix: O::Value) -> DataResult<O::Value> {
let size = self.0.len();
if size < MIN {
create_too_short_error(MIN, MAX, size)
} else if size > MAX {
create_too_long_error(MIN, MAX, size)
} else {
let mut builder = ops.list_builder();
for e in &self.0 {
builder = builder.add_data_result(e.encode_start(ops));
}
builder.build(prefix)
}
}
}
impl<T, const MIN: usize, const MAX: usize> Decode for BoundedVec<T, MIN, MAX>
where
T: Decode,
{
fn decode<O: DynamicOps>(input: O::Value, ops: &'static O) -> DataResult<(Self, O::Value)> {
let iter = ops.get_iter(input).with_lifecycle(Lifecycle::Stable);
iter.flat_map(|i| {
let mut total_count = 0;
let mut elements: Vec<T> = vec![];
let mut failed: Vec<O::Value> = vec![];
// This is used to keep track of the overall `DataResult`.
// If any one element has a partial result, this turns into a partial result.
// If any one element has no result, this turns into a non-result.
let mut result = DataResult::new_success(());
for element in i {
total_count += 1;
if elements.len() >= MAX {
failed.push(element.clone());
continue;
}
let element_result = T::decode(element.clone(), ops);
result = result.add_message(&element_result);
if let Some(element) = element_result.into_result_or_partial() {
elements.push(element.0);
}
}
if total_count < MIN {
return create_too_short_error(MIN, MAX, total_count);
}
let pair = (Self(elements), ops.create_list(failed));
if total_count > MAX {
result = create_too_long_error(MIN, MAX, total_count);
}
result.with_complete_or_partial(pair)
})
}
}
impl<T> Encode for Vec<T>
where
T: Encode,
{
fn encode<O: DynamicOps>(&self, ops: &'static O, prefix: O::Value) -> DataResult<O::Value> {
let mut builder = ops.list_builder();
for e in self {
builder = builder.add_data_result(e.encode_start(ops));
}
builder.build(prefix)
}
}
impl<T> Decode for Vec<T>
where
T: Decode,
{
fn decode<O: DynamicOps>(input: O::Value, ops: &'static O) -> DataResult<(Self, O::Value)> {
let iter = ops.get_iter(input).with_lifecycle(Lifecycle::Stable);
iter.flat_map(|i| {
let mut elements: Self = vec![];
let mut result = DataResult::new_success(());
for element in i {
let element_result = T::decode(element.clone(), ops);
result = result.add_message(&element_result);
if let Some(element) = element_result.into_result_or_partial() {
elements.push(element.0);
}
}
let pair = (elements, ops.create_list(Vec::new()));
result.with_complete_or_partial(pair)
})
}
}
#[cfg(test)]
mod test {
use crate::assert_decode;
use crate::assert_encode_success;
use crate::json_ops::JsonOps;
use serde_json::json;
#[test]
fn encoding() {
assert_encode_success!(vec![1, 2], JsonOps, json!([1, 2]));
let vec: Vec<i32> = vec![];
assert_encode_success!(vec, JsonOps, json!([]));
assert_encode_success!(vec![-3, 192, 182], JsonOps, json!([-3, 192, 182]));
assert_encode_success!(
vec!["a".to_string(), "b".to_string()],
JsonOps,
json!(["a", "b"])
);
assert_encode_success!(vec!["one".to_string()], JsonOps, json!(["one"]));
assert_encode_success!(
vec!["1".to_string(), "2".to_string(), "3".to_string()],
JsonOps,
json!(["1", "2", "3"])
);
assert_encode_success!(vec![1, 2], JsonOps, json!([1, 2]));
assert_encode_success!(vec![true, false], JsonOps, json!([true, false]));
assert_encode_success!(
vec![vec![true, false], vec![true, false]],
JsonOps,
json!([[true, false], [true, false]])
);
assert_encode_success!(
vec![vec![vec![true, true], vec![false, false]]],
JsonOps,
json!([[[true, true], [false, false]]])
);
}
#[test]
fn decoding() {
type NumberGrid = Vec<Vec<f64>>;
assert_decode!(Vec<i16>, json!([1, 2, 3]), JsonOps, is_success);
assert_decode!(Vec<i16>, json!([1, 2, 6, 24, 120]), JsonOps, is_success);
assert_decode!(Vec<i16>, json!(["string", "b"]), JsonOps, is_error);
assert_decode!(Vec<i16>, json!(false), JsonOps, is_error);
assert_decode!(NumberGrid, json!([[0, 0.5, 1.0]]), JsonOps, is_success);
assert_decode!(
NumberGrid,
json!([[0, 0.5, 1.0], [1, 4, 5], [-293.4, 1, 293]]),
JsonOps,
is_success
);
assert_decode!(
NumberGrid,
json!([[0, 0.5, 1.0], [1, false, 5], [-293.4, 1, 293]]),
JsonOps,
is_error
);
assert_decode!(
NumberGrid,
json!([[1, 1.5, 2.0], [-20]]),
JsonOps,
is_success
);
assert_decode!(NumberGrid, json!([[]]), JsonOps, is_success);
assert_decode!(NumberGrid, json!([[[[]]]]), JsonOps, is_error);
}
}

View File

@@ -0,0 +1,264 @@
use crate::map_like::MapLike;
use crate::struct_builder::StructBuilder;
use crate::{DataResult, Decode, DynamicOps, Encode, Lifecycle};
use std::collections::HashMap;
use std::fmt::Display;
use std::hash::{BuildHasher, Hash};
fn encode_base_hash_map<
O: DynamicOps,
K: Encode + Eq + Hash + Display,
V: Encode,
S: BuildHasher + Default,
>(
map: &HashMap<K, V, S>,
ops: &'static O,
mut prefix: impl StructBuilder<Value = O::Value>,
) -> impl StructBuilder<Value = O::Value> {
for (key, element) in map {
prefix =
prefix.add_key_result_value_result(key.encode_start(ops), element.encode_start(ops));
}
prefix
}
fn decode_base_hash_map<
O: DynamicOps,
K: Decode + Eq + Hash + Display,
V: Decode,
S: BuildHasher + Default,
>(
input: &impl MapLike<Value = O::Value>,
ops: &'static O,
) -> DataResult<HashMap<K, V, S>> {
let mut read_map: HashMap<K, V, S> = HashMap::with_hasher(S::default());
let mut failed: Vec<(O::Value, O::Value)> = vec![];
let result = input.iter().fold(
DataResult::new_success_with_lifecycle((), Lifecycle::Stable),
|r, (k, e)| {
// First, we try to parse the key and value.
let key_result = K::parse(k.clone(), ops);
let element_result = V::parse(e.clone(), ops);
let entry_result =
key_result.apply_2_and_make_stable(|kr, er| (kr, er), element_result);
let accumulated = r.add_message(&entry_result);
let entry = entry_result.into_result_or_partial();
if let Some((key, element)) = entry {
// If this parses successfully, we try adding it to our map.
if read_map.contains_key(&key) {
// There was already a value for this key.
failed.push((k, e.clone()));
return accumulated.add_message::<()>(&DataResult::new_error(format!(
"Duplicate entry for key: {key}"
)));
}
read_map.insert(key, element);
} else {
// Could not parse.
failed.push((k, e.clone()));
}
accumulated
},
);
let errors = ops.create_map(failed);
result
.with_complete_or_partial(read_map)
.map_error(|e| format!("{e} (Missed inputs: {errors})"))
}
impl<K, V, S: BuildHasher + Default> Encode for HashMap<K, V, S>
where
K: Encode + Eq + Hash + Display,
V: Encode,
{
fn encode<O: DynamicOps>(&self, ops: &'static O, prefix: O::Value) -> DataResult<O::Value> {
encode_base_hash_map::<O, K, V, S>(self, ops, ops.map_builder()).build(prefix)
}
}
impl<K, V, S: BuildHasher + Default> Decode for HashMap<K, V, S>
where
K: Decode + Eq + Hash + Display,
V: Decode,
{
fn decode<O: DynamicOps>(input: O::Value, ops: &'static O) -> DataResult<(Self, O::Value)> {
ops.get_map(&input)
.with_lifecycle(Lifecycle::Stable)
.flat_map(|map| decode_base_hash_map(&map, ops))
.map(|r| (r, input))
}
}
#[cfg(test)]
mod test {
use crate::codec::*;
use crate::json_ops::JsonOps;
use crate::{assert_decode, assert_encode_success};
use serde_json::json;
use std::collections::HashMap;
use std::fmt::{Display, Formatter};
#[test]
fn simple_encoding() {
let mut map = HashMap::<String, i32>::new();
map.insert("Amy".to_string(), 10);
map.insert("Leo".to_string(), 24);
map.insert("Patrick".to_string(), -65);
assert_encode_success!(map, JsonOps, json!({"Amy": 10, "Leo": 24, "Patrick": -65}));
}
#[test]
fn string_integer_map() {
// A basic implementation to check if a number is prime.
fn is_prime(number: u32) -> bool {
if number < 2 {
return false;
}
for i in 2..number {
if number.is_multiple_of(i) {
return false;
}
}
true
}
/// A `u32` wrapper that encodes a `String`.
#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Eq, Hash)]
struct StringInteger(u32);
impl Display for StringInteger {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl Encode for StringInteger {
fn encode<O: DynamicOps>(
&self,
ops: &'static O,
prefix: O::Value,
) -> DataResult<O::Value> {
// This will always succeed.
self.0.to_string().encode(ops, prefix)
}
}
impl Decode for StringInteger {
fn decode<O: DynamicOps>(
input: O::Value,
ops: &'static O,
) -> DataResult<(Self, O::Value)> {
String::decode(input, ops).flat_map(|s| {
// Try to parse an integer.
s.0.parse().map_or_else(
|_| DataResult::new_error("Could not parse String"),
|i| DataResult::new_success((Self(i), s.1)),
)
})
}
}
let mut map = HashMap::<StringInteger, bool>::new();
// Calculate the map for the first 20 numbers.
for i in 1..=20 {
map.insert(StringInteger(i), is_prime(i));
}
assert_encode_success!(
map,
JsonOps,
json!({
"1": false, "2": true, "3": true, "4": false, "5": true, "6": false, "7": true, "8": false, "9": false, "10": false,
"11": true, "12": false, "13": true, "14": false, "15": false, "16": false, "17": true, "18": false, "19": true, "20": false
})
);
assert_decode!(
HashMap<StringInteger, bool>,
json!({
"1": true, "2": true, "3": true, "4": false, "5": false
}),
JsonOps,
is_success
);
}
#[test]
fn letter_frequency() {
/// A wrapper of a `String` that only allows a single letter.
#[derive(Debug, Clone, PartialOrd, PartialEq, Eq, Hash)]
struct Letter(String);
fn check_letter(string: String) -> DataResult<Letter> {
// Try to parse a single letter.
if string.len() == 1 {
DataResult::new_success(Letter(string))
} else {
DataResult::new_error(format!("Not a letter: {string}"))
}
}
impl Display for Letter {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl Encode for Letter {
fn encode<O: DynamicOps>(
&self,
ops: &'static O,
prefix: O::Value,
) -> DataResult<O::Value> {
// This will always succeed.
check_letter(self.0.clone()).flat_map(|l| l.0.encode(ops, prefix))
}
}
impl Decode for Letter {
fn decode<O: DynamicOps>(
input: O::Value,
ops: &'static O,
) -> DataResult<(Self, O::Value)> {
String::decode(input, ops).flat_map(|(s, v)| check_letter(s).map(|l| (l, v)))
}
}
type LetterData = HashMap<Letter, u64>;
let mut map = LetterData::new();
map.insert(Letter("b".to_string()), 62);
map.insert(Letter("z".to_string()), 2342);
assert_encode_success!(map, JsonOps, json!({"b": 62, "z": 2342}));
let mut map = LetterData::new();
map.insert(Letter("d".to_string()), 12452);
map.insert(Letter("candy".to_string()), 2342);
assert!(map.encode_start(&JsonOps).is_error());
assert_decode!(
LetterData,
json!({"a": 13, "c": 34, "x": 1, "e": 21}),
JsonOps,
is_success
);
assert_decode!(
LetterData,
json!({"b": 45, "w": 10, "l": 90, "word": 5}),
JsonOps,
is_error
);
}
}

View File

@@ -0,0 +1,142 @@
mod either;
pub mod list;
pub mod map;
pub mod optional_field;
pub(crate) mod primitive;
use crate::codec::optional_field::OptionalFieldDecode;
use crate::map_like::MapLike;
use crate::struct_builder::StructBuilder;
use crate::{DataResult, DynamicOps};
/// A trait for something that can be encoded by a [`DynamicOps`] to its format.
pub trait Encode {
/// Encodes this value to a value represented by the provided [`DynamicOps`]
/// with the provided prefix.
fn encode<O: DynamicOps>(&self, ops: &'static O, prefix: O::Value) -> DataResult<O::Value>;
/// Encodes this value to a value represented by the provided [`DynamicOps`] without a prefix.
fn encode_start<O: DynamicOps>(&self, ops: &'static O) -> DataResult<O::Value> {
self.encode(ops, ops.empty())
}
}
/// A trait for something which can be added to a [`MapLike`] as a field with a provided name.
pub trait FieldEncode {
/// Encodes this value to a map by adding a field, whose:
/// - key is the field's `name`.
/// - value is the encoded value represented by the provided [`DynamicOps`].
fn encode_field<O: DynamicOps, B: StructBuilder<Value = O::Value>>(
&self,
name: &'static str,
ops: &'static O,
prefix: B,
) -> B;
/// Encodes this value to a map by adding a defaulted field, whose:
/// - key is the field's `name`.
/// - value is the encoded value represented by the provided [`DynamicOps`].
///
/// The field may not be encoded if `default` == `*self`.
fn encode_defaulted_field<O: DynamicOps, B: StructBuilder<Value = O::Value>>(
&self,
name: &'static str,
ops: &'static O,
prefix: B,
default: Self,
) -> B
where
Self: PartialEq;
}
impl<T: Encode> FieldEncode for T {
fn encode_field<O: DynamicOps, B: StructBuilder<Value = O::Value>>(
&self,
name: &'static str,
ops: &'static O,
prefix: B,
) -> B {
prefix.add_string_key_value_result(name, self.encode_start(ops))
}
fn encode_defaulted_field<O: DynamicOps, B: StructBuilder<Value = O::Value>>(
&self,
name: &'static str,
ops: &'static O,
prefix: B,
default: Self,
) -> B
where
Self: PartialEq,
{
if default == *self {
prefix.add_string_key_value_result(name, self.encode_start(ops))
} else {
prefix
}
}
}
/// A trait for something that can be decoded from the value represented by a [`DynamicOps`].
pub trait Decode: Sized {
/// Decodes a value of this type from a value represented by the provided [`DynamicOps`],
/// along with the remaining data.
fn decode<O: DynamicOps>(input: O::Value, ops: &'static O) -> DataResult<(Self, O::Value)>;
/// Decodes a value of this type from a value represented by the provided [`DynamicOps`],
/// without providing any other data.
fn parse<O: DynamicOps>(input: O::Value, ops: &'static O) -> DataResult<Self> {
Self::decode(input, ops).map(|(r, _)| r)
}
}
/// A trait for something which can be decoded from a [`MapLike`] from a field with a provided name.
pub trait FieldDecode: Sized {
/// Decodes a value of this type from a map by decoding one of its fields, whose:
/// - key is the field's `name`.
/// - value is the value represented by a [`DynamicsOps`] that is meant to be decoded.
fn decode_field<O: DynamicOps>(
name: &'static str,
input: &impl MapLike<Value = O::Value>,
ops: &'static impl DynamicOps<Value = O::Value>,
) -> DataResult<Self>;
/// Decodes a value of this type from a map by decoding one of its defaulted fields, whose:
/// - key is the field's `name`.
/// - value is the value represented by a [`DynamicsOps`] that is meant to be decoded.
///
/// If a value could not be decoded, the `default` value is returned.
/// This method has an extra `lenient` parameter. If it is `true`, errors
/// while trying to decode an explicit value, and the default value will be decoded instead.
fn decode_defaulted_field<O: DynamicOps>(
name: &'static str,
input: &impl MapLike<Value = O::Value>,
ops: &'static impl DynamicOps<Value = O::Value>,
default: Self,
lenient: bool,
) -> DataResult<Self>;
}
impl<T: Decode> FieldDecode for T {
fn decode_field<O: DynamicOps>(
name: &'static str,
input: &impl MapLike<Value = O::Value>,
ops: &'static impl DynamicOps<Value = O::Value>,
) -> DataResult<Self> {
input.get_str(name).map_or_else(
|| DataResult::new_error(format!("No key {name} in map")),
|v| Self::parse(v.clone(), ops),
)
}
fn decode_defaulted_field<O: DynamicOps>(
name: &'static str,
input: &impl MapLike<Value = O::Value>,
ops: &'static impl DynamicOps<Value = O::Value>,
default: Self,
lenient: bool,
) -> DataResult<Self> {
let decoded_option = Option::decode_optional_field::<O>(name, input, ops, lenient);
decoded_option.map(|o| o.unwrap_or(default))
}
}

View File

@@ -0,0 +1,77 @@
use crate::codec::FieldEncode;
use crate::map_like::MapLike;
use crate::struct_builder::StructBuilder;
use crate::{DataResult, Decode, DynamicOps, Encode};
/// A trait for something which can be added to a [`MapLike`] as an optional field with a provided name.
pub trait OptionalFieldEncode {
/// Encodes this value to a map by adding an optional field, whose:
/// - key is the field's `name`.
/// - value is the encoded value represented by the provided [`DynamicOps`].
fn encode_optional_field<O: DynamicOps, B: StructBuilder<Value = O::Value>>(
&self,
name: &'static str,
ops: &'static O,
prefix: B,
) -> B;
}
impl<T> OptionalFieldEncode for Option<T>
where
T: Encode,
{
fn encode_optional_field<O: DynamicOps, B: StructBuilder<Value = O::Value>>(
&self,
name: &'static str,
ops: &'static O,
prefix: B,
) -> B {
if let Some(value) = self {
value.encode_field(name, ops, prefix)
} else {
prefix
}
}
}
/// A trait to decode an optional field of a [`MapLike`] into a
/// value of the implementing type.
///
/// There is no `OptionalFieldEncode` variant of this trait; just
/// use [`FieldEncode::encode_field`] for encoding an optional field.
pub trait OptionalFieldDecode: Sized {
/// Decodes an optional field from a map, similar to [`FieldDecode::decode_field`].
///
/// However, this method has an extra `lenient` parameter. If it is `true`, errors
/// while decoding a `Some` option will not occur, and a `None` will be decoded instead.
fn decode_optional_field<O: DynamicOps>(
name: &'static str,
input: &impl MapLike<Value = O::Value>,
ops: &'static impl DynamicOps<Value = O::Value>,
lenient: bool,
) -> DataResult<Self>;
}
impl<T> OptionalFieldDecode for Option<T>
where
T: Decode,
{
fn decode_optional_field<O: DynamicOps>(
name: &'static str,
input: &impl MapLike<Value = O::Value>,
ops: &'static impl DynamicOps<Value = O::Value>,
lenient: bool,
) -> DataResult<Self> {
input.get_str(name).map_or_else(
|| DataResult::new_success(None),
|value| {
let result = T::parse(value.clone(), ops);
if result.is_error() && lenient {
DataResult::new_success(None)
} else {
result.map(Some)
}
},
)
}
}

View File

@@ -0,0 +1,229 @@
use crate::codec::primitive::sealed::Primitive;
use crate::{DataResult, Decode, DynamicOps, Encode};
mod sealed {
use super::{DataResult, DynamicOps};
/// Sealed trait to easily implement `Encode` and `Decode` for
/// primitive DFU types.
pub trait Primitive: Sized {
fn primitive_encode<O: DynamicOps>(&self, ops: &'static O) -> O::Value;
fn primitive_decode<O: DynamicOps>(ops: &'static O, input: O::Value) -> DataResult<Self>;
}
}
impl<T: Primitive> Encode for T {
fn encode<O: DynamicOps>(&self, ops: &'static O, prefix: O::Value) -> DataResult<O::Value> {
ops.merge_into_primitive(prefix, self.primitive_encode(ops))
}
}
impl<T: Primitive> Decode for T {
fn decode<O: DynamicOps>(input: O::Value, ops: &'static O) -> DataResult<(Self, O::Value)> {
T::primitive_decode(ops, input).map(|r| (r, ops.empty()))
}
}
macro_rules! impl_number {
($ty:ty, $create_func:ident) => {
impl Primitive for $ty {
fn primitive_encode<O: DynamicOps>(&self, ops: &'static O) -> O::Value {
ops.$create_func(*self)
}
fn primitive_decode<O: DynamicOps>(
ops: &'static O,
input: O::Value,
) -> DataResult<Self> {
ops.get_number(&input).map(|n| <$ty>::from(n))
}
}
};
}
macro_rules! impl_number_and_unsigned {
($ty:ty, $uty:ty, $create_func:ident) => {
impl_number!($ty, $create_func);
// Unsigned type
impl Encode for $uty {
fn encode<O: DynamicOps>(
&self,
ops: &'static O,
prefix: O::Value,
) -> DataResult<O::Value> {
<$ty>::try_from(*self).map_or_else(
|_| {
DataResult::new_error(concat!(
"Could not fit ",
stringify!($uty),
" into ",
stringify!($ty)
))
},
|i| i.encode(ops, prefix),
)
}
}
impl Decode for $uty {
fn decode<O: DynamicOps>(
input: O::Value,
ops: &'static O,
) -> DataResult<(Self, O::Value)> {
<$ty>::parse(input, ops).flat_map(|i| {
<$uty>::try_from(i).map_or_else(
|_| {
DataResult::new_error(concat!(
"Could not fit ",
stringify!($ty),
" into ",
stringify!($uty)
))
},
|u| DataResult::new_success((u, ops.empty())),
)
})
}
}
};
}
impl_number_and_unsigned!(i8, u8, create_byte);
impl_number_and_unsigned!(i16, u16, create_short);
impl_number_and_unsigned!(i32, u32, create_int);
impl_number_and_unsigned!(i64, u64, create_long);
impl_number!(f32, create_float);
impl_number!(f64, create_double);
impl Primitive for bool {
fn primitive_encode<O: DynamicOps>(&self, ops: &'static O) -> O::Value {
ops.create_bool(*self)
}
fn primitive_decode<O: DynamicOps>(ops: &'static O, input: O::Value) -> DataResult<Self> {
ops.get_bool(&input)
}
}
impl Primitive for String {
fn primitive_encode<O: DynamicOps>(&self, ops: &'static O) -> O::Value {
ops.create_string(self.as_str())
}
fn primitive_decode<O: DynamicOps>(ops: &'static O, input: O::Value) -> DataResult<Self> {
ops.get_string(&input)
}
}
macro_rules! stream_struct {
($stream:ident, $ty:ty, $create_func:ident, $get_func:ident) => {
#[doc = concat!("A [`Vec<", stringify!($ty), ">`] wrapper that has built-in DFU support for encoding and decoding.")]
#[derive(Debug, Clone)]
pub struct $stream(pub Vec<$ty>);
impl From<Vec<$ty>> for $stream {
fn from(value: Vec<$ty>) -> Self {
Self(value)
}
}
impl From<$stream> for Vec<$ty> {
fn from(value: $stream) -> Vec<$ty> {
value.0
}
}
impl Primitive for $stream {
fn primitive_encode<O: DynamicOps>(&self, ops: &'static O) -> O::Value {
ops.$create_func(self.0.clone())
}
fn primitive_decode<O: DynamicOps>(ops: &'static O, input: O::Value) -> DataResult<Self> {
ops.$get_func(input).map(From::from)
}
}
};
}
stream_struct!(IntStream, i32, create_int_list, get_int_list);
stream_struct!(LongStream, i64, create_long_list, get_long_list);
/// A [`Box<[u8]>`] wrapper that has built-in DFU support for encoding and decoding.
#[derive(Debug, Clone)]
pub struct ByteBuffer(pub Box<[u8]>);
impl From<Box<[u8]>> for ByteBuffer {
fn from(value: Box<[u8]>) -> Self {
Self(value)
}
}
impl<const N: usize> From<[u8; N]> for ByteBuffer {
fn from(value: [u8; N]) -> Self {
Self(Box::from(value))
}
}
impl From<ByteBuffer> for Box<[u8]> {
fn from(value: ByteBuffer) -> Self {
value.0
}
}
impl Primitive for ByteBuffer {
fn primitive_encode<O: DynamicOps>(&self, ops: &'static O) -> O::Value {
ops.create_byte_buffer(self.0.to_vec())
}
fn primitive_decode<O: DynamicOps>(ops: &'static O, input: O::Value) -> DataResult<Self> {
ops.get_byte_buffer(input).map(From::from)
}
}
#[cfg(test)]
mod test {
use crate::json_ops::JsonOps;
use crate::{ByteBuffer, IntStream, LongStream, assert_decode, assert_encode_success};
use serde_json::json;
#[test]
fn encoding() {
assert_encode_success!(3, JsonOps, json!(3));
assert_encode_success!(-68i8, JsonOps, json!(-68));
assert_encode_success!(-913813743, JsonOps, json!(-913813743));
assert_encode_success!("Hello, world!".to_string(), JsonOps, json!("Hello, world!"));
assert_encode_success!(String::new(), JsonOps, json!(""));
assert_encode_success!(ByteBuffer::from([1u8, 2u8, 3u8]), JsonOps, json!([1, 2, 3]));
assert_encode_success!(
IntStream::from(vec![3, 6, 9, 11, 15]),
JsonOps,
json!([3, 6, 9, 11, 15])
);
assert_encode_success!(
LongStream::from(vec![4, 6, 9, 12]),
JsonOps,
json!([4, 6, 9, 12])
);
assert_encode_success!(3u8, JsonOps, json!(3));
assert_encode_success!(923482312u64, JsonOps, json!(923482312));
}
#[test]
fn decoding() {
assert_decode!(i32, json!(-2), JsonOps, is_success);
assert_decode!(bool, json!("hello"), JsonOps, is_error);
assert_decode!(bool, json!(0), JsonOps, is_error);
assert_decode!(IntStream, json!([1, 2, 3]), JsonOps, is_success);
assert_decode!(LongStream, json!([]), JsonOps, is_success);
assert_decode!(ByteBuffer, json!(["not a number"]), JsonOps, is_error);
assert_decode!(String, json!("cool"), JsonOps, is_success);
assert_decode!(String, json!(1), JsonOps, is_error);
assert_decode!(u32, json!(-45), JsonOps, is_error);
assert_decode!(u64, json!(-132541235), JsonOps, is_error);
assert_decode!(u64, json!(132541235), JsonOps, is_success);
}
}

View File

@@ -1,252 +0,0 @@
use crate::HasValue;
use crate::codec::Codec;
use crate::coders::{Decoder, Encoder};
use crate::data_result::DataResult;
use crate::dynamic_ops::DynamicOps;
use either::Either;
use std::fmt::Display;
/// A codec that can serialize/deserialize one of two types, with a codec for each one.
///
/// This evaluates the left codec first, and if the [`DataResult`] for it is invalid,
/// it evaluates the right codec.
pub struct EitherCodec<L: Codec + 'static, R: Codec + 'static> {
left_codec: &'static L,
right_codec: &'static R,
}
impl<L: Codec, R: Codec> HasValue for EitherCodec<L, R> {
type Value = Either<L::Value, R::Value>;
}
impl<L: Codec, R: Codec> Encoder for EitherCodec<L, R> {
fn encode<T: Display + PartialEq + Clone>(
&self,
input: &Self::Value,
ops: &'static impl DynamicOps<Value = T>,
prefix: T,
) -> DataResult<T> {
match &input {
Either::Left(l) => self.left_codec.encode(l, ops, prefix),
Either::Right(r) => self.right_codec.encode(r, ops, prefix),
}
}
}
impl<L: Codec, R: Codec> Decoder for EitherCodec<L, R> {
fn decode<T: Display + PartialEq + Clone>(
&self,
input: T,
ops: &'static impl DynamicOps<Value = T>,
) -> DataResult<(Self::Value, T)> {
let left = self
.left_codec
.decode(input.clone(), ops)
.map(|(l, t)| (Either::Left(l), t));
// If the left result is a success, return that.
if left.is_success() {
return left;
}
let right = self
.right_codec
.decode(input, ops)
.map(|(r, t)| (Either::Right(r), t));
// If the right result is a success, return that.
if right.is_success() {
return right;
}
// Since no result is a complete success by this point, we look for partial results.
if left.has_result_or_partial() {
return left;
}
if right.has_result_or_partial() {
return right;
}
DataResult::new_error(format!(
"Failed to parse either. First: {}; Second: {}",
left.get_message().unwrap(),
right.get_message().unwrap()
))
}
}
/// Creates a new `EitherCodec` with the provided left and right codecs for serializing/deserializing both possible types.
pub(crate) const fn new_either_codec<L: Codec, R: Codec>(
left_codec: &'static L,
right_codec: &'static R,
) -> EitherCodec<L, R> {
EitherCodec {
left_codec,
right_codec,
}
}
#[cfg(test)]
mod test {
use crate::codec::{
DOUBLE_CODEC, FieldMapCodec, INT_CODEC, STRING_CODEC, either, field, unbounded_map,
};
use crate::codecs::either::EitherCodec;
use crate::codecs::primitive::{DoubleCodec, IntCodec, StringCodec};
use crate::codecs::unbounded_map::UnboundedMapCodec;
use crate::coders::{Decoder, Encoder};
use crate::json_ops;
use crate::map_codec::for_getter;
use crate::struct_codec;
use crate::struct_codecs::StructCodec2;
use either::Either;
use serde_json::json;
#[test]
fn simple() {
pub static EITHER_INT_STRING_CODEC: EitherCodec<IntCodec, StringCodec> =
either(&INT_CODEC, &STRING_CODEC);
// Encoding
assert_eq!(
EITHER_INT_STRING_CODEC
.encode_start(&Either::Left(5), &json_ops::INSTANCE)
.expect("Encoding should succeed"),
json!(5)
);
assert_eq!(
EITHER_INT_STRING_CODEC
.encode_start(
&Either::Right("I am some text.".to_string()),
&json_ops::INSTANCE
)
.expect("Encoding should succeed"),
json!("I am some text.")
);
// Decoding
assert_eq!(
EITHER_INT_STRING_CODEC
.parse(json!(-238), &json_ops::INSTANCE)
.expect("Decoding should succeed"),
Either::Left(-238)
);
assert_eq!(
EITHER_INT_STRING_CODEC
.parse(json!("hello"), &json_ops::INSTANCE)
.expect("Decoding should succeed"),
Either::Right("hello".to_string())
);
assert!(
EITHER_INT_STRING_CODEC
.parse(json!(true), &json_ops::INSTANCE)
.get_message()
.expect("Decoding should fail")
.starts_with("Failed to parse either.")
);
}
// A situation where two codecs could possibly decode valid but different values.
// This test only checks for decoding.
#[test]
fn intersecting_codecs() {
/// A type to store a complex number (a number with both a real and imaginary part).
#[derive(Debug, PartialEq, Clone)]
struct ComplexNumber(f64, f64);
pub type ComplexNumberCodec =
StructCodec2<ComplexNumber, FieldMapCodec<DoubleCodec>, FieldMapCodec<DoubleCodec>>;
pub static COMPLEX_NUMBER_CODEC: ComplexNumberCodec = struct_codec!(
for_getter(field(&DoubleCodec, "real"), |n| &n.0),
for_getter(field(&DoubleCodec, "imaginary"), |n| &n.1),
ComplexNumber
);
pub type DoubleMapCodec = UnboundedMapCodec<StringCodec, DoubleCodec>;
pub static DOUBLE_MAP_CODEC: DoubleMapCodec = unbounded_map(&STRING_CODEC, &DOUBLE_CODEC);
pub static COMPLEX_NUMBER_FIRST_EITHER_CODEC: EitherCodec<
ComplexNumberCodec,
DoubleMapCodec,
> = either(&COMPLEX_NUMBER_CODEC, &DOUBLE_MAP_CODEC);
pub static DOUBLE_MAP_FIRST_EITHER_CODEC: EitherCodec<DoubleMapCodec, ComplexNumberCodec> =
either(&DOUBLE_MAP_CODEC, &COMPLEX_NUMBER_CODEC);
// We expect a complex number first, as that is the first to be checked.
assert_eq!(
COMPLEX_NUMBER_FIRST_EITHER_CODEC
.parse(
json!(
{
"real": 10.1,
"imaginary": -4.5
}
),
&json_ops::INSTANCE
)
.expect("Decoding should succeed")
.expect_left("Expected complex number"),
ComplexNumber(10.1, -4.5)
);
// This should be a map, as the complex number codec fails due to a missing field.
assert_eq!(
COMPLEX_NUMBER_FIRST_EITHER_CODEC
.parse(
json!(
{
"real": 10.1,
"second": -4.5,
"third": 1
}
),
&json_ops::INSTANCE
)
.expect("Decoding should succeed")
.expect_right("Expected double map")
.len(),
3
);
assert_eq!(
DOUBLE_MAP_FIRST_EITHER_CODEC
.parse(
json!(
{
"real": -124.6,
"imaginary": 134,
}
),
&json_ops::INSTANCE
)
.expect("Decoding should succeed")
.expect_left("Expected double map")
.len(),
2
);
assert_eq!(
DOUBLE_MAP_FIRST_EITHER_CODEC
.parse(
json!(
{
"a": 1,
"b": 2,
"c": 3,
"d": 4
}
),
&json_ops::INSTANCE
)
.expect("Decoding should succeed")
.expect_left("Expected double map")
.len(),
4
);
}
}

View File

@@ -1,47 +0,0 @@
use crate::HasValue;
use crate::codec::Codec;
use crate::coders::{Decoder, Encoder};
use crate::data_result::DataResult;
use crate::dynamic_ops::DynamicOps;
use std::fmt::Display;
use std::sync::LazyLock;
/// A type of [`Codec`] that initializes an inner [`Codec`] on first use.
pub struct LazyCodec<C>
where
C: Codec,
{
codec: LazyLock<C>,
}
impl<C: Codec> HasValue for LazyCodec<C> {
type Value = C::Value;
}
impl<C: Codec> Encoder for LazyCodec<C> {
fn encode<T: Display + PartialEq + Clone>(
&self,
input: &Self::Value,
ops: &'static impl DynamicOps<Value = T>,
prefix: T,
) -> DataResult<T> {
self.codec.encode(input, ops, prefix)
}
}
impl<C: Codec> Decoder for LazyCodec<C> {
fn decode<T: Display + PartialEq + Clone>(
&self,
input: T,
ops: &'static impl DynamicOps<Value = T>,
) -> DataResult<(Self::Value, T)> {
self.codec.decode(input, ops)
}
}
/// Creates a new [`LazyCodec`].
pub(crate) const fn new_lazy_codec<C: Codec>(f: fn() -> C) -> LazyCodec<C> {
LazyCodec {
codec: LazyLock::new(f),
}
}

View File

@@ -1,269 +0,0 @@
use crate::HasValue;
use crate::codec::Codec;
use crate::coders::{Decoder, Encoder};
use crate::data_result::DataResult;
use crate::dynamic_ops::DynamicOps;
use crate::lifecycle::Lifecycle;
use crate::list_builder::ListBuilder;
use std::fmt::{Debug, Display};
/// A list codec type. For a type `A`, this codec serializes/deserializes a [`Vec<A>`].
/// `C` is the codec used for each element of this list.
///
/// A `ListCodec` can also specify a minimum and maximum number of elements to allow in the list.
#[derive(Debug)]
pub struct ListCodec<C>
where
C: Codec + ?Sized + 'static,
{
element_codec: &'static C,
min_size: usize,
max_size: usize,
}
impl<C: Codec> ListCodec<C> {
fn create_too_short_error<T>(&self, size: usize) -> DataResult<T> {
DataResult::new_error(format!(
"List is too short: {size}, expected range [{}-{}]",
self.min_size, self.max_size
))
}
fn create_too_long_error<T>(&self, size: usize) -> DataResult<T> {
DataResult::new_error(format!(
"List is too long: {size}, expected range [{}-{}]",
self.min_size, self.max_size
))
}
}
impl<C: Codec> HasValue for ListCodec<C> {
type Value = Vec<C::Value>;
}
impl<C: Codec> Encoder for ListCodec<C> {
fn encode<T: Display + PartialEq + Clone>(
&self,
input: &Self::Value,
ops: &'static impl DynamicOps<Value = T>,
prefix: T,
) -> DataResult<T> {
let size = input.len();
if size < self.min_size {
self.create_too_short_error(size)
} else if size > self.max_size {
self.create_too_long_error(size)
} else {
let mut builder = ops.list_builder();
for e in input {
builder = builder.add_data_result(self.element_codec.encode_start(e, ops));
}
builder.build(prefix)
}
}
}
impl<C> Decoder for ListCodec<C>
where
C: Codec,
{
fn decode<T: Display + PartialEq + Clone>(
&self,
input: T,
ops: &'static impl DynamicOps<Value = T>,
) -> DataResult<(Self::Value, T)> {
let iter = ops.get_iter(input).with_lifecycle(Lifecycle::Stable);
iter.flat_map(|i| {
let mut total_count = 0;
let mut elements: Self::Value = vec![];
let mut failed: Vec<T> = vec![];
// This is used to keep track of the overall `DataResult`.
// If any one element has a partial result, this turns into a partial result.
// If any one element has no result, this turns into a non-result.
let mut result = DataResult::new_success(());
for element in i {
total_count += 1;
if elements.len() >= self.max_size {
failed.push(element.clone());
continue;
}
let element_result = self.element_codec.decode(element.clone(), ops);
result = result.add_message(&element_result);
if let Some(element) = element_result.into_result_or_partial() {
elements.push(element.0);
}
}
if total_count < self.min_size {
return self.create_too_short_error(total_count);
}
let pair = (elements, ops.create_list(failed));
if total_count > self.max_size {
result = self.create_too_long_error(total_count);
}
result.with_complete_or_partial(pair)
})
}
}
/// Creates a new [`ListCodec`].
pub(crate) const fn new_list_codec<C: Codec>(
codec: &'static C,
min_size: usize,
max_size: usize,
) -> ListCodec<C> {
ListCodec {
element_codec: codec,
min_size,
max_size,
}
}
#[cfg(test)]
mod test {
use crate::codec::*;
use crate::codecs::list::ListCodec;
use crate::codecs::primitive::{BoolCodec, DoubleCodec, IntCodec, ShortCodec, StringCodec};
use crate::coders::Decoder;
use crate::coders::Encoder;
use crate::json_ops;
use crate::{assert_decode, assert_success};
use serde_json::json;
#[test]
fn encoding() {
{
pub static INT_LIST_CODEC: ListCodec<IntCodec> = list(&INT_CODEC, 1, 3);
assert_success!(
INT_LIST_CODEC.encode_start(&vec![1, 2], &json_ops::INSTANCE),
json!([1, 2])
);
assert!(
INT_LIST_CODEC
.encode_start(&vec![], &json_ops::INSTANCE)
.is_error()
);
assert!(
INT_LIST_CODEC
.encode_start(&vec![50, 52, 54, 56], &json_ops::INSTANCE)
.is_error()
);
};
{
pub static STRING_LIST_CODEC: ListCodec<StringCodec> = limited_list(&STRING_CODEC, 2);
assert_success!(
STRING_LIST_CODEC
.encode_start(&vec!["a".to_string(), "b".to_string()], &json_ops::INSTANCE),
json!(["a", "b"])
);
assert_success!(
STRING_LIST_CODEC.encode_start(&vec!["one".to_string()], &json_ops::INSTANCE),
json!(["one"])
);
assert!(
STRING_LIST_CODEC
.encode_start(
&vec!["1".to_string(), "2".to_string(), "3".to_string()],
&json_ops::INSTANCE
)
.is_error()
);
};
{
// The inner lists have a max size of 2, while the main list has a max size of 3.
pub static BOOL_LIST_LIST_CODEC: ListCodec<ListCodec<BoolCodec>> =
limited_list(&limited_list(&BOOL_CODEC, 2), 3);
assert_success!(
BOOL_LIST_LIST_CODEC.encode_start(&vec![vec![true, true]], &json_ops::INSTANCE),
json!([[true, true]])
);
assert_success!(
BOOL_LIST_LIST_CODEC
.encode_start(&vec![vec![], vec![false, true]], &json_ops::INSTANCE),
json!([[], [false, true]])
);
assert!(
BOOL_LIST_LIST_CODEC
.encode_start(&vec![vec![true, false, true, false]], &json_ops::INSTANCE)
.is_error()
);
};
}
#[test]
fn decoding() {
{
pub static SHORT_LIST_CODEC: ListCodec<ShortCodec> = list(&SHORT_CODEC, 2, 4);
assert_decode!(
SHORT_LIST_CODEC,
json!([1, 2]),
&json_ops::INSTANCE,
is_success
);
assert_decode!(
SHORT_LIST_CODEC,
json!([1, 2, 6, 24]),
&json_ops::INSTANCE,
is_success
);
assert_decode!(
SHORT_LIST_CODEC,
json!([1, 2, 6, 24, 120]),
&json_ops::INSTANCE,
is_error
);
assert_decode!(
SHORT_LIST_CODEC,
json!([-45, 252, 1000]),
&json_ops::INSTANCE,
is_success
);
assert_decode!(
SHORT_LIST_CODEC,
json!(["string", "b"]),
&json_ops::INSTANCE,
is_error
);
assert_decode!(
SHORT_LIST_CODEC,
json!(["1", "2"]),
&json_ops::INSTANCE,
is_error
);
};
{
// The inner lists have a size of 3, while the main list has a max size of 2.
pub static POS_LIST_CODEC: ListCodec<ListCodec<DoubleCodec>> =
limited_list(&list(&DOUBLE_CODEC, 3, 3), 2);
assert_decode!(
POS_LIST_CODEC,
json!([[0, 0.5, 1.0]]),
&json_ops::INSTANCE,
is_success
);
assert_decode!(
POS_LIST_CODEC,
json!([0, 0.5, 1.0]),
&json_ops::INSTANCE,
is_error
);
assert_decode!(
POS_LIST_CODEC,
json!([[3.56, 123.4, -0.144], [12.34, 56.78]]),
&json_ops::INSTANCE,
is_error
);
assert_decode!(POS_LIST_CODEC, json!([]), &json_ops::INSTANCE, is_success);
}
}
}

View File

@@ -1,53 +0,0 @@
use crate::HasValue;
use crate::coders::{Decoder, Encoder};
use crate::data_result::DataResult;
use crate::dynamic_ops::DynamicOps;
use crate::map_codec::MapCodec;
use crate::struct_builder::StructBuilder;
use std::fmt::Display;
/// A [`Codec`] implementation for a [`MapCodec`].
///
/// The `MapCodec` held by this `Codec` can either be *owned* or a static reference (*borrowed*).
pub enum MapCodecCodec<C: MapCodec + 'static> {
Owned(C),
Borrowed(&'static C),
}
impl<C: MapCodec> MapCodecCodec<C> {
const fn codec(&self) -> &C {
match self {
Self::Owned(c) => c,
Self::Borrowed(c) => c,
}
}
}
impl<C: MapCodec> HasValue for MapCodecCodec<C> {
type Value = C::Value;
}
impl<C: MapCodec> Encoder for MapCodecCodec<C> {
fn encode<T: Display + PartialEq + Clone>(
&self,
input: &Self::Value,
ops: &'static impl DynamicOps<Value = T>,
prefix: T,
) -> DataResult<T> {
self.codec()
.encode(input, ops, self.codec().builder(ops))
.build(prefix)
}
}
impl<C: MapCodec> Decoder for MapCodecCodec<C> {
fn decode<T: Display + PartialEq + Clone>(
&self,
input: T,
ops: &'static impl DynamicOps<Value = T>,
) -> DataResult<(Self::Value, T)> {
self.codec()
.compressed_decode(input.clone(), ops)
.map(|a| (a, input))
}
}

View File

@@ -1,8 +0,0 @@
pub mod either;
pub mod lazy;
pub mod list;
pub mod map_codec;
pub mod primitive;
pub mod range;
pub mod unbounded_map;
pub mod validated;

View File

@@ -1,208 +0,0 @@
use crate::HasValue;
use crate::codec::Codec;
use crate::coders::{Decoder, Encoder};
use crate::data_result::DataResult;
use crate::dynamic_ops::DynamicOps;
// DFU types
/// Helper macro to generate the struct and [`HasValue`] trait implementation for a `PrimitiveCodec` struct.
macro_rules! impl_primitive_codec_start {
($name:ident, $prim:ty) => {
/// A primitive [`Codec`] for the
#[doc = concat!("[`", stringify!($prim), "`]")]
/// data type.
pub struct $name;
impl HasValue for $name {
type Value = $prim;
}
};
}
/// Helper macro to generate an entire implementation for a number `PrimitiveCodec`.
macro_rules! impl_primitive_number_codec {
($name:ident, $prim:ty, $create_func:ident) => {
impl_primitive_codec_start!($name, $prim);
impl PrimitiveCodec for $name {
fn read<T>(
&self,
ops: &'static impl DynamicOps<Value = T>,
input: T,
) -> DataResult<$prim> {
ops.get_number(&input).map(|n| <$prim>::from(n))
}
fn write<T>(&self, ops: &'static impl DynamicOps<Value = T>, value: &$prim) -> T {
ops.$create_func(*value)
}
}
};
}
/// Helper macro to generate an entire implementation for a list `PrimitiveCodec`.
macro_rules! impl_primitive_list_codec {
($name:ident, $elem:ty, $get_func:ident, $create_func:ident) => {
impl_primitive_codec_start!($name, Vec<$elem>);
impl PrimitiveCodec for $name {
fn read<T>(
&self,
ops: &'static impl DynamicOps<Value = T>,
input: T,
) -> DataResult<Vec<$elem>> {
ops.$get_func(input)
}
fn write<T>(&self, ops: &'static impl DynamicOps<Value = T>, value: &Vec<$elem>) -> T {
ops.$create_func(value.to_vec())
}
}
};
}
/// A generic primitive codec.
trait PrimitiveCodec: Codec {
fn read<T>(
&self,
ops: &'static impl DynamicOps<Value = T>,
input: T,
) -> DataResult<Self::Value>;
fn write<T>(&self, ops: &'static impl DynamicOps<Value = T>, value: &Self::Value) -> T;
}
impl<C: PrimitiveCodec> Encoder for C {
fn encode<T: PartialEq>(
&self,
input: &<C as HasValue>::Value,
ops: &'static impl DynamicOps<Value = T>,
prefix: T,
) -> DataResult<T> {
ops.merge_into_primitive(prefix, self.write(ops, input))
}
}
impl<C: PrimitiveCodec> Decoder for C {
fn decode<T: PartialEq>(
&self,
input: T,
ops: &'static impl DynamicOps<Value = T>,
) -> DataResult<(<C as HasValue>::Value, T)> {
self.read(ops, input).map(|r| (r, ops.empty()))
}
}
// Implementations
impl_primitive_codec_start!(BoolCodec, bool);
impl PrimitiveCodec for BoolCodec {
fn read<T>(&self, ops: &'static impl DynamicOps<Value = T>, input: T) -> DataResult<bool> {
ops.get_bool(&input)
}
fn write<T>(&self, ops: &'static impl DynamicOps<Value = T>, value: &bool) -> T {
ops.create_bool(*value)
}
}
impl_primitive_number_codec!(ByteCodec, i8, create_byte);
impl_primitive_number_codec!(ShortCodec, i16, create_short);
impl_primitive_number_codec!(IntCodec, i32, create_int);
impl_primitive_number_codec!(LongCodec, i64, create_long);
impl_primitive_number_codec!(FloatCodec, f32, create_float);
impl_primitive_number_codec!(DoubleCodec, f64, create_double);
impl_primitive_codec_start!(StringCodec, String);
impl PrimitiveCodec for StringCodec {
fn read<T>(&self, ops: &'static impl DynamicOps<Value = T>, input: T) -> DataResult<String> {
ops.get_string(&input)
}
fn write<T>(&self, ops: &'static impl DynamicOps<Value = T>, value: &String) -> T {
ops.create_string(value)
}
}
impl_primitive_codec_start!(ByteBufferCodec, Box<[u8]>);
impl PrimitiveCodec for ByteBufferCodec {
fn read<T>(&self, ops: &'static impl DynamicOps<Value = T>, input: T) -> DataResult<Box<[u8]>> {
ops.get_byte_buffer(input)
}
fn write<T>(&self, ops: &'static impl DynamicOps<Value = T>, value: &Box<[u8]>) -> T {
ops.create_byte_buffer(value.to_vec())
}
}
impl_primitive_list_codec!(IntStreamCodec, i32, get_int_list, create_int_list);
impl_primitive_list_codec!(LongStreamCodec, i64, get_long_list, create_long_list);
#[cfg(test)]
mod test {
use crate::codec::*;
use crate::coders::*;
use crate::json_ops;
use crate::{assert_decode, assert_success};
use serde_json::json;
#[test]
fn encoding() {
assert_success!(INT_CODEC.encode_start(&3, &json_ops::INSTANCE), json!(3));
assert_success!(
BYTE_CODEC.encode_start(&-68i8, &json_ops::INSTANCE),
json!(-68)
);
assert_success!(
LONG_CODEC.encode_start(&-913813743, &json_ops::INSTANCE),
json!(-913813743)
);
assert_success!(
STRING_CODEC.encode_start(&"Hello, world!".to_string(), &json_ops::INSTANCE),
json!("Hello, world!")
);
assert_success!(
STRING_CODEC.encode_start(&String::new(), &json_ops::INSTANCE),
json!("")
);
assert_success!(
BYTE_BUFFER_CODEC.encode_start(&Box::from([1u8, 2u8, 3u8]), &json_ops::INSTANCE),
json!([1, 2, 3])
);
assert_success!(
LONG_STREAM_CODEC.encode_start(&vec![4, 6, 9, 12], &json_ops::INSTANCE),
json!([4, 6, 9, 12])
);
}
#[test]
fn decoding() {
assert_decode!(INT_CODEC, json!(-2), &json_ops::INSTANCE, is_success);
assert_decode!(SHORT_CODEC, json!("hello"), &json_ops::INSTANCE, is_error);
assert_decode!(BOOL_CODEC, json!(0), &json_ops::INSTANCE, is_error);
assert_decode!(
INT_STREAM_CODEC,
json!([1, 2, 3]),
&json_ops::INSTANCE,
is_success
);
assert_decode!(
LONG_STREAM_CODEC,
json!([]),
&json_ops::INSTANCE,
is_success
);
assert_decode!(
BYTE_BUFFER_CODEC,
json!(["not a number"]),
&json_ops::INSTANCE,
is_error
);
assert_decode!(STRING_CODEC, json!("cool"), &json_ops::INSTANCE, is_success);
assert_decode!(STRING_CODEC, json!(1), &json_ops::INSTANCE, is_error);
}
}

View File

@@ -1,176 +0,0 @@
use crate::HasValue;
use crate::codec::Codec;
use crate::coders::{Decoder, Encoder};
use crate::data_result::DataResult;
use crate::dynamic_ops::DynamicOps;
use std::fmt::Display;
/// A codec for a specific number range.
/// - `C` is the type of codec used to serialize them (as if there was no range).
/// - `C::Value` (the codec type) is the type of number to restrict (by providing a range), while
pub struct RangeCodec<C: Codec + 'static>
where
C::Value: PartialOrd + Display + Clone,
{
codec: &'static C,
min: C::Value,
max: C::Value,
}
impl<C: Codec> HasValue for RangeCodec<C>
where
<C as HasValue>::Value: PartialOrd + Display + Clone,
{
type Value = C::Value;
}
impl<C: Codec> Encoder for RangeCodec<C>
where
<C as HasValue>::Value: PartialOrd + Display + Clone,
{
fn encode<T: Display + PartialEq + Clone>(
&self,
input: &Self::Value,
ops: &'static impl DynamicOps<Value = T>,
prefix: T,
) -> DataResult<T> {
check_range(input, &self.min, &self.max).flat_map(|t| self.codec.encode(&t, ops, prefix))
}
}
impl<C: Codec> Decoder for RangeCodec<C>
where
<C as HasValue>::Value: PartialOrd + Display + Clone,
{
fn decode<T: Display + PartialEq + Clone>(
&self,
input: T,
ops: &'static impl DynamicOps<Value = T>,
) -> DataResult<(Self::Value, T)> {
self.codec
.decode(input, ops)
.flat_map(|(i, t)| check_range(&i, &self.min, &self.max).map(|n| (n, t)))
}
}
/// A helper function to check whether a number is between the range `[min, max]` (both inclusive).
fn check_range<T: PartialOrd + Display + Clone>(input: &T, min: &T, max: &T) -> DataResult<T> {
if input >= min && input <= max {
DataResult::new_success(input.clone())
} else {
DataResult::new_error(format!("Value {input} is outside range [{min}, {max}]"))
}
}
pub(crate) const fn new_range_codec<A: Display + PartialOrd + Clone, C: Codec<Value = A>>(
codec: &'static C,
min: A,
max: A,
) -> RangeCodec<C> {
RangeCodec { codec, min, max }
}
#[cfg(test)]
mod test {
use crate::codec::*;
use crate::coders::*;
use crate::json_ops;
use crate::{assert_decode, assert_success};
use serde_json::json;
#[test]
fn encoding() {
{
// A codec that does not allow negative numbers.
pub static NON_NEGATIVE_INT_CODEC: IntRangeCodec = int_range(0, i32::MAX);
assert_success!(
NON_NEGATIVE_INT_CODEC.encode_start(&3, &json_ops::INSTANCE),
json!(3)
);
assert_success!(
NON_NEGATIVE_INT_CODEC.encode_start(&6745, &json_ops::INSTANCE),
json!(6745)
);
assert_success!(
NON_NEGATIVE_INT_CODEC.encode_start(&0, &json_ops::INSTANCE),
json!(0)
);
assert!(
NON_NEGATIVE_INT_CODEC
.encode_start(&-93, &json_ops::INSTANCE)
.is_error()
);
};
{
// A codec accepting a double value from 0 to 100.
pub static PERCENTAGE_CODEC: DoubleRangeCodec = double_range(0.0, 100.0);
assert!(
PERCENTAGE_CODEC
.encode_start(&16.0, &json_ops::INSTANCE)
.is_success()
);
assert!(
PERCENTAGE_CODEC
.encode_start(&45.5, &json_ops::INSTANCE)
.is_success()
);
assert!(
PERCENTAGE_CODEC
.encode_start(&99.999, &json_ops::INSTANCE)
.is_success()
);
assert!(
PERCENTAGE_CODEC
.encode_start(&134.4, &json_ops::INSTANCE)
.is_error()
);
};
}
#[test]
fn decoding() {
assert_decode!(int_range(1, 5), json!(3), &json_ops::INSTANCE, is_success);
assert_decode!(int_range(-5, 5), json!(6), &json_ops::INSTANCE, is_error);
assert_decode!(
double_range(-100.0, 100.0),
json!(45.5),
&json_ops::INSTANCE,
is_success
);
assert_decode!(
double_range(-100.0, 100.0),
json!(-100),
&json_ops::INSTANCE,
is_success
);
assert_decode!(
double_range(1.0, f64::MAX),
json!(88.44),
&json_ops::INSTANCE,
is_success
);
assert_decode!(
double_range(1.0, f64::MAX),
json!(0.999),
&json_ops::INSTANCE,
is_error
);
assert_decode!(
float_range(0.04, 0.08),
json!(0.05),
&json_ops::INSTANCE,
is_success
);
assert_decode!(
float_range(0.006, 0.012),
json!(0.013),
&json_ops::INSTANCE,
is_error
);
}
}

View File

@@ -1,216 +0,0 @@
use crate::HasValue;
use crate::base_map_codec::BaseMapCodec;
use crate::codec::Codec;
use crate::coders::{Decoder, Encoder};
use crate::data_result::DataResult;
use crate::dynamic_ops::DynamicOps;
use crate::lifecycle::Lifecycle;
use crate::struct_builder::StructBuilder;
use std::collections::HashMap;
use std::fmt::Display;
use std::hash::Hash;
/// A type of [`Codec`] for a map with no known list of keys.
pub struct UnboundedMapCodec<K: Codec + 'static, V: Codec + 'static>
where
K::Value: Display + Eq + Hash,
{
key_codec: &'static K,
element_codec: &'static V,
}
impl<K: Codec, V: Codec> BaseMapCodec for UnboundedMapCodec<K, V>
where
<K as HasValue>::Value: Display + Eq + Hash,
{
type Key = K::Value;
type KeyCodec = K;
type Element = V::Value;
type ElementCodec = V;
fn key_codec(&self) -> &'static Self::KeyCodec {
self.key_codec
}
fn element_codec(&self) -> &'static Self::ElementCodec {
self.element_codec
}
}
impl<K: Codec, V: Codec> HasValue for UnboundedMapCodec<K, V>
where
<K as HasValue>::Value: Display + Eq + Hash,
{
type Value = HashMap<K::Value, V::Value>;
}
impl<K: Codec, V: Codec> Encoder for UnboundedMapCodec<K, V>
where
<K as HasValue>::Value: Display + Eq + Hash,
{
fn encode<T: Display + PartialEq + Clone>(
&self,
input: &Self::Value,
ops: &'static impl DynamicOps<Value = T>,
prefix: T,
) -> DataResult<T> {
BaseMapCodec::encode(self, input, ops, ops.map_builder()).build(prefix)
}
}
impl<K: Codec, V: Codec> Decoder for UnboundedMapCodec<K, V>
where
<K as HasValue>::Value: Display + Eq + Hash,
{
fn decode<T: Display + PartialEq + Clone>(
&self,
input: T,
ops: &'static impl DynamicOps<Value = T>,
) -> DataResult<(Self::Value, T)> {
ops.get_map(&input)
.with_lifecycle(Lifecycle::Stable)
.flat_map(|map| BaseMapCodec::decode(self, &map, ops))
.map(|r| (r, input))
}
}
/// Creates a new [`UnboundedMapCodec`].
pub(crate) const fn new_unbounded_map_codec<K: Codec, V: Codec>(
key_codec: &'static K,
element_codec: &'static V,
) -> UnboundedMapCodec<K, V>
where
<K as HasValue>::Value: Display + Eq + Hash,
{
UnboundedMapCodec {
key_codec,
element_codec,
}
}
#[cfg(test)]
mod test {
use crate::assert_decode;
use crate::codec::*;
use crate::codecs::primitive::{BoolCodec, IntCodec, StringCodec};
use crate::codecs::unbounded_map::UnboundedMapCodec;
use crate::codecs::validated::ValidatedCodec;
use crate::coders::Decoder;
use crate::coders::Encoder;
use crate::json_ops;
use serde_json::json;
use std::collections::HashMap;
#[test]
fn simple_encoding() {
pub static SCORES_CODEC: UnboundedMapCodec<StringCodec, IntCodec> =
unbounded_map(&STRING_CODEC, &INT_CODEC);
let mut map = HashMap::<String, i32>::new();
map.insert("Amy".to_string(), 10);
map.insert("Leo".to_string(), 24);
map.insert("Patrick".to_string(), -65);
assert_eq!(
SCORES_CODEC
.encode_start(&map, &json_ops::INSTANCE)
.expect("Encoding scores failed"),
json!({"Amy": 10, "Leo": 24, "Patrick": -65})
);
}
#[test]
fn number_key_encoding() {
// A basic implementation to check if a number is prime.
fn is_prime(number: u32) -> bool {
if number < 2 {
return false;
}
for i in 2..number {
if number.is_multiple_of(i) {
return false;
}
}
true
}
// A codec to store whether a number is prime or not.
// We use a transformer to keep the keys in a string form even while working with `u32` keys.
pub static PRIME_MAP_CODEC: UnboundedMapCodec<XmapCodec<u32, StringCodec>, BoolCodec> =
unbounded_map(
&xmap(
&STRING_CODEC,
|s| s.parse().expect("Could not parse String"),
|u: &u32| u.to_string(),
),
&BOOL_CODEC,
);
let mut map = HashMap::<u32, bool>::new();
// Calculate the map for the first 20 numbers.
for i in 1..=20 {
map.insert(i, is_prime(i));
}
assert_eq!(
PRIME_MAP_CODEC
.encode_start(&map, &json_ops::INSTANCE)
.expect("Encoding prime map failed"),
json!({
"1": false, "2": true, "3": true, "4": false, "5": true, "6": false, "7": true, "8": false, "9": false, "10": false,
"11": true, "12": false, "13": true, "14": false, "15": false, "16": false, "17": true, "18": false, "19": true, "20": false
})
);
}
#[test]
fn decoding() {
// A codec storing a frequency for each letter.
// Each key must only be 1 character long (to make it a letter).
// There must be at least 1 key.
pub static LETTER_FREQUENCY_CODEC: ValidatedCodec<
UnboundedMapCodec<ValidatedCodec<StringCodec>, UlongCodec>,
> = validate(
&unbounded_map(
&validate(&STRING_CODEC, |s| {
if s.len() == 1 {
Ok(())
} else {
Err("String must be exactly 1 character long".to_string())
}
}),
&ULONG_CODEC,
),
|m| {
if m.is_empty() {
Err("Map must not be empty".to_string())
} else {
Ok(())
}
},
);
assert_decode!(
LETTER_FREQUENCY_CODEC,
json!({"a": 13, "c": 34, "x": 1, "e": 21}),
&json_ops::INSTANCE,
is_success
);
assert_decode!(
LETTER_FREQUENCY_CODEC,
json!({"b": 45, "w": 10, "l": 90, "word": 5}),
&json_ops::INSTANCE,
is_error
);
assert_decode!(
LETTER_FREQUENCY_CODEC,
json!({}),
&json_ops::INSTANCE,
is_error
);
}
}

View File

@@ -1,164 +0,0 @@
use crate::HasValue;
use crate::codec::Codec;
use crate::coders::{Decoder, Encoder};
use crate::data_result::DataResult;
use crate::dynamic_ops::DynamicOps;
use std::fmt::Display;
/// A validator codec that validates any values before encoding and after decoding.
pub struct ValidatedCodec<C: Codec + 'static> {
codec: &'static C,
/// The validator function used.
validator: fn(&C::Value) -> Result<(), String>,
}
impl<C: Codec> HasValue for ValidatedCodec<C> {
type Value = C::Value;
}
impl<C: Codec> Encoder for ValidatedCodec<C> {
fn encode<T: Display + PartialEq + Clone>(
&self,
input: &Self::Value,
ops: &'static impl DynamicOps<Value = T>,
prefix: T,
) -> DataResult<T> {
(self.validator)(input).map_or_else(
|error| DataResult::new_error(error),
|()| self.codec.encode(input, ops, prefix),
)
}
}
impl<C: Codec> Decoder for ValidatedCodec<C> {
fn decode<T: Display + PartialEq + Clone>(
&self,
input: T,
ops: &'static impl DynamicOps<Value = T>,
) -> DataResult<(Self::Value, T)> {
self.codec.decode(input, ops).flat_map(|decoded| {
(self.validator)(&decoded.0)
.map_or_else(DataResult::new_error, |()| DataResult::new_success(decoded))
})
}
}
/// Creates a new [`ValidatedCodec`].
pub(crate) const fn new_validated_codec<C: Codec>(
codec: &'static C,
validator: fn(&C::Value) -> Result<(), String>,
) -> ValidatedCodec<C> {
ValidatedCodec { codec, validator }
}
#[cfg(test)]
mod test {
use crate::assert_decode;
use crate::codec::*;
use crate::codecs::primitive::{IntCodec, StringCodec};
use crate::codecs::validated::ValidatedCodec;
use crate::coders::Decoder;
use crate::coders::Encoder;
use crate::json_ops;
use serde_json::json;
#[test]
fn even_int_validation() {
// An `int` codec that only accepts even numbers.
pub static EVEN_INT_CODEC: ValidatedCodec<IntCodec> = validate(&INT_CODEC, |value| {
if value % 2 == 0 {
Ok(())
} else {
Err(String::from("Not an even number"))
}
});
assert_eq!(
EVEN_INT_CODEC
.encode_start(&2, &json_ops::INSTANCE)
.expect("Encoding panicked"),
json!(2)
);
assert_eq!(
EVEN_INT_CODEC
.encode_start(&-56, &json_ops::INSTANCE)
.expect("Encoding panicked"),
json!(-56)
);
assert!(
EVEN_INT_CODEC
.encode_start(&-135, &json_ops::INSTANCE)
.is_error()
);
assert_decode!(EVEN_INT_CODEC, json!(0), &json_ops::INSTANCE, is_success);
assert_decode!(EVEN_INT_CODEC, json!(3456), &json_ops::INSTANCE, is_success);
assert_decode!(EVEN_INT_CODEC, json!(-12345), &json_ops::INSTANCE, is_error);
assert_decode!(EVEN_INT_CODEC, json!(153453), &json_ops::INSTANCE, is_error);
}
#[test]
fn player_name_validation() {
// A codec of a Minecraft player name, which has the following rules:
// - The length must be between 3-16 characters long.
// - They must only have alphanumeric characters and underscores.
pub static PLAYER_NAME_CODEC: ValidatedCodec<StringCodec> = validate(&STRING_CODEC, |s| {
if !(3..=16).contains(&s.len()) {
return Err(String::from(
"Player name must be between 3-16 characters long (inclusive)",
));
}
if !s.chars().all(|c| c.is_alphanumeric() || c == '_') {
return Err(String::from(
"Player name must only contain alphanumeric characters and underscores",
));
}
Ok(())
});
assert!(
PLAYER_NAME_CODEC
.encode_start(&String::from("Player"), &json_ops::INSTANCE)
.is_success()
);
assert!(
PLAYER_NAME_CODEC
.encode_start(&String::from("abcd1234"), &json_ops::INSTANCE)
.is_success()
);
assert!(
PLAYER_NAME_CODEC
.encode_start(&String::from("has some spaces"), &json_ops::INSTANCE)
.is_error()
);
assert!(
PLAYER_NAME_CODEC
.encode_start(&String::from("XxXxVeryLongNamexXxX"), &json_ops::INSTANCE)
.is_error()
);
assert!(
PLAYER_NAME_CODEC
.encode_start(&String::from("ILovePizza$"), &json_ops::INSTANCE)
.is_error()
);
assert_decode!(
PLAYER_NAME_CODEC,
json!("Pumpkin"),
&json_ops::INSTANCE,
is_success
);
assert_decode!(
PLAYER_NAME_CODEC,
json!("IGoByNoNames__"),
&json_ops::INSTANCE,
is_success
);
assert_decode!(
PLAYER_NAME_CODEC,
json!("#idk"),
&json_ops::INSTANCE,
is_error
);
}
}

View File

@@ -1,192 +0,0 @@
use crate::HasValue;
use crate::data_result::DataResult;
use crate::dynamic_ops::DynamicOps;
use crate::map_codecs::field_coders::{FieldDecoder, FieldEncoder};
use std::fmt::Display;
/// A trait describing the way to encode something of a type `Value` into something else (`Value -> ?`).
pub trait Encoder: HasValue {
/// Encodes an input of this encoder's type (`A`) into an output of type `T`,
/// along with the `prefix` (already encoded data).
fn encode<T: Display + PartialEq + Clone>(
&self,
input: &Self::Value,
ops: &'static impl DynamicOps<Value = T>,
prefix: T,
) -> DataResult<T>;
/// Encodes an input of this encoder's type (`A`) into an output of type `T`
/// with no prefix (no already encoded data).
fn encode_start<T: Display + PartialEq + Clone>(
&self,
input: &Self::Value,
ops: &'static impl DynamicOps<Value = T>,
) -> DataResult<T> {
self.encode(input, ops, ops.empty())
}
}
pub struct ComappedEncoderImpl<B, E: Encoder + 'static> {
encoder: &'static E,
function: fn(&B) -> E::Value,
}
impl<B, E: Encoder> HasValue for ComappedEncoderImpl<B, E> {
type Value = B;
}
impl<B, E: Encoder> Encoder for ComappedEncoderImpl<B, E> {
fn encode<T: Display + PartialEq + Clone>(
&self,
input: &Self::Value,
ops: &'static impl DynamicOps<Value = T>,
prefix: T,
) -> DataResult<T> {
self.encoder.encode(&(self.function)(input), ops, prefix)
}
}
/// Returns a *contramapped* (*comapped*) transformation of a provided [`Encoder`].
/// A *comapped* encoder transforms the input before encoding.
pub(crate) const fn comap<B, E: Encoder>(
encoder: &'static E,
f: fn(&B) -> E::Value,
) -> ComappedEncoderImpl<B, E> {
ComappedEncoderImpl {
encoder,
function: f,
}
}
pub struct FlatComappedEncoderImpl<B, E: Encoder + 'static> {
encoder: &'static E,
function: fn(&B) -> DataResult<E::Value>,
}
impl<B, E: Encoder> HasValue for FlatComappedEncoderImpl<B, E> {
type Value = B;
}
impl<B, E: Encoder> Encoder for FlatComappedEncoderImpl<B, E> {
fn encode<T: Display + PartialEq + Clone>(
&self,
input: &Self::Value,
ops: &'static impl DynamicOps<Value = T>,
prefix: T,
) -> DataResult<T> {
(self.function)(input).flat_map(|a| self.encoder.encode(&a, ops, prefix))
}
}
/// Returns a *flat contramapped* (*flat-comapped*) transformation of a provided [`Encoder`].
/// A *flat comapped* encoder transforms the input before encoding, but the transformation can fail.
pub(crate) const fn flat_comap<B, E: Encoder>(
encoder: &'static E,
f: fn(&B) -> DataResult<E::Value>,
) -> FlatComappedEncoderImpl<B, E> {
FlatComappedEncoderImpl {
encoder,
function: f,
}
}
pub(crate) const fn encoder_field<A, E: Encoder<Value = A>>(
name: &'static str,
encoder: &'static E,
) -> FieldEncoder<A, E> {
FieldEncoder::new(name, encoder)
}
/// A trait describing the way to decode something of some type to something of type `Value` (`? -> Value`).
pub trait Decoder: HasValue {
/// Decodes an input of this decoder's type (`A`) into an output of type `T`,
/// keeping the remaining undecoded data as another element of the tuple.
fn decode<T: Display + PartialEq + Clone>(
&self,
input: T,
ops: &'static impl DynamicOps<Value = T>,
) -> DataResult<(Self::Value, T)>;
/// Decodes an input of this decoder's type (`A`) into an output of type `T`,
/// discarding any remaining undecoded data.
fn parse<T: Display + PartialEq + Clone>(
&self,
input: T,
ops: &'static impl DynamicOps<Value = T>,
) -> DataResult<Self::Value> {
self.decode(input, ops).map(|r| r.0)
}
}
pub struct MappedDecoderImpl<B, D: Decoder + 'static> {
decoder: &'static D,
function: fn(D::Value) -> B,
}
impl<B, D: Decoder> HasValue for MappedDecoderImpl<B, D> {
type Value = B;
}
impl<B, D: Decoder> Decoder for MappedDecoderImpl<B, D> {
fn decode<T: Display + PartialEq + Clone>(
&self,
input: T,
ops: &'static impl DynamicOps<Value = T>,
) -> DataResult<(Self::Value, T)> {
self.decoder
.decode(input, ops)
.map(|(a, t)| ((self.function)(a), t))
}
}
/// Returns a *covariant mapped* transformation of a provided [`Decoder`].
/// A *mapped* decoder transforms the output after decoding.
pub(crate) const fn map<B, D: Decoder>(
decoder: &'static D,
f: fn(D::Value) -> B,
) -> MappedDecoderImpl<B, D> {
MappedDecoderImpl {
decoder,
function: f,
}
}
pub struct FlatMappedDecoderImpl<B, D: Decoder + 'static> {
decoder: &'static D,
function: fn(D::Value) -> DataResult<B>,
}
impl<B, D: Decoder> HasValue for FlatMappedDecoderImpl<B, D> {
type Value = B;
}
impl<B, D: Decoder> Decoder for FlatMappedDecoderImpl<B, D> {
fn decode<T: Display + PartialEq + Clone>(
&self,
input: T,
ops: &'static impl DynamicOps<Value = T>,
) -> DataResult<(Self::Value, T)> {
self.decoder
.decode(input, ops)
.flat_map(|(a, t)| (self.function)(a).map(|b| (b, t)))
}
}
/// Returns a *covariant flat-mapped* transformation of a provided [`Decoder`].
/// A *flat-mapped* decoder transforms the output after decoding, but the transformation can fail.
pub(crate) const fn flat_map<B, D: Decoder>(
decoder: &'static D,
f: fn(D::Value) -> DataResult<B>,
) -> FlatMappedDecoderImpl<B, D> {
FlatMappedDecoderImpl {
decoder,
function: f,
}
}
pub(crate) const fn decoder_field<A, D: Decoder<Value = A>>(
name: &'static str,
decoder: &'static D,
) -> FieldDecoder<A, D> {
FieldDecoder::new(name, decoder)
}

View File

@@ -628,8 +628,78 @@ macro_rules! assert_success {
}};
}
/// Asserts that encoding the left expression will lead to a complete result (success) whose stored result is `$right`.
#[macro_export]
macro_rules! assert_encode_success {
($left:expr, $ops:expr, $right:expr $(,)?) => {{
let result = $crate::codec::Encode::encode_start(&$left, &$ops);
assert!(
result.is_success(),
"Expected a `DataResult` success, got: {:?}",
result
);
assert_eq!(
result.unwrap(),
$right,
"`DataResult` was successful but the value doesn't match"
);
}};
}
/// Asserts that decoding the left expression will lead to a `DataResult` whose provided method returns `true`.
#[macro_export]
macro_rules! assert_decode {
($ty:ty, $input:expr, $ops:expr, $func:ident $(,)?) => {{
let result = <$ty as $crate::codec::Decode>::parse($input, &$ops);
assert!(
result.$func(),
concat!(
"Expected a `DataResult` that returns `true` for ",
stringify!($func),
", got: {:?}"
),
result
);
}};
}
impl<T> Default for DataResult<T> {
fn default() -> Self {
Self::new_error("Default DataResult")
}
}
/// A type conversion from one type to this type that may fail, resulting in a [`DataResult`].
///
/// Always prefer using [`FlatTryFrom`] over [`FlatTryInto`] for implementing the conversion,
/// as an implementation of [`FlatTryInto`] will automatically work as well.
pub trait FlatTryFrom<T>: Sized {
/// Performs the conversion.
fn flat_try_from(value: T) -> DataResult<Self>;
}
impl<T> FlatTryFrom<T> for T {
fn flat_try_from(value: T) -> DataResult<Self> {
DataResult::new_success(value)
}
}
impl<T, U> FlatTryInto<U> for T
where
U: FlatTryFrom<T>,
{
#[inline]
/// Calls `U::flat_try_from()`, which performs the conversion.
fn flat_try_into(self) -> DataResult<U> {
U::flat_try_from(self)
}
}
/// A type conversion from this type to another that may fail, resulting in a [`DataResult`].
///
/// Always prefer using [`FlatTryFrom`] over [`FlatTryInto`] for implementing the conversion,
/// as an implementation of [`FlatTryInto`] will automatically work as well.
pub trait FlatTryInto<T>: Sized {
/// Performs the conversion.
fn flat_try_into(self) -> DataResult<T>;
}

View File

@@ -248,11 +248,6 @@ pub trait DynamicOps {
/// This returns the new value if successful, otherwise, this returns itself.
fn remove(&self, input: Self::Value, key: &str) -> Self::Value;
/// Whether maps should be compressed under this `DynamicOps`.
fn compress_maps(&self) -> bool {
false
}
/// Tries to get a value from a value represented by this `DynamicOps` using a key.
/// Only works for values that can be [`MapLike`]-viewed.
fn get_element<'a>(&'a self, input: &'a Self::Value, key: &str) -> DataResult<&'a Self::Value> {

View File

@@ -9,18 +9,7 @@ use serde_json::{Map, Value};
use tracing::warn;
/// A [`DynamicOps`] to serialize to/deserialize from JSON data.
pub struct JsonOps {
compressed: bool,
}
/// A normal instance of [`JsonOps`], which serializes/deserializes normal JSON data.
pub static INSTANCE: JsonOps = JsonOps { compressed: false };
/// A normal instance of [`JsonOps`], which serializes/deserializes compressed JSON data.
///
/// *Compressed* JSON data is a little more lenient with placing values at places that expect something else.
/// This allows JSON to be compressed to a single string.
pub static COMPRESSED: JsonOps = JsonOps { compressed: true };
pub struct JsonOps;
impl JsonOps {
/// A function to get a JSON value as a string, similar to Google's GSON's `getAsString()` method for `JsonElement`.
@@ -53,14 +42,8 @@ impl JsonOps {
/// Whether a JSON value is considered to be a valid key.
///
/// If this returns `true`, it is safe to say that calling [`get_as_string`] with `input` will always return a [`Some`].
const fn is_valid_key(&self, input: &Value) -> bool {
// Normal mode: has to be a string.
// Compressed mode: can be any JSON primitive.
if self.compressed {
matches!(input, Value::String(_) | Value::Number(_) | Value::Bool(_))
} else {
matches!(input, Value::String(_))
}
const fn is_valid_key(input: &Value) -> bool {
matches!(input, Value::String(_))
}
}
@@ -112,35 +95,19 @@ impl DynamicOps for JsonOps {
}
fn get_number(&self, input: &Self::Value) -> DataResult<Number> {
match input {
Value::Number(_) => {
return input.try_into().map_or_else(
|_| DataResult::new_error(format!("Not a number: {input}")),
DataResult::new_success,
);
}
Value::String(string) if self.compressed => {
if let Ok(i) = string.parse::<i32>() {
return DataResult::new_success(Number::Int(i));
}
if let Ok(l) = string.parse::<i64>() {
return DataResult::new_success(Number::Long(l));
}
if let Ok(d) = string.parse::<f64>() {
return DataResult::new_success(Number::Double(d));
}
return DataResult::new_error(format!("Number could not be parsed: {string}"));
}
_ => {}
if let Value::Number(_) = input {
input.try_into().map_or_else(
|_| DataResult::new_error(format!("Not a number: {input}")),
DataResult::new_success,
)
} else {
DataResult::new_error(format!("Not a number: {input}"))
}
DataResult::new_error(format!("Not a number: {input}"))
}
fn get_string(&self, input: &Self::Value) -> DataResult<String> {
if matches!(input, Value::String(_))
|| (matches!(input, Value::Number(_)) && self.compressed)
{
// Unwrapping is fine as only strings and numbers are possible here.
if let Value::String(_) = input {
// Unwrapping is fine as only strings are possible here.
DataResult::new_success(Self::get_as_string(input).unwrap())
} else {
DataResult::new_error(format!("Not a string: {input}"))
@@ -224,7 +191,7 @@ impl DynamicOps for JsonOps {
return DataResult::new_partial_error(format!("Not a map: {map}"), map);
}
if !self.is_valid_key(&key) {
if !Self::is_valid_key(&key) {
return DataResult::new_partial_error(format!("Key is not a string: {key}"), map);
}
@@ -258,7 +225,7 @@ impl DynamicOps for JsonOps {
let mut missed = vec![];
for entry in other_map_like.iter() {
if self.is_valid_key(&entry.0) {
if Self::is_valid_key(&entry.0) {
output_map.insert(Self::get_as_string(&entry.0).unwrap(), entry.1.clone());
} else {
missed.push(entry.0);
@@ -291,10 +258,6 @@ impl DynamicOps for JsonOps {
}
}
fn compress_maps(&self) -> bool {
self.compressed
}
fn convert_to<U>(&self, out_ops: &impl DynamicOps<Value = U>, input: Self::Value) -> U {
match input {
Value::Null => out_ops.empty(),
@@ -407,7 +370,7 @@ impl StructBuilder for JsonStructBuilder {
type Value = Value;
impl_struct_builder!(builder);
impl_string_struct_builder!(builder, INSTANCE);
impl_string_struct_builder!(builder, JsonOps);
}
impl StringStructBuilder for JsonStructBuilder {

View File

@@ -1,78 +0,0 @@
use crate::dynamic_ops::DynamicOps;
use dashmap::DashMap;
use std::collections::HashMap;
use std::sync::{Arc, LazyLock};
/// A cache for all [`crate::map_coders::CompressorHolder`] structs.
///
/// This `HashMap` stores a `KeyCompressor` for each `MapCodec` instance.
/// This way, we don't have to use `OnceLock` in every `MapCodec`, so we can easily
/// capture their pointers while calling other functions without any destructor
/// compile-time errors.
pub(crate) static KEY_COMPRESSOR_CACHE: LazyLock<DashMap<usize, Arc<KeyCompressor>>> =
LazyLock::new(DashMap::new);
/// A struct to compress keys of a map by converting them to numbers (making a kind of list) and back.
pub struct KeyCompressor {
compress_map: HashMap<String, usize>,
decompress_map: HashMap<usize, String>,
size: usize,
}
impl KeyCompressor {
/// Returns a new `KeyCompressor`, which can be populated later via [`KeyCompressor::populate`].
///
pub(crate) fn new() -> Self {
Self {
compress_map: HashMap::new(),
decompress_map: HashMap::new(),
size: 0,
}
}
/// Populates a `KeyCompressor` with the calculated compressor and decompressor maps.
pub(crate) fn populate(&mut self, keys: impl IntoIterator<Item = String>) {
// Iterate over every key.
keys.into_iter().for_each(|key: String| {
if self.compress_map.contains_key(&key) {
return;
}
// The index that the key will correspond to.
let i = self.size;
self.compress_map.insert(key.clone(), i);
self.decompress_map.insert(i, key);
self.size += 1;
});
}
/// Gets the decompressed key of an index with the provided dynamic type.
pub fn decompress_key<T>(
&self,
key: usize,
ops: &'static impl DynamicOps<Value = T>,
) -> Option<T> {
self.decompress_map.get(&key).map(|s| ops.create_string(s))
}
/// Gets the compressed key of the provided dynamic type.
pub fn compress_key<T>(
&self,
key: &T,
ops: &'static impl DynamicOps<Value = T>,
) -> Option<usize> {
let string = ops.get_string(key).into_result()?;
self.compress_key_str(&string)
}
/// Gets the compressed key of a string value.
pub(crate) fn compress_key_str(&self, key: &str) -> Option<usize> {
self.compress_map.get(key).copied()
}
/// Returns the size of the compressed/decompressed maps.
#[must_use]
pub const fn size(&self) -> usize {
self.size
}
}

View File

@@ -1,6 +0,0 @@
/// A trait that specifies that an object can be represented with keys, like maps or `struct` types.
pub trait Keyable {
/// Returns a new copy of a [`Vec`] of the keys of this `Keyable`.
#[must_use]
fn keys(&self) -> Vec<String>;
}

View File

@@ -1,175 +1,26 @@
extern crate core;
use core::fmt;
use std::fmt::{Display, Formatter};
pub mod base_map_codec;
pub mod codec;
pub mod codecs;
pub mod coders;
pub mod data_result;
pub mod dynamic_ops;
mod data_result;
mod dynamic_ops;
pub mod json_ops;
pub mod key_compressor;
pub mod keyable;
pub mod lifecycle;
pub mod list_builder;
pub mod map_codec;
pub mod map_codecs;
pub mod map_coders;
pub mod map_like;
mod lifecycle;
mod list_builder;
mod map_like;
pub mod struct_builder;
pub mod struct_codecs;
/// A trait specifying a single type.
/// This is used to prevent type conflicts for `Codec`s and `MapCodec`s implementing an encoder and decoder.
pub trait HasValue {
type Value;
}
pub mod codec;
mod number;
/// Represents a generic number in Java.
pub enum Number {
Byte(i8),
Short(i16),
Int(i32),
Long(i64),
Float(f32),
Double(f64),
}
pub use crate::data_result::DataResult;
pub use crate::data_result::FlatTryFrom;
pub use crate::data_result::FlatTryInto;
pub use crate::dynamic_ops::DynamicOps;
pub use crate::lifecycle::Lifecycle;
pub use crate::list_builder::ListBuilder;
pub use crate::map_like::MapLike;
pub use number::Number;
impl From<Number> for i64 {
fn from(num: Number) -> Self {
match num {
Number::Byte(b) => b as Self,
Number::Short(s) => s as Self,
Number::Int(i) => i as Self,
Number::Long(l) => l,
Number::Float(f) => f as Self,
Number::Double(d) => d as Self,
}
}
}
pub use crate::codec::Decode;
pub use crate::codec::Encode;
impl From<Number> for i32 {
fn from(num: Number) -> Self {
match num {
Number::Byte(b) => b as Self,
Number::Short(s) => s as Self,
Number::Int(i) => i,
Number::Long(l) => l as Self,
Number::Float(f) => f as Self,
Number::Double(d) => d as Self,
}
}
}
impl From<Number> for i16 {
fn from(num: Number) -> Self {
// Similar to Java, we will first convert the number to an `i16`, and then to an `i8`.
i32::from(num) as Self
}
}
impl From<Number> for i8 {
fn from(num: Number) -> Self {
// Similar to Java, we will first convert the number to an `i32`, and then to an `i8`.
i32::from(num) as Self
}
}
impl From<Number> for u8 {
fn from(num: Number) -> Self {
i32::from(num) as Self
}
}
impl From<Number> for f32 {
fn from(num: Number) -> Self {
match num {
Number::Byte(b) => b as Self,
Number::Short(s) => s as Self,
Number::Int(i) => i as Self,
Number::Long(l) => l as Self,
Number::Float(f) => f,
Number::Double(d) => d as Self,
}
}
}
impl From<Number> for f64 {
fn from(num: Number) -> Self {
match num {
Number::Byte(b) => b as Self,
Number::Short(s) => s as Self,
Number::Int(i) => i as Self,
Number::Long(l) => l as Self,
Number::Float(f) => f as Self,
Number::Double(d) => d,
}
}
}
impl Display for Number {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::Byte(v) => write!(f, "{v}"),
Self::Short(v) => write!(f, "{v}"),
Self::Int(v) => write!(f, "{v}"),
Self::Long(v) => write!(f, "{v}"),
Self::Float(v) => write!(f, "{v}"),
Self::Double(v) => write!(f, "{v}"),
}
}
}
impl From<Number> for serde_json::Value {
fn from(num: Number) -> Self {
match num {
Number::Byte(n) => n.into(),
Number::Short(n) => n.into(),
Number::Int(n) => n.into(),
Number::Long(n) => n.into(),
Number::Float(n) => n.into(),
Number::Double(n) => n.into(),
}
}
}
/// An error struct returned for an invalid conversion to [`Number`] from a [`serde_json::Value`].
pub struct FromJsonValueError;
impl TryFrom<&serde_json::Value> for Number {
type Error = FromJsonValueError;
fn try_from(num: &serde_json::Value) -> Result<Self, Self::Error> {
num.clone().try_into()
}
}
impl TryFrom<serde_json::Value> for Number {
type Error = FromJsonValueError;
fn try_from(num: serde_json::Value) -> Result<Self, Self::Error> {
match num {
serde_json::Value::Number(n) => n.try_into(),
_ => Err(FromJsonValueError),
}
}
}
impl TryFrom<serde_json::Number> for Number {
type Error = FromJsonValueError;
fn try_from(num: serde_json::Number) -> Result<Self, Self::Error> {
// Try converting the number to an integer first.
num.as_i64().map_or_else(
// Try the float conversion.
|| {
num.as_f64()
.map_or(Err(FromJsonValueError), |f| Ok(Self::Double(f)))
},
// Do the integer conversion.
|n| Ok(Self::Long(n)),
)
}
}
pub use crate::codec::primitive::ByteBuffer;
pub use crate::codec::primitive::IntStream;
pub use crate::codec::primitive::LongStream;

View File

@@ -1,257 +0,0 @@
use crate::HasValue;
use crate::data_result::DataResult;
use crate::dynamic_ops::DynamicOps;
use crate::key_compressor::KeyCompressor;
use crate::keyable::Keyable;
use crate::lifecycle::Lifecycle;
use crate::map_codecs::either::{EitherMapCodec, new_either_map_codec};
use crate::map_codecs::validated::{ValidatedMapCodec, new_validated_map_codec};
use crate::map_coders::{
ComappedMapEncoderImpl, CompressorHolder, FlatComappedMapEncoderImpl, FlatMappedMapDecoderImpl,
MapDecoder, MapEncoder, MappedMapDecoderImpl, comap, flat_comap, flat_map, map,
};
use crate::map_like::MapLike;
use crate::struct_builder::StructBuilder;
use crate::struct_codecs::Field;
use std::fmt::Display;
use std::sync::Arc;
/// A type of *codec* which encodes/decodes fields of a map.
///
/// The number of keys a `MapCodec` can work with can be one or many keys.
///
/// **This is functionally different from [`Codec`].**
/// The main difference is that while a `Codec` works on encoding/decoding values, a `MapCodec`
/// works on a [`MapLike`].
///
/// # Using Map Codecs
/// They can be used in struct codecs as one part of a struct.
/// **Just like codecs, map codecs are also meant to be static instances, and they should not be created at runtime.
/// They are also immutable, which means they cannot be modified after they are created.**
///
/// # Creating Map Codecs
/// There are a few ways to create map codecs.
///
/// ## Field Map Codecs
/// These are the most commonly used map codecs. The `codec` module has methods for creating them with a `Codec` instance:
/// - [`field`]: For required fields.
/// - [`optional_field`] and [`lenient_optional_field`]: For optional fields encoding/decoding an [`Option`] type.
/// - [`optional_field_with_default`] and [`lenient_optional_field_with_default`]:
/// For optional fields which have a default value for when no value is found while decoding.
///
/// ## Either
/// Use [`either`] to create an [`EitherMapCodec`] that can use one of two provided codecs to serialize/deserialize
/// an [`Either`].
///
/// # Transformers
/// A map codec of a type `B` can be implemented by *transforming* another codec of type `A` to work with type `B`,
/// similar to a `Codec`.
/// The following methods can be used depending on the equivalence relation between the two types:
/// - [`xmap`]
/// - [`flat_xmap`]
///
/// # Validator Map Codecs
/// The [`validate`] function returns a codec wrapper that validates a value before encoding and after decoding.
/// A validated codec takes a function that can either return an [`Ok`] for a success,
/// or an [`Err`] with the provided message to place in a `DataResult`.
///
/// [`Codec`]: super::codec::Codec
/// [`field`]: super::codec::field
/// [`optional_field`]: super::codec::optional_field
/// [`lenient_optional_field`]: super::codec::lenient_optional_field
/// [`optional_field_with_default`]: super::codec::optional_field_with_default
/// [`lenient_optional_field_with_default`]: super::codec::lenient_optional_field_with_default
///
/// [`Either`]: crate::util::either::Either
pub trait MapCodec: MapEncoder + MapDecoder {}
// Any struct implementing MapEncoder<Value = A> and MapDecoder<Value = A> will also implement MapCodec<Value = A>.
impl<T> MapCodec for T where T: MapEncoder + MapDecoder {}
/// A map codec allowing an arbitrary encoder and decoder.
pub struct ComposedMapCodec<E: MapEncoder + 'static, D: MapDecoder<Value = E::Value> + 'static> {
pub(crate) encoder: E,
pub(crate) decoder: D,
}
impl<E: MapEncoder, D: MapDecoder<Value = E::Value>> HasValue for ComposedMapCodec<E, D> {
type Value = E::Value;
}
impl<E: MapEncoder, D: MapDecoder<Value = E::Value>> Keyable for ComposedMapCodec<E, D> {
fn keys(&self) -> Vec<String> {
let mut vec = self.encoder.keys();
vec.extend(self.decoder.keys());
vec
}
}
impl<E: MapEncoder, D: MapDecoder<Value = E::Value>> CompressorHolder for ComposedMapCodec<E, D> {
fn compressor(&self) -> Arc<KeyCompressor> {
// This could return either the encoder or decoder's compressor, but we'll stick with the encoder's.
self.encoder.compressor()
}
}
impl<E: MapEncoder, D: MapDecoder<Value = E::Value>> MapEncoder for ComposedMapCodec<E, D> {
fn encode<T: Display + PartialEq + Clone, B: StructBuilder<Value = T>>(
&self,
input: &Self::Value,
ops: &'static impl DynamicOps<Value = T>,
prefix: B,
) -> B {
self.encoder.encode(input, ops, prefix)
}
}
impl<E: MapEncoder, D: MapDecoder<Value = E::Value>> MapDecoder for ComposedMapCodec<E, D> {
fn decode<T: Display + PartialEq + Clone>(
&self,
input: &impl MapLike<Value = T>,
ops: &'static impl DynamicOps<Value = T>,
) -> DataResult<Self::Value> {
self.decoder.decode(input, ops)
}
}
/// Wraps a [`MapCodec`] to make its [`DataResult`]s stable.
pub struct StableMapCodec<C: MapCodec> {
map_codec: C,
}
impl<C: MapCodec> HasValue for StableMapCodec<C> {
type Value = C::Value;
}
impl<C: MapCodec> Keyable for StableMapCodec<C> {
fn keys(&self) -> Vec<String> {
self.map_codec.keys()
}
}
impl<C: MapCodec> CompressorHolder for StableMapCodec<C> {
fn compressor(&self) -> Arc<KeyCompressor> {
self.map_codec.compressor()
}
}
impl<C: MapCodec> MapEncoder for StableMapCodec<C> {
fn encode<T: Display + PartialEq + Clone, B: StructBuilder<Value = T>>(
&self,
input: &Self::Value,
ops: &'static impl DynamicOps<Value = T>,
prefix: B,
) -> B {
self.map_codec
.encode(input, ops, prefix)
.set_lifecycle(Lifecycle::Stable)
}
}
impl<C: MapCodec> MapDecoder for StableMapCodec<C> {
fn decode<T: Display + PartialEq + Clone>(
&self,
input: &impl MapLike<Value = T>,
ops: &'static impl DynamicOps<Value = T>,
) -> DataResult<Self::Value> {
self.map_codec
.decode(input, ops)
.with_lifecycle(Lifecycle::Stable)
}
}
/// Returns a [`Field`] with the provided owned [`MapCodec`] and a getter,
/// which tells the field how to get a part of a struct to serialize.
pub const fn for_getter<T, C: MapCodec + 'static>(
map_codec: C,
getter: fn(&T) -> &C::Value,
) -> Field<T, C> {
Field::Owned(map_codec, getter)
}
/// Returns a [`Field`] with the provided [`MapCodec`] reference and a getter,
/// which tells the field how to get a part of a struct to serialize.
pub const fn for_getter_ref<T, C: MapCodec>(
map_codec: &'static C,
getter: fn(&T) -> &C::Value,
) -> Field<T, C> {
Field::Borrowed(map_codec, getter)
}
/// Returns another [`MapCodec`] of a provided `MapCodec` which provides [`DataResult`]s of the wrapped `map_codec`,
/// but always sets their lifecycle to [`Lifecycle::Stable`].
pub const fn stable<C: MapCodec>(map_codec: C) -> StableMapCodec<C> {
StableMapCodec { map_codec }
}
/// Helper macro to generate the shorthand types and functions of the transformer [`MapCodec`] methods.
macro_rules! make_map_codec_transformation_function {
($name:ident, $short_type:ident, $encoder_type:ident, $decoder_type:ident, $encoder_func:ident, $decoder_func:ident, $to_func_result:ty, $from_func_result:ty, $a_equivalency:literal, $s_equivalency:literal) => {
pub type $short_type<S, C> = ComposedMapCodec<$encoder_type<S, C>, $decoder_type<S, C>>;
#[doc = "Transforms a [`MapCodec`] of type `A` to another [`MapCodec`] of type `S`."]
///
/// - `to` is the function called on `A` after decoding to convert it to `S`.
/// - `from` is the function called on `S` before encoding to convert it to `A`.
///
/// Use this if:
#[doc = concat!("- `A` is **", $a_equivalency, "** to `S`.")]
#[doc = concat!("- `S` is **", $s_equivalency, "** to `A`.")]
#[doc = ""]
#[doc = "A type `A` is *fully equivalent* to `B` if *A can always successfully be converted to B*."]
pub const fn $name<A, C: MapCodec<Value = A>, S>(map_codec: &'static C, to: fn(A) -> $to_func_result, from: fn(&S) -> $from_func_result) -> $short_type<S, C> {
ComposedMapCodec {
encoder: $encoder_func(map_codec, from),
decoder: $decoder_func(map_codec, to)
}
}
};
}
make_map_codec_transformation_function!(
xmap,
XmapMapCodec,
ComappedMapEncoderImpl,
MappedMapDecoderImpl,
comap,
map,
S,
A,
"equivalent",
"equivalent"
);
make_map_codec_transformation_function!(
flat_xmap,
FlatXmapMapCodec,
FlatComappedMapEncoderImpl,
FlatMappedMapDecoderImpl,
flat_comap,
flat_map,
DataResult<S>,
DataResult<A>,
"partially equivalent",
"partially equivalent"
);
/// Returns a transformer map codec that validates a value before encoding and after decoding by calling a function,
/// which provides a [`DataResult`] depending on that value's validity.
///
/// `validator` is a function that takes the pointer of a value and returns a [`Result`].
/// - If the returned result is an [`Ok`], the codec works as normal.
/// - Otherwise, it always returns a non-result with the message [`String`].
pub const fn validate<C: MapCodec>(
codec: &'static C,
validator: fn(&C::Value) -> Result<(), String>,
) -> ValidatedMapCodec<C> {
new_validated_map_codec(codec, validator)
}
/// Creates an [`EitherMapCodec`] with the provided left and right codecs to tell the way to serialize/deserialize
/// their respective types.
pub const fn either<L: MapCodec, R: MapCodec>(
left_codec: &'static L,
right_codec: &'static R,
) -> EitherMapCodec<L, R> {
new_either_map_codec(left_codec, right_codec)
}

View File

@@ -1,80 +0,0 @@
use crate::HasValue;
use crate::data_result::DataResult;
use crate::dynamic_ops::DynamicOps;
use crate::impl_compressor;
use crate::key_compressor::KeyCompressor;
use crate::keyable::Keyable;
use crate::map_codec::MapCodec;
use crate::map_coders::{CompressorHolder, MapDecoder, MapEncoder};
use crate::map_like::MapLike;
use crate::struct_builder::StructBuilder;
use either::Either;
use std::fmt::Display;
/// A [`MapCodec`] that can serialize/deserialize one of two types, with a map codec for each one.
///
/// This evaluates the left map codec first, and if the [`DataResult`] for it is invalid,
/// it evaluates the right map codec.
pub struct EitherMapCodec<L: MapCodec + 'static, R: MapCodec + 'static> {
left_codec: &'static L,
right_codec: &'static R,
}
impl<L: MapCodec, R: MapCodec> HasValue for EitherMapCodec<L, R> {
type Value = Either<L::Value, R::Value>;
}
impl<L: MapCodec, R: MapCodec> Keyable for EitherMapCodec<L, R> {
fn keys(&self) -> Vec<String> {
let mut keys = self.left_codec.keys();
keys.extend(self.right_codec.keys());
keys
}
}
impl<L: MapCodec, R: MapCodec> CompressorHolder for EitherMapCodec<L, R> {
impl_compressor!();
}
impl<L: MapCodec, R: MapCodec> MapEncoder for EitherMapCodec<L, R> {
fn encode<T: Display + PartialEq + Clone, B: StructBuilder<Value = T>>(
&self,
input: &Self::Value,
ops: &'static impl DynamicOps<Value = T>,
prefix: B,
) -> B {
match &input {
Either::Left(l) => self.left_codec.encode(l, ops, prefix),
Either::Right(r) => self.right_codec.encode(r, ops, prefix),
}
}
}
impl<L: MapCodec, R: MapCodec> MapDecoder for EitherMapCodec<L, R> {
fn decode<T: Display + PartialEq + Clone>(
&self,
input: &impl MapLike<Value = T>,
ops: &'static impl DynamicOps<Value = T>,
) -> DataResult<Self::Value> {
let left = self.left_codec.decode(input, ops).map(Either::Left);
if left.is_success() {
return left;
}
let right = self.right_codec.decode(input, ops).map(Either::Right);
if right.is_success() {
return right;
}
left.apply_2(|_, r| r, right)
}
}
/// Creates a new `EitherMapCodec` with the provided left and right codecs for serializing/deserializing both possible types.
pub(crate) const fn new_either_map_codec<L: MapCodec, R: MapCodec>(
left_codec: &'static L,
right_codec: &'static R,
) -> EitherMapCodec<L, R> {
EitherMapCodec {
left_codec,
right_codec,
}
}

View File

@@ -1,103 +0,0 @@
use crate::HasValue;
use crate::coders::{Decoder, Encoder};
use crate::data_result::DataResult;
use crate::dynamic_ops::DynamicOps;
use crate::impl_compressor;
use crate::key_compressor::KeyCompressor;
use crate::keyable::Keyable;
use crate::map_coders::{CompressorHolder, MapDecoder, MapEncoder};
use crate::map_like::MapLike;
use crate::struct_builder::StructBuilder;
use std::fmt::Display;
/// A [`MapEncoder`] that knows how to encode an entire field (key + value), where the value is encoded by an [`Encoder`].
///
/// `A` is the type of value encoded.
pub struct FieldEncoder<A, E: Encoder<Value = A> + 'static> {
/// The name of the key.
name: &'static str,
/// The [`Encoder`] for encoding the value.
element_encoder: &'static E,
}
impl<A, E: Encoder<Value = A>> HasValue for FieldEncoder<A, E> {
type Value = A;
}
impl<A, E: Encoder<Value = A>> Keyable for FieldEncoder<A, E> {
fn keys(&self) -> Vec<String> {
vec![self.name.to_string()]
}
}
impl<A, E: Encoder<Value = A>> CompressorHolder for FieldEncoder<A, E> {
impl_compressor!();
}
impl<A, E: Encoder<Value = A>> MapEncoder for FieldEncoder<A, E> {
fn encode<T: Display + PartialEq + Clone, B: StructBuilder<Value = T>>(
&self,
input: &Self::Value,
ops: &'static impl DynamicOps<Value = T>,
prefix: B,
) -> B {
prefix.add_string_key_value_result(self.name, self.element_encoder.encode_start(input, ops))
}
}
impl<A, E: Encoder<Value = A>> FieldEncoder<A, E> {
/// Returns a new [`FieldEncoder`] with the provided name and [`Encoder`].
pub(crate) const fn new(name: &'static str, element_encoder: &'static E) -> Self {
Self {
name,
element_encoder,
}
}
}
/// A [`MapDecoder`] that knows how to decode an entire field (key + value), where the value is encoded by a [`Decoder`].
///
/// `A` is the type of value that the decoder can decode to.
pub struct FieldDecoder<A, D: Decoder<Value = A> + 'static> {
/// The name of the key.
name: &'static str,
/// The [`Decoder`] for encoding the value.
element_decoder: &'static D,
}
impl<A, D: Decoder<Value = A>> HasValue for FieldDecoder<A, D> {
type Value = A;
}
impl<A, D: Decoder<Value = A>> Keyable for FieldDecoder<A, D> {
fn keys(&self) -> Vec<String> {
vec![self.name.to_string()]
}
}
impl<A, D: Decoder<Value = A>> CompressorHolder for FieldDecoder<A, D> {
impl_compressor!();
}
impl<A, D: Decoder<Value = A>> MapDecoder for FieldDecoder<A, D> {
fn decode<T: Display + PartialEq + Clone>(
&self,
input: &impl MapLike<Value = T>,
ops: &'static impl DynamicOps<Value = T>,
) -> DataResult<Self::Value> {
input.get_str(self.name).map_or_else(
|| DataResult::new_error(format!("No key {} in map", self.name)),
|v| self.element_decoder.parse(v.clone(), ops),
)
}
}
impl<A, D: Decoder<Value = A>> FieldDecoder<A, D> {
/// Returns a new [`FieldDecoder`] with the provided name and [`Decoder`].
pub(crate) const fn new(name: &'static str, element_decoder: &'static D) -> Self {
Self {
name,
element_decoder,
}
}
}

View File

@@ -1,5 +0,0 @@
pub mod either;
pub mod field_coders;
pub mod optional_field;
pub mod simple;
pub mod validated;

View File

@@ -1,168 +0,0 @@
use crate::HasValue;
use crate::codec::Codec;
use crate::data_result::DataResult;
use crate::dynamic_ops::DynamicOps;
use crate::impl_compressor;
use crate::key_compressor::KeyCompressor;
use crate::keyable::Keyable;
use crate::map_codec::MapCodec;
use crate::map_coders::{CompressorHolder, MapDecoder, MapEncoder};
use crate::map_like::MapLike;
use crate::struct_builder::StructBuilder;
use std::fmt::Display;
use std::sync::Arc;
/// A [`MapCodec`] that describes an optional field.
pub struct OptionalFieldMapCodec<C: Codec + 'static> {
element_codec: &'static C,
name: &'static str,
/// Whether this field should give a complete result for an
/// error result (partial or no result) of the underlying codec.
lenient: bool,
}
impl<C: Codec> HasValue for OptionalFieldMapCodec<C> {
// The type of this `MapCodec` should be an `Option`.
type Value = Option<C::Value>;
}
impl<C: Codec> Keyable for OptionalFieldMapCodec<C> {
fn keys(&self) -> Vec<String> {
vec![self.name.to_string()]
}
}
impl<C: Codec> CompressorHolder for OptionalFieldMapCodec<C> {
impl_compressor!();
}
impl<C: Codec> MapEncoder for OptionalFieldMapCodec<C> {
fn encode<T: Display + PartialEq + Clone, B: StructBuilder<Value = T>>(
&self,
input: &Self::Value,
ops: &'static impl DynamicOps<Value = T>,
prefix: B,
) -> B {
if let Some(input) = input.as_ref() {
prefix
.add_string_key_value_result(self.name, self.element_codec.encode_start(input, ops))
} else {
prefix
}
}
}
impl<C: Codec> MapDecoder for OptionalFieldMapCodec<C> {
fn decode<T: Display + PartialEq + Clone>(
&self,
input: &impl MapLike<Value = T>,
ops: &'static impl DynamicOps<Value = T>,
) -> DataResult<Self::Value> {
input.get_str(self.name).map_or_else(
|| DataResult::new_success(None),
|value| {
let result = self.element_codec.parse(value.clone(), ops);
if result.is_error() && self.lenient {
DataResult::new_success(None)
} else {
result.map(Some)
}
},
)
}
}
/// A wrapper around a [`MapCodec`] returning an [`Option`] type that
/// can provide a default value to transform the `MapCodec` type into its non-`Option` type.
pub struct DefaultValueProviderMapCodec<
T: PartialEq + Clone,
C: MapCodec<Value = Option<T>> + 'static,
> {
codec: C,
default: fn() -> T,
}
impl<T: PartialEq + Clone, C: MapCodec<Value = Option<T>>> HasValue
for DefaultValueProviderMapCodec<T, C>
{
type Value = T;
}
impl<T: PartialEq + Clone, C: MapCodec<Value = Option<T>>> Keyable
for DefaultValueProviderMapCodec<T, C>
{
fn keys(&self) -> Vec<String> {
self.codec.keys()
}
}
impl<T: PartialEq + Clone, C: MapCodec<Value = Option<T>>> CompressorHolder
for DefaultValueProviderMapCodec<T, C>
{
fn compressor(&self) -> Arc<KeyCompressor> {
self.codec.compressor()
}
}
impl<T: PartialEq + Clone, C: MapCodec<Value = Option<T>>> MapEncoder
for DefaultValueProviderMapCodec<T, C>
{
fn encode<U: Display + PartialEq + Clone, B: StructBuilder<Value = U>>(
&self,
input: &Self::Value,
ops: &'static impl DynamicOps<Value = U>,
prefix: B,
) -> B {
let clone = Some(input.clone());
self.codec.encode(
if *input == (self.default)() {
&None
} else {
&clone
},
ops,
prefix,
)
}
}
impl<T: PartialEq + Clone, C: MapCodec<Value = Option<T>>> MapDecoder
for DefaultValueProviderMapCodec<T, C>
{
fn decode<U: Display + PartialEq + Clone>(
&self,
input: &impl MapLike<Value = U>,
ops: &'static impl DynamicOps<Value = U>,
) -> DataResult<Self::Value> {
self.codec
.decode(input, ops)
.map(|value| value.unwrap_or_else(self.default))
}
}
/// Returns a new [`DefaultValueProviderMapCodec`] with the provided [`Option`] [`MapCodec`] and a default value factory.
pub(crate) const fn new_default_value_provider_map_codec<
T: PartialEq + Clone,
C: MapCodec<Value = Option<T>>,
>(
map_codec: C,
default: fn() -> T,
) -> DefaultValueProviderMapCodec<T, C> {
DefaultValueProviderMapCodec {
codec: map_codec,
default,
}
}
/// Returns a new [`OptionalFieldMapCodec`].
pub(crate) const fn new_optional_field_map_codec<C: Codec>(
element_codec: &'static C,
name: &'static str,
lenient: bool,
) -> OptionalFieldMapCodec<C> {
OptionalFieldMapCodec {
element_codec,
name,
lenient,
}
}

View File

@@ -1,71 +0,0 @@
use crate::HasValue;
use crate::base_map_codec::BaseMapCodec;
use crate::codec::Codec;
use crate::key_compressor::KeyCompressor;
use crate::keyable::Keyable;
use crate::map_coders::CompressorHolder;
use std::fmt::Display;
use crate::impl_compressor;
use std::hash::Hash;
/// A simple [`MapCodec`] implementation of [`BaseMapCodec`].
/// This codec has a fixed set of keys.
pub struct SimpleMapCodec<K: Codec + 'static, V: Codec + 'static, Key: Keyable>
where
K::Value: Display + Eq + Hash,
{
key_codec: &'static K,
element_codec: &'static V,
keyable: Key,
}
impl<K: Codec, V: Codec, Key: Keyable> Keyable for SimpleMapCodec<K, V, Key>
where
K::Value: Display + Eq + Hash,
{
fn keys(&self) -> Vec<String> {
self.keyable.keys()
}
}
impl<K: Codec, V: Codec, Key: Keyable> CompressorHolder for SimpleMapCodec<K, V, Key>
where
K::Value: Display + Eq + Hash,
{
impl_compressor!();
}
impl<K: Codec, V: Codec, Key: Keyable> BaseMapCodec for SimpleMapCodec<K, V, Key>
where
K::Value: Display + Eq + Hash,
{
type Key = K::Value;
type KeyCodec = K;
type Element = V::Value;
type ElementCodec = V;
fn key_codec(&self) -> &'static Self::KeyCodec {
self.key_codec
}
fn element_codec(&self) -> &'static Self::ElementCodec {
self.element_codec
}
}
pub(crate) const fn new_simple_map_codec<K: Codec, V: Codec, Key: Keyable>(
key_codec: &'static K,
element_codec: &'static V,
keyable: Key,
) -> SimpleMapCodec<K, V, Key>
where
<K as HasValue>::Value: Display + Eq + Hash,
{
SimpleMapCodec {
key_codec,
element_codec,
keyable,
}
}

View File

@@ -1,71 +0,0 @@
use crate::HasValue;
use crate::data_result::DataResult;
use crate::dynamic_ops::DynamicOps;
use crate::key_compressor::KeyCompressor;
use crate::keyable::Keyable;
use crate::map_codec::MapCodec;
use crate::map_coders::{CompressorHolder, MapDecoder, MapEncoder};
use crate::map_like::MapLike;
use crate::struct_builder::StructBuilder;
use std::fmt::Display;
use std::sync::Arc;
/// A validator [`MapCodec`] that validates any values before encoding and after decoding.
pub struct ValidatedMapCodec<C: MapCodec + 'static> {
codec: &'static C,
/// The validator function used.
validator: fn(&C::Value) -> Result<(), String>,
}
impl<C: MapCodec> HasValue for ValidatedMapCodec<C> {
type Value = C::Value;
}
impl<C: MapCodec> Keyable for ValidatedMapCodec<C> {
fn keys(&self) -> Vec<String> {
self.codec.keys()
}
}
impl<C: MapCodec> CompressorHolder for ValidatedMapCodec<C> {
fn compressor(&self) -> Arc<KeyCompressor> {
self.codec.compressor()
}
}
impl<C: MapCodec> MapEncoder for ValidatedMapCodec<C> {
fn encode<T: Display + PartialEq + Clone, B: StructBuilder<Value = T>>(
&self,
input: &Self::Value,
ops: &'static impl DynamicOps<Value = T>,
prefix: B,
) -> B {
match (self.validator)(input) {
Ok(()) => self.codec.encode(input, ops, prefix),
Err(s) => prefix.with_errors_from(&DataResult::<()>::new_error(s)),
}
}
}
impl<C: MapCodec> MapDecoder for ValidatedMapCodec<C> {
fn decode<T: Display + PartialEq + Clone>(
&self,
input: &impl MapLike<Value = T>,
ops: &'static impl DynamicOps<Value = T>,
) -> DataResult<Self::Value> {
let result = self.codec.decode(input, ops);
if let Some(v) = result.result_or_partial_as_ref() {
(self.validator)(v).map_or_else(DataResult::new_error, |()| result)
} else {
result
}
}
}
/// Creates a new [`ValidatedMapCodec`].
pub(crate) const fn new_validated_map_codec<C: MapCodec>(
codec: &'static C,
validator: fn(&C::Value) -> Result<(), String>,
) -> ValidatedMapCodec<C> {
ValidatedMapCodec { codec, validator }
}

View File

@@ -1,416 +0,0 @@
use crate::HasValue;
use crate::data_result::DataResult;
use crate::dynamic_ops::DynamicOps;
use crate::key_compressor::KeyCompressor;
use crate::keyable::Keyable;
use crate::lifecycle::Lifecycle;
use crate::map_like::MapLike;
use crate::struct_builder::{
MapBuilder, ResultStructBuilder, StructBuilder, UniversalStructBuilder,
};
use crate::{impl_struct_builder, impl_universal_struct_builder};
use std::fmt::Display;
use std::sync::Arc;
/// A [`StructBuilder`] for compressed map data.
pub struct CompressedStructBuilder<'a, T, O: DynamicOps<Value = T> + 'static> {
builder: DataResult<Vec<T>>,
ops: &'static O,
compressor: &'a KeyCompressor,
}
impl<'a, T: Clone, O: DynamicOps<Value = T> + 'static> CompressedStructBuilder<'a, T, O> {
#[expect(dead_code)]
pub(crate) const fn new(ops: &'static O, compressor: &'a KeyCompressor) -> Self {
Self {
builder: DataResult::new_success_with_lifecycle(vec![], Lifecycle::Stable),
ops,
compressor,
}
}
}
impl<T: Clone, O: DynamicOps<Value = T>> StructBuilder for CompressedStructBuilder<'_, T, O> {
type Value = T;
impl_struct_builder!(builder);
impl_universal_struct_builder!(builder, self.ops);
}
impl<T: Clone, O: DynamicOps<Value = T>> ResultStructBuilder for CompressedStructBuilder<'_, T, O> {
type Result = Vec<T>;
fn build_with_builder(
self,
builder: Self::Result,
prefix: Self::Value,
) -> DataResult<Self::Value> {
self.ops.merge_values_into_list(prefix, builder)
}
}
impl<T: Clone, O: DynamicOps<Value = T>> UniversalStructBuilder
for CompressedStructBuilder<'_, T, O>
{
fn append(
&self,
key: Self::Value,
value: Self::Value,
mut builder: Self::Result,
) -> Self::Result {
if let Some(i) = self.compressor.compress_key(&key, self.ops) {
builder[i] = value;
}
builder
}
}
/// A [`StructBuilder`] that could be compressed or uncompressed.
pub enum EncoderStructBuilder<T, O: DynamicOps<Value = T> + 'static> {
Normal(O::StructBuilder),
Compressed(MapBuilder<T, O>),
}
/// Outsources a function of [`EncoderStructBuilder`] to call the inner builder's method.
macro_rules! delegate_encoder_struct_builder_method {
($target:ident, $name:ident $(, $args:expr)*) => {
match $target {
Self::Normal(b) => Self::Normal(b.$name($($args),*)),
Self::Compressed(b) => Self::Compressed(b.$name($($args),*)),
}
};
}
impl<T: Clone, O: DynamicOps<Value = T>> StructBuilder for EncoderStructBuilder<T, O> {
type Value = T;
fn add_key_value(self, key: Self::Value, value: Self::Value) -> Self {
delegate_encoder_struct_builder_method!(self, add_key_value, key, value)
}
fn add_key_value_result(self, key: Self::Value, value: DataResult<Self::Value>) -> Self {
delegate_encoder_struct_builder_method!(self, add_key_value_result, key, value)
}
fn add_key_result_value_result(
self,
key: DataResult<Self::Value>,
value: DataResult<Self::Value>,
) -> Self {
delegate_encoder_struct_builder_method!(self, add_key_result_value_result, key, value)
}
fn with_errors_from<U>(self, result: &DataResult<U>) -> Self {
delegate_encoder_struct_builder_method!(self, with_errors_from, result)
}
fn add_string_key_value(self, key: &str, value: Self::Value) -> Self {
delegate_encoder_struct_builder_method!(self, add_string_key_value, key, value)
}
fn add_string_key_value_result(self, key: &str, value: DataResult<Self::Value>) -> Self {
delegate_encoder_struct_builder_method!(self, add_string_key_value_result, key, value)
}
fn set_lifecycle(self, lifecycle: Lifecycle) -> Self {
delegate_encoder_struct_builder_method!(self, set_lifecycle, lifecycle)
}
fn map_error(self, f: impl FnOnce(String) -> String) -> Self {
delegate_encoder_struct_builder_method!(self, map_error, f)
}
fn build(self, prefix: Self::Value) -> DataResult<Self::Value> {
match self {
Self::Normal(e) => e.build(prefix),
Self::Compressed(e) => e.build(prefix),
}
}
}
/// A trait specifying that an object holds a [`KeyCompressor`].
pub trait CompressorHolder: Keyable {
/// Returns the [`KeyCompressor`] of this object with the provided [`DynamicOps`].
fn compressor(&self) -> Arc<KeyCompressor>;
}
/// A different encoder that encodes a value of type `Value` for a map.
pub trait MapEncoder: HasValue + Keyable + CompressorHolder {
/// Encodes an input by working on a [`StructBuilder`].
fn encode<T: Display + PartialEq + Clone, B: StructBuilder<Value = T>>(
&self,
input: &Self::Value,
ops: &'static impl DynamicOps<Value = T>,
prefix: B,
) -> B;
/// Returns a [`StructBuilder`] of this `MapEncoder` with the provided [`DynamicOps`].
fn builder<'a, T: Display + Clone + 'a, O: DynamicOps<Value = T> + 'static>(
&'a self,
ops: &'static O,
) -> EncoderStructBuilder<T, O> {
if ops.compress_maps() {
EncoderStructBuilder::Compressed(MapBuilder::new(ops))
} else {
EncoderStructBuilder::Normal(ops.map_builder())
}
}
}
/// A different decoder that decodes into something of type `Value` for a map.
pub trait MapDecoder: HasValue + Keyable + CompressorHolder {
/// Decodes a map input.
fn decode<T: Display + PartialEq + Clone>(
&self,
input: &impl MapLike<Value = T>,
ops: &'static impl DynamicOps<Value = T>,
) -> DataResult<Self::Value>;
fn compressed_decode<T: Display + PartialEq + Clone>(
&self,
input: T,
ops: &'static impl DynamicOps<Value = T>,
) -> DataResult<Self::Value> {
if ops.compress_maps() {
// Since compressed maps are really just lists, we parse a list instead.
return ops.get_iter(input).into_result().map_or_else(
|| DataResult::new_error("Input is not a list"),
|iter| {
/// A [`MapLike`] for handling [`KeyCompressor`] methods.
struct CompressorMapLikeImpl<T, O: DynamicOps<Value = T> + 'static> {
list: Vec<T>,
compressor: Arc<KeyCompressor>,
ops: &'static O,
}
impl<T, O: DynamicOps<Value = T>> MapLike for CompressorMapLikeImpl<T, O> {
type Value = T;
fn get(&self, key: &Self::Value) -> Option<&Self::Value> {
self.compressor
.compress_key(key, self.ops)
.and_then(|i| self.list.get(i))
}
fn get_str(&self, key: &str) -> Option<&Self::Value> {
self.compressor
.compress_key_str(key)
.and_then(|i| self.list.get(i))
}
fn iter(&self) -> impl Iterator<Item = (Self::Value, &Self::Value)> + '_ {
self.list.iter().enumerate().filter_map(|(i, v)| {
self.compressor.decompress_key(i, self.ops).map(|k| (k, v))
})
}
}
self.decode(
&CompressorMapLikeImpl {
list: iter.collect(),
compressor: self.compressor(),
ops,
},
ops,
)
},
);
}
ops.get_map(&input)
.with_lifecycle(Lifecycle::Stable)
.flat_map(|map| self.decode(&map, ops))
}
}
/// A helper macro for generating the [`CompressorHolder::compressor`] method
/// for structs implementing `CompressorHolder`.
///
/// This macro caches the [`KeyCompressor`] of this [`CompressorHolder`]
/// in a global map.
///
/// Implement this in an `impl` block for `CompressorHolder`.
#[macro_export]
macro_rules! impl_compressor {
() => {
fn compressor(&self) -> std::sync::Arc<KeyCompressor> {
// We get the unique pointer of this holder.
let key = std::ptr::from_ref::<Self>(self) as usize;
// Then, we get the cache or store it.
$crate::key_compressor::KEY_COMPRESSOR_CACHE
.entry(key)
.or_insert_with(|| {
let mut c = KeyCompressor::new();
c.populate(self.keys());
std::sync::Arc::new(c)
})
.value()
.clone()
}
};
}
// Transformer map encoders and decoders
macro_rules! impl_map_encoder_transformer {
($name:ident, $function_return:ty) => {
pub struct $name<B, E: MapEncoder + 'static> {
encoder: &'static E,
function: fn(&B) -> $function_return,
}
impl<B, E: MapEncoder> HasValue for $name<B, E> {
type Value = B;
}
impl<B, E: MapEncoder> Keyable for $name<B, E> {
fn keys(&self) -> Vec<String> {
self.encoder.keys()
}
}
impl<B, E: MapEncoder> CompressorHolder for $name<B, E> {
fn compressor(&self) -> Arc<KeyCompressor> {
self.encoder.compressor()
}
}
};
}
impl_map_encoder_transformer!(ComappedMapEncoderImpl, E::Value);
impl<B, E: MapEncoder> MapEncoder for ComappedMapEncoderImpl<B, E> {
fn encode<T: Display + PartialEq + Clone, S: StructBuilder<Value = T>>(
&self,
input: &Self::Value,
ops: &'static impl DynamicOps<Value = T>,
prefix: S,
) -> S {
self.encoder.encode(&(self.function)(input), ops, prefix)
}
}
/// Returns a *contramapped* (*comapped*) transformation of a provided [`MapEncoder`].
/// A *comapped* encoder transforms the input before encoding.
pub(crate) const fn comap<B, E: MapEncoder>(
encoder: &'static E,
f: fn(&B) -> E::Value,
) -> ComappedMapEncoderImpl<B, E> {
ComappedMapEncoderImpl {
encoder,
function: f,
}
}
impl_map_encoder_transformer!(FlatComappedMapEncoderImpl, DataResult<E::Value>);
impl<B, E: MapEncoder> MapEncoder for FlatComappedMapEncoderImpl<B, E> {
fn encode<T: Display + PartialEq + Clone, S: StructBuilder<Value = T>>(
&self,
input: &Self::Value,
ops: &'static impl DynamicOps<Value = T>,
prefix: S,
) -> S {
let result = (self.function)(input);
let builder = prefix.with_errors_from(&result);
// We want to encode either a complete or partial result if there is one.
// Otherwise, we do nothing.
match result {
DataResult::Success { result: r, .. }
| DataResult::Error {
partial_result: Some(r),
..
} => self.encoder.encode(&r, ops, builder),
DataResult::Error {
partial_result: None,
..
} => builder,
}
}
}
/// Returns a *flat contramapped* (*flat-comapped*) transformation of a provided [`MapEncoder`].
/// A *flat comapped* encoder transforms the input before encoding, but the transformation can fail.
pub(crate) const fn flat_comap<B, E: MapEncoder>(
encoder: &'static E,
f: fn(&B) -> DataResult<E::Value>,
) -> FlatComappedMapEncoderImpl<B, E> {
FlatComappedMapEncoderImpl {
encoder,
function: f,
}
}
macro_rules! impl_map_decoder_transformer {
($name:ident, $function_return:ty) => {
pub struct $name<B, D: MapDecoder + 'static> {
decoder: &'static D,
function: fn(D::Value) -> $function_return,
}
impl<B, D: MapDecoder> HasValue for $name<B, D> {
type Value = B;
}
impl<B, D: MapDecoder> Keyable for $name<B, D> {
fn keys(&self) -> Vec<String> {
self.decoder.keys()
}
}
impl<B, D: MapDecoder> CompressorHolder for $name<B, D> {
fn compressor(&self) -> Arc<KeyCompressor> {
self.decoder.compressor()
}
}
};
}
impl_map_decoder_transformer!(MappedMapDecoderImpl, B);
impl<B, D: MapDecoder> MapDecoder for MappedMapDecoderImpl<B, D> {
fn decode<T: Display + PartialEq + Clone>(
&self,
input: &impl MapLike<Value = T>,
ops: &'static impl DynamicOps<Value = T>,
) -> DataResult<Self::Value> {
self.decoder.decode(input, ops).map(|a| (self.function)(a))
}
}
/// Returns a *covariant mapped* transformation of a provided [`MapDecoder`].
/// A *mapped* decoder transforms the output after decoding.
pub(crate) const fn map<B, D: MapDecoder>(
decoder: &'static D,
f: fn(D::Value) -> B,
) -> MappedMapDecoderImpl<B, D> {
MappedMapDecoderImpl {
decoder,
function: f,
}
}
impl_map_decoder_transformer!(FlatMappedMapDecoderImpl, DataResult<B>);
impl<B, D: MapDecoder> MapDecoder for FlatMappedMapDecoderImpl<B, D> {
fn decode<T: Display + PartialEq + Clone>(
&self,
input: &impl MapLike<Value = T>,
ops: &'static impl DynamicOps<Value = T>,
) -> DataResult<Self::Value> {
self.decoder
.decode(input, ops)
.flat_map(|a| (self.function)(a))
}
}
/// Returns a *covariant flat-mapped* transformation of a provided [`MapDecoder`].
/// A *flat-mapped* decoder transforms the output after decoding, but the transformation can fail.
pub(crate) const fn flat_map<B, D: MapDecoder>(
decoder: &'static D,
f: fn(D::Value) -> DataResult<B>,
) -> FlatMappedMapDecoderImpl<B, D> {
FlatMappedMapDecoderImpl {
decoder,
function: f,
}
}

View File

@@ -0,0 +1,150 @@
use core::fmt;
use std::fmt::{Display, Formatter};
/// Represents a generic number in Java.
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum Number {
Byte(i8),
Short(i16),
Int(i32),
Long(i64),
Float(f32),
Double(f64),
}
impl From<Number> for i64 {
fn from(num: Number) -> Self {
match num {
Number::Byte(b) => b as Self,
Number::Short(s) => s as Self,
Number::Int(i) => i as Self,
Number::Long(l) => l,
Number::Float(f) => f as Self,
Number::Double(d) => d as Self,
}
}
}
impl From<Number> for i32 {
fn from(num: Number) -> Self {
match num {
Number::Byte(b) => b as Self,
Number::Short(s) => s as Self,
Number::Int(i) => i,
Number::Long(l) => l as Self,
Number::Float(f) => f as Self,
Number::Double(d) => d as Self,
}
}
}
impl From<Number> for i16 {
fn from(num: Number) -> Self {
// Similar to Java, we will first convert the number to an `i16`, and then to an `i8`.
i32::from(num) as Self
}
}
impl From<Number> for i8 {
fn from(num: Number) -> Self {
// Similar to Java, we will first convert the number to an `i32`, and then to an `i8`.
i32::from(num) as Self
}
}
impl From<Number> for u8 {
fn from(num: Number) -> Self {
i32::from(num) as Self
}
}
impl From<Number> for f32 {
fn from(num: Number) -> Self {
match num {
Number::Byte(b) => b as Self,
Number::Short(s) => s as Self,
Number::Int(i) => i as Self,
Number::Long(l) => l as Self,
Number::Float(f) => f,
Number::Double(d) => d as Self,
}
}
}
impl From<Number> for f64 {
fn from(num: Number) -> Self {
match num {
Number::Byte(b) => b as Self,
Number::Short(s) => s as Self,
Number::Int(i) => i as Self,
Number::Long(l) => l as Self,
Number::Float(f) => f as Self,
Number::Double(d) => d,
}
}
}
impl Display for Number {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::Byte(v) => write!(f, "{v}"),
Self::Short(v) => write!(f, "{v}"),
Self::Int(v) => write!(f, "{v}"),
Self::Long(v) => write!(f, "{v}"),
Self::Float(v) => write!(f, "{v}"),
Self::Double(v) => write!(f, "{v}"),
}
}
}
impl From<Number> for serde_json::Value {
fn from(num: Number) -> Self {
match num {
Number::Byte(n) => n.into(),
Number::Short(n) => n.into(),
Number::Int(n) => n.into(),
Number::Long(n) => n.into(),
Number::Float(n) => n.into(),
Number::Double(n) => n.into(),
}
}
}
/// An error struct returned for an invalid conversion to [`Number`] from a [`serde_json::Value`].
pub struct FromJsonValueError;
impl TryFrom<&serde_json::Value> for Number {
type Error = FromJsonValueError;
fn try_from(num: &serde_json::Value) -> Result<Self, Self::Error> {
num.clone().try_into()
}
}
impl TryFrom<serde_json::Value> for Number {
type Error = FromJsonValueError;
fn try_from(num: serde_json::Value) -> Result<Self, Self::Error> {
match num {
serde_json::Value::Number(n) => n.try_into(),
_ => Err(FromJsonValueError),
}
}
}
impl TryFrom<serde_json::Number> for Number {
type Error = FromJsonValueError;
fn try_from(num: serde_json::Number) -> Result<Self, Self::Error> {
// Try converting the number to an integer first.
num.as_i64().map_or_else(
// Try the float conversion.
|| {
num.as_f64()
.map_or(Err(FromJsonValueError), |f| Ok(Self::Double(f)))
},
// Do the integer conversion.
|n| Ok(Self::Long(n)),
)
}
}

View File

@@ -260,15 +260,6 @@ pub struct MapBuilder<T, O: DynamicOps<Value = T> + 'static> {
ops: &'static O,
}
impl<T: Clone, O: DynamicOps<Value = T>> MapBuilder<T, O> {
pub(crate) const fn new(ops: &'static O) -> Self {
Self {
builder: DataResult::new_success_with_lifecycle(vec![], Lifecycle::Stable),
ops,
}
}
}
impl<T: Clone, O: DynamicOps<Value = T>> StructBuilder for MapBuilder<T, O> {
type Value = T;

View File

@@ -1,740 +0,0 @@
use crate::HasValue;
#[allow(unused_imports)] // Only used for documentation.
use crate::codec::Codec;
use crate::codecs::map_codec::MapCodecCodec;
use crate::data_result::DataResult;
use crate::dynamic_ops::DynamicOps;
use crate::impl_compressor;
use crate::key_compressor::KeyCompressor;
use crate::keyable::Keyable;
use crate::map_codec::MapCodec;
use crate::map_coders::{CompressorHolder, MapDecoder, MapEncoder};
use crate::map_like::MapLike;
use crate::struct_builder::StructBuilder;
use std::fmt::Display;
/// A single field object to build a struct codec, which either takes an *owned* or *borrowed* [`MapCodec`] and a getter.
///
/// - `T` is the composite type to get from.
/// - `C` is the [`MapCodec`] for serializing/deserializing the field.
pub enum Field<T, C: MapCodec + 'static> {
Owned(C, fn(&T) -> &C::Value),
Borrowed(&'static C, fn(&T) -> &C::Value),
}
impl<T, C: MapCodec + 'static> Field<T, C> {
fn getter(&self) -> &fn(&T) -> &C::Value {
match self {
Self::Owned(_, g) => g,
Self::Borrowed(_, g) => g,
}
}
const fn map_codec(&self) -> &C {
match self {
Self::Owned(c, _) => c,
Self::Borrowed(c, _) => c,
}
}
}
/// Macro to generate a `StructMapCodecN` struct (structure codec of `N` arguments).
/// This also creates a function to get a normal [`Codec`] from `N` fields.
macro_rules! impl_struct_map_codec {
(@internal_start $n:literal $name:ident $alias:ident $apply_func:ident $func_name:ident $($codec_type:ident, $field:ident),*) => {
#[doc = concat!("A [`MapCodec`] for a map with ", stringify!($n) , " rigid field(s).")]
///
/// A [`Codec`] can then be made from this object.
pub struct $name<T, C1: MapCodec + 'static $(, $codec_type: MapCodec + 'static)* > {
field_1: Field<T, C1>,
$( $field: Field<T, $codec_type> ,)*
apply_function: fn(C1::Value $(, $codec_type::Value)*) -> T
}
impl<T, C1: MapCodec $(, $codec_type: MapCodec)* > HasValue for $name<T, C1 $(, $codec_type)*> {
type Value = T;
}
impl<T, C1: MapCodec $(, $codec_type: MapCodec)* > Keyable for $name<T, C1 $(, $codec_type)*> {
#[allow(unused_mut)]
fn keys(&self) -> Vec<String> {
let mut keys = self.field_1.map_codec().keys();
$( keys.extend(self.$field.map_codec().keys()); )*
keys
}
}
impl<T, C1: MapCodec $(, $codec_type: MapCodec)* > CompressorHolder for $name<T, C1 $(, $codec_type)*> {
impl_compressor!();
}
impl<T, C1: MapCodec $(, $codec_type: MapCodec)* > MapEncoder for $name<T, C1 $(, $codec_type)*> {
#[allow(clippy::let_and_return)]
fn encode<U: Display + PartialEq + Clone, B: StructBuilder<Value = U>>(&self, input: &Self::Value, ops: &'static impl DynamicOps<Value=U>, prefix: B) -> B {
let prefix =
self.field_1.map_codec()
.encode((self.field_1.getter())(input), ops, prefix);
$(
let prefix =
self.$field.map_codec()
.encode((self.$field.getter())(input), ops, prefix);
)*
prefix
}
}
impl<T, C1: MapCodec $(, $codec_type: MapCodec)* > MapDecoder for $name<T, C1 $(, $codec_type)*> {
fn decode<U: Display + PartialEq + Clone>(
&self,
input: &impl MapLike<Value = U>,
ops: &'static impl DynamicOps<Value = U>,
) -> DataResult<Self::Value> {
self.field_1.map_codec().decode(input, ops).$apply_func(
self.apply_function,
$( self.$field.map_codec().decode(input, ops), )*
)
}
}
#[doc = concat!("A type alias of a struct [`Codec`] with ", stringify!($n), " field(s).")]
pub type $alias<T, C1 $(, $codec_type)* > = MapCodecCodec<$name<T, C1 $(, $codec_type)*>>;
};
($n:literal, $name:ident, $alias:ident, $apply_func:ident, $func_name:ident $(,)? $($codec_type:ident, $field:ident),*) => {
impl_struct_map_codec!(@internal_start $n $name $alias $apply_func $func_name $($codec_type, $field),*);
#[doc = concat!("Returns a struct [`Codec`] with ", stringify!($n), " field(s).")]
pub const fn $func_name<T, C1: MapCodec $(, $codec_type: MapCodec)*>(
field_1: Field<T, C1>,
$($field: Field<T, $codec_type>,)*
f: fn(C1::Value $(, $codec_type::Value)*) -> T,
) -> $alias<T, C1 $(, $codec_type)*> {
MapCodecCodec::Owned(
$name {
field_1,
$( $field, )*
apply_function: f
}
)
}
};
(expect $n:literal, $name:ident, $alias:ident, $apply_func:ident, $func_name:ident $(,)? $($codec_type:ident, $field:ident),*) => {
impl_struct_map_codec!(@internal_start $n $name $alias $apply_func $func_name $($codec_type, $field),*);
#[doc = concat!("Returns a struct [`Codec`] with ", stringify!($n), " field(s).")]
#[expect(clippy::too_many_arguments)]
pub const fn $func_name<T, C1: MapCodec $(, $codec_type: MapCodec)*>(
field_1: Field<T, C1>,
$($field: Field<T, $codec_type>,)*
f: fn(C1::Value $(, $codec_type::Value)*) -> T,
) -> $alias<T, C1 $(, $codec_type)*> {
MapCodecCodec::Owned(
$name {
field_1,
$( $field, )*
apply_function: f
}
)
}
};
}
impl_struct_map_codec!(1, StructMapCodec1, StructCodec1, map, struct_1,);
impl_struct_map_codec!(
2,
StructMapCodec2,
StructCodec2,
apply_2,
struct_2,
C2,
field_2
);
impl_struct_map_codec!(
3,
StructMapCodec3,
StructCodec3,
apply_3,
struct_3,
C2,
field_2,
C3,
field_3
);
impl_struct_map_codec!(
4,
StructMapCodec4,
StructCodec4,
apply_4,
struct_4,
C2,
field_2,
C3,
field_3,
C4,
field_4
);
impl_struct_map_codec!(
5,
StructMapCodec5,
StructCodec5,
apply_5,
struct_5,
C2,
field_2,
C3,
field_3,
C4,
field_4,
C5,
field_5
);
impl_struct_map_codec!(
6,
StructMapCodec6,
StructCodec6,
apply_6,
struct_6,
C2,
field_2,
C3,
field_3,
C4,
field_4,
C5,
field_5,
C6,
field_6
);
impl_struct_map_codec!(
expect 7,
StructMapCodec7,
StructCodec7,
apply_7,
struct_7,
C2,
field_2,
C3,
field_3,
C4,
field_4,
C5,
field_5,
C6,
field_6,
C7,
field_7
);
impl_struct_map_codec!(
expect 8,
StructMapCodec8,
StructCodec8,
apply_8,
struct_8,
C2,
field_2,
C3,
field_3,
C4,
field_4,
C5,
field_5,
C6,
field_6,
C7,
field_7,
C8,
field_8
);
impl_struct_map_codec!(
expect 9,
StructMapCodec9,
StructCodec9,
apply_9,
struct_9,
C2,
field_2,
C3,
field_3,
C4,
field_4,
C5,
field_5,
C6,
field_6,
C7,
field_7,
C8,
field_8,
C9,
field_9
);
impl_struct_map_codec!(
expect 10,
StructMapCodec10,
StructCodec10,
apply_10,
struct_10,
C2,
field_2,
C3,
field_3,
C4,
field_4,
C5,
field_5,
C6,
field_6,
C7,
field_7,
C8,
field_8,
C9,
field_9,
C10,
field_10
);
impl_struct_map_codec!(
expect 11,
StructMapCodec11,
StructCodec11,
apply_11,
struct_11,
C2,
field_2,
C3,
field_3,
C4,
field_4,
C5,
field_5,
C6,
field_6,
C7,
field_7,
C8,
field_8,
C9,
field_9,
C10,
field_10,
C11,
field_11
);
impl_struct_map_codec!(
expect 12,
StructMapCodec12,
StructCodec12,
apply_12,
struct_12,
C2,
field_2,
C3,
field_3,
C4,
field_4,
C5,
field_5,
C6,
field_6,
C7,
field_7,
C8,
field_8,
C9,
field_9,
C10,
field_10,
C11,
field_11,
C12,
field_12
);
impl_struct_map_codec!(
expect 13,
StructMapCodec13,
StructCodec13,
apply_13,
struct_13,
C2,
field_2,
C3,
field_3,
C4,
field_4,
C5,
field_5,
C6,
field_6,
C7,
field_7,
C8,
field_8,
C9,
field_9,
C10,
field_10,
C11,
field_11,
C12,
field_12,
C13,
field_13
);
impl_struct_map_codec!(
expect 14,
StructMapCodec14,
StructCodec14,
apply_14,
struct_14,
C2,
field_2,
C3,
field_3,
C4,
field_4,
C5,
field_5,
C6,
field_6,
C7,
field_7,
C8,
field_8,
C9,
field_9,
C10,
field_10,
C11,
field_11,
C12,
field_12,
C13,
field_13,
C14,
field_14
);
impl_struct_map_codec!(
expect 15,
StructMapCodec15,
StructCodec15,
apply_15,
struct_15,
C2,
field_2,
C3,
field_3,
C4,
field_4,
C5,
field_5,
C6,
field_6,
C7,
field_7,
C8,
field_8,
C9,
field_9,
C10,
field_10,
C11,
field_11,
C12,
field_12,
C13,
field_13,
C14,
field_14,
C15,
field_15
);
impl_struct_map_codec!(
expect 16,
StructMapCodec16,
StructCodec16,
apply_16,
struct_16,
C2,
field_2,
C3,
field_3,
C4,
field_4,
C5,
field_5,
C6,
field_6,
C7,
field_7,
C8,
field_8,
C9,
field_9,
C10,
field_10,
C11,
field_11,
C12,
field_12,
C13,
field_13,
C14,
field_14,
C15,
field_15,
C16,
field_16
);
#[cfg(test)]
mod test {
use crate::codec::*;
use crate::codecs::list::ListCodec;
use crate::codecs::primitive::StringCodec;
use crate::codecs::validated::ValidatedCodec;
use crate::coders::{Decoder, Encoder};
use crate::json_ops;
use crate::map_codec::for_getter;
use crate::struct_codecs::StructCodec3;
use crate::{assert_decode, struct_codec};
use serde_json::json;
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Book {
name: String,
author: String,
pages: u32,
}
pub type BookCodec = StructCodec3<
Book,
FieldMapCodec<StringCodec>,
FieldMapCodec<StringCodec>,
FieldMapCodec<UintCodec>,
>;
pub static BOOK_CODEC: BookCodec = struct_codec!(
for_getter(field(&STRING_CODEC, "name"), |book: &Book| &book.name),
for_getter(field(&STRING_CODEC, "author"), |book: &Book| &book.author),
for_getter(field(&UINT_CODEC, "pages"), |book: &Book| &book.pages),
|name, author, pages| Book {
name,
author,
pages
}
);
#[test]
fn book_struct() {
let object = Book {
name: "Sample Book".to_string(),
author: "Sample Author".to_string(),
pages: 16,
};
assert_eq!(
BOOK_CODEC
.encode_start(&object, &json_ops::INSTANCE)
.expect("Could not encode book"),
json![{
"name": "Sample Book",
"author": "Sample Author",
"pages": 16
}]
);
assert_eq!(BOOK_CODEC.parse(json!({"name": "The Great Gatsby", "author": "F. Scott Fitzgerald", "pages": 180}), &json_ops::INSTANCE).expect("Parsing book object failed"),
Book {
name: "The Great Gatsby".to_string(),
author: "F. Scott Fitzgerald".to_string(),
pages: 180
}
);
assert_decode!(
BOOK_CODEC,
json!({"name": "Untitled Book", "pages": 345}),
&json_ops::INSTANCE,
is_error
);
assert_decode!(
BOOK_CODEC,
json!({"name": "Untitled Book 2", "author": "Untitled Author", "pages": "98"}),
&json_ops::INSTANCE,
is_error
);
}
#[test]
#[allow(clippy::too_many_lines)]
fn bookshelf_struct() {
// A struct for a bookshelf.
#[derive(Debug, PartialEq)]
struct Bookshelf {
id: u32,
// Optional, defaults to no books.
books: Vec<Book>,
capacity: u32,
}
pub type BookshelfCodec = ValidatedCodec<
StructCodec3<
Bookshelf,
FieldMapCodec<UintCodec>,
DefaultedFieldCodec<ListCodec<BookCodec>>,
FieldMapCodec<UintCodec>,
>,
>;
pub static BOOKSHELF_CODEC: BookshelfCodec = validate(
&struct_codec!(
for_getter(field(&UINT_CODEC, "id"), |b: &Bookshelf| &b.id),
for_getter(
optional_field_with_default(&unbounded_list(&BOOK_CODEC), "books", Vec::new),
|b: &Bookshelf| &b.books
),
for_getter(field(&UINT_CODEC, "capacity"), |b: &Bookshelf| &b.capacity),
|id, books, capacity| Bookshelf {
id,
books,
capacity
}
),
|b| {
// The number of books on the bookshelf must be less than or equal to its capacity.
if b.books.len() <= b.capacity as usize {
Ok(())
} else {
Err(format!(
"Bookshelf cannot have {} books because its capacity is {}",
b.books.len(),
b.capacity
))
}
},
);
let example = Bookshelf {
id: 1234,
books: vec![
Book {
name: "Charlie and the Chocolate Factory".to_string(),
author: "Roald Dahl".to_string(),
pages: 192,
},
Book {
name: "Infinibook".to_string(),
author: "Infiniauthor".to_string(),
pages: 1_000_000,
},
],
capacity: 2,
};
assert_eq!(
BOOKSHELF_CODEC
.encode_start(&example, &json_ops::INSTANCE)
.expect("Could not encode bookshelf"),
json![{
"id": 1234,
"capacity": 2,
"books": [
{
"name": "Charlie and the Chocolate Factory",
"author": "Roald Dahl",
"pages": 192,
},
{
"name": "Infinibook",
"author": "Infiniauthor",
"pages": 1_000_000,
}
]
}]
);
let example = Bookshelf {
id: 5678,
books: vec![
Book {
name: "The Lord of the Rings".to_string(),
author: "J.R.R. Tolkien".to_string(),
pages: 1150,
},
Book {
name: "Sherlock Holmes".to_string(),
author: "Arthur Conan Doyle".to_string(),
pages: 1320,
},
Book {
name: "Empty Book".to_string(),
author: String::new(),
pages: 0,
},
],
capacity: 2,
};
assert!(
BOOKSHELF_CODEC
.encode_start(&example, &json_ops::INSTANCE)
// We should get an error because the bookshelf cannot handle
// more than 2 books.
.get_message()
.expect("Encoding bookshelf here should be an error")
.starts_with("Bookshelf cannot have")
);
assert_decode!(
BOOKSHELF_CODEC,
json!({"id": 36, "capacity": 6, "books": [
{"name": "Book A", "author": "Author A", "pages": 10},
{"name": "Book B", "author": "Author B", "pages": 20},
{"name": "Book C", "author": "Author C", "pages": 30},
{"name": "Book D", "author": "Author D", "pages": 40},
{"name": "Book E", "author": "Author E", "pages": 50}
]}),
&json_ops::INSTANCE,
is_success
);
assert_decode!(
BOOKSHELF_CODEC,
json!({"id": 93273, "capacity": 4, "books": [
{"name": "Book 1", "author": "Author 1", "pages": 100},
{"name": "Book 2", "author": "Author 2", "pages": 200},
{"name": "Book 3", "author": "Author 3", "pages": 300},
{"name": "Book 4", "author": "Author 4", "pages": 400},
// This should fail because 5 > 4.
{"name": "Book 5", "author": "Author 5", "pages": 500}
]}),
&json_ops::INSTANCE,
is_error
);
assert_decode!(
BOOKSHELF_CODEC,
// This will work because "books" is an optional field.
json!({"id": 254, "capacity": 10}),
&json_ops::INSTANCE,
is_success
);
assert_decode!(
BOOKSHELF_CODEC,
// This will not work because "books" expects an array.
json!({"id": 6252, "capacity": 1, "books": {"name": "A Tale of Two Cities", "author": "Charles Dickens", "pages": 480}}),
&json_ops::INSTANCE,
is_error
);
assert_decode!(
BOOKSHELF_CODEC,
json!({"id": 6253, "capacity": 1, "books": [{"name": "A Tale of Two Cities", "author": "Charles Dickens"}]}),
&json_ops::INSTANCE,
is_error
);
}
}

View File

@@ -157,20 +157,6 @@ dependencies = [
"typenum",
]
[[package]]
name = "dashmap"
version = "6.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
dependencies = [
"cfg-if",
"crossbeam-utils",
"hashbrown 0.14.5",
"lock_api",
"once_cell",
"parking_lot_core",
]
[[package]]
name = "der"
version = "0.7.10"
@@ -325,12 +311,6 @@ dependencies = [
"subtle",
]
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "hashbrown"
version = "0.15.5"
@@ -416,15 +396,6 @@ version = "0.2.180"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.29"
@@ -501,19 +472,6 @@ dependencies = [
"sha2",
]
[[package]]
name = "parking_lot_core"
version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-link",
]
[[package]]
name = "pem-rfc7468"
version = "0.7.0"
@@ -614,7 +572,6 @@ dependencies = [
name = "pumpkin-codecs"
version = "0.1.0-dev+26.1"
dependencies = [
"dashmap",
"either",
"serde_json",
"tracing",
@@ -720,15 +677,6 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags",
]
[[package]]
name = "rfc6979"
version = "0.4.0"
@@ -745,12 +693,6 @@ version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sec1"
version = "0.7.3"
@@ -852,12 +794,6 @@ version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "spki"
version = "0.7.3"

View File

@@ -1,10 +1,10 @@
use crate::compound::NbtCompound;
use crate::tag::NbtTag;
use pumpkin_codecs::DataResult;
use pumpkin_codecs::DynamicOps;
use pumpkin_codecs::Lifecycle;
use pumpkin_codecs::MapLike;
use pumpkin_codecs::Number;
use pumpkin_codecs::data_result::DataResult;
use pumpkin_codecs::dynamic_ops::DynamicOps;
use pumpkin_codecs::lifecycle::Lifecycle;
use pumpkin_codecs::map_like::MapLike;
use pumpkin_codecs::struct_builder::{ResultStructBuilder, StringStructBuilder, StructBuilder};
use pumpkin_codecs::{impl_get_list, impl_string_struct_builder, impl_struct_builder};
use std::iter::Map;
@@ -14,9 +14,6 @@ use tracing::warn;
/// A [`DynamicOps`] to serialize to/deserialize from NBT data.
pub struct NbtOps;
/// An instance of [`NbtOps`], which serializes/deserializes NBT data.
pub static INSTANCE: NbtOps = NbtOps;
impl DynamicOps for NbtOps {
type Value = NbtTag;
type StructBuilder = NbtStructBuilder;
@@ -428,7 +425,7 @@ impl StructBuilder for NbtStructBuilder {
type Value = NbtTag;
impl_struct_builder!(builder);
impl_string_struct_builder!(builder, INSTANCE);
impl_string_struct_builder!(builder, NbtOps);
}
impl StringStructBuilder for NbtStructBuilder {
@@ -667,168 +664,8 @@ add_inner_specific_array_collector_impl!(InnerLongListCollector, Long, LongArray
#[cfg(test)]
mod test {
use crate::compound::NbtCompound;
use crate::nbt_ops::{INSTANCE, ListCollector};
use crate::nbt_ops::ListCollector;
use crate::tag::NbtTag;
use pumpkin_codecs::codec::{
BOOL_CODEC, BYTE_BUFFER_CODEC, BYTE_CODEC, ComapFlatMapCodec, DOUBLE_CODEC,
DefaultedFieldCodec, FieldMapCodec, INT_CODEC, INT_STREAM_CODEC, LONG_CODEC,
LONG_STREAM_CODEC, SHORT_CODEC, STRING_CODEC, UBYTE_CODEC, UINT_CODEC, UbyteCodec,
UintCodec, comap_flat_map, field, optional_field_with_default, unbounded_list,
unbounded_map, validate,
};
use pumpkin_codecs::codecs::list::ListCodec;
use pumpkin_codecs::codecs::primitive::{ByteBufferCodec, StringCodec};
use pumpkin_codecs::codecs::unbounded_map::UnboundedMapCodec;
use pumpkin_codecs::codecs::validated::ValidatedCodec;
use pumpkin_codecs::coders::{Decoder, Encoder};
use pumpkin_codecs::data_result::DataResult;
use pumpkin_codecs::map_codec::for_getter;
use pumpkin_codecs::struct_codec;
use pumpkin_codecs::struct_codecs::{StructCodec2, StructCodec3};
use std::collections::HashMap;
/// Convenience function to easily create an [`NbtTag::Compound`].
macro_rules! nbt_compound_tag {
( { $($key:literal : $tag:expr),+ $(,)* } ) => {
{
let mut compound = NbtCompound::new();
$( compound.put($key, $tag); )+
NbtTag::Compound(compound)
}
};
// For empty compounds
( {} ) => {
NbtTag::Compound(NbtCompound::new())
};
}
#[test]
#[allow(clippy::too_many_lines)]
fn primitives() {
// Simple types
assert_eq!(
INT_CODEC
.encode_start(&45, &INSTANCE)
.expect("Encoding should succeed"),
NbtTag::Int(45)
);
assert_eq!(
BOOL_CODEC
.encode_start(&true, &INSTANCE)
.expect("Encoding should succeed"),
NbtTag::Byte(1)
);
assert_eq!(
BYTE_CODEC
.encode_start(&-89, &INSTANCE)
.expect("Encoding should succeed"),
NbtTag::Byte(-89)
);
assert_eq!(
DOUBLE_CODEC
.encode_start(&1.0, &INSTANCE)
.expect("Encoding should succeed"),
NbtTag::Double(1.0)
);
assert_eq!(
STRING_CODEC
.encode_start(&"Sample Text".to_string(), &INSTANCE)
.expect("Encoding should succeed"),
NbtTag::String("Sample Text".to_string())
);
assert_eq!(
INT_CODEC
.parse(NbtTag::Int(50), &INSTANCE)
.expect("Decoding should succeed"),
50
);
assert_eq!(
SHORT_CODEC
.parse(NbtTag::Short(-1235), &INSTANCE)
.expect("Decoding should succeed"),
-1235
);
assert_eq!(
LONG_CODEC
.parse(NbtTag::Long(53234), &INSTANCE)
.expect("Decoding should succeed"),
53234
);
// Packed array types
let byte_vec = vec![
1u8, 45u8, 100u8, 170u8, 203u8, 98u8, 245u8, 255u8, 0u8, 13u8,
];
assert_eq!(
BYTE_BUFFER_CODEC
.encode_start(&Box::from(&byte_vec[0..3]), &INSTANCE)
.expect("Encoding should succeed"),
NbtTag::ByteArray(Box::from(vec![1, 45, 100]))
);
assert_eq!(
BYTE_BUFFER_CODEC
.encode_start(&Box::from(&byte_vec[2..7]), &INSTANCE)
.expect("Encoding should succeed"),
NbtTag::ByteArray(Box::from(vec![100, 170, 203, 98, 245]))
);
assert_eq!(
INT_STREAM_CODEC
.encode_start(&vec![-100, 1234, 23948], &INSTANCE)
.expect("Encoding should succeed"),
NbtTag::IntArray(vec![-100, 1234, 23948])
);
assert_eq!(
INT_STREAM_CODEC
.encode_start(&vec![1, 120938, 1231909999], &INSTANCE)
.expect("Encoding should succeed"),
NbtTag::IntArray(vec![1, 120938, 1231909999])
);
assert_eq!(
LONG_STREAM_CODEC
.encode_start(&vec![10_000_000_000, -99_999_999_999], &INSTANCE)
.expect("Encoding should succeed"),
NbtTag::LongArray(vec![10_000_000_000, -99_999_999_999])
);
assert_eq!(
LONG_STREAM_CODEC
.encode_start(&vec![123_456_789_012_345, 66], &INSTANCE)
.expect("Encoding should succeed"),
NbtTag::LongArray(vec![123_456_789_012_345, 66])
);
assert_eq!(
BYTE_BUFFER_CODEC
.parse(NbtTag::ByteArray(Box::new([1, 4])), &INSTANCE)
.expect("Decoding should succeed"),
vec![1, 4].into_boxed_slice()
);
// All `get_...` packed array functions allow any arbitrary number array.
assert_eq!(
BYTE_BUFFER_CODEC
.parse(NbtTag::IntArray(vec![120]), &INSTANCE)
.expect("Decoding should succeed"),
vec![120].into_boxed_slice()
);
assert_eq!(
INT_STREAM_CODEC
.parse(NbtTag::LongArray(vec![1, 2, 3]), &INSTANCE)
.expect("Decoding should succeed"),
vec![1, 2, 3]
);
assert_eq!(
LONG_STREAM_CODEC
.parse(NbtTag::IntArray(vec![0, 0]), &INSTANCE)
.expect("Decoding should succeed"),
vec![0, 0]
);
}
#[test]
fn list_collecting() {
@@ -890,586 +727,4 @@ mod test {
])
);
}
// Specific codec tests
#[test]
fn employee() {
/// A struct to store a single employee.
/// The `name` and `department` of the employee should not be empty.
#[derive(Debug, PartialEq)]
struct Employee {
name: String,
department: String,
salary: u32,
}
pub type NonEmptyStringCodec = ValidatedCodec<StringCodec>;
/// Convenience codec for only encoding/decoding non-empty strings.
pub static NON_EMPTY_STRING_CODEC: NonEmptyStringCodec = validate(&STRING_CODEC, |s| {
if s.is_empty() {
Err("String should not be empty".to_string())
} else {
Ok(())
}
});
pub type EmployeeCodec = StructCodec3<
Employee,
FieldMapCodec<NonEmptyStringCodec>,
FieldMapCodec<NonEmptyStringCodec>,
FieldMapCodec<UintCodec>,
>;
pub static EMPLOYEE_CODEC: EmployeeCodec = struct_codec!(
for_getter(field(&NON_EMPTY_STRING_CODEC, "name"), |s: &Employee| &s
.name),
for_getter(
field(&NON_EMPTY_STRING_CODEC, "department"),
|s: &Employee| &s.department
),
for_getter(field(&UINT_CODEC, "salary"), |s: &Employee| &s.salary),
|name, department, salary| Employee {
name,
department,
salary
}
);
// Encoding
assert_eq!(
EMPLOYEE_CODEC
.encode_start(
&Employee {
name: "John Doe".to_string(),
department: "Marketing".to_string(),
salary: 82_000
},
&INSTANCE
)
.expect("Encoding should succeed"),
nbt_compound_tag!({
"name": NbtTag::String("John Doe".to_string()),
"department": NbtTag::String("Marketing".to_string()),
"salary": NbtTag::Int(82_000)
})
);
assert_eq!(
EMPLOYEE_CODEC
.encode_start(
&Employee {
name: "Linna Hall".to_string(),
// Department is empty.
department: String::new(),
salary: 90_000
},
&INSTANCE
)
.get_message()
.expect("Encoding should fail"),
"String should not be empty"
);
// Decoding
assert_eq!(
EMPLOYEE_CODEC
.parse(
nbt_compound_tag!({
"name": NbtTag::String("Kelly Peak".to_string()),
"department": NbtTag::String("Sales".to_string()),
"salary": NbtTag::Int(72_000)
}),
&INSTANCE
)
.expect("Decoding should succeed"),
Employee {
name: "Kelly Peak".to_string(),
department: "Sales".to_string(),
salary: 72_000
}
);
assert_eq!(
EMPLOYEE_CODEC
.parse(
nbt_compound_tag!({
"name": NbtTag::String(String::new()),
"department": NbtTag::String("Information Technology".to_string()),
"salary": NbtTag::Int(100_000)
}),
&INSTANCE
)
.get_message()
.expect("Decoding should fail"),
"String should not be empty"
);
}
#[test]
#[allow(clippy::too_many_lines)]
fn text() {
/// Alignments of a line of text.
#[derive(Debug, PartialEq, Clone)]
enum TextAlignment {
Left,
Center,
Right,
}
impl From<&TextAlignment> for String {
fn from(value: &TextAlignment) -> Self {
match value {
TextAlignment::Left => "left",
TextAlignment::Center => "center",
TextAlignment::Right => "right",
}
.to_string()
}
}
struct InvalidTextAlignmentError;
impl TryFrom<String> for TextAlignment {
type Error = InvalidTextAlignmentError;
fn try_from(value: String) -> Result<Self, Self::Error> {
match value.as_str() {
"left" => Ok(Self::Left),
"center" => Ok(Self::Center),
"right" => Ok(Self::Right),
_ => Err(InvalidTextAlignmentError),
}
}
}
pub type TextAlignmentCodec = ComapFlatMapCodec<TextAlignment, StringCodec>;
// The transformer codec:
// - always converts `TextAlignment` -> `String`
// - but only converts `String` -> `TextAlignment` if the string is valid.
pub static TEXT_ALIGNMENT_CODEC: TextAlignmentCodec = comap_flat_map(
&STRING_CODEC,
|string| {
string.clone().try_into().map_or_else(
|_| DataResult::new_error(format!("Invalid alignment: {string}")),
DataResult::new_success,
)
},
|modifier: &TextAlignment| modifier.into(),
);
/// A single piece of text.
#[derive(Debug, PartialEq, Clone)]
struct Text {
content: String,
/// Optional field, defaults to `Left` alignment.
alignment: TextAlignment,
}
pub type TextCodec =
StructCodec2<Text, FieldMapCodec<StringCodec>, DefaultedFieldCodec<TextAlignmentCodec>>;
pub static TEXT_CODEC: TextCodec = struct_codec!(
for_getter(field(&STRING_CODEC, "content"), |t: &Text| &t.content),
for_getter(
optional_field_with_default(&TEXT_ALIGNMENT_CODEC, "alignment", || {
TextAlignment::Left
}),
|t| &t.alignment
),
|content, alignment| Text { content, alignment }
);
// Encoding
assert_eq!(
TEXT_CODEC
.encode_start(
&Text {
content: "Lorem ipsum".to_string(),
alignment: TextAlignment::Left
},
&INSTANCE
)
.expect("Encoding should succeed"),
nbt_compound_tag!({
"content": NbtTag::String("Lorem ipsum".to_string()),
// Since "left" is the default, it will not be included.
})
);
assert_eq!(
TEXT_CODEC
.encode_start(
&Text {
content: "An apple a day keeps the doctor away".to_string(),
alignment: TextAlignment::Center
},
&INSTANCE
)
.expect("Encoding should succeed"),
nbt_compound_tag!({
"content": NbtTag::String("An apple a day keeps the doctor away".to_string()),
"alignment": NbtTag::String("center".to_string())
})
);
// Decoding
assert_eq!(
TEXT_CODEC
.parse(
nbt_compound_tag!({
"content": NbtTag::String("Surprise Sample Text".to_string()),
"alignment": NbtTag::String("right".to_string())
}),
&INSTANCE
)
.expect("Decoding should succeed"),
Text {
content: "Surprise Sample Text".to_string(),
alignment: TextAlignment::Right
}
);
assert_eq!(
TEXT_CODEC
.parse(
nbt_compound_tag!({
"content": NbtTag::String("Will the test succeed?".to_string()),
// Alignment omitted; it will default to `Left`.
}),
&INSTANCE
)
.expect("Decoding should succeed"),
Text {
content: "Will the test succeed?".to_string(),
alignment: TextAlignment::Left
}
);
assert!(
TEXT_CODEC
.parse(
nbt_compound_tag!({
"content": NbtTag::String("Some random document".to_string()),
// Unfortunately, we don't have *justify* in our possible alignments.
"alignment": NbtTag::String("justify".to_string())
}),
&INSTANCE
)
.get_message()
.expect("Decoding should fail")
.starts_with("Invalid alignment")
);
}
#[test]
#[allow(clippy::too_many_lines)]
fn dog_park() {
/// Represents an arbitrary dog.
#[derive(Debug, PartialEq, Clone)]
struct Dog {
breed: String,
age: u8,
// Optional, defaults to an empty `Vec`.
tricks: Vec<String>,
}
/// A dog park representation.
#[derive(Debug, PartialEq)]
struct DogPark {
name: String,
/// Each key of this map is the dog's name.
dogs: HashMap<String, Dog>,
}
pub type DogCodec = StructCodec3<
Dog,
FieldMapCodec<StringCodec>,
FieldMapCodec<UbyteCodec>,
DefaultedFieldCodec<ListCodec<StringCodec>>,
>;
pub static DOG_CODEC: DogCodec = struct_codec!(
for_getter(field(&STRING_CODEC, "breed"), |t: &Dog| &t.breed),
for_getter(field(&UBYTE_CODEC, "age"), |t: &Dog| &t.age),
for_getter(
optional_field_with_default(&unbounded_list(&STRING_CODEC), "tricks", Vec::new),
|t: &Dog| &t.tricks
),
|breed, age, tricks| Dog { breed, age, tricks }
);
pub type DogParkCodec = StructCodec2<
DogPark,
FieldMapCodec<StringCodec>,
FieldMapCodec<UnboundedMapCodec<StringCodec, DogCodec>>,
>;
pub static DOG_PARK_CODEC: DogParkCodec = struct_codec!(
for_getter(field(&STRING_CODEC, "name"), |p: &DogPark| &p.name),
for_getter(
field(&unbounded_map(&STRING_CODEC, &DOG_CODEC), "dogs"),
|p| &p.dogs
),
|name, dogs| DogPark { name, dogs }
);
// Encoding
let mut dogs = HashMap::new();
dogs.insert(
"Rodrick".to_string(),
Dog {
breed: "German Shepherd".to_string(),
age: 4,
tricks: vec!["spin".to_string()],
},
);
dogs.insert(
"Lucy".to_string(),
Dog {
breed: "Beagle".to_string(),
age: 6,
tricks: vec!["fetch".to_string(), "sit".to_string()],
},
);
dogs.insert(
"Dan".to_string(),
Dog {
breed: "Chihuahua".to_string(),
age: 3,
tricks: vec![],
},
);
let serialized_park = DOG_PARK_CODEC
.encode_start(
&DogPark {
name: "Sunny Side Park".to_string(),
dogs,
},
&INSTANCE,
)
.expect("Encoding should succeed");
let compound = serialized_park
.extract_compound()
.expect("Tag should be a compound");
assert_eq!(
compound
.clone()
.get_string("name")
.expect("Compound tag should have a 'name' key"),
"Sunny Side Park"
);
for (k, v) in compound
.get_compound("dogs")
.expect("Compound tag should have a 'dogs' key")
.clone()
{
match k.as_str() {
"Rodrick" => assert_eq!(
v,
nbt_compound_tag!({
"breed": NbtTag::String("German Shepherd".to_string()),
"age": NbtTag::Byte(4),
"tricks": NbtTag::List(vec![NbtTag::String("spin".to_string())])
})
),
"Lucy" => assert_eq!(
v,
nbt_compound_tag!({
"breed": NbtTag::String("Beagle".to_string()),
"age": NbtTag::Byte(6),
"tricks": NbtTag::List(vec![NbtTag::String("fetch".to_string()), NbtTag::String("sit".to_string())])
})
),
"Dan" => assert_eq!(
v,
nbt_compound_tag!({
"breed": NbtTag::String("Chihuahua".to_string()),
"age": NbtTag::Byte(3),
// 'tricks' will be omitted for an empty list.
})
),
_ => panic!("Unexpected dog {k} found"),
}
}
// Decoding
let deserialized_park = DOG_PARK_CODEC
.parse(
nbt_compound_tag!({
"name": NbtTag::String("Lighthouse Meadow Park".to_string()),
"dogs": nbt_compound_tag!({
"Adam": nbt_compound_tag!({
"breed": NbtTag::String("Bulldog".to_string()),
"age": NbtTag::Byte(8),
"tricks": NbtTag::List(vec![NbtTag::String("catch".to_string())])
})
})
}),
&INSTANCE,
)
.expect("Decoding should succeed");
assert_eq!(deserialized_park.name, "Lighthouse Meadow Park");
assert_eq!(deserialized_park.dogs.len(), 1);
assert_eq!(
deserialized_park
.dogs
.get("Adam")
.expect("No dog 'Adam' in dogs"),
&Dog {
breed: "Bulldog".to_string(),
age: 8,
tricks: vec!["catch".to_string()]
}
);
assert!(
DOG_PARK_CODEC
.parse(
nbt_compound_tag!({
"name": NbtTag::String("Dark Park".to_string()),
"dogs": nbt_compound_tag!({
"Adam": nbt_compound_tag!({
"breed": NbtTag::String("Poodle".to_string()),
// Negative ages are not allowed.
"age": NbtTag::Byte(-2)
})
})
}),
&INSTANCE
)
.get_message()
.expect("Decoding should fail")
.starts_with("Could not fit i8")
);
}
#[test]
#[allow(clippy::too_many_lines)]
fn packed_color() {
/// A color stored using 4 bytes, one each for red, green, blue and alpha.
#[derive(Debug, PartialEq, Clone)]
struct PackedColor {
r: u8,
g: u8,
b: u8,
/// Optional field, defaults to `255` (full alpha).
a: u8,
}
pub type PackedColorCodec = ComapFlatMapCodec<PackedColor, ByteBufferCodec>;
pub static PACKED_COLOR_CODEC: PackedColorCodec = comap_flat_map(
&BYTE_BUFFER_CODEC,
|v| {
// While decoding, our codec only accepts byte buffers (arrays) with exactly 3 or 4 elements.
if v.len() == 4 {
DataResult::new_success(PackedColor {
r: v[0],
g: v[1],
b: v[2],
a: v[3],
})
} else if v.len() == 3 {
// Alpha defaults to 255.
DataResult::new_success(PackedColor {
r: v[0],
g: v[1],
b: v[2],
a: 255,
})
} else {
DataResult::new_error(format!("Invalid byte buffer for color: {v:?}"))
}
},
|c| vec![c.r, c.g, c.b, c.a].into_boxed_slice(),
);
// Encoding
assert_eq!(
PACKED_COLOR_CODEC
.encode_start(
&PackedColor {
r: 100,
g: 121,
b: 89,
a: 201
},
&INSTANCE
)
.expect("Encoding should succeed"),
NbtTag::ByteArray(Box::new([100, 121, 89, 201]))
);
assert_eq!(
PACKED_COLOR_CODEC
.encode_start(
&PackedColor {
r: 0,
g: 0,
b: 0,
a: 255
},
&INSTANCE
)
.expect("Encoding should succeed"),
NbtTag::ByteArray(Box::new([0, 0, 0, 255]))
);
// Decoding
assert_eq!(
PACKED_COLOR_CODEC
.parse(NbtTag::ByteArray(Box::new([100, 121, 89, 201])), &INSTANCE)
.expect("Decoding should succeed"),
PackedColor {
r: 100,
g: 121,
b: 89,
a: 201
}
);
assert_eq!(
PACKED_COLOR_CODEC
.parse(NbtTag::ByteArray(Box::new([255, 255, 0])), &INSTANCE)
.expect("Decoding should succeed"),
PackedColor {
r: 255,
g: 255,
b: 0,
a: 255
}
);
assert!(
PACKED_COLOR_CODEC
.parse(NbtTag::ByteArray(Box::new([120])), &INSTANCE)
.get_message()
.expect("Decoding should fail")
.starts_with("Invalid byte buffer for color")
);
// Even other number array types will work.
assert_eq!(
PACKED_COLOR_CODEC
.parse(NbtTag::IntArray(vec![1, 2, 3, 4]), &INSTANCE)
.expect("Decoding should succeed"),
PackedColor {
r: 1,
g: 2,
b: 3,
a: 4
}
);
}
}