aboutsummaryrefslogtreecommitdiff
path: root/src/encoder/mod.rs
blob: 910c970b0ed55ca05b602468a77ad48baba6dd9b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
use std::collections::BTreeMap;
use std::error;
use std::fmt;
use std::mem;

use {Value, Table};

#[cfg(feature = "rustc-serialize")] mod rustc_serialize;
#[cfg(feature = "serde")] mod serde;

/// A structure to transform Rust values into TOML values.
///
/// This encoder implements the serialization `Encoder` interface, allowing
/// `Encodable` rust types to be fed into the encoder. The output of this
/// encoder is a TOML `Table` structure. The resulting TOML can be stringified
/// if necessary.
///
/// # Example
///
/// ```
/// extern crate rustc_serialize;
/// extern crate toml;
///
/// # fn main() {
/// use toml::{Encoder, Value};
/// use rustc_serialize::Encodable;
///
/// #[derive(RustcEncodable)]
/// struct MyStruct { foo: isize, bar: String }
/// let my_struct = MyStruct { foo: 4, bar: "hello!".to_string() };
///
/// let mut e = Encoder::new();
/// my_struct.encode(&mut e).unwrap();
///
/// assert_eq!(e.toml.get(&"foo".to_string()), Some(&Value::Integer(4)))
/// # }
/// ```
#[derive(Default, Debug)]
pub struct Encoder {
    /// Output TOML that is emitted. The current version of this encoder forces
    /// the top-level representation of a structure to be a table.
    ///
    /// This field can be used to extract the return value after feeding a value
    /// into this `Encoder`.
    pub toml: Table,
    state: State,
}

/// Enumeration of errors which can occur while encoding a rust value into a
/// TOML value.
#[allow(missing_copy_implementations)]
#[derive(Debug)]
pub enum Error {
    /// Indication that a key was needed when a value was emitted, but no key
    /// was previously emitted.
    NeedsKey,
    /// Indication that a key was emitted, but no value was emitted.
    NoValue,
    /// Indicates that a map key was attempted to be emitted at an invalid
    /// location.
    InvalidMapKeyLocation,
    /// Indicates that a type other than a string was attempted to be used as a
    /// map key type.
    InvalidMapKeyType,
    /// An error returned whenever a `NaN` value for a float is attempted to be
    /// encoded
    NanEncoded,
    /// An error returned whenever an infinity value for a float is attempted to
    /// be encoded
    InfinityEncoded,
    /// A custom error type was generated
    Custom(String),
}

/// Internal state of the encoder when encoding transitions
#[derive(Debug)]
pub struct EncoderState {
    inner: State,
}

#[derive(PartialEq, Debug)]
enum State {
    Start,
    NextKey(String),
    NextArray(Vec<Value>),
    NextMapKey,
}

impl Default for State {
    fn default() -> State { State::Start }
}

impl Encoder {
    /// Constructs a new encoder which will emit to the given output stream.
    pub fn new() -> Encoder {
        Encoder { state: State::Start, toml: BTreeMap::new() }
    }

    fn emit_value(&mut self, v: Value) -> Result<(), Error> {
        match v {
            Value::Float(f) => {
                if f.is_nan() {
                    return Err(Error::NanEncoded)
                }
                if f.is_infinite() {
                    return Err(Error::InfinityEncoded)
                }
            }
            _ => {}
        }
        match mem::replace(&mut self.state, State::Start) {
            State::NextKey(key) => { self.toml.insert(key, v); Ok(()) }
            State::NextArray(mut vec) => {
                // TODO: validate types
                vec.push(v);
                self.state = State::NextArray(vec);
                Ok(())
            }
            State::NextMapKey => {
                match v {
                    Value::String(s) => { self.state = State::NextKey(s); Ok(()) }
                    _ => Err(Error::InvalidMapKeyType)
                }
            }
            _ => Err(Error::NeedsKey)
        }
    }

    fn emit_none(&mut self) -> Result<(), Error> {
        match mem::replace(&mut self.state, State::Start) {
            State::Start => unreachable!(),
            State::NextKey(_) => Ok(()),
            State::NextArray(..) => panic!("how to encode None in an array?"),
            State::NextMapKey => Err(Error::InvalidMapKeyLocation),
        }
    }

    fn seq_begin(&mut self) -> Result<State, Error> {
        Ok(mem::replace(&mut self.state, State::NextArray(Vec::new())))
    }

    fn seq_end(&mut self, old: State) -> Result<(), Error> {
        match mem::replace(&mut self.state, old) {
            State::NextArray(v) => self.emit_value(Value::Array(v)),
            _ => unreachable!(),
        }
    }

    fn table_key<F>(&mut self, f: F) -> Result<(), Error>
        where F: FnOnce(&mut Encoder) -> Result<(), Error>
    {
        match mem::replace(&mut self.state, State::NextMapKey) {
            State::Start => {}
            _ => return Err(Error::InvalidMapKeyLocation),
        }
        try!(f(self));
        match self.state {
            State::NextKey(_) => Ok(()),
            _ => Err(Error::InvalidMapKeyLocation),
        }
    }
}

/// Encodes an encodable value into a TOML value.
///
/// This function expects the type given to represent a TOML table in some form.
/// If encoding encounters an error, then this function will fail the task.
#[cfg(feature = "rustc-serialize")]
pub fn encode<T: ::rustc_serialize::Encodable>(t: &T) -> Value {
    let mut e = Encoder::new();
    t.encode(&mut e).unwrap();
    Value::Table(e.toml)
}

/// Encodes an encodable value into a TOML value.
///
/// This function expects the type given to represent a TOML table in some form.
/// If encoding encounters an error, then this function will fail the task.
#[cfg(all(not(feature = "rustc-serialize"), feature = "serde"))]
pub fn encode<T: ::serde::Serialize>(t: &T) -> Value {
    let mut e = Encoder::new();
    t.serialize(&mut e).unwrap();
    Value::Table(e.toml)
}

/// Encodes an encodable value into a TOML string.
///
/// This function expects the type given to represent a TOML table in some form.
/// If encoding encounters an error, then this function will fail the task.
#[cfg(feature = "rustc-serialize")]
pub fn encode_str<T: ::rustc_serialize::Encodable>(t: &T) -> String {
    encode(t).to_string()
}

/// Encodes an encodable value into a TOML string.
///
/// This function expects the type given to represent a TOML table in some form.
/// If encoding encounters an error, then this function will fail the task.
#[cfg(all(not(feature = "rustc-serialize"), feature = "serde"))]
pub fn encode_str<T: ::serde::Serialize>(t: &T) -> String {
    encode(t).to_string()
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::NeedsKey => write!(f, "need a key to encode"),
            Error::NoValue => write!(f, "no value to emit for a previous key"),
            Error::InvalidMapKeyLocation => write!(f, "a map cannot be emitted \
                                                       at this location"),
            Error::InvalidMapKeyType => write!(f, "only strings can be used as \
                                                   key types"),
            Error::NanEncoded => write!(f, "cannot encode NaN"),
            Error::InfinityEncoded => write!(f, "cannot encode infinity"),
            Error::Custom(ref s) => write!(f, "custom error: {}", s),
        }
    }
}

impl error::Error for Error {
    fn description(&self) -> &str { "TOML encoding error" }
}