Files
Pumpkin/pumpkin-codecs/src/codecs/primitive.rs
SomeYellowGuy 24f6e2732d feat: Implement basic codecs and DynamicOps implementations from DataFixerUpper (#1483)
* initial commit

# Conflicts:
#	Cargo.toml

* implemented more `DataResult` methods

* finished data result struct

* fixed clippy errors

* implemented `map_like` and more `dynamic_ops` methods

* finished most other `DynamicOps` methods

* Fixed doc in get_bytes

* added encoder and decoder files

* fixed some stuff and added primitive codecs

* added some primitive codecs (has errors for now)

* changed the "stream" ops methods

* reworked types of the coders and codecs, added list codecs

* added JSON ops

* fixed list codecs, added some doc test examples in `JsonOps`

* added lazy codecs

* added map encoders, decoders and struct builders

* actually added mapdecoder

* added encoder and decoder transformations

* fixed encoder and decoder transformations and added transformation functions and range codecs

* added field encoders and decoders and separated range codecs

* added field encoders and decoders and separated range codecs

* added BaseMapCodec and SimpleMapCodec

* added struct codecs and moved tests

* added `NbtOps`, unsigned number codec types, range codec tests, a struct test, detailed `Codec` documentation

* added optional field map codecs, the validator function and reworked struct builder functions

* added unbounded map and struct codec tests and reworked validated codecs

* did some visibility refactoring

* fixed some stuff and renamed unsigned number codecs

* fixed formatting

* fixed docs to reflect changes on renaming unsigned number codecs

* fixed validated test

* fixed doctest

* moved base_map_codec and fixed docs about codecs

* fixed some mistakes in the code

* fixed some more mistakes in the code and converted some `DataResult::error` calls to `DataResult::partial_error`

* fixed formatting

* fixed some incorrect docs

* removed `OnceLock`s in `MapCodec`s so they can be used more easily

* fixed transformer codec descriptions, added more `NbtOps` tests and fixed some `NbtOps` list methods

* fixed doctest

* added a fourth specific `NbtOps` test for byte buffers (`ByteArray`s in NBT)

* fixed grammar mistake

* log messages for discarded keys in `create_map`

* use the `tracing` crate for logging

* added a method to allow creating a field using a `MapCodec` reference

* bifurcated `MapCodecCodec`s as well

* removed imports only for documentation and replaced them with intra-doc links

* added additional docs to `optional_field_with_default` and `lenient_optional_field_with_default` Codec methods for additional details on encoding

* renamed crate to `pumpkin-codecs` so that data fixing can be placed somewhere else not frequently used

* fixed some more errors and reworked `nbt_ops`'s `ListCollector` to be like 1.21.11, also allows &str in `DataResult`'s creation methods

* fixed invalid nbt tag for test

* reverted wrapping the tags for heterogeneous elements

* renamed function parameter in `DataResult::apply_2`

* removed unnecessary comment

* fixed a few more issues

* fixed formatting

* fixed another mistake for `DataResult::add_message`

* renamed `DataResult` factory methods to be more idiomatic and removed unnecessary `dyn` method

* removed unnecessary `clone()`

* updated Cargo.lock
2026-03-24 09:07:51 +01:00

209 lines
6.4 KiB
Rust

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