aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Crichton <alex@alexcrichton.com>2017-02-10 15:29:36 -0800
committerAlex Crichton <alex@alexcrichton.com>2017-02-10 15:29:36 -0800
commit79fc6c3fcbc2e77d25e18d5ef5bcd160ff9c900a (patch)
tree068c0cb7d5bf10b05154e0447b280d5b91ee2b47
parentf4f0c2ca7bdc0d04d3c4947d30a2cbfbfd024e16 (diff)
downloadmilf-rs-79fc6c3fcbc2e77d25e18d5ef5bcd160ff9c900a.tar.gz
milf-rs-79fc6c3fcbc2e77d25e18d5ef5bcd160ff9c900a.zip
Add a serialization helper to put tables last
This should help serializing maps where it's unknown up front whether the tables and/or values come first. Closes #142
-rw-r--r--src/ser.rs268
-rw-r--r--tests/tables-last.rs30
2 files changed, 296 insertions, 2 deletions
diff --git a/src/ser.rs b/src/ser.rs
index 7ca19f8..9460704 100644
--- a/src/ser.rs
+++ b/src/ser.rs
@@ -3,10 +3,33 @@
//! This module contains all the Serde support for serializing Rust structures
//! into TOML documents (as strings). Note that some top-level functions here
//! are also provided at the top of the crate.
+//!
+//! Note that the TOML format has a restriction that if a table itself contains
+//! tables, all keys with non-table values must be emitted first. This is
+//! typically easy to ensure happens when you're defining a `struct` as you can
+//! reorder the fields manually, but when working with maps (such as `BTreeMap`
+//! or `HashMap`) this can lead to serialization errors. In those situations you
+//! may use the `tables_last` function in this module like so:
+//!
+//! ```rust
+//! # #[macro_use] extern crate serde_derive;
+//! # extern crate toml;
+//! # use std::collections::HashMap;
+//! #[derive(Serialize)]
+//! struct Manifest {
+//! package: Package,
+//! #[serde(serialize_with = "toml::ser::tables_last")]
+//! dependencies: HashMap<String, Dependency>,
+//! }
+//! # type Package = String;
+//! # type Dependency = String;
+//! # fn main() {}
+//! ```
-use std::fmt::{self, Write};
-use std::error;
use std::cell::Cell;
+use std::error;
+use std::fmt::{self, Write};
+use std::marker;
use serde::ser;
use datetime::{SERDE_STRUCT_FIELD_NAME, SERDE_STRUCT_NAME};
@@ -1004,3 +1027,244 @@ impl ser::Error for Error {
Error::Custom(msg.to_string())
}
}
+
+enum Category {
+ Primitive,
+ Array,
+ Table,
+}
+
+/// Convenience function to serialize items in a map in an order valid with
+/// TOML.
+///
+/// TOML carries the restriction that keys in a table must be serialized last if
+/// their value is a table itself. This isn't always easy to guarantee, so this
+/// helper can be used like so:
+///
+/// ```rust
+/// # #[macro_use] extern crate serde_derive;
+/// # extern crate toml;
+/// # use std::collections::HashMap;
+/// #[derive(Serialize)]
+/// struct Manifest {
+/// package: Package,
+/// #[serde(serialize_with = "toml::ser::tables_last")]
+/// dependencies: HashMap<String, Dependency>,
+/// }
+/// # type Package = String;
+/// # type Dependency = String;
+/// # fn main() {}
+/// ```
+pub fn tables_last<'a, I, K, V, S>(data: &'a I, serializer: S)
+ -> Result<S::Ok, S::Error>
+ where &'a I: IntoIterator<Item = (K, V)>,
+ K: ser::Serialize,
+ V: ser::Serialize,
+ S: ser::Serializer
+{
+ use serde::ser::SerializeMap;
+
+ let mut map = serializer.serialize_map(None)?;
+ for (k, v) in data {
+ if let Category::Primitive = v.serialize(Categorize::new())? {
+ map.serialize_entry(&k, &v)?;
+ }
+ }
+ for (k, v) in data {
+ if let Category::Array = v.serialize(Categorize::new())? {
+ map.serialize_entry(&k, &v)?;
+ }
+ }
+ for (k, v) in data {
+ if let Category::Table = v.serialize(Categorize::new())? {
+ map.serialize_entry(&k, &v)?;
+ }
+ }
+ map.end()
+}
+
+struct Categorize<E>(marker::PhantomData<E>);
+
+impl<E> Categorize<E> {
+ fn new() -> Self {
+ Categorize(marker::PhantomData)
+ }
+}
+
+impl<E: ser::Error> ser::Serializer for Categorize<E> {
+ type Ok = Category;
+ type Error = E;
+ type SerializeSeq = Self;
+ type SerializeTuple = ser::Impossible<Category, E>;
+ type SerializeTupleStruct = ser::Impossible<Category, E>;
+ type SerializeTupleVariant = ser::Impossible<Category, E>;
+ type SerializeMap = Self;
+ type SerializeStruct = Self;
+ type SerializeStructVariant = ser::Impossible<Category, E>;
+
+ fn serialize_bool(self, _: bool) -> Result<Self::Ok, Self::Error> {
+ Ok(Category::Primitive)
+ }
+
+ fn serialize_i8(self, _: i8) -> Result<Self::Ok, Self::Error> {
+ Ok(Category::Primitive)
+ }
+
+ fn serialize_i16(self, _: i16) -> Result<Self::Ok, Self::Error> {
+ Ok(Category::Primitive)
+ }
+
+ fn serialize_i32(self, _: i32) -> Result<Self::Ok, Self::Error> {
+ Ok(Category::Primitive)
+ }
+
+ fn serialize_i64(self, _: i64) -> Result<Self::Ok, Self::Error> {
+ Ok(Category::Primitive)
+ }
+
+ fn serialize_u8(self, _: u8) -> Result<Self::Ok, Self::Error> {
+ Ok(Category::Primitive)
+ }
+
+ fn serialize_u16(self, _: u16) -> Result<Self::Ok, Self::Error> {
+ Ok(Category::Primitive)
+ }
+
+ fn serialize_u32(self, _: u32) -> Result<Self::Ok, Self::Error> {
+ Ok(Category::Primitive)
+ }
+
+ fn serialize_u64(self, _: u64) -> Result<Self::Ok, Self::Error> {
+ Ok(Category::Primitive)
+ }
+
+ fn serialize_f32(self, _: f32) -> Result<Self::Ok, Self::Error> {
+ Ok(Category::Primitive)
+ }
+
+ fn serialize_f64(self, _: f64) -> Result<Self::Ok, Self::Error> {
+ Ok(Category::Primitive)
+ }
+
+ fn serialize_char(self, _: char) -> Result<Self::Ok, Self::Error> {
+ Ok(Category::Primitive)
+ }
+
+ fn serialize_str(self, _: &str) -> Result<Self::Ok, Self::Error> {
+ Ok(Category::Primitive)
+ }
+
+ fn serialize_bytes(self, _: &[u8]) -> Result<Self::Ok, Self::Error> {
+ Err(ser::Error::custom("unsupported"))
+ }
+
+ fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
+ Err(ser::Error::custom("unsupported"))
+ }
+
+ fn serialize_some<T: ?Sized + ser::Serialize>(self, v: &T) -> Result<Self::Ok, Self::Error> {
+ v.serialize(self)
+ }
+
+ fn serialize_unit(self) -> Result<Self::Ok, Self::Error> {
+ Err(ser::Error::custom("unsupported"))
+ }
+
+ fn serialize_unit_struct(self, _: &'static str) -> Result<Self::Ok, Self::Error> {
+ Err(ser::Error::custom("unsupported"))
+ }
+
+ fn serialize_unit_variant(self, _: &'static str, _: usize, _: &'static str) -> Result<Self::Ok, Self::Error> {
+ Err(ser::Error::custom("unsupported"))
+ }
+
+ fn serialize_newtype_struct<T: ?Sized + ser::Serialize>(self, _: &'static str, v: &T) -> Result<Self::Ok, Self::Error> {
+ v.serialize(self)
+ }
+
+ fn serialize_newtype_variant<T: ?Sized + ser::Serialize>(self, _: &'static str, _: usize, _: &'static str, _: &T) -> Result<Self::Ok, Self::Error> {
+ Err(ser::Error::custom("unsupported"))
+ }
+
+ fn serialize_seq(self, _: Option<usize>) -> Result<Self, Self::Error> {
+ Ok(self)
+ }
+
+ fn serialize_seq_fixed_size(self, _: usize) -> Result<Self, Self::Error> {
+ Ok(self)
+ }
+
+ fn serialize_tuple(self, _: usize) -> Result<Self::SerializeTuple, Self::Error> {
+ Err(ser::Error::custom("unsupported"))
+ }
+
+ fn serialize_tuple_struct(self, _: &'static str, _: usize) -> Result<Self::SerializeTupleStruct, Self::Error> {
+ Err(ser::Error::custom("unsupported"))
+ }
+
+ fn serialize_tuple_variant(self, _: &'static str, _: usize, _: &'static str, _: usize) -> Result<Self::SerializeTupleVariant, Self::Error> {
+ Err(ser::Error::custom("unsupported"))
+ }
+
+ fn serialize_map(self, _: Option<usize>) -> Result<Self, Self::Error> {
+ Ok(self)
+ }
+
+ fn serialize_struct(self, _: &'static str, _: usize) -> Result<Self, Self::Error> {
+ Ok(self)
+ }
+
+ fn serialize_struct_variant(self, _: &'static str, _: usize, _: &'static str, _: usize) -> Result<Self::SerializeStructVariant, Self::Error> {
+ Err(ser::Error::custom("unsupported"))
+ }
+}
+
+impl<E: ser::Error> ser::SerializeSeq for Categorize<E> {
+ type Ok = Category;
+ type Error = E;
+
+ fn serialize_element<T: ?Sized + ser::Serialize>(&mut self, _: &T)
+ -> Result<(), Self::Error> {
+ Ok(())
+ }
+
+ fn end(self) -> Result<Self::Ok, Self::Error> {
+ Ok(Category::Array)
+ }
+}
+
+impl<E: ser::Error> ser::SerializeMap for Categorize<E> {
+ type Ok = Category;
+ type Error = E;
+
+ fn serialize_key<T: ?Sized + ser::Serialize>(&mut self, _: &T)
+ -> Result<(), Self::Error> {
+ Ok(())
+ }
+
+ fn serialize_value<T: ?Sized + ser::Serialize>(&mut self, _: &T)
+ -> Result<(), Self::Error> {
+ Ok(())
+ }
+
+ fn end(self) -> Result<Self::Ok, Self::Error> {
+ Ok(Category::Table)
+ }
+}
+
+impl<E: ser::Error> ser::SerializeStruct for Categorize<E> {
+ type Ok = Category;
+ type Error = E;
+
+ fn serialize_field<T: ?Sized>(&mut self,
+ _: &'static str,
+ _: &T) -> Result<(), Self::Error>
+ where T: ser::Serialize,
+ {
+ Ok(())
+ }
+
+ fn end(self) -> Result<Self::Ok, Self::Error> {
+ Ok(Category::Table)
+ }
+}
diff --git a/tests/tables-last.rs b/tests/tables-last.rs
new file mode 100644
index 0000000..d05c8f0
--- /dev/null
+++ b/tests/tables-last.rs
@@ -0,0 +1,30 @@
+#[macro_use]
+extern crate serde_derive;
+extern crate toml;
+
+use std::collections::HashMap;
+
+#[derive(Serialize)]
+struct A {
+ #[serde(serialize_with = "toml::ser::tables_last")]
+ vals: HashMap<&'static str, Value>,
+}
+
+#[derive(Serialize)]
+#[serde(untagged)]
+enum Value {
+ Map(HashMap<&'static str, &'static str>),
+ Int(i32),
+}
+
+#[test]
+fn always_works() {
+ let mut a = A { vals: HashMap::new() };
+ a.vals.insert("foo", Value::Int(0));
+
+ let mut sub = HashMap::new();
+ sub.insert("foo", "bar");
+ a.vals.insert("bar", Value::Map(sub));
+
+ toml::to_string(&a).unwrap();
+}