From 266cd75e928d48bba16ad24bf25c8312bb2a4615 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 23 Jun 2014 08:38:45 -0700 Subject: [PATCH] Improve decoder errors --- src/serialization.rs | 263 ++++++++++++++++++++++++++++--------------- 1 file changed, 174 insertions(+), 89 deletions(-) diff --git a/src/serialization.rs b/src/serialization.rs index ef2ac88..f610f6e 100644 --- a/src/serialization.rs +++ b/src/serialization.rs @@ -48,6 +48,7 @@ pub struct Encoder { /// arbitrary TOML value. pub struct Decoder { toml: Option, + cur_field: String, } /// Enumeration of errors which can occur while encoding a rust value into a @@ -65,11 +66,13 @@ pub enum Error { /// Indicates that a type other than a string was attempted to be used as a /// map key type. InvalidMapKeyType, - /// Indicates that a type was decoded against a TOML value of a different - /// type. - InvalidType, - /// Indicates that a field was attempted to be read that does not exist. - MissingField, +} + +/// Description for errors which can occur while decoding a type. +#[deriving(Show)] +pub struct DecodeError { + /// Human-readable description of this error. + pub desc: String, } #[deriving(PartialEq, Show)] @@ -322,7 +325,7 @@ impl serialize::Encoder for Encoder { /// into the type specified. If decoding fails, `None` will be returned. If a /// finer-grained error is desired, then it is recommended to use `Decodable` /// directly. -pub fn decode>(toml: Value) +pub fn decode>(toml: Value) -> Option { serialize::Decodable::decode(&mut Decoder::new(toml)).ok() @@ -334,7 +337,7 @@ pub fn decode>(toml: Value) /// the TOML value into the desired type. If any error occurs `None` is return. /// If more fine-grained errors are desired, these steps should be driven /// manually. -pub fn decode_str>(s: &str) +pub fn decode_str>(s: &str) -> Option { Parser::new(s).parse().and_then(|t| decode(Table(t))) @@ -346,169 +349,207 @@ impl Decoder { /// This decoder can be passed to the `Decodable` methods or driven /// manually. pub fn new(toml: Value) -> Decoder { - Decoder { toml: Some(toml) } + Decoder { toml: Some(toml), cur_field: "".to_string() } + } + + fn sub_decoder(&self, toml: Option, field: &str) -> Decoder { + Decoder { + toml: toml, + cur_field: if self.cur_field.len() == 0 { + field.to_string() + } else if field.len() == 0 { + self.cur_field.to_string() + } else { + format!("{}.{}", self.cur_field, field) + } + } + } + + fn field_error(&self, desc: &str) -> DecodeError { + DecodeError { + desc: format!("{}", desc), + } + } + + fn type_error(&self, expected: &str, found: &Option) -> DecodeError { + DecodeError { + desc: format!("{}expected type `{}`, but found {}", + if self.cur_field.len() > 0 { + format!("for field `{}` ", self.cur_field) + } else { + "".to_string() + }, + expected, + match *found { + Some(ref val) => format!("`{}`", val.type_str()), + None => "no value".to_string(), + }) + } } } -impl serialize::Decoder for Decoder { - fn read_nil(&mut self) -> Result<(), Error> { +impl serialize::Decoder for Decoder { + fn read_nil(&mut self) -> Result<(), DecodeError> { match self.toml { Some(String(ref s)) if s.len() == 0 => Ok(()), - _ => Err(InvalidType), + Some(String(..)) => Err(self.field_error("expected 0-length string")), + ref found => Err(self.type_error("string", found)), } } - fn read_uint(&mut self) -> Result { + fn read_uint(&mut self) -> Result { self.read_i64().map(|i| i as uint) } - fn read_u64(&mut self) -> Result { + fn read_u64(&mut self) -> Result { self.read_i64().map(|i| i as u64) } - fn read_u32(&mut self) -> Result { + fn read_u32(&mut self) -> Result { self.read_i64().map(|i| i as u32) } - fn read_u16(&mut self) -> Result { + fn read_u16(&mut self) -> Result { self.read_i64().map(|i| i as u16) } - fn read_u8(&mut self) -> Result { + fn read_u8(&mut self) -> Result { self.read_i64().map(|i| i as u8) } - fn read_int(&mut self) -> Result { + fn read_int(&mut self) -> Result { self.read_i64().map(|i| i as int) } - fn read_i64(&mut self) -> Result { + fn read_i64(&mut self) -> Result { match self.toml { Some(Integer(i)) => Ok(i), - _ => Err(InvalidType), + ref found => Err(self.type_error("integer", found)), } } - fn read_i32(&mut self) -> Result { + fn read_i32(&mut self) -> Result { self.read_i64().map(|i| i as i32) } - fn read_i16(&mut self) -> Result { + fn read_i16(&mut self) -> Result { self.read_i64().map(|i| i as i16) } - fn read_i8(&mut self) -> Result { + fn read_i8(&mut self) -> Result { self.read_i64().map(|i| i as i8) } - fn read_bool(&mut self) -> Result { + fn read_bool(&mut self) -> Result { match self.toml { Some(Boolean(b)) => Ok(b), - _ => Err(InvalidType), + ref found => Err(self.type_error("bool", found)), } } - fn read_f64(&mut self) -> Result { + fn read_f64(&mut self) -> Result { match self.toml { Some(Float(f)) => Ok(f), - _ => Err(InvalidType), + ref found => Err(self.type_error("float", found)), } } - fn read_f32(&mut self) -> Result { + fn read_f32(&mut self) -> Result { self.read_f64().map(|f| f as f32) } - fn read_char(&mut self) -> Result { + fn read_char(&mut self) -> Result { match self.toml { Some(String(ref s)) if s.as_slice().char_len() == 1 => Ok(s.as_slice().char_at(0)), - _ => Err(InvalidType), + ref found => Err(self.type_error("string", found)), } } - fn read_str(&mut self) -> Result { + fn read_str(&mut self) -> Result { match self.toml.take() { Some(String(s)) => Ok(s), - toml => { self.toml = toml; Err(InvalidType) } + ref found => Err(self.type_error("string", found)), } } // Compound types: fn read_enum(&mut self, _name: &str, - _f: |&mut Decoder| -> Result) -> Result { + _f: |&mut Decoder| -> Result) + -> Result + { fail!() } fn read_enum_variant(&mut self, _names: &[&str], - _f: |&mut Decoder, uint| -> Result) - -> Result { + _f: |&mut Decoder, uint| -> Result) + -> Result { fail!() } fn read_enum_variant_arg(&mut self, _a_idx: uint, - _f: |&mut Decoder| -> Result) - -> Result { + _f: |&mut Decoder| -> Result) + -> Result { fail!() } fn read_enum_struct_variant(&mut self, _names: &[&str], - _f: |&mut Decoder, uint| -> Result) - -> Result { + _f: |&mut Decoder, uint| + -> Result) + -> Result + { fail!() } fn read_enum_struct_variant_field(&mut self, _f_name: &str, _f_idx: uint, - _f: |&mut Decoder| -> Result) - -> Result { + _f: |&mut Decoder| + -> Result) + -> Result + { fail!() } fn read_struct(&mut self, _s_name: &str, _len: uint, - f: |&mut Decoder| -> Result) - -> Result + f: |&mut Decoder| -> Result) + -> Result { match self.toml { Some(Table(..)) => f(self), - _ => Err(InvalidType), + ref found => Err(self.type_error("table", found)), } } fn read_struct_field(&mut self, f_name: &str, _f_idx: uint, - f: |&mut Decoder| -> Result) - -> Result { - match self.toml { - Some(Table(ref mut table)) => { - match table.pop(&f_name.to_string()) { - Some(field) => f(&mut Decoder::new(field)), - None => f(&mut Decoder { toml: None }), - } - } - _ => Err(InvalidType) - } + f: |&mut Decoder| -> Result) + -> Result { + let toml = match self.toml { + Some(Table(ref mut table)) => table.pop(&f_name.to_string()), + ref found => return Err(self.type_error("table", found)), + }; + f(&mut self.sub_decoder(toml, f_name)) } fn read_tuple(&mut self, - f: |&mut Decoder, uint| -> Result) - -> Result + f: |&mut Decoder, uint| -> Result) + -> Result { self.read_seq(f) } fn read_tuple_arg(&mut self, a_idx: uint, - f: |&mut Decoder| -> Result) - -> Result + f: |&mut Decoder| -> Result) + -> Result { self.read_seq_elt(a_idx, f) } fn read_tuple_struct(&mut self, _s_name: &str, - _f: |&mut Decoder, uint| -> Result) - -> Result + _f: |&mut Decoder, uint| -> Result) + -> Result { fail!() } fn read_tuple_struct_arg(&mut self, _a_idx: uint, - _f: |&mut Decoder| -> Result) - -> Result + _f: |&mut Decoder| -> Result) + -> Result { fail!() } // Specialized types: fn read_option(&mut self, - f: |&mut Decoder, bool| -> Result) - -> Result + f: |&mut Decoder, bool| -> Result) + -> Result { match self.toml { Some(..) => f(self, true), @@ -516,66 +557,71 @@ impl serialize::Decoder for Decoder { } } - fn read_seq(&mut self, f: |&mut Decoder, uint| -> Result) - -> Result + fn read_seq(&mut self, f: |&mut Decoder, uint| -> Result) + -> Result { let len = match self.toml { Some(Array(ref arr)) => arr.len(), - _ => return Err(InvalidType), + ref found => return Err(self.type_error("array", found)), }; f(self, len) } - fn read_seq_elt(&mut self, idx: uint, f: |&mut Decoder| -> Result) - -> Result + fn read_seq_elt(&mut self, idx: uint, + f: |&mut Decoder| -> Result) + -> Result { - match self.toml { - Some(Array(ref mut arr)) => { - f(&mut Decoder::new(mem::replace(arr.get_mut(idx), Integer(0)))) - } - _ => Err(InvalidType), - } + let toml = match self.toml { + Some(Array(ref mut arr)) => mem::replace(arr.get_mut(idx), Integer(0)), + ref found => return Err(self.type_error("array", found)), + }; + f(&mut self.sub_decoder(Some(toml), "")) } - fn read_map(&mut self, f: |&mut Decoder, uint| -> Result) - -> Result + fn read_map(&mut self, f: |&mut Decoder, uint| -> Result) + -> Result { let len = match self.toml { Some(Table(ref table)) => table.len(), - _ => return Err(InvalidType), + ref found => return Err(self.type_error("table", found)), }; f(self, len) } fn read_map_elt_key(&mut self, idx: uint, - f: |&mut Decoder| -> Result) - -> Result + f: |&mut Decoder| -> Result) + -> Result { match self.toml { Some(Table(ref table)) => { match table.keys().skip(idx).next() { Some(key) => { - f(&mut Decoder::new(String(key.to_str()))) + f(&mut self.sub_decoder(Some(String(key.to_string())), + key.as_slice())) } - None => Err(InvalidType), + None => Err(DecodeError { + desc: format!("map key `{}` does not exist", idx), + }), } } - _ => Err(InvalidType), + ref found => Err(self.type_error("table", found)), } } fn read_map_elt_val(&mut self, idx: uint, - f: |&mut Decoder| -> Result) - -> Result + f: |&mut Decoder| -> Result) + -> Result { match self.toml { Some(Table(ref table)) => { match table.values().skip(idx).next() { Some(key) => { // XXX: this shouldn't clone - f(&mut Decoder::new(key.clone())) + f(&mut self.sub_decoder(Some(key.clone()), "")) } - None => Err(InvalidType), + None => Err(DecodeError { + desc: format!("map element `{}` does not exist", idx), + }) } } - _ => Err(InvalidType), + ref found => Err(self.type_error("table", found)), } } } @@ -585,7 +631,7 @@ mod tests { use std::collections::{HashMap, HashSet}; use serialize::{Encodable, Decodable}; - use super::{Encoder, Decoder}; + use super::{Encoder, Decoder, DecodeError}; use {Table, Integer, String, Array, Float}; macro_rules! encode( ($t:expr) => ({ @@ -754,4 +800,43 @@ mod tests { ); assert_eq!(v, decode!(Table(encode!(v)))); } + + #[test] + fn table_array() { + #[deriving(Encodable, Decodable, PartialEq, Show)] + struct Foo { a: Vec, } + #[deriving(Encodable, Decodable, PartialEq, Show)] + struct Bar { a: int } + + let v = Foo { a: vec![Bar { a: 1 }, Bar { a: 2 }] }; + assert_eq!( + encode!(v), + map! { + a: Array(vec![ + Table(map!{ a: Integer(1) }), + Table(map!{ a: Integer(2) }), + ]) + } + ); + assert_eq!(v, decode!(Table(encode!(v)))); + } + + #[test] + fn errors() { + #[deriving(Encodable, Decodable, PartialEq, Show)] + struct Foo { bar: int } + + let mut d = Decoder::new(Table(map! { + bar: Float(1.0) + })); + let a: Result = Decodable::decode(&mut d); + match a { + Ok(..) => fail!("should not have decoded"), + Err(e) => { + assert_eq!(e.desc.as_slice(), + "for field `bar` expected type `integer`, but \ + found `float`"); + } + } + } }