dtolnay / erased-serde

Type-erased Serialize, Serializer and Deserializer traits
Apache License 2.0
709 stars 36 forks source link

Make `Serialize*` traits public #102

Open oscartbeaumont opened 9 months ago

oscartbeaumont commented 9 months ago

I am working on a library that I would like the library to remain format-agnostic. To do this I am using erased-serde to "inject" the format at runtime as a &mut dyn Serializer.

In my library the user defines a schema which looks something like this and the resolve function would be implemented by them:

pub struct Schema {
   types: Vec<Type>
}

pub struct Type {
   name: Cow<'static, str>,
   resolve: Box<dyn Fn(Serializer)> // `Serializer` is shown in the next code snippet
}

As discussed in #39 erased-serde isn't something that should be in the public API so I am wrapping all of its types. This also has the added side-effect that I can require self instead of &mut self for extra type safety.

An example of what I am doing is below.

pub struct Serializer<'a>(&'a mut dyn erased_serde::Serializer);

impl<'a> Serializer<'a> {
    pub fn serialize_bool(self, value: bool) {
        self.0.erased_serialize_bool(value)
    }

    // ...
}

However, right now I can only deal with primitive types as SerializeMap and similar types are private. This PR makes them public so the following code would compile.

pub struct SerializeMap<'a>(&'a mut dyn erased_serde::SerializeMap);
greenboxal commented 1 month ago

fwiw, I had to do the following so I could delegate the serialization to a dynamic vtable:

impl<'a> serde::Serialize for Object<'a> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer
    {
        let mut erased = erased_serde::SerializerImpl::new(serializer);

        match (self.0.vtable.serialize)(self, &mut erased) {
            Ok(()) => {}
            Err(err) => {
                let err: erased_serde::ErrorImpl = err.into();

                match err {
                    erased_serde::ErrorImpl::Custom(msg) => return Err(serde::ser::Error::custom(msg)),
                    erased_serde::ErrorImpl::ShortCircuit => {}
                }
            }
        }

        match erased {
            erased_serde::SerializerImpl::Complete(ok) => Ok(ok),
            erased_serde::SerializerImpl::Error(err) => Err(err),
            _ => unreachable!(),
        }
    }
}

This meant I had to copy the serialize fn, and needed a few more types to be public. I can push a PR but I'm also not sure this is the best because of the error handling