auto merge of #4840 : jbclements/rust/add-json-enum-encoding, r=catamorphism
r? I added code to the JSON encoder to support the serialization of enums. Before this, the JSON serializer only handled Option, and encoded None as 'null'. Following this change, all enums are encoded as arrays containing the enum name followed by the encoded fields. This appears consistent with the unstated invariant that the resulting output can be mapped back to the input *if* there's a decoder around that knows the types that were in existence when the serialization occurred. Also, added test cases.
This commit is contained in:
commit
5e6d7871c6
@ -121,19 +121,49 @@ pub impl Encoder: serialize::Encoder {
|
||||
fn emit_owned(&self, f: fn()) { f() }
|
||||
fn emit_managed(&self, f: fn()) { f() }
|
||||
|
||||
fn emit_enum(&self, name: &str, f: fn()) {
|
||||
if name != "option" { die!(~"only supports option enum") }
|
||||
fn emit_enum(&self, _name: &str, f: fn()) {
|
||||
f()
|
||||
}
|
||||
fn emit_enum_variant(&self, _name: &str, id: uint, _cnt: uint, f: fn()) {
|
||||
if id == 0 {
|
||||
self.emit_nil();
|
||||
|
||||
fn emit_enum_variant(&self, name: &str, _id: uint, _cnt: uint, f: fn()) {
|
||||
// encoding of enums is special-cased for Option. Specifically:
|
||||
// Some(34) => 34
|
||||
// None => null
|
||||
|
||||
// other enums are encoded as vectors:
|
||||
// Kangaroo(34,"William") => ["Kangaroo",[34,"William"]]
|
||||
|
||||
// the default expansion for enums is more verbose than I'd like;
|
||||
// specifically, the inner pair of brackets seems superfluous,
|
||||
// BUT the design of the enumeration framework and the requirements
|
||||
// of the special-case for Option mean that a first argument must
|
||||
// be encoded "naked"--with no commas--and that the option name
|
||||
// can't be followed by just a comma, because there might not
|
||||
// be any elements in the tuple.
|
||||
|
||||
// FIXME #4872: this would be more precise and less frightening
|
||||
// with fully-qualified option names. To get that information,
|
||||
// we'd have to change the expansion of auto-encode to pass
|
||||
// those along.
|
||||
|
||||
if (name == ~"Some") {
|
||||
f();
|
||||
} else if (name == ~"None") {
|
||||
self.wr.write_str(~"null");
|
||||
} else {
|
||||
f()
|
||||
self.wr.write_char('[');
|
||||
self.wr.write_str(escape_str(name));
|
||||
self.wr.write_char(',');
|
||||
self.wr.write_char('[');
|
||||
f();
|
||||
self.wr.write_char(']');
|
||||
self.wr.write_char(']');
|
||||
}
|
||||
}
|
||||
fn emit_enum_variant_arg(&self, _idx: uint, f: fn()) {
|
||||
f()
|
||||
|
||||
fn emit_enum_variant_arg(&self, idx: uint, f: fn()) {
|
||||
if (idx != 0) {self.wr.write_char(',');}
|
||||
f();
|
||||
}
|
||||
|
||||
fn emit_borrowed_vec(&self, _len: uint, f: fn()) {
|
||||
@ -141,6 +171,7 @@ pub impl Encoder: serialize::Encoder {
|
||||
f();
|
||||
self.wr.write_char(']');
|
||||
}
|
||||
|
||||
fn emit_owned_vec(&self, len: uint, f: fn()) {
|
||||
self.emit_borrowed_vec(len, f)
|
||||
}
|
||||
@ -1180,6 +1211,8 @@ mod tests {
|
||||
|
||||
use core::result;
|
||||
use core::hashmap::linear::LinearMap;
|
||||
use core::cmp;
|
||||
|
||||
|
||||
fn mk_object(items: &[(~str, Json)]) -> Json {
|
||||
let mut d = ~LinearMap::new();
|
||||
@ -1247,6 +1280,72 @@ mod tests {
|
||||
assert a == b;
|
||||
}
|
||||
|
||||
// two fns copied from libsyntax/util/testing.rs.
|
||||
// Should they be in their own crate?
|
||||
pub pure fn check_equal_ptr<T : cmp::Eq> (given : &T, expected: &T) {
|
||||
if !((given == expected) && (expected == given )) {
|
||||
die!(fmt!("given %?, expected %?",given,expected));
|
||||
}
|
||||
}
|
||||
|
||||
pub pure fn check_equal<T : cmp::Eq> (given : T, expected: T) {
|
||||
if !((given == expected) && (expected == given )) {
|
||||
die!(fmt!("given %?, expected %?",given,expected));
|
||||
}
|
||||
}
|
||||
|
||||
// testing both auto_encode's calling patterns
|
||||
// and json... not sure where to put these tests.
|
||||
#[test]
|
||||
fn test_write_enum () {
|
||||
let bw = @io::BytesWriter {bytes: dvec::DVec(), pos: 0};
|
||||
let bww : @io::Writer = (bw as @io::Writer);
|
||||
let encoder = (@Encoder(bww) as @serialize::Encoder);
|
||||
do encoder.emit_enum(~"animal") {
|
||||
do encoder.emit_enum_variant (~"frog",37,1242) {
|
||||
// name of frog:
|
||||
do encoder.emit_enum_variant_arg (0) {
|
||||
encoder.emit_owned_str(~"Henry")
|
||||
}
|
||||
// mass of frog in grams:
|
||||
do encoder.emit_enum_variant_arg (1) {
|
||||
encoder.emit_int(349);
|
||||
}
|
||||
}
|
||||
}
|
||||
check_equal(str::from_bytes(bw.bytes.data),
|
||||
~"[\"frog\",[\"Henry\",349]]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_some () {
|
||||
let bw = @io::BytesWriter {bytes: dvec::DVec(), pos: 0};
|
||||
let bww : @io::Writer = (bw as @io::Writer);
|
||||
let encoder = (@Encoder(bww) as @serialize::Encoder);
|
||||
do encoder.emit_enum(~"Option") {
|
||||
do encoder.emit_enum_variant (~"Some",37,1242) {
|
||||
do encoder.emit_enum_variant_arg (0) {
|
||||
encoder.emit_owned_str(~"jodhpurs")
|
||||
}
|
||||
}
|
||||
}
|
||||
check_equal(str::from_bytes(bw.bytes.data),
|
||||
~"\"jodhpurs\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_none () {
|
||||
let bw = @io::BytesWriter {bytes: dvec::DVec(), pos: 0};
|
||||
let bww : @io::Writer = (bw as @io::Writer);
|
||||
let encoder = (@Encoder(bww) as @serialize::Encoder);
|
||||
do encoder.emit_enum(~"Option") {
|
||||
do encoder.emit_enum_variant (~"None",37,1242) {
|
||||
}
|
||||
}
|
||||
check_equal(str::from_bytes(bw.bytes.data),
|
||||
~"null");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trailing_characters() {
|
||||
assert from_str(~"nulla") ==
|
||||
|
@ -23,23 +23,23 @@ For example, a type like:
|
||||
|
||||
would generate two implementations like:
|
||||
|
||||
impl<S: Encoder> node_id: Encodable<S> {
|
||||
fn encode(s: &S) {
|
||||
do s.emit_struct("Node", 1) {
|
||||
s.emit_field("id", 0, || s.emit_uint(self))
|
||||
}
|
||||
impl<S: std::serialize::Encoder> Node: Encodable<S> {
|
||||
fn encode(&self, s: &S) {
|
||||
do s.emit_struct("Node", 1) {
|
||||
s.emit_field("id", 0, || s.emit_uint(self.id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Decoder> node_id: Decodable {
|
||||
static fn decode(d: &D) -> Node {
|
||||
do d.read_struct("Node", 1) {
|
||||
Node {
|
||||
id: d.read_field(~"x", 0, || decode(d))
|
||||
}
|
||||
impl<D: Decoder> node_id: Decodable {
|
||||
static fn decode(d: &D) -> Node {
|
||||
do d.read_struct("Node", 1) {
|
||||
Node {
|
||||
id: d.read_field(~"x", 0, || decode(d))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Other interesting scenarios are whe the item has type parameters or
|
||||
references other non-built-in types. A type definition like:
|
||||
@ -1150,3 +1150,154 @@ fn mk_enum_deser_body(
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::serialize::Encodable;
|
||||
use std::serialize::Encoder;
|
||||
use core::dvec::*;
|
||||
use util::testing::*;
|
||||
use core::io;
|
||||
use core::str;
|
||||
use core::option::Option;
|
||||
use core::option::Some;
|
||||
use core::option::None;
|
||||
use std;
|
||||
|
||||
// just adding the ones I want to test, for now:
|
||||
#[deriving_eq]
|
||||
pub enum call {
|
||||
CallToEmitEnum(~str),
|
||||
CallToEmitEnumVariant(~str, uint, uint),
|
||||
CallToEmitEnumVariantArg(uint),
|
||||
CallToEmitUint(uint),
|
||||
CallToEmitNil,
|
||||
// all of the ones I was too lazy to handle:
|
||||
CallToOther
|
||||
}
|
||||
// using a mutable field rather than changing the
|
||||
// type of self in every method of every encoder everywhere.
|
||||
pub struct TestEncoder {mut call_log : ~[call]}
|
||||
|
||||
pub impl TestEncoder {
|
||||
// these self's should be &mut self's, as well....
|
||||
fn add_to_log (&self, c : call) {
|
||||
self.call_log.push(copy c);
|
||||
}
|
||||
fn add_unknown_to_log (&self) {
|
||||
self.add_to_log (CallToOther)
|
||||
}
|
||||
}
|
||||
|
||||
pub impl Encoder for TestEncoder {
|
||||
fn emit_nil(&self) { self.add_to_log(CallToEmitNil) }
|
||||
|
||||
fn emit_uint(&self, +v: uint) {self.add_to_log(CallToEmitUint(v)); }
|
||||
fn emit_u64(&self, +_v: u64) { self.add_unknown_to_log(); }
|
||||
fn emit_u32(&self, +_v: u32) { self.add_unknown_to_log(); }
|
||||
fn emit_u16(&self, +_v: u16) { self.add_unknown_to_log(); }
|
||||
fn emit_u8(&self, +_v: u8) { self.add_unknown_to_log(); }
|
||||
|
||||
fn emit_int(&self, +_v: int) { self.add_unknown_to_log(); }
|
||||
fn emit_i64(&self, +_v: i64) { self.add_unknown_to_log(); }
|
||||
fn emit_i32(&self, +_v: i32) { self.add_unknown_to_log(); }
|
||||
fn emit_i16(&self, +_v: i16) { self.add_unknown_to_log(); }
|
||||
fn emit_i8(&self, +_v: i8) { self.add_unknown_to_log(); }
|
||||
|
||||
fn emit_bool(&self, +_v: bool) { self.add_unknown_to_log(); }
|
||||
|
||||
fn emit_f64(&self, +_v: f64) { self.add_unknown_to_log(); }
|
||||
fn emit_f32(&self, +_v: f32) { self.add_unknown_to_log(); }
|
||||
fn emit_float(&self, +_v: float) { self.add_unknown_to_log(); }
|
||||
|
||||
fn emit_char(&self, +_v: char) { self.add_unknown_to_log(); }
|
||||
|
||||
fn emit_borrowed_str(&self, +_v: &str) { self.add_unknown_to_log(); }
|
||||
fn emit_owned_str(&self, +_v: &str) { self.add_unknown_to_log(); }
|
||||
fn emit_managed_str(&self, +_v: &str) { self.add_unknown_to_log(); }
|
||||
|
||||
fn emit_borrowed(&self, f: fn()) { self.add_unknown_to_log(); f() }
|
||||
fn emit_owned(&self, f: fn()) { self.add_unknown_to_log(); f() }
|
||||
fn emit_managed(&self, f: fn()) { self.add_unknown_to_log(); f() }
|
||||
|
||||
fn emit_enum(&self, name: &str, f: fn()) {
|
||||
self.add_to_log(CallToEmitEnum(name.to_str())); f(); }
|
||||
|
||||
fn emit_enum_variant(&self, name: &str, +id: uint,
|
||||
+cnt: uint, f: fn()) {
|
||||
self.add_to_log(CallToEmitEnumVariant (name.to_str(),id,cnt));
|
||||
f();
|
||||
}
|
||||
|
||||
fn emit_enum_variant_arg(&self, +idx: uint, f: fn()) {
|
||||
self.add_to_log(CallToEmitEnumVariantArg (idx)); f();
|
||||
}
|
||||
|
||||
fn emit_borrowed_vec(&self, +_len: uint, f: fn()) {
|
||||
self.add_unknown_to_log(); f();
|
||||
}
|
||||
|
||||
fn emit_owned_vec(&self, +_len: uint, f: fn()) {
|
||||
self.add_unknown_to_log(); f();
|
||||
}
|
||||
fn emit_managed_vec(&self, +_len: uint, f: fn()) {
|
||||
self.add_unknown_to_log(); f();
|
||||
}
|
||||
fn emit_vec_elt(&self, +_idx: uint, f: fn()) {
|
||||
self.add_unknown_to_log(); f();
|
||||
}
|
||||
|
||||
fn emit_rec(&self, f: fn()) {
|
||||
self.add_unknown_to_log(); f();
|
||||
}
|
||||
fn emit_struct(&self, _name: &str, +_len: uint, f: fn()) {
|
||||
self.add_unknown_to_log(); f();
|
||||
}
|
||||
fn emit_field(&self, _name: &str, +_idx: uint, f: fn()) {
|
||||
self.add_unknown_to_log(); f();
|
||||
}
|
||||
|
||||
fn emit_tup(&self, +_len: uint, f: fn()) {
|
||||
self.add_unknown_to_log(); f();
|
||||
}
|
||||
fn emit_tup_elt(&self, +_idx: uint, f: fn()) {
|
||||
self.add_unknown_to_log(); f();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[auto_decode]
|
||||
#[auto_encode]
|
||||
struct Node {id: uint}
|
||||
|
||||
fn to_call_log (val: Encodable<TestEncoder>) -> ~[call] {
|
||||
let mut te = TestEncoder {call_log: ~[]};
|
||||
val.encode(&te);
|
||||
te.call_log
|
||||
}
|
||||
/*
|
||||
#[test] fn encode_test () {
|
||||
check_equal (to_call_log(Node{id:34}
|
||||
as Encodable::<std::json::Encoder>),
|
||||
~[CallToEnum (~"Node"),
|
||||
CallToEnumVariant]);
|
||||
}
|
||||
*/
|
||||
#[auto_encode]
|
||||
enum Written {
|
||||
Book(uint,uint),
|
||||
Magazine(~str)
|
||||
}
|
||||
|
||||
#[test] fn encode_enum_test () {
|
||||
check_equal (to_call_log(Book(34,44)
|
||||
as Encodable::<TestEncoder>),
|
||||
~[CallToEmitEnum (~"Written"),
|
||||
CallToEmitEnumVariant (~"Book",0,2),
|
||||
CallToEmitEnumVariantArg (0),
|
||||
CallToEmitUint (34),
|
||||
CallToEmitEnumVariantArg (1),
|
||||
CallToEmitUint (44)]);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user