Source code
Revision control
Copy as Markdown
Other Tools
use serde::{
de::{Deserialize, DeserializeOwned},
ser::Serialize,
};
use std::{collections::BTreeMap, fmt::Debug, fs::File, io::Cursor, path::Path};
use crate::{
stream::{private::Sealed, Event, OwnedEvent, Writer},
Date, Deserializer, Dictionary, Error, Integer, Serializer, Uid, Value,
};
struct VecWriter {
events: Vec<OwnedEvent>,
}
impl VecWriter {
pub fn new() -> VecWriter {
VecWriter { events: Vec::new() }
}
pub fn into_inner(self) -> Vec<OwnedEvent> {
self.events
}
}
impl Writer for VecWriter {
fn write_start_array(&mut self, len: Option<u64>) -> Result<(), Error> {
self.events.push(Event::StartArray(len));
Ok(())
}
fn write_start_dictionary(&mut self, len: Option<u64>) -> Result<(), Error> {
self.events.push(Event::StartDictionary(len));
Ok(())
}
fn write_end_collection(&mut self) -> Result<(), Error> {
self.events.push(Event::EndCollection);
Ok(())
}
fn write_boolean(&mut self, value: bool) -> Result<(), Error> {
self.events.push(Event::Boolean(value));
Ok(())
}
fn write_data(&mut self, value: &[u8]) -> Result<(), Error> {
self.events.push(Event::Data(value.to_owned().into()));
Ok(())
}
fn write_date(&mut self, value: Date) -> Result<(), Error> {
self.events.push(Event::Date(value));
Ok(())
}
fn write_integer(&mut self, value: Integer) -> Result<(), Error> {
self.events.push(Event::Integer(value));
Ok(())
}
fn write_real(&mut self, value: f64) -> Result<(), Error> {
self.events.push(Event::Real(value));
Ok(())
}
fn write_string(&mut self, value: &str) -> Result<(), Error> {
self.events.push(Event::String(value.to_owned().into()));
Ok(())
}
fn write_uid(&mut self, value: Uid) -> Result<(), Error> {
self.events.push(Event::Uid(value));
Ok(())
}
}
impl Sealed for VecWriter {}
fn new_serializer() -> Serializer<VecWriter> {
Serializer::new(VecWriter::new())
}
fn new_deserializer(events: Vec<OwnedEvent>) -> Deserializer<Vec<Result<OwnedEvent, Error>>> {
let result_events = events.into_iter().map(Ok).collect();
Deserializer::new(result_events)
}
fn assert_roundtrip<T>(obj: T, comparison: Option<&[Event]>)
where
T: Debug + DeserializeOwned + PartialEq + Serialize,
{
let mut se = new_serializer();
obj.serialize(&mut se).unwrap();
let events = se.into_inner().into_inner();
if let Some(comparison) = comparison {
assert_eq!(&events[..], comparison);
}
let mut de = new_deserializer(events);
let new_obj = T::deserialize(&mut de).unwrap();
assert_eq!(new_obj, obj);
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
enum Animal {
Cow,
Dog(DogOuter),
Frog(Result<String, bool>, Option<Vec<f64>>),
Cat {
age: Integer,
name: String,
firmware: Option<Vec<u8>>,
},
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct DogOuter {
inner: Vec<DogInner>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct DogInner {
a: (),
b: usize,
c: Vec<String>,
d: Option<Uid>,
}
#[test]
fn cow() {
let cow = Animal::Cow;
let comparison = &[Event::String("Cow".into())];
assert_roundtrip(cow, Some(comparison));
}
#[test]
fn dog() {
let dog = Animal::Dog(DogOuter {
inner: vec![DogInner {
a: (),
b: 12,
c: vec!["a".to_string(), "b".to_string()],
d: Some(Uid::new(42)),
}],
});
let comparison = &[
Event::StartDictionary(Some(1)),
Event::String("Dog".into()),
Event::StartDictionary(None),
Event::String("inner".into()),
Event::StartArray(Some(1)),
Event::StartDictionary(None),
Event::String("a".into()),
Event::String("".into()),
Event::String("b".into()),
Event::Integer(12.into()),
Event::String("c".into()),
Event::StartArray(Some(2)),
Event::String("a".into()),
Event::String("b".into()),
Event::EndCollection,
Event::String("d".into()),
Event::Uid(Uid::new(42)),
Event::EndCollection,
Event::EndCollection,
Event::EndCollection,
Event::EndCollection,
];
assert_roundtrip(dog, Some(comparison));
}
#[test]
fn frog() {
let frog = Animal::Frog(
Ok("hello".to_owned()),
Some(vec![1.0, 2.0, 3.14159, 0.000000001, 1.27e31]),
);
let comparison = &[
Event::StartDictionary(Some(1)),
Event::String("Frog".into()),
Event::StartArray(Some(2)),
Event::StartDictionary(Some(1)),
Event::String("Ok".into()),
Event::String("hello".into()),
Event::EndCollection,
Event::StartDictionary(Some(1)),
Event::String("Some".into()),
Event::StartArray(Some(5)),
Event::Real(1.0),
Event::Real(2.0),
Event::Real(3.14159),
Event::Real(0.000000001),
Event::Real(1.27e31),
Event::EndCollection,
Event::EndCollection,
Event::EndCollection,
Event::EndCollection,
];
assert_roundtrip(frog, Some(comparison));
}
#[test]
fn cat_with_firmware() {
let cat = Animal::Cat {
age: 12.into(),
name: "Paws".to_owned(),
firmware: Some(vec![0, 1, 2, 3, 4, 5, 6, 7, 8]),
};
let comparison = &[
Event::StartDictionary(Some(1)),
Event::String("Cat".into()),
Event::StartDictionary(None),
Event::String("age".into()),
Event::Integer(12.into()),
Event::String("name".into()),
Event::String("Paws".into()),
Event::String("firmware".into()),
Event::StartArray(Some(9)),
Event::Integer(0.into()),
Event::Integer(1.into()),
Event::Integer(2.into()),
Event::Integer(3.into()),
Event::Integer(4.into()),
Event::Integer(5.into()),
Event::Integer(6.into()),
Event::Integer(7.into()),
Event::Integer(8.into()),
Event::EndCollection,
Event::EndCollection,
Event::EndCollection,
];
assert_roundtrip(cat, Some(comparison));
}
#[test]
fn cat_without_firmware() {
let cat = Animal::Cat {
age: Integer::from(-12),
name: "Paws".to_owned(),
firmware: None,
};
let comparison = &[
Event::StartDictionary(Some(1)),
Event::String("Cat".into()),
Event::StartDictionary(None),
Event::String("age".into()),
Event::Integer(Integer::from(-12)),
Event::String("name".into()),
Event::String("Paws".into()),
Event::EndCollection,
Event::EndCollection,
];
assert_roundtrip(cat, Some(comparison));
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct NewtypeStruct(NewtypeInner);
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct NewtypeInner(u8, u8, u8);
#[test]
fn newtype_struct() {
let newtype = NewtypeStruct(NewtypeInner(34, 32, 13));
let comparison = &[
Event::StartArray(Some(3)),
Event::Integer(34.into()),
Event::Integer(32.into()),
Event::Integer(13.into()),
Event::EndCollection,
];
assert_roundtrip(newtype, Some(comparison));
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct TypeWithOptions {
a: Option<String>,
b: Option<Option<u32>>,
c: Option<Box<TypeWithOptions>>,
}
#[test]
fn type_with_options() {
let inner = TypeWithOptions {
a: None,
b: Some(Some(12)),
c: None,
};
let obj = TypeWithOptions {
a: Some("hello".to_owned()),
b: Some(None),
c: Some(Box::new(inner)),
};
let comparison = &[
Event::StartDictionary(None),
Event::String("a".into()),
Event::String("hello".into()),
Event::String("b".into()),
Event::StartDictionary(Some(1)),
Event::String("None".into()),
Event::String("".into()),
Event::EndCollection,
Event::String("c".into()),
Event::StartDictionary(None),
Event::String("b".into()),
Event::StartDictionary(Some(1)),
Event::String("Some".into()),
Event::Integer(12.into()),
Event::EndCollection,
Event::EndCollection,
Event::EndCollection,
];
assert_roundtrip(obj, Some(comparison));
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct TypeWithDate {
a: Option<i32>,
b: Option<Date>,
}
#[test]
fn type_with_date() {
let date = Date::from_rfc3339("1920-01-01T00:10:00Z").unwrap();
let obj = TypeWithDate {
a: Some(28),
b: Some(date.clone()),
};
let comparison = &[
Event::StartDictionary(None),
Event::String("a".into()),
Event::Integer(28.into()),
Event::String("b".into()),
Event::Date(date),
Event::EndCollection,
];
assert_roundtrip(obj, Some(comparison));
}
#[test]
fn option_some() {
let obj = Some(12);
let comparison = &[Event::Integer(12.into())];
assert_roundtrip(obj, Some(comparison));
}
#[test]
fn option_none() {
let obj: Option<u32> = None;
let comparison = &[];
assert_roundtrip(obj, Some(comparison));
}
#[test]
fn option_some_some() {
let obj = Some(Some(12));
let comparison = &[
Event::StartDictionary(Some(1)),
Event::String("Some".into()),
Event::Integer(12.into()),
Event::EndCollection,
];
assert_roundtrip(obj, Some(comparison));
}
#[test]
fn option_some_none() {
let obj: Option<Option<u32>> = Some(None);
let comparison = &[
Event::StartDictionary(Some(1)),
Event::String("None".into()),
Event::String("".into()),
Event::EndCollection,
];
assert_roundtrip(obj, Some(comparison));
}
#[test]
fn option_dictionary_values() {
let mut obj = BTreeMap::new();
obj.insert("a".to_owned(), None);
obj.insert("b".to_owned(), Some(None));
obj.insert("c".to_owned(), Some(Some(144)));
let comparison = &[
Event::StartDictionary(Some(3)),
Event::String("a".into()),
Event::StartDictionary(Some(1)),
Event::String("None".into()),
Event::String("".into()),
Event::EndCollection,
Event::String("b".into()),
Event::StartDictionary(Some(1)),
Event::String("Some".into()),
Event::StartDictionary(Some(1)),
Event::String("None".into()),
Event::String("".into()),
Event::EndCollection,
Event::EndCollection,
Event::String("c".into()),
Event::StartDictionary(Some(1)),
Event::String("Some".into()),
Event::StartDictionary(Some(1)),
Event::String("Some".into()),
Event::Integer(144.into()),
Event::EndCollection,
Event::EndCollection,
Event::EndCollection,
];
assert_roundtrip(obj, Some(comparison));
}
#[test]
fn option_dictionary_keys() {
let mut obj = BTreeMap::new();
obj.insert(None, 1);
obj.insert(Some(None), 2);
obj.insert(Some(Some(144)), 3);
let comparison = &[
Event::StartDictionary(Some(3)),
Event::StartDictionary(Some(1)),
Event::String("None".into()),
Event::String("".into()),
Event::EndCollection,
Event::Integer(1.into()),
Event::StartDictionary(Some(1)),
Event::String("Some".into()),
Event::StartDictionary(Some(1)),
Event::String("None".into()),
Event::String("".into()),
Event::EndCollection,
Event::EndCollection,
Event::Integer(2.into()),
Event::StartDictionary(Some(1)),
Event::String("Some".into()),
Event::StartDictionary(Some(1)),
Event::String("Some".into()),
Event::Integer(144.into()),
Event::EndCollection,
Event::EndCollection,
Event::Integer(3.into()),
Event::EndCollection,
];
assert_roundtrip(obj, Some(comparison));
}
#[test]
fn option_array() {
let obj = vec![None, Some(None), Some(Some(144))];
let comparison = &[
Event::StartArray(Some(3)),
Event::StartDictionary(Some(1)),
Event::String("None".into()),
Event::String("".into()),
Event::EndCollection,
Event::StartDictionary(Some(1)),
Event::String("Some".into()),
Event::StartDictionary(Some(1)),
Event::String("None".into()),
Event::String("".into()),
Event::EndCollection,
Event::EndCollection,
Event::StartDictionary(Some(1)),
Event::String("Some".into()),
Event::StartDictionary(Some(1)),
Event::String("Some".into()),
Event::Integer(144.into()),
Event::EndCollection,
Event::EndCollection,
Event::EndCollection,
];
assert_roundtrip(obj, Some(comparison));
}
#[test]
fn enum_variant_types() {
#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
enum Foo {
Unit,
Newtype(u32),
Tuple(u32, String),
Struct { v: u32, s: String },
}
let expected = &[Event::String("Unit".into())];
assert_roundtrip(Foo::Unit, Some(expected));
let expected = &[
Event::StartDictionary(Some(1)),
Event::String("Newtype".into()),
Event::Integer(42.into()),
Event::EndCollection,
];
assert_roundtrip(Foo::Newtype(42), Some(expected));
let expected = &[
Event::StartDictionary(Some(1)),
Event::String("Tuple".into()),
Event::StartArray(Some(2)),
Event::Integer(42.into()),
Event::String("bar".into()),
Event::EndCollection,
Event::EndCollection,
];
assert_roundtrip(Foo::Tuple(42, "bar".into()), Some(expected));
let expected = &[
Event::StartDictionary(Some(1)),
Event::String("Struct".into()),
Event::StartDictionary(None),
Event::String("v".into()),
Event::Integer(42.into()),
Event::String("s".into()),
Event::String("bar".into()),
Event::EndCollection,
Event::EndCollection,
];
assert_roundtrip(
Foo::Struct {
v: 42,
s: "bar".into(),
},
Some(expected),
);
}
#[test]
fn deserialise_old_enum_unit_variant_encoding() {
#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
enum Foo {
Bar,
Baz,
}
// `plist` before v1.1 serialised unit enum variants as if they were newtype variants
// containing an empty string.
let events = &[
Event::StartDictionary(Some(1)),
Event::String("Baz".into()),
Event::String("".into()),
Event::EndCollection,
];
let mut de = new_deserializer(events.to_vec());
let obj = Foo::deserialize(&mut de).unwrap();
assert_eq!(obj, Foo::Baz);
}
#[test]
fn deserialize_dictionary_xml() {
let reader = File::open(&Path::new("./tests/data/xml.plist")).unwrap();
let dict: Dictionary = crate::from_reader(reader).unwrap();
check_common_plist(&dict);
// xml.plist has this member, but binary.plist does not.
assert_eq!(
dict.get("HexademicalNumber") // sic
.unwrap()
.as_unsigned_integer()
.unwrap(),
0xDEADBEEF
);
}
#[test]
fn deserialize_dictionary_binary() {
let reader = File::open(&Path::new("./tests/data/binary.plist")).unwrap();
let dict: Dictionary = crate::from_reader(reader).unwrap();
check_common_plist(&dict);
}
// Shared checks used by the tests deserialize_dictionary_xml() and
// deserialize_dictionary_binary(), which load files with different formats
// but the same data elements.
fn check_common_plist(dict: &Dictionary) {
// Array elements
let lines = dict.get("Lines").unwrap().as_array().unwrap();
assert_eq!(lines.len(), 2);
assert_eq!(
lines[0].as_string().unwrap(),
"It is a tale told by an idiot,"
);
assert_eq!(
lines[1].as_string().unwrap(),
"Full of sound and fury, signifying nothing."
);
// Dictionary
//
// There is no embedded dictionary in this plist. See
// deserialize_dictionary_binary_nskeyedarchiver() below for an example
// of that.
// Boolean elements
assert_eq!(dict.get("IsTrue").unwrap().as_boolean().unwrap(), true);
assert_eq!(dict.get("IsNotFalse").unwrap().as_boolean().unwrap(), false);
// Data
let data = dict.get("Data").unwrap().as_data().unwrap();
assert_eq!(data.len(), 15);
assert_eq!(
data,
&[0, 0, 0, 0xbe, 0, 0, 0, 0x03, 0, 0, 0, 0x1e, 0, 0, 0]
);
// Date
assert_eq!(
dict.get("Birthdate").unwrap().as_date().unwrap(),
Date::from_rfc3339("1981-05-16T11:32:06Z").unwrap()
);
// Real
assert_eq!(dict.get("Height").unwrap().as_real().unwrap(), 1.6);
// Integer
assert_eq!(
dict.get("BiggestNumber")
.unwrap()
.as_unsigned_integer()
.unwrap(),
18446744073709551615
);
assert_eq!(
dict.get("Death").unwrap().as_unsigned_integer().unwrap(),
1564
);
assert_eq!(
dict.get("SmallestNumber")
.unwrap()
.as_signed_integer()
.unwrap(),
-9223372036854775808
);
// String
assert_eq!(
dict.get("Author").unwrap().as_string().unwrap(),
"William Shakespeare"
);
assert_eq!(dict.get("Blank").unwrap().as_string().unwrap(), "");
// Uid
//
// No checks for Uid value type in this test. See
// deserialize_dictionary_binary_nskeyedarchiver() below for an example
// of that.
}
#[test]
fn deserialize_dictionary_binary_nskeyedarchiver() {
let reader = File::open(&Path::new("./tests/data/binary_NSKeyedArchiver.plist")).unwrap();
let dict: Dictionary = crate::from_reader(reader).unwrap();
assert_eq!(
dict.get("$archiver").unwrap().as_string().unwrap(),
"NSKeyedArchiver"
);
let objects = dict.get("$objects").unwrap().as_array().unwrap();
assert_eq!(objects.len(), 5);
assert_eq!(objects[0].as_string().unwrap(), "$null");
let objects_1 = objects[1].as_dictionary().unwrap();
assert_eq!(
*objects_1.get("$class").unwrap().as_uid().unwrap(),
Uid::new(4)
);
assert_eq!(
objects_1
.get("NSRangeCount")
.unwrap()
.as_unsigned_integer()
.unwrap(),
42
);
assert_eq!(
*objects_1.get("NSRangeData").unwrap().as_uid().unwrap(),
Uid::new(2)
);
let objects_2 = objects[2].as_dictionary().unwrap();
assert_eq!(
*objects_2.get("$class").unwrap().as_uid().unwrap(),
Uid::new(3)
);
let objects_2_nsdata = objects_2.get("NS.data").unwrap().as_data().unwrap();
assert_eq!(objects_2_nsdata.len(), 103);
assert_eq!(objects_2_nsdata[0], 0x03);
assert_eq!(objects_2_nsdata[102], 0x01);
let objects_3 = objects[3].as_dictionary().unwrap();
let objects_3_classes = objects_3.get("$classes").unwrap().as_array().unwrap();
assert_eq!(objects_3_classes.len(), 3);
assert_eq!(objects_3_classes[0].as_string().unwrap(), "NSMutableData");
assert_eq!(objects_3_classes[1].as_string().unwrap(), "NSData");
assert_eq!(objects_3_classes[2].as_string().unwrap(), "NSObject");
assert_eq!(
objects_3.get("$classname").unwrap().as_string().unwrap(),
"NSMutableData"
);
let objects_4 = objects[4].as_dictionary().unwrap();
let objects_4_classes = objects_4.get("$classes").unwrap().as_array().unwrap();
assert_eq!(objects_4_classes.len(), 3);
assert_eq!(
objects_4_classes[0].as_string().unwrap(),
"NSMutableIndexSet"
);
assert_eq!(objects_4_classes[1].as_string().unwrap(), "NSIndexSet");
assert_eq!(objects_4_classes[2].as_string().unwrap(), "NSObject");
assert_eq!(
objects_4.get("$classname").unwrap().as_string().unwrap(),
"NSMutableIndexSet"
);
let top = dict.get("$top").unwrap().as_dictionary().unwrap();
assert_eq!(
*top.get("foundItems").unwrap().as_uid().unwrap(),
Uid::new(1)
);
let version = dict.get("$version").unwrap().as_unsigned_integer().unwrap();
assert_eq!(version, 100000);
}
#[test]
fn dictionary_deserialize_dictionary_in_struct() {
#[derive(Deserialize)]
struct LayerinfoData {
color: Option<String>,
lib: Option<Dictionary>,
}
let lib_dict: LayerinfoData = crate::from_bytes(r#"
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>color</key>
<string>1,0.75,0,0.7</string>
<key>lib</key>
<dict>
<key>com.typemytype.robofont.segmentType</key>
<string>curve</string>
</dict>
</dict>
</plist>
"#.as_bytes()).unwrap();
assert_eq!(lib_dict.color.unwrap(), "1,0.75,0,0.7");
assert_eq!(
lib_dict
.lib
.unwrap()
.get("com.typemytype.robofont.segmentType")
.unwrap()
.as_string()
.unwrap(),
"curve"
);
}
#[test]
fn dictionary_serialize_xml() {
// Dictionary to be embedded in dict, below.
let mut inner_dict = Dictionary::new();
inner_dict.insert(
"FirstKey".to_owned(),
Value::String("FirstValue".to_owned()),
);
inner_dict.insert("SecondKey".to_owned(), Value::Data(vec![10, 20, 30, 40]));
inner_dict.insert("ThirdKey".to_owned(), Value::Real(1.234));
inner_dict.insert(
"FourthKey".to_owned(),
Value::Date(Date::from_rfc3339("1981-05-16T11:32:06Z").unwrap()),
);
// Top-level dictionary.
let mut dict = Dictionary::new();
dict.insert(
"AnArray".to_owned(),
Value::Array(vec![
Value::String("Hello, world!".to_owned()),
Value::Integer(Integer::from(345)),
]),
);
dict.insert("ADictionary".to_owned(), Value::Dictionary(inner_dict));
dict.insert("AnInteger".to_owned(), Value::Integer(Integer::from(123)));
dict.insert("ATrueBoolean".to_owned(), Value::Boolean(true));
dict.insert("AFalseBoolean".to_owned(), Value::Boolean(false));
// Serialize dictionary as an XML plist.
let mut buf = Cursor::new(Vec::new());
crate::to_writer_xml(&mut buf, &dict).unwrap();
let buf = buf.into_inner();
let xml = std::str::from_utf8(&buf).unwrap();
let comparison = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
<plist version=\"1.0\">
<dict>
\t<key>AnArray</key>
\t<array>
\t\t<string>Hello, world!</string>
\t\t<integer>345</integer>
\t</array>
\t<key>ADictionary</key>
\t<dict>
\t\t<key>FirstKey</key>
\t\t<string>FirstValue</string>
\t\t<key>SecondKey</key>
\t\t<data>\n\t\tChQeKA==\n\t\t</data>
\t\t<key>ThirdKey</key>
\t\t<real>1.234</real>
\t\t<key>FourthKey</key>
\t\t<date>1981-05-16T11:32:06Z</date>
\t</dict>
\t<key>AnInteger</key>
\t<integer>123</integer>
\t<key>ATrueBoolean</key>
\t<true/>
\t<key>AFalseBoolean</key>
\t<false/>
</dict>
</plist>";
assert_eq!(xml, comparison);
}
#[test]
fn serde_yaml_to_value() {
let value: Value = serde_yaml::from_str("true").unwrap();
assert_eq!(value, Value::Boolean(true));
}