diff --git a/Cargo.lock b/Cargo.lock index 12aebe6d5..c45a1ca1b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2892,7 +2892,6 @@ dependencies = [ name = "pumpkin-codecs" version = "0.1.0-dev+26.1" dependencies = [ - "dashmap", "either", "serde_json", "tracing", diff --git a/pumpkin-codecs/Cargo.toml b/pumpkin-codecs/Cargo.toml index 926d2b874..a0f81a991 100644 --- a/pumpkin-codecs/Cargo.toml +++ b/pumpkin-codecs/Cargo.toml @@ -7,7 +7,6 @@ license.workspace = true [dependencies] serde_json.workspace = true -dashmap.workspace = true tracing.workspace = true either.workspace = true diff --git a/pumpkin-codecs/src/base_map_codec.rs b/pumpkin-codecs/src/base_map_codec.rs deleted file mode 100644 index 50e19845f..000000000 --- a/pumpkin-codecs/src/base_map_codec.rs +++ /dev/null @@ -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 + 'static; - - /// The value (element) type of this map codec. - type Element; - type ElementCodec: Codec + 'static; - - fn key_codec(&self) -> &'static Self::KeyCodec; - fn element_codec(&self) -> &'static Self::ElementCodec; - - fn encode( - &self, - input: &HashMap, - ops: &'static impl DynamicOps, - mut prefix: impl StructBuilder, - ) -> impl StructBuilder { - 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( - &self, - input: &impl MapLike, - ops: &'static impl DynamicOps, - ) -> DataResult> { - let mut read_map: HashMap = 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})")) - } -} diff --git a/pumpkin-codecs/src/codec.rs b/pumpkin-codecs/src/codec.rs deleted file mode 100644 index f42371023..000000000 --- a/pumpkin-codecs/src/codec.rs +++ /dev/null @@ -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` and `Vec`). -/// -/// # 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 and Decoder will also implement Codec. -impl Codec for T where T: Encoder + Decoder {} - -/// A codec allowing an arbitrary encoder and decoder. -pub struct ComposedCodec + 'static> { - encoder: E, - decoder: D, -} - -impl> HasValue for ComposedCodec { - type Value = E::Value; -} - -impl> Encoder for ComposedCodec { - fn encode( - &self, - input: &Self::Value, - ops: &'static impl DynamicOps, - prefix: T, - ) -> DataResult { - self.encoder.encode(input, ops, prefix) - } -} - -impl> Decoder for ComposedCodec { - fn decode( - &self, - input: T, - ops: &'static impl DynamicOps, - ) -> 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(f: fn() -> C) -> LazyCodec { - new_lazy_codec(f) -} - -/// Creates a [`ListCodec`] of another [`Codec`] with the provided minimum and maximum size. -pub const fn list(codec: &'static C, min_size: usize, max_size: usize) -> ListCodec { - new_list_codec(codec, min_size, max_size) -} - -/// Creates a [`ListCodec`] of another [`Codec`] with the provided maximum size. -pub const fn limited_list(codec: &'static C, max_size: usize) -> ListCodec { - new_list_codec(codec, 0, max_size) -} - -/// Creates a [`ListCodec`] of another [`Codec`], which allows any size. -pub const fn unbounded_list(codec: &'static C) -> ListCodec { - 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 = ComposedCodec<$encoder_type, $decoder_type>; - - #[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, S>(codec: &'static C, to: fn(A) -> $to_func_result, from: fn(&S) -> $from_func_result) -> $short_type { - 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, - A, - "partially equivalent", - "equivalent" -); - -make_codec_transformation_function!( - flat_map_comap, - FlatMapComapCodec, - FlatComappedEncoderImpl, - MappedDecoderImpl, - flat_comap, - map, - S, - DataResult, - "equivalent", - "partially equivalent" -); - -make_codec_transformation_function!( - flat_xmap, - FlatXmapCodec, - FlatComappedEncoderImpl, - FlatMappedDecoderImpl, - flat_comap, - flat_map, - DataResult, - DataResult, - "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( - codec: &'static C, - validator: fn(&C::Value) -> Result<(), String>, -) -> ValidatedCodec { - 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( - key_codec: &'static K, - element_codec: &'static V, - keyable: Key, -) -> SimpleMapCodec -where - ::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( - key_codec: &'static K, - element_codec: &'static V, -) -> UnboundedMapCodec -where - ::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( - left_codec: &'static L, - right_codec: &'static R, -) -> EitherCodec { - 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, FieldMapCodec>; -/// -/// // 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 = ComposedMapCodec< - FieldEncoder<::Value, C>, - FieldDecoder<::Value, C>, ->; - -/// Creates a [`MapCodec`] for a field which relies on the provided [`Codec`] for serialization/deserialization. -pub const fn field(codec: &'static C, name: &'static str) -> FieldMapCodec { - 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( - codec: &'static C, - name: &'static str, -) -> OptionalFieldMapCodec { - 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( - codec: &'static C, - name: &'static str, -) -> OptionalFieldMapCodec { - new_optional_field_map_codec(codec, name, true) -} - -pub type DefaultedFieldCodec = - DefaultValueProviderMapCodec<::Value, OptionalFieldMapCodec>; - -/// 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( - codec: &'static C, - name: &'static str, - factory: fn() -> C::Value, -) -> DefaultedFieldCodec -where - ::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( - codec: &'static C, - name: &'static str, - factory: fn() -> C::Value, -) -> DefaultedFieldCodec -where - ::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()); - }}; -} diff --git a/pumpkin-codecs/src/codec/either.rs b/pumpkin-codecs/src/codec/either.rs new file mode 100644 index 000000000..7967df859 --- /dev/null +++ b/pumpkin-codecs/src/codec/either.rs @@ -0,0 +1,91 @@ +use crate::{DataResult, Decode, DynamicOps, Encode}; +use either::Either; + +impl Encode for Either { + fn encode(&self, ops: &'static O, prefix: O::Value) -> DataResult { + match self { + Self::Left(l) => l.encode(ops, prefix), + Self::Right(r) => r.encode(ops, prefix), + } + } +} + +impl Decode for Either { + fn decode(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::::Left(5), JsonOps, json!(5)); + + assert_encode_success!( + Either::::Right("I am some text.".to_string()), + JsonOps, + json!("I am some text.") + ); + + // Decoding + + assert_decode!( + Either, + json!(-238), + JsonOps, + is_success + ); + assert_decode!( + Either, + json!(-238), + JsonOps, + is_error + ); + assert_decode!( + Either, + json!("hello"), + JsonOps, + is_success + ); + assert_decode!( + Either, + json!(true), + JsonOps, + is_error + ); + } +} diff --git a/pumpkin-codecs/src/codec/list.rs b/pumpkin-codecs/src/codec/list.rs new file mode 100644 index 000000000..b13e51acc --- /dev/null +++ b/pumpkin-codecs/src/codec/list.rs @@ -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(Vec); + +impl From> for Vec { + fn from(value: BoundedVec) -> Self { + value.0 + } +} + +impl FlatTryFrom> for BoundedVec { + fn flat_try_from(value: Vec) -> DataResult { + 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(min: usize, max: usize, size: usize) -> DataResult { + DataResult::new_error(format!( + "List is too short: {size}, expected range [{min}-{max}]" + )) +} + +fn create_too_long_error(min: usize, max: usize, size: usize) -> DataResult { + DataResult::new_error(format!( + "List is too long: {size}, expected range [{min}-{max}]" + )) +} + +impl Encode for BoundedVec +where + T: Encode, +{ + fn encode(&self, ops: &'static O, prefix: O::Value) -> DataResult { + 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 Decode for BoundedVec +where + T: Decode, +{ + fn decode(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 = vec![]; + let mut failed: Vec = 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 Encode for Vec +where + T: Encode, +{ + fn encode(&self, ops: &'static O, prefix: O::Value) -> DataResult { + let mut builder = ops.list_builder(); + for e in self { + builder = builder.add_data_result(e.encode_start(ops)); + } + builder.build(prefix) + } +} + +impl Decode for Vec +where + T: Decode, +{ + fn decode(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 = 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>; + + assert_decode!(Vec, json!([1, 2, 3]), JsonOps, is_success); + assert_decode!(Vec, json!([1, 2, 6, 24, 120]), JsonOps, is_success); + assert_decode!(Vec, json!(["string", "b"]), JsonOps, is_error); + assert_decode!(Vec, 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); + } +} diff --git a/pumpkin-codecs/src/codec/map.rs b/pumpkin-codecs/src/codec/map.rs new file mode 100644 index 000000000..81057f07d --- /dev/null +++ b/pumpkin-codecs/src/codec/map.rs @@ -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, + ops: &'static O, + mut prefix: impl StructBuilder, +) -> impl StructBuilder { + 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, + ops: &'static O, +) -> DataResult> { + let mut read_map: HashMap = 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 Encode for HashMap +where + K: Encode + Eq + Hash + Display, + V: Encode, +{ + fn encode(&self, ops: &'static O, prefix: O::Value) -> DataResult { + encode_base_hash_map::(self, ops, ops.map_builder()).build(prefix) + } +} + +impl Decode for HashMap +where + K: Decode + Eq + Hash + Display, + V: Decode, +{ + fn decode(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::::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( + &self, + ops: &'static O, + prefix: O::Value, + ) -> DataResult { + // This will always succeed. + self.0.to_string().encode(ops, prefix) + } + } + + impl Decode for StringInteger { + fn decode( + 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::::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, + 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 { + // 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( + &self, + ops: &'static O, + prefix: O::Value, + ) -> DataResult { + // This will always succeed. + check_letter(self.0.clone()).flat_map(|l| l.0.encode(ops, prefix)) + } + } + + impl Decode for Letter { + fn decode( + 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; + + 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 + ); + } +} diff --git a/pumpkin-codecs/src/codec/mod.rs b/pumpkin-codecs/src/codec/mod.rs new file mode 100644 index 000000000..3f84da77e --- /dev/null +++ b/pumpkin-codecs/src/codec/mod.rs @@ -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(&self, ops: &'static O, prefix: O::Value) -> DataResult; + + /// Encodes this value to a value represented by the provided [`DynamicOps`] without a prefix. + fn encode_start(&self, ops: &'static O) -> DataResult { + 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>( + &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>( + &self, + name: &'static str, + ops: &'static O, + prefix: B, + default: Self, + ) -> B + where + Self: PartialEq; +} + +impl FieldEncode for T { + fn encode_field>( + &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>( + &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(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(input: O::Value, ops: &'static O) -> DataResult { + 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( + name: &'static str, + input: &impl MapLike, + ops: &'static impl DynamicOps, + ) -> DataResult; + + /// 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( + name: &'static str, + input: &impl MapLike, + ops: &'static impl DynamicOps, + default: Self, + lenient: bool, + ) -> DataResult; +} + +impl FieldDecode for T { + fn decode_field( + name: &'static str, + input: &impl MapLike, + ops: &'static impl DynamicOps, + ) -> DataResult { + 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( + name: &'static str, + input: &impl MapLike, + ops: &'static impl DynamicOps, + default: Self, + lenient: bool, + ) -> DataResult { + let decoded_option = Option::decode_optional_field::(name, input, ops, lenient); + decoded_option.map(|o| o.unwrap_or(default)) + } +} diff --git a/pumpkin-codecs/src/codec/optional_field.rs b/pumpkin-codecs/src/codec/optional_field.rs new file mode 100644 index 000000000..ffd7083ac --- /dev/null +++ b/pumpkin-codecs/src/codec/optional_field.rs @@ -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>( + &self, + name: &'static str, + ops: &'static O, + prefix: B, + ) -> B; +} + +impl OptionalFieldEncode for Option +where + T: Encode, +{ + fn encode_optional_field>( + &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( + name: &'static str, + input: &impl MapLike, + ops: &'static impl DynamicOps, + lenient: bool, + ) -> DataResult; +} + +impl OptionalFieldDecode for Option +where + T: Decode, +{ + fn decode_optional_field( + name: &'static str, + input: &impl MapLike, + ops: &'static impl DynamicOps, + lenient: bool, + ) -> DataResult { + 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) + } + }, + ) + } +} diff --git a/pumpkin-codecs/src/codec/primitive.rs b/pumpkin-codecs/src/codec/primitive.rs new file mode 100644 index 000000000..6a33cbce3 --- /dev/null +++ b/pumpkin-codecs/src/codec/primitive.rs @@ -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(&self, ops: &'static O) -> O::Value; + fn primitive_decode(ops: &'static O, input: O::Value) -> DataResult; + } +} + +impl Encode for T { + fn encode(&self, ops: &'static O, prefix: O::Value) -> DataResult { + ops.merge_into_primitive(prefix, self.primitive_encode(ops)) + } +} + +impl Decode for T { + fn decode(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(&self, ops: &'static O) -> O::Value { + ops.$create_func(*self) + } + fn primitive_decode( + ops: &'static O, + input: O::Value, + ) -> DataResult { + 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( + &self, + ops: &'static O, + prefix: O::Value, + ) -> DataResult { + <$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( + 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(&self, ops: &'static O) -> O::Value { + ops.create_bool(*self) + } + + fn primitive_decode(ops: &'static O, input: O::Value) -> DataResult { + ops.get_bool(&input) + } +} + +impl Primitive for String { + fn primitive_encode(&self, ops: &'static O) -> O::Value { + ops.create_string(self.as_str()) + } + + fn primitive_decode(ops: &'static O, input: O::Value) -> DataResult { + 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> 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(&self, ops: &'static O) -> O::Value { + ops.$create_func(self.0.clone()) + } + + fn primitive_decode(ops: &'static O, input: O::Value) -> DataResult { + 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> for ByteBuffer { + fn from(value: Box<[u8]>) -> Self { + Self(value) + } +} + +impl From<[u8; N]> for ByteBuffer { + fn from(value: [u8; N]) -> Self { + Self(Box::from(value)) + } +} + +impl From for Box<[u8]> { + fn from(value: ByteBuffer) -> Self { + value.0 + } +} + +impl Primitive for ByteBuffer { + fn primitive_encode(&self, ops: &'static O) -> O::Value { + ops.create_byte_buffer(self.0.to_vec()) + } + + fn primitive_decode(ops: &'static O, input: O::Value) -> DataResult { + 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); + } +} diff --git a/pumpkin-codecs/src/codecs/either.rs b/pumpkin-codecs/src/codecs/either.rs deleted file mode 100644 index f6ce83f3c..000000000 --- a/pumpkin-codecs/src/codecs/either.rs +++ /dev/null @@ -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 { - left_codec: &'static L, - right_codec: &'static R, -} - -impl HasValue for EitherCodec { - type Value = Either; -} - -impl Encoder for EitherCodec { - fn encode( - &self, - input: &Self::Value, - ops: &'static impl DynamicOps, - prefix: T, - ) -> DataResult { - match &input { - Either::Left(l) => self.left_codec.encode(l, ops, prefix), - Either::Right(r) => self.right_codec.encode(r, ops, prefix), - } - } -} - -impl Decoder for EitherCodec { - fn decode( - &self, - input: T, - ops: &'static impl DynamicOps, - ) -> 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( - left_codec: &'static L, - right_codec: &'static R, -) -> EitherCodec { - 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 = - 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, FieldMapCodec>; - 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; - 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 = - 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 - ); - } -} diff --git a/pumpkin-codecs/src/codecs/lazy.rs b/pumpkin-codecs/src/codecs/lazy.rs deleted file mode 100644 index 5224f3838..000000000 --- a/pumpkin-codecs/src/codecs/lazy.rs +++ /dev/null @@ -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 -where - C: Codec, -{ - codec: LazyLock, -} - -impl HasValue for LazyCodec { - type Value = C::Value; -} - -impl Encoder for LazyCodec { - fn encode( - &self, - input: &Self::Value, - ops: &'static impl DynamicOps, - prefix: T, - ) -> DataResult { - self.codec.encode(input, ops, prefix) - } -} - -impl Decoder for LazyCodec { - fn decode( - &self, - input: T, - ops: &'static impl DynamicOps, - ) -> DataResult<(Self::Value, T)> { - self.codec.decode(input, ops) - } -} - -/// Creates a new [`LazyCodec`]. -pub(crate) const fn new_lazy_codec(f: fn() -> C) -> LazyCodec { - LazyCodec { - codec: LazyLock::new(f), - } -} diff --git a/pumpkin-codecs/src/codecs/list.rs b/pumpkin-codecs/src/codecs/list.rs deleted file mode 100644 index ddbdaecbc..000000000 --- a/pumpkin-codecs/src/codecs/list.rs +++ /dev/null @@ -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`]. -/// `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 -where - C: Codec + ?Sized + 'static, -{ - element_codec: &'static C, - min_size: usize, - max_size: usize, -} - -impl ListCodec { - fn create_too_short_error(&self, size: usize) -> DataResult { - DataResult::new_error(format!( - "List is too short: {size}, expected range [{}-{}]", - self.min_size, self.max_size - )) - } - - fn create_too_long_error(&self, size: usize) -> DataResult { - DataResult::new_error(format!( - "List is too long: {size}, expected range [{}-{}]", - self.min_size, self.max_size - )) - } -} - -impl HasValue for ListCodec { - type Value = Vec; -} - -impl Encoder for ListCodec { - fn encode( - &self, - input: &Self::Value, - ops: &'static impl DynamicOps, - prefix: T, - ) -> DataResult { - 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 Decoder for ListCodec -where - C: Codec, -{ - fn decode( - &self, - input: T, - ops: &'static impl DynamicOps, - ) -> 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 = 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( - codec: &'static C, - min_size: usize, - max_size: usize, -) -> ListCodec { - 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 = 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 = 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> = - 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 = 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> = - 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); - } - } -} diff --git a/pumpkin-codecs/src/codecs/map_codec.rs b/pumpkin-codecs/src/codecs/map_codec.rs deleted file mode 100644 index 2a1cab42e..000000000 --- a/pumpkin-codecs/src/codecs/map_codec.rs +++ /dev/null @@ -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 { - Owned(C), - Borrowed(&'static C), -} - -impl MapCodecCodec { - const fn codec(&self) -> &C { - match self { - Self::Owned(c) => c, - Self::Borrowed(c) => c, - } - } -} - -impl HasValue for MapCodecCodec { - type Value = C::Value; -} - -impl Encoder for MapCodecCodec { - fn encode( - &self, - input: &Self::Value, - ops: &'static impl DynamicOps, - prefix: T, - ) -> DataResult { - self.codec() - .encode(input, ops, self.codec().builder(ops)) - .build(prefix) - } -} - -impl Decoder for MapCodecCodec { - fn decode( - &self, - input: T, - ops: &'static impl DynamicOps, - ) -> DataResult<(Self::Value, T)> { - self.codec() - .compressed_decode(input.clone(), ops) - .map(|a| (a, input)) - } -} diff --git a/pumpkin-codecs/src/codecs/mod.rs b/pumpkin-codecs/src/codecs/mod.rs deleted file mode 100644 index 6ee2f0d38..000000000 --- a/pumpkin-codecs/src/codecs/mod.rs +++ /dev/null @@ -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; diff --git a/pumpkin-codecs/src/codecs/primitive.rs b/pumpkin-codecs/src/codecs/primitive.rs deleted file mode 100644 index e4b61a49f..000000000 --- a/pumpkin-codecs/src/codecs/primitive.rs +++ /dev/null @@ -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( - &self, - ops: &'static impl DynamicOps, - input: T, - ) -> DataResult<$prim> { - ops.get_number(&input).map(|n| <$prim>::from(n)) - } - - fn write(&self, ops: &'static impl DynamicOps, 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( - &self, - ops: &'static impl DynamicOps, - input: T, - ) -> DataResult> { - ops.$get_func(input) - } - - fn write(&self, ops: &'static impl DynamicOps, value: &Vec<$elem>) -> T { - ops.$create_func(value.to_vec()) - } - } - }; -} - -/// A generic primitive codec. -trait PrimitiveCodec: Codec { - fn read( - &self, - ops: &'static impl DynamicOps, - input: T, - ) -> DataResult; - - fn write(&self, ops: &'static impl DynamicOps, value: &Self::Value) -> T; -} - -impl Encoder for C { - fn encode( - &self, - input: &::Value, - ops: &'static impl DynamicOps, - prefix: T, - ) -> DataResult { - ops.merge_into_primitive(prefix, self.write(ops, input)) - } -} - -impl Decoder for C { - fn decode( - &self, - input: T, - ops: &'static impl DynamicOps, - ) -> DataResult<(::Value, T)> { - self.read(ops, input).map(|r| (r, ops.empty())) - } -} - -// Implementations - -impl_primitive_codec_start!(BoolCodec, bool); -impl PrimitiveCodec for BoolCodec { - fn read(&self, ops: &'static impl DynamicOps, input: T) -> DataResult { - ops.get_bool(&input) - } - - fn write(&self, ops: &'static impl DynamicOps, 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(&self, ops: &'static impl DynamicOps, input: T) -> DataResult { - ops.get_string(&input) - } - - fn write(&self, ops: &'static impl DynamicOps, value: &String) -> T { - ops.create_string(value) - } -} - -impl_primitive_codec_start!(ByteBufferCodec, Box<[u8]>); -impl PrimitiveCodec for ByteBufferCodec { - fn read(&self, ops: &'static impl DynamicOps, input: T) -> DataResult> { - ops.get_byte_buffer(input) - } - - fn write(&self, ops: &'static impl DynamicOps, 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); - } -} diff --git a/pumpkin-codecs/src/codecs/range.rs b/pumpkin-codecs/src/codecs/range.rs deleted file mode 100644 index 5b9fccb0c..000000000 --- a/pumpkin-codecs/src/codecs/range.rs +++ /dev/null @@ -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 -where - C::Value: PartialOrd + Display + Clone, -{ - codec: &'static C, - min: C::Value, - max: C::Value, -} - -impl HasValue for RangeCodec -where - ::Value: PartialOrd + Display + Clone, -{ - type Value = C::Value; -} - -impl Encoder for RangeCodec -where - ::Value: PartialOrd + Display + Clone, -{ - fn encode( - &self, - input: &Self::Value, - ops: &'static impl DynamicOps, - prefix: T, - ) -> DataResult { - check_range(input, &self.min, &self.max).flat_map(|t| self.codec.encode(&t, ops, prefix)) - } -} - -impl Decoder for RangeCodec -where - ::Value: PartialOrd + Display + Clone, -{ - fn decode( - &self, - input: T, - ops: &'static impl DynamicOps, - ) -> 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(input: &T, min: &T, max: &T) -> DataResult { - 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>( - codec: &'static C, - min: A, - max: A, -) -> RangeCodec { - 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 - ); - } -} diff --git a/pumpkin-codecs/src/codecs/unbounded_map.rs b/pumpkin-codecs/src/codecs/unbounded_map.rs deleted file mode 100644 index 7f2d0b110..000000000 --- a/pumpkin-codecs/src/codecs/unbounded_map.rs +++ /dev/null @@ -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 -where - K::Value: Display + Eq + Hash, -{ - key_codec: &'static K, - element_codec: &'static V, -} - -impl BaseMapCodec for UnboundedMapCodec -where - ::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 HasValue for UnboundedMapCodec -where - ::Value: Display + Eq + Hash, -{ - type Value = HashMap; -} - -impl Encoder for UnboundedMapCodec -where - ::Value: Display + Eq + Hash, -{ - fn encode( - &self, - input: &Self::Value, - ops: &'static impl DynamicOps, - prefix: T, - ) -> DataResult { - BaseMapCodec::encode(self, input, ops, ops.map_builder()).build(prefix) - } -} - -impl Decoder for UnboundedMapCodec -where - ::Value: Display + Eq + Hash, -{ - fn decode( - &self, - input: T, - ops: &'static impl DynamicOps, - ) -> 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( - key_codec: &'static K, - element_codec: &'static V, -) -> UnboundedMapCodec -where - ::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 = - unbounded_map(&STRING_CODEC, &INT_CODEC); - - let mut map = HashMap::::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, 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::::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, 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 - ); - } -} diff --git a/pumpkin-codecs/src/codecs/validated.rs b/pumpkin-codecs/src/codecs/validated.rs deleted file mode 100644 index 02f79e9ff..000000000 --- a/pumpkin-codecs/src/codecs/validated.rs +++ /dev/null @@ -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 { - codec: &'static C, - /// The validator function used. - validator: fn(&C::Value) -> Result<(), String>, -} - -impl HasValue for ValidatedCodec { - type Value = C::Value; -} - -impl Encoder for ValidatedCodec { - fn encode( - &self, - input: &Self::Value, - ops: &'static impl DynamicOps, - prefix: T, - ) -> DataResult { - (self.validator)(input).map_or_else( - |error| DataResult::new_error(error), - |()| self.codec.encode(input, ops, prefix), - ) - } -} - -impl Decoder for ValidatedCodec { - fn decode( - &self, - input: T, - ops: &'static impl DynamicOps, - ) -> 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( - codec: &'static C, - validator: fn(&C::Value) -> Result<(), String>, -) -> ValidatedCodec { - 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 = 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 = 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 - ); - } -} diff --git a/pumpkin-codecs/src/coders.rs b/pumpkin-codecs/src/coders.rs deleted file mode 100644 index 0c299419d..000000000 --- a/pumpkin-codecs/src/coders.rs +++ /dev/null @@ -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( - &self, - input: &Self::Value, - ops: &'static impl DynamicOps, - prefix: T, - ) -> DataResult; - - /// 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( - &self, - input: &Self::Value, - ops: &'static impl DynamicOps, - ) -> DataResult { - self.encode(input, ops, ops.empty()) - } -} - -pub struct ComappedEncoderImpl { - encoder: &'static E, - function: fn(&B) -> E::Value, -} - -impl HasValue for ComappedEncoderImpl { - type Value = B; -} - -impl Encoder for ComappedEncoderImpl { - fn encode( - &self, - input: &Self::Value, - ops: &'static impl DynamicOps, - prefix: T, - ) -> DataResult { - 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( - encoder: &'static E, - f: fn(&B) -> E::Value, -) -> ComappedEncoderImpl { - ComappedEncoderImpl { - encoder, - function: f, - } -} - -pub struct FlatComappedEncoderImpl { - encoder: &'static E, - function: fn(&B) -> DataResult, -} - -impl HasValue for FlatComappedEncoderImpl { - type Value = B; -} - -impl Encoder for FlatComappedEncoderImpl { - fn encode( - &self, - input: &Self::Value, - ops: &'static impl DynamicOps, - prefix: T, - ) -> DataResult { - (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( - encoder: &'static E, - f: fn(&B) -> DataResult, -) -> FlatComappedEncoderImpl { - FlatComappedEncoderImpl { - encoder, - function: f, - } -} - -pub(crate) const fn encoder_field>( - name: &'static str, - encoder: &'static E, -) -> FieldEncoder { - 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( - &self, - input: T, - ops: &'static impl DynamicOps, - ) -> 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( - &self, - input: T, - ops: &'static impl DynamicOps, - ) -> DataResult { - self.decode(input, ops).map(|r| r.0) - } -} - -pub struct MappedDecoderImpl { - decoder: &'static D, - function: fn(D::Value) -> B, -} - -impl HasValue for MappedDecoderImpl { - type Value = B; -} - -impl Decoder for MappedDecoderImpl { - fn decode( - &self, - input: T, - ops: &'static impl DynamicOps, - ) -> 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( - decoder: &'static D, - f: fn(D::Value) -> B, -) -> MappedDecoderImpl { - MappedDecoderImpl { - decoder, - function: f, - } -} - -pub struct FlatMappedDecoderImpl { - decoder: &'static D, - function: fn(D::Value) -> DataResult, -} - -impl HasValue for FlatMappedDecoderImpl { - type Value = B; -} - -impl Decoder for FlatMappedDecoderImpl { - fn decode( - &self, - input: T, - ops: &'static impl DynamicOps, - ) -> 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( - decoder: &'static D, - f: fn(D::Value) -> DataResult, -) -> FlatMappedDecoderImpl { - FlatMappedDecoderImpl { - decoder, - function: f, - } -} - -pub(crate) const fn decoder_field>( - name: &'static str, - decoder: &'static D, -) -> FieldDecoder { - FieldDecoder::new(name, decoder) -} diff --git a/pumpkin-codecs/src/data_result.rs b/pumpkin-codecs/src/data_result.rs index 267d5c876..0c3dec2d2 100644 --- a/pumpkin-codecs/src/data_result.rs +++ b/pumpkin-codecs/src/data_result.rs @@ -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 Default for DataResult { 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: Sized { + /// Performs the conversion. + fn flat_try_from(value: T) -> DataResult; +} + +impl FlatTryFrom for T { + fn flat_try_from(value: T) -> DataResult { + DataResult::new_success(value) + } +} + +impl FlatTryInto for T +where + U: FlatTryFrom, +{ + #[inline] + /// Calls `U::flat_try_from()`, which performs the conversion. + fn flat_try_into(self) -> DataResult { + 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: Sized { + /// Performs the conversion. + fn flat_try_into(self) -> DataResult; +} diff --git a/pumpkin-codecs/src/dynamic_ops.rs b/pumpkin-codecs/src/dynamic_ops.rs index 187ca7dcc..d62671065 100644 --- a/pumpkin-codecs/src/dynamic_ops.rs +++ b/pumpkin-codecs/src/dynamic_ops.rs @@ -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> { diff --git a/pumpkin-codecs/src/json_ops.rs b/pumpkin-codecs/src/json_ops.rs index 76d4040a8..6f1d2548f 100644 --- a/pumpkin-codecs/src/json_ops.rs +++ b/pumpkin-codecs/src/json_ops.rs @@ -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 { - 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::() { - return DataResult::new_success(Number::Int(i)); - } - if let Ok(l) = string.parse::() { - return DataResult::new_success(Number::Long(l)); - } - if let Ok(d) = string.parse::() { - 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 { - 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(&self, out_ops: &impl DynamicOps, 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 { diff --git a/pumpkin-codecs/src/key_compressor.rs b/pumpkin-codecs/src/key_compressor.rs deleted file mode 100644 index ea9bbd937..000000000 --- a/pumpkin-codecs/src/key_compressor.rs +++ /dev/null @@ -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>> = - 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, - decompress_map: HashMap, - 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) { - // 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( - &self, - key: usize, - ops: &'static impl DynamicOps, - ) -> Option { - self.decompress_map.get(&key).map(|s| ops.create_string(s)) - } - - /// Gets the compressed key of the provided dynamic type. - pub fn compress_key( - &self, - key: &T, - ops: &'static impl DynamicOps, - ) -> Option { - 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 { - self.compress_map.get(key).copied() - } - - /// Returns the size of the compressed/decompressed maps. - #[must_use] - pub const fn size(&self) -> usize { - self.size - } -} diff --git a/pumpkin-codecs/src/keyable.rs b/pumpkin-codecs/src/keyable.rs deleted file mode 100644 index a12df7be6..000000000 --- a/pumpkin-codecs/src/keyable.rs +++ /dev/null @@ -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; -} diff --git a/pumpkin-codecs/src/lib.rs b/pumpkin-codecs/src/lib.rs index f712c6fdc..3d6461588 100644 --- a/pumpkin-codecs/src/lib.rs +++ b/pumpkin-codecs/src/lib.rs @@ -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 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 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 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 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 for u8 { - fn from(num: Number) -> Self { - i32::from(num) as Self - } -} - -impl From 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 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 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 { - num.clone().try_into() - } -} - -impl TryFrom for Number { - type Error = FromJsonValueError; - - fn try_from(num: serde_json::Value) -> Result { - match num { - serde_json::Value::Number(n) => n.try_into(), - _ => Err(FromJsonValueError), - } - } -} - -impl TryFrom for Number { - type Error = FromJsonValueError; - - fn try_from(num: serde_json::Number) -> Result { - // 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; diff --git a/pumpkin-codecs/src/map_codec.rs b/pumpkin-codecs/src/map_codec.rs deleted file mode 100644 index a04695b08..000000000 --- a/pumpkin-codecs/src/map_codec.rs +++ /dev/null @@ -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 and MapDecoder will also implement MapCodec. -impl MapCodec for T where T: MapEncoder + MapDecoder {} - -/// A map codec allowing an arbitrary encoder and decoder. -pub struct ComposedMapCodec + 'static> { - pub(crate) encoder: E, - pub(crate) decoder: D, -} - -impl> HasValue for ComposedMapCodec { - type Value = E::Value; -} - -impl> Keyable for ComposedMapCodec { - fn keys(&self) -> Vec { - let mut vec = self.encoder.keys(); - vec.extend(self.decoder.keys()); - vec - } -} - -impl> CompressorHolder for ComposedMapCodec { - fn compressor(&self) -> Arc { - // This could return either the encoder or decoder's compressor, but we'll stick with the encoder's. - self.encoder.compressor() - } -} - -impl> MapEncoder for ComposedMapCodec { - fn encode>( - &self, - input: &Self::Value, - ops: &'static impl DynamicOps, - prefix: B, - ) -> B { - self.encoder.encode(input, ops, prefix) - } -} - -impl> MapDecoder for ComposedMapCodec { - fn decode( - &self, - input: &impl MapLike, - ops: &'static impl DynamicOps, - ) -> DataResult { - self.decoder.decode(input, ops) - } -} - -/// Wraps a [`MapCodec`] to make its [`DataResult`]s stable. -pub struct StableMapCodec { - map_codec: C, -} - -impl HasValue for StableMapCodec { - type Value = C::Value; -} - -impl Keyable for StableMapCodec { - fn keys(&self) -> Vec { - self.map_codec.keys() - } -} - -impl CompressorHolder for StableMapCodec { - fn compressor(&self) -> Arc { - self.map_codec.compressor() - } -} - -impl MapEncoder for StableMapCodec { - fn encode>( - &self, - input: &Self::Value, - ops: &'static impl DynamicOps, - prefix: B, - ) -> B { - self.map_codec - .encode(input, ops, prefix) - .set_lifecycle(Lifecycle::Stable) - } -} - -impl MapDecoder for StableMapCodec { - fn decode( - &self, - input: &impl MapLike, - ops: &'static impl DynamicOps, - ) -> DataResult { - 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( - map_codec: C, - getter: fn(&T) -> &C::Value, -) -> Field { - 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( - map_codec: &'static C, - getter: fn(&T) -> &C::Value, -) -> Field { - 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(map_codec: C) -> StableMapCodec { - 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 = ComposedMapCodec<$encoder_type, $decoder_type>; - - #[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, S>(map_codec: &'static C, to: fn(A) -> $to_func_result, from: fn(&S) -> $from_func_result) -> $short_type { - 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, - DataResult, - "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( - codec: &'static C, - validator: fn(&C::Value) -> Result<(), String>, -) -> ValidatedMapCodec { - 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( - left_codec: &'static L, - right_codec: &'static R, -) -> EitherMapCodec { - new_either_map_codec(left_codec, right_codec) -} diff --git a/pumpkin-codecs/src/map_codecs/either.rs b/pumpkin-codecs/src/map_codecs/either.rs deleted file mode 100644 index 083afe064..000000000 --- a/pumpkin-codecs/src/map_codecs/either.rs +++ /dev/null @@ -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 { - left_codec: &'static L, - right_codec: &'static R, -} - -impl HasValue for EitherMapCodec { - type Value = Either; -} - -impl Keyable for EitherMapCodec { - fn keys(&self) -> Vec { - let mut keys = self.left_codec.keys(); - keys.extend(self.right_codec.keys()); - keys - } -} - -impl CompressorHolder for EitherMapCodec { - impl_compressor!(); -} - -impl MapEncoder for EitherMapCodec { - fn encode>( - &self, - input: &Self::Value, - ops: &'static impl DynamicOps, - 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 MapDecoder for EitherMapCodec { - fn decode( - &self, - input: &impl MapLike, - ops: &'static impl DynamicOps, - ) -> DataResult { - 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( - left_codec: &'static L, - right_codec: &'static R, -) -> EitherMapCodec { - EitherMapCodec { - left_codec, - right_codec, - } -} diff --git a/pumpkin-codecs/src/map_codecs/field_coders.rs b/pumpkin-codecs/src/map_codecs/field_coders.rs deleted file mode 100644 index 842180745..000000000 --- a/pumpkin-codecs/src/map_codecs/field_coders.rs +++ /dev/null @@ -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 + 'static> { - /// The name of the key. - name: &'static str, - /// The [`Encoder`] for encoding the value. - element_encoder: &'static E, -} - -impl> HasValue for FieldEncoder { - type Value = A; -} - -impl> Keyable for FieldEncoder { - fn keys(&self) -> Vec { - vec![self.name.to_string()] - } -} - -impl> CompressorHolder for FieldEncoder { - impl_compressor!(); -} - -impl> MapEncoder for FieldEncoder { - fn encode>( - &self, - input: &Self::Value, - ops: &'static impl DynamicOps, - prefix: B, - ) -> B { - prefix.add_string_key_value_result(self.name, self.element_encoder.encode_start(input, ops)) - } -} - -impl> FieldEncoder { - /// 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 + 'static> { - /// The name of the key. - name: &'static str, - /// The [`Decoder`] for encoding the value. - element_decoder: &'static D, -} - -impl> HasValue for FieldDecoder { - type Value = A; -} - -impl> Keyable for FieldDecoder { - fn keys(&self) -> Vec { - vec![self.name.to_string()] - } -} - -impl> CompressorHolder for FieldDecoder { - impl_compressor!(); -} - -impl> MapDecoder for FieldDecoder { - fn decode( - &self, - input: &impl MapLike, - ops: &'static impl DynamicOps, - ) -> DataResult { - 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> FieldDecoder { - /// 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, - } - } -} diff --git a/pumpkin-codecs/src/map_codecs/mod.rs b/pumpkin-codecs/src/map_codecs/mod.rs deleted file mode 100644 index 8764dadc5..000000000 --- a/pumpkin-codecs/src/map_codecs/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod either; -pub mod field_coders; -pub mod optional_field; -pub mod simple; -pub mod validated; diff --git a/pumpkin-codecs/src/map_codecs/optional_field.rs b/pumpkin-codecs/src/map_codecs/optional_field.rs deleted file mode 100644 index 1242a2e01..000000000 --- a/pumpkin-codecs/src/map_codecs/optional_field.rs +++ /dev/null @@ -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 { - 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 HasValue for OptionalFieldMapCodec { - // The type of this `MapCodec` should be an `Option`. - type Value = Option; -} - -impl Keyable for OptionalFieldMapCodec { - fn keys(&self) -> Vec { - vec![self.name.to_string()] - } -} - -impl CompressorHolder for OptionalFieldMapCodec { - impl_compressor!(); -} - -impl MapEncoder for OptionalFieldMapCodec { - fn encode>( - &self, - input: &Self::Value, - ops: &'static impl DynamicOps, - 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 MapDecoder for OptionalFieldMapCodec { - fn decode( - &self, - input: &impl MapLike, - ops: &'static impl DynamicOps, - ) -> DataResult { - 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> + 'static, -> { - codec: C, - default: fn() -> T, -} - -impl>> HasValue - for DefaultValueProviderMapCodec -{ - type Value = T; -} - -impl>> Keyable - for DefaultValueProviderMapCodec -{ - fn keys(&self) -> Vec { - self.codec.keys() - } -} - -impl>> CompressorHolder - for DefaultValueProviderMapCodec -{ - fn compressor(&self) -> Arc { - self.codec.compressor() - } -} - -impl>> MapEncoder - for DefaultValueProviderMapCodec -{ - fn encode>( - &self, - input: &Self::Value, - ops: &'static impl DynamicOps, - prefix: B, - ) -> B { - let clone = Some(input.clone()); - self.codec.encode( - if *input == (self.default)() { - &None - } else { - &clone - }, - ops, - prefix, - ) - } -} - -impl>> MapDecoder - for DefaultValueProviderMapCodec -{ - fn decode( - &self, - input: &impl MapLike, - ops: &'static impl DynamicOps, - ) -> DataResult { - 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>, ->( - map_codec: C, - default: fn() -> T, -) -> DefaultValueProviderMapCodec { - DefaultValueProviderMapCodec { - codec: map_codec, - default, - } -} - -/// Returns a new [`OptionalFieldMapCodec`]. -pub(crate) const fn new_optional_field_map_codec( - element_codec: &'static C, - name: &'static str, - lenient: bool, -) -> OptionalFieldMapCodec { - OptionalFieldMapCodec { - element_codec, - name, - lenient, - } -} diff --git a/pumpkin-codecs/src/map_codecs/simple.rs b/pumpkin-codecs/src/map_codecs/simple.rs deleted file mode 100644 index 92146c0b9..000000000 --- a/pumpkin-codecs/src/map_codecs/simple.rs +++ /dev/null @@ -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 -where - K::Value: Display + Eq + Hash, -{ - key_codec: &'static K, - element_codec: &'static V, - - keyable: Key, -} -impl Keyable for SimpleMapCodec -where - K::Value: Display + Eq + Hash, -{ - fn keys(&self) -> Vec { - self.keyable.keys() - } -} - -impl CompressorHolder for SimpleMapCodec -where - K::Value: Display + Eq + Hash, -{ - impl_compressor!(); -} - -impl BaseMapCodec for SimpleMapCodec -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( - key_codec: &'static K, - element_codec: &'static V, - keyable: Key, -) -> SimpleMapCodec -where - ::Value: Display + Eq + Hash, -{ - SimpleMapCodec { - key_codec, - element_codec, - keyable, - } -} diff --git a/pumpkin-codecs/src/map_codecs/validated.rs b/pumpkin-codecs/src/map_codecs/validated.rs deleted file mode 100644 index 53cb1c776..000000000 --- a/pumpkin-codecs/src/map_codecs/validated.rs +++ /dev/null @@ -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 { - codec: &'static C, - /// The validator function used. - validator: fn(&C::Value) -> Result<(), String>, -} - -impl HasValue for ValidatedMapCodec { - type Value = C::Value; -} - -impl Keyable for ValidatedMapCodec { - fn keys(&self) -> Vec { - self.codec.keys() - } -} - -impl CompressorHolder for ValidatedMapCodec { - fn compressor(&self) -> Arc { - self.codec.compressor() - } -} - -impl MapEncoder for ValidatedMapCodec { - fn encode>( - &self, - input: &Self::Value, - ops: &'static impl DynamicOps, - 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 MapDecoder for ValidatedMapCodec { - fn decode( - &self, - input: &impl MapLike, - ops: &'static impl DynamicOps, - ) -> DataResult { - 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( - codec: &'static C, - validator: fn(&C::Value) -> Result<(), String>, -) -> ValidatedMapCodec { - ValidatedMapCodec { codec, validator } -} diff --git a/pumpkin-codecs/src/map_coders.rs b/pumpkin-codecs/src/map_coders.rs deleted file mode 100644 index 6e1e82847..000000000 --- a/pumpkin-codecs/src/map_coders.rs +++ /dev/null @@ -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 + 'static> { - builder: DataResult>, - ops: &'static O, - compressor: &'a KeyCompressor, -} - -impl<'a, T: Clone, O: DynamicOps + '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> StructBuilder for CompressedStructBuilder<'_, T, O> { - type Value = T; - - impl_struct_builder!(builder); - impl_universal_struct_builder!(builder, self.ops); -} - -impl> ResultStructBuilder for CompressedStructBuilder<'_, T, O> { - type Result = Vec; - - fn build_with_builder( - self, - builder: Self::Result, - prefix: Self::Value, - ) -> DataResult { - self.ops.merge_values_into_list(prefix, builder) - } -} - -impl> 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 + 'static> { - Normal(O::StructBuilder), - Compressed(MapBuilder), -} - -/// 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> StructBuilder for EncoderStructBuilder { - 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 { - delegate_encoder_struct_builder_method!(self, add_key_value_result, key, value) - } - - fn add_key_result_value_result( - self, - key: DataResult, - value: DataResult, - ) -> Self { - delegate_encoder_struct_builder_method!(self, add_key_result_value_result, key, value) - } - - fn with_errors_from(self, result: &DataResult) -> 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 { - 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 { - 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; -} - -/// 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>( - &self, - input: &Self::Value, - ops: &'static impl DynamicOps, - prefix: B, - ) -> B; - - /// Returns a [`StructBuilder`] of this `MapEncoder` with the provided [`DynamicOps`]. - fn builder<'a, T: Display + Clone + 'a, O: DynamicOps + 'static>( - &'a self, - ops: &'static O, - ) -> EncoderStructBuilder { - 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( - &self, - input: &impl MapLike, - ops: &'static impl DynamicOps, - ) -> DataResult; - - fn compressed_decode( - &self, - input: T, - ops: &'static impl DynamicOps, - ) -> DataResult { - 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 + 'static> { - list: Vec, - compressor: Arc, - ops: &'static O, - } - - impl> MapLike for CompressorMapLikeImpl { - 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 + '_ { - 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 { - // We get the unique pointer of this holder. - let key = std::ptr::from_ref::(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 { - encoder: &'static E, - function: fn(&B) -> $function_return, - } - - impl HasValue for $name { - type Value = B; - } - - impl Keyable for $name { - fn keys(&self) -> Vec { - self.encoder.keys() - } - } - - impl CompressorHolder for $name { - fn compressor(&self) -> Arc { - self.encoder.compressor() - } - } - }; -} - -impl_map_encoder_transformer!(ComappedMapEncoderImpl, E::Value); - -impl MapEncoder for ComappedMapEncoderImpl { - fn encode>( - &self, - input: &Self::Value, - ops: &'static impl DynamicOps, - 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( - encoder: &'static E, - f: fn(&B) -> E::Value, -) -> ComappedMapEncoderImpl { - ComappedMapEncoderImpl { - encoder, - function: f, - } -} - -impl_map_encoder_transformer!(FlatComappedMapEncoderImpl, DataResult); - -impl MapEncoder for FlatComappedMapEncoderImpl { - fn encode>( - &self, - input: &Self::Value, - ops: &'static impl DynamicOps, - 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( - encoder: &'static E, - f: fn(&B) -> DataResult, -) -> FlatComappedMapEncoderImpl { - FlatComappedMapEncoderImpl { - encoder, - function: f, - } -} - -macro_rules! impl_map_decoder_transformer { - ($name:ident, $function_return:ty) => { - pub struct $name { - decoder: &'static D, - function: fn(D::Value) -> $function_return, - } - - impl HasValue for $name { - type Value = B; - } - - impl Keyable for $name { - fn keys(&self) -> Vec { - self.decoder.keys() - } - } - - impl CompressorHolder for $name { - fn compressor(&self) -> Arc { - self.decoder.compressor() - } - } - }; -} - -impl_map_decoder_transformer!(MappedMapDecoderImpl, B); - -impl MapDecoder for MappedMapDecoderImpl { - fn decode( - &self, - input: &impl MapLike, - ops: &'static impl DynamicOps, - ) -> DataResult { - 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( - decoder: &'static D, - f: fn(D::Value) -> B, -) -> MappedMapDecoderImpl { - MappedMapDecoderImpl { - decoder, - function: f, - } -} - -impl_map_decoder_transformer!(FlatMappedMapDecoderImpl, DataResult); - -impl MapDecoder for FlatMappedMapDecoderImpl { - fn decode( - &self, - input: &impl MapLike, - ops: &'static impl DynamicOps, - ) -> DataResult { - 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( - decoder: &'static D, - f: fn(D::Value) -> DataResult, -) -> FlatMappedMapDecoderImpl { - FlatMappedMapDecoderImpl { - decoder, - function: f, - } -} diff --git a/pumpkin-codecs/src/number.rs b/pumpkin-codecs/src/number.rs new file mode 100644 index 000000000..445f2d59b --- /dev/null +++ b/pumpkin-codecs/src/number.rs @@ -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 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 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 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 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 for u8 { + fn from(num: Number) -> Self { + i32::from(num) as Self + } +} + +impl From 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 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 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 { + num.clone().try_into() + } +} + +impl TryFrom for Number { + type Error = FromJsonValueError; + + fn try_from(num: serde_json::Value) -> Result { + match num { + serde_json::Value::Number(n) => n.try_into(), + _ => Err(FromJsonValueError), + } + } +} + +impl TryFrom for Number { + type Error = FromJsonValueError; + + fn try_from(num: serde_json::Number) -> Result { + // 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)), + ) + } +} diff --git a/pumpkin-codecs/src/struct_builder.rs b/pumpkin-codecs/src/struct_builder.rs index 8c9218a91..7748ce698 100644 --- a/pumpkin-codecs/src/struct_builder.rs +++ b/pumpkin-codecs/src/struct_builder.rs @@ -260,15 +260,6 @@ pub struct MapBuilder + 'static> { ops: &'static O, } -impl> MapBuilder { - pub(crate) const fn new(ops: &'static O) -> Self { - Self { - builder: DataResult::new_success_with_lifecycle(vec![], Lifecycle::Stable), - ops, - } - } -} - impl> StructBuilder for MapBuilder { type Value = T; diff --git a/pumpkin-codecs/src/struct_codecs.rs b/pumpkin-codecs/src/struct_codecs.rs deleted file mode 100644 index 7144c3907..000000000 --- a/pumpkin-codecs/src/struct_codecs.rs +++ /dev/null @@ -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 { - Owned(C, fn(&T) -> &C::Value), - Borrowed(&'static C, fn(&T) -> &C::Value), -} - -impl Field { - 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 { - field_1: Field, - $( $field: Field ,)* - apply_function: fn(C1::Value $(, $codec_type::Value)*) -> T - } - - impl HasValue for $name { - type Value = T; - } - - impl Keyable for $name { - #[allow(unused_mut)] - fn keys(&self) -> Vec { - let mut keys = self.field_1.map_codec().keys(); - $( keys.extend(self.$field.map_codec().keys()); )* - keys - } - } - - impl CompressorHolder for $name { - impl_compressor!(); - } - - impl MapEncoder for $name { - #[allow(clippy::let_and_return)] - fn encode>(&self, input: &Self::Value, ops: &'static impl DynamicOps, 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 MapDecoder for $name { - fn decode( - &self, - input: &impl MapLike, - ops: &'static impl DynamicOps, - ) -> DataResult { - 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 = MapCodecCodec<$name>; - }; - - ($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( - field_1: Field, - $($field: Field,)* - f: fn(C1::Value $(, $codec_type::Value)*) -> T, - ) -> $alias { - 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( - field_1: Field, - $($field: Field,)* - f: fn(C1::Value $(, $codec_type::Value)*) -> T, - ) -> $alias { - 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, - FieldMapCodec, - FieldMapCodec, - >; - - 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, - capacity: u32, - } - - pub type BookshelfCodec = ValidatedCodec< - StructCodec3< - Bookshelf, - FieldMapCodec, - DefaultedFieldCodec>, - FieldMapCodec, - >, - >; - 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 - ); - } -} diff --git a/pumpkin-codegen/Cargo.lock b/pumpkin-codegen/Cargo.lock index 15c549301..e00474e8a 100644 --- a/pumpkin-codegen/Cargo.lock +++ b/pumpkin-codegen/Cargo.lock @@ -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" diff --git a/pumpkin-nbt/src/nbt_ops.rs b/pumpkin-nbt/src/nbt_ops.rs index 497343531..9b20f1de7 100644 --- a/pumpkin-nbt/src/nbt_ops.rs +++ b/pumpkin-nbt/src/nbt_ops.rs @@ -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; - /// 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, - FieldMapCodec, - FieldMapCodec, - >; - 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 for TextAlignment { - type Error = InvalidTextAlignmentError; - - fn try_from(value: String) -> Result { - match value.as_str() { - "left" => Ok(Self::Left), - "center" => Ok(Self::Center), - "right" => Ok(Self::Right), - - _ => Err(InvalidTextAlignmentError), - } - } - } - - pub type TextAlignmentCodec = ComapFlatMapCodec; - - // 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, DefaultedFieldCodec>; - 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, - } - - /// A dog park representation. - #[derive(Debug, PartialEq)] - struct DogPark { - name: String, - /// Each key of this map is the dog's name. - dogs: HashMap, - } - - pub type DogCodec = StructCodec3< - Dog, - FieldMapCodec, - FieldMapCodec, - DefaultedFieldCodec>, - >; - 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, - FieldMapCodec>, - >; - 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; - 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 - } - ); - } }