Skip to content

Instantly share code, notes, and snippets.

@tobz
Last active December 4, 2025 15:06
Show Gist options
  • Select an option

  • Save tobz/ad9907fd6cf2a001717cc6311392fdd0 to your computer and use it in GitHub Desktop.

Select an option

Save tobz/ad9907fd6cf2a001717cc6311392fdd0 to your computer and use it in GitHub Desktop.
use std::{marker::PhantomData, u8};
use facet::{
EnumType, Facet, Field, NumericType, PointerType, PrimitiveType, SequenceType, Shape, ShapeLayout, StructType,
TextualType, Type, UserType,
};
use facet_reflect::Partial;
use facet_value::{DestructuredRef, Value};
use saluki_error::{generic_error, ErrorContext, GenericError};
macro_rules! oversized_numeric {
($ty:ty, $value:expr) => {
generic_error!(
"Value {} is too large for type '{}' ({}-{}).",
$value,
stringify!($ty),
<$ty>::MIN,
<$ty>::MAX
)
};
}
pub struct Deserializer<'a, T>
where
T: Facet<'a> + 'a,
{
partial: Option<Partial<'a>>,
_type: PhantomData<T>,
}
impl<'a, T> Deserializer<'a, T>
where
T: Facet<'a> + 'a,
{
pub fn new() -> Result<Self, GenericError> {
let partial = Partial::alloc::<T>().error_context("Failed to allocate storage for deserializing object.")?;
Ok(Self {
partial: Some(partial),
_type: PhantomData,
})
}
pub fn apply_value(&mut self, value: &'a Value) -> Result<(), GenericError> {
match self.partial.take() {
Some(partial) => {
let new_partial = apply_value_to_partial(partial, value)?;
self.partial = Some(new_partial);
Ok(())
}
None => Err(generic_error!(
"Deserializer in non-deterministic state due to prior failure.",
)),
}
}
pub fn build(mut self) -> Result<T, GenericError> {
match self.partial.take() {
Some(partial) => partial
.build()
.error_context("Failed to build object from partial.")?
.materialize()
.error_context("Failed to materialize object to specified type."),
None => Err(generic_error!(
"Deserializer in non-deterministic state due to prior failure.",
)),
}
}
}
fn apply_value_to_partial<'a>(partial: Partial<'a>, value: &'a Value) -> Result<Partial<'a>, GenericError> {
debug_log(&partial, "apply_value_to_partial");
match partial.shape().ty {
Type::Primitive(pt) => apply_primitive_value_to_partial(pt, partial, value),
Type::Sequence(st) => apply_sequence_value_to_partial(st, partial, value),
Type::User(ut) => apply_user_value_to_partial(ut, partial, value),
Type::Pointer(pt) => apply_pointer_value_to_partial(pt, partial, value),
}
}
fn apply_primitive_value_to_partial<'a>(
pt: PrimitiveType, partial: Partial<'a>, value: &'a Value,
) -> Result<Partial<'a>, GenericError> {
debug_log(&partial, "apply_primitive_value_to_partial");
match pt {
PrimitiveType::Boolean => value
.as_bool()
.ok_or_else(|| type_mismatch("boolean", value))
.and_then(|value| set_partial_value(partial, value)),
PrimitiveType::Numeric(nt) => apply_numeric_value_to_partial(nt, partial, value),
PrimitiveType::Textual(tt) => apply_textual_value_to_partial(tt, partial, value),
// TODO: Add frame path to error.
PrimitiveType::Never => Err(generic_error!("Never type cannot be deserialized.")),
}
}
fn apply_numeric_value_to_partial<'a>(
nt: NumericType, partial: Partial<'a>, value: &Value,
) -> Result<Partial<'a>, GenericError> {
debug_log(&partial, "apply_numeric_value_to_partial");
match nt {
NumericType::Integer { signed } => {
if signed {
assign_signed_integer_value_to_partial(partial, value)
} else {
assign_unsigned_integer_value_to_partial(partial, value)
}
}
NumericType::Float => assign_float_value_to_partial(partial, value),
}
}
fn assign_signed_integer_value_to_partial<'a>(
partial: Partial<'a>, value: &Value,
) -> Result<Partial<'a>, GenericError> {
debug_log(&partial, "apply_signed_integer_value_to_partial");
let int_value = value
.as_number()
.and_then(|n| n.to_i64())
.ok_or_else(|| type_mismatch("int", value))?;
match get_shape_bitwidth(partial.shape()) {
i8::BITS => {
let value = i8::try_from(int_value).map_err(|_| oversized_numeric!(i8, int_value))?;
set_partial_value(partial, value)
}
i16::BITS => {
let value = i16::try_from(int_value).map_err(|_| oversized_numeric!(i16, int_value))?;
set_partial_value(partial, value)
}
i32::BITS => {
let value = i32::try_from(int_value).map_err(|_| oversized_numeric!(i32, int_value))?;
set_partial_value(partial, value)
}
i64::BITS => set_partial_value(partial, int_value),
_ => Err(generic_error!("Unhandled signed integer type '{}'.", partial.shape())),
}
}
fn assign_unsigned_integer_value_to_partial<'a>(
partial: Partial<'a>, value: &Value,
) -> Result<Partial<'a>, GenericError> {
debug_log(&partial, "apply_unsigned_integer_value_to_partial");
let uint_value = value
.as_number()
.and_then(|n| n.to_u64())
.ok_or_else(|| type_mismatch("uint", value))?;
match get_shape_bitwidth(partial.shape()) {
u8::BITS => {
let value = u8::try_from(uint_value).map_err(|_| oversized_numeric!(u8, uint_value))?;
set_partial_value(partial, value)
}
u16::BITS => {
let value = u16::try_from(uint_value).map_err(|_| oversized_numeric!(u16, uint_value))?;
set_partial_value(partial, value)
}
u32::BITS => {
let value = u32::try_from(uint_value).map_err(|_| oversized_numeric!(u32, uint_value))?;
set_partial_value(partial, value)
}
u64::BITS => set_partial_value(partial, uint_value),
_ => Err(generic_error!("Unhandled unsigned integer type '{}'.", partial.shape())),
}
}
fn assign_float_value_to_partial<'a>(partial: Partial<'a>, value: &Value) -> Result<Partial<'a>, GenericError> {
debug_log(&partial, "apply_float_value_to_partial");
if partial.shape() == f32::SHAPE {
value
.as_number()
.and_then(|n| n.to_f32())
.ok_or_else(|| type_mismatch("f32", value))
.and_then(|value| set_partial_value(partial, value))
} else if partial.shape() == f64::SHAPE {
value
.as_number()
.and_then(|n| n.to_f64())
.ok_or_else(|| type_mismatch("f64", value))
.and_then(|value| set_partial_value(partial, value))
} else {
Err(generic_error!("Unhandled float type '{}'.", partial.shape()))
}
}
fn apply_textual_value_to_partial<'a>(
tt: TextualType, partial: Partial<'a>, value: &'a Value,
) -> Result<Partial<'a>, GenericError> {
debug_log(&partial, "apply_textual_value_to_partial");
let str_value = value.as_string().ok_or_else(|| type_mismatch("string", value))?;
match tt {
TextualType::Char => {
let mut str_chars = str_value.chars();
// We expect a single character, so if we got an empty string, or a string with two or more characters, that's an error.
let char_value = str_chars
.next()
.ok_or_else(|| generic_error!("Expected a single character, got '{}'.", str_value))?;
if str_chars.next().is_some() {
return Err(generic_error!("Expected a single character, got '{}'.", str_value));
}
set_partial_value(partial, char_value)
}
TextualType::Str => set_partial_value(partial, str_value.as_str()),
}
}
fn apply_sequence_value_to_partial<'a>(
st: SequenceType, partial: Partial<'a>, value: &Value,
) -> Result<Partial<'a>, GenericError> {
debug_log(&partial, "apply_sequence_value_to_partial");
Err(generic_error!("Sequences are not supported."))
}
fn apply_user_value_to_partial<'a>(
ut: UserType, partial: Partial<'a>, value: &'a Value,
) -> Result<Partial<'a>, GenericError> {
debug_log(&partial, "apply_user_value_to_partial");
match ut {
UserType::Struct(st) => apply_struct_value_to_partial(st, partial, value),
UserType::Enum(et) => apply_enum_value_to_partial(et, partial, value),
UserType::Union(_) => Err(generic_error!("Union types are not supported.")),
UserType::Opaque => {
debug_log(&partial, format!("arrived at opaque type: {:#?}", partial.shape()));
Err(generic_error!("Opaque types are not supported."))
}
}
}
fn apply_struct_value_to_partial<'a>(
st: StructType, partial: Partial<'a>, value: &'a Value,
) -> Result<Partial<'a>, GenericError> {
debug_log(&partial, "apply_struct_value_to_partial");
match st.kind {
facet::StructKind::Unit => todo!(),
facet::StructKind::TupleStruct => todo!(),
facet::StructKind::Struct => apply_named_field_struct_value_to_partial(st.fields, partial, value),
facet::StructKind::Tuple => todo!(),
}
}
fn apply_named_field_struct_value_to_partial<'a>(
fields: &'static [Field], mut partial: Partial<'a>, value: &'a Value,
) -> Result<Partial<'a>, GenericError> {
debug_log(&partial, "apply_named_field_struct_value_to_partial");
let obj_value = value.as_object().ok_or_else(|| type_mismatch("object", value))?;
for field in fields {
debug_log(&partial, format!("handling field {}", field.name));
if let Some(field_value) = obj_value.get(field.name) {
debug_log(&partial, format!("found value for field {}, applying...", field.name));
partial = partial.begin_field(field.name)?;
partial = apply_value_to_partial(partial, field_value)?;
partial = partial.end()?;
}
}
Ok(partial)
}
fn apply_enum_value_to_partial<'a>(
et: EnumType, partial: Partial<'a>, value: &Value,
) -> Result<Partial<'a>, GenericError> {
debug_log(&partial, "apply_enum_value_to_partial");
todo!()
}
fn apply_pointer_value_to_partial<'a>(
pt: PointerType, partial: Partial<'a>, value: &Value,
) -> Result<Partial<'a>, GenericError> {
debug_log(&partial, "apply_pointer_value_to_partial");
todo!()
}
fn get_shape_bitwidth(shape: &Shape) -> u32 {
match shape.layout {
ShapeLayout::Sized(layout) => u32::try_from(layout.size() * 8).unwrap_or(u32::MAX),
ShapeLayout::Unsized => 0,
}
}
fn set_partial_value<'a, V: Facet<'a>>(partial: Partial<'a>, value: V) -> Result<Partial<'a>, GenericError> {
let path = partial.path();
partial
.set(value)
.with_error_context(|| format!("Failed to set value for {}.", path))
}
fn type_mismatch(expected: &'static str, value: &Value) -> GenericError {
generic_error!("Type mismatch: expected {}, got {}", expected, value_type(value))
}
fn value_type(value: &Value) -> &'static str {
match value.destructure_ref() {
DestructuredRef::Null => "null",
DestructuredRef::Bool(_) => "bool",
DestructuredRef::Number(vnumber) => {
if vnumber.is_float() {
"float"
} else if vnumber.to_u64().is_some() {
"unsigned-int"
} else {
"signed-int"
}
}
DestructuredRef::String(_) => "string",
DestructuredRef::Bytes(_) => "bytes",
DestructuredRef::Array(_) => "array",
DestructuredRef::Object(_) => "object",
DestructuredRef::DateTime(_) => "datetime",
}
}
fn debug_log<'a, S>(partial: &Partial<'a>, message: S)
where
S: AsRef<str>,
{
let indent = partial.frame_count().saturating_sub(1);
let indent_str = " ".repeat(indent);
println!("{}{}", indent_str, message.as_ref());
}
#[cfg(test)]
mod tests {
use facet::Facet;
use facet_value::{VObject, VString};
use super::*;
#[track_caller]
fn finalize_partial<'a, T: Facet<'a>>(partial: Partial<'a>) -> Result<T, GenericError> {
partial.build()?.materialize().map_err(|e| e.into())
}
#[test]
fn test_apply_value_to_partial_primitive() {
let partial = Partial::alloc::<bool>().unwrap();
let value = Value::TRUE;
let partial = apply_value_to_partial(partial, &value).unwrap();
let final_value = finalize_partial::<bool>(partial).unwrap();
assert_eq!(final_value, true);
}
#[test]
fn test_deserialize_basic() {
#[derive(Facet)]
struct Basic {
flag: bool,
api_key: String,
}
println!("Shape of `Basic`: {:#?}", Basic::SHAPE);
let mut object = VObject::default();
object.insert("flag", Value::TRUE);
object.insert("api_key", VString::from("123456"));
let value = object.into_value();
let partial = Partial::alloc::<Basic>().unwrap();
let partial = apply_value_to_partial(partial, &value).unwrap();
let final_value = finalize_partial::<Basic>(partial).unwrap();
assert_eq!(final_value.flag, true);
assert_eq!(final_value.api_key, "123456");
}
}
running 1 test
test deser::tests::test_deserialize_basic ... FAILED
successes:
successes:
failures:
---- deser::tests::test_deserialize_basic stdout ----
Shape of `Basic`: Shape {
id: TypeId(0x4effc403bbd6aa3dd1d49011c085043a),
layout: Sized(Layout { size: 32, align: 8 (1 << 3) }),
vtable: ValueVTable { .. },
ty: User(
Struct(
StructType {
repr: Repr {
base: Rust,
packed: false,
},
kind: Struct,
fields: [
Field {
name: "flag",
shape: 0x00005b2e767c2830,
offset: 24,
attributes: [],
doc: [],
},
Field {
name: "api_key",
shape: 0x00005b2e767c2be0,
offset: 0,
attributes: [],
doc: [],
},
],
},
),
),
def: Undefined,
type_identifier: "Basic",
type_params: [],
doc: [],
attributes: [],
type_tag: None,
inner: None,
}
apply_value_to_partial
apply_user_value_to_partial
apply_struct_value_to_partial
apply_named_field_struct_value_to_partial
handling field flag
found value for field flag, applying...
apply_value_to_partial
apply_primitive_value_to_partial
handling field api_key
found value for field api_key, applying...
apply_value_to_partial
apply_user_value_to_partial
arrived at opaque type: Shape {
id: TypeId(0x652f329a49e7c67669df4a6e3ac0879a),
layout: Sized(Layout { size: 24, align: 8 (1 << 3) }),
vtable: ValueVTable { .. },
ty: User(
Opaque,
),
def: Scalar,
type_identifier: "String",
type_params: [],
doc: [],
attributes: [],
type_tag: None,
inner: None,
}
thread 'deser::tests::test_deserialize_basic' panicked at lib/saluki-config2/src/deser.rs:376:63:
called `Result::unwrap()` on an `Err` value: Opaque types are not supported.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment