ion_rs/text/
text_writer.rs

1use crate::element::writer::TextKind;
2use crate::raw_symbol_token_ref::{AsRawSymbolTokenRef, RawSymbolTokenRef};
3use crate::result::IonResult;
4use crate::text::raw_text_writer::RawTextWriter;
5use crate::types::{Decimal, Timestamp};
6use crate::writer::IonWriter;
7use crate::{Int, IonType, RawTextWriterBuilder, SymbolTable};
8use delegate::delegate;
9use std::io::Write;
10
11pub struct TextWriterBuilder {
12    text_kind: TextKind,
13}
14
15impl TextWriterBuilder {
16    /// Constructs a text Ion writer with the specified formatting. See [`TextKind`] for details.
17    pub fn new(format: TextKind) -> TextWriterBuilder {
18        TextWriterBuilder { text_kind: format }
19    }
20
21    /// Constructs a text Ion writer that serializes data with modest (but not strictly minimal)
22    /// spacing.
23    ///
24    /// For example:
25    /// ```text
26    /// {foo: 1, bar: 2, baz: 3} [1, 2, 3] true "hello"
27    /// ```
28    pub fn compact() -> TextWriterBuilder {
29        TextWriterBuilder {
30            text_kind: TextKind::Compact,
31        }
32    }
33
34    /// Constructs a newline-delimited text Ion writer that adds UNIX and human-friendly newlines
35    /// between top-level values.
36    ///
37    /// For example:
38    /// ```text
39    /// {foo: 1, bar: 2, baz: 3}
40    /// [1, 2, 3]
41    /// true
42    /// "hello"
43    /// ```
44    pub fn lines() -> TextWriterBuilder {
45        TextWriterBuilder {
46            text_kind: TextKind::Lines,
47        }
48    }
49
50    /// Constructs a 'pretty' text Ion writer that adds human-friendly spacing between values.
51    ///
52    /// For example:
53    /// ```text
54    /// {
55    ///     foo: 1,
56    ///     bar: 2,
57    ///     baz: 3
58    /// }
59    /// [
60    ///     1,
61    ///     2,
62    ///     3
63    /// ]
64    /// true
65    /// "hello"
66    /// ```
67    pub fn pretty() -> TextWriterBuilder {
68        TextWriterBuilder {
69            text_kind: TextKind::Pretty,
70        }
71    }
72
73    /// Constructs a new instance of TextWriter that writes values to the provided io::Write
74    /// implementation.
75    pub fn build<W: Write>(self, sink: W) -> IonResult<TextWriter<W>> {
76        let builder = match self.text_kind {
77            TextKind::Compact => RawTextWriterBuilder::default(),
78            TextKind::Pretty => RawTextWriterBuilder::pretty(),
79            TextKind::Lines => RawTextWriterBuilder::lines(),
80        };
81        let raw_writer = builder.build(sink)?;
82        let text_writer = TextWriter {
83            raw_writer,
84            symbol_table: SymbolTable::new(),
85        };
86        Ok(text_writer)
87    }
88}
89
90impl Default for TextWriterBuilder {
91    fn default() -> Self {
92        TextWriterBuilder::new(TextKind::Compact)
93    }
94}
95
96/**
97 * An application-level text Ion writer. This writer manages a symbol table and so can convert
98 * symbol IDs to their corresponding text. However, unlike the BinaryWriter, it is capable of writing
99 * text to the output stream without first adding it to the symbol table.
100 */
101pub struct TextWriter<W: Write> {
102    raw_writer: RawTextWriter<W>,
103    symbol_table: SymbolTable,
104}
105
106impl<W: Write> IonWriter for TextWriter<W> {
107    type Output = W;
108
109    fn supports_text_symbol_tokens(&self) -> bool {
110        // The TextWriter can always write text field names, annotations, and symbols.
111        true
112    }
113
114    fn set_annotations<I, A>(&mut self, annotations: I)
115    where
116        A: AsRawSymbolTokenRef,
117        I: IntoIterator<Item = A>,
118    {
119        for annotation in annotations {
120            let raw_symbol_token_ref = match annotation.as_raw_symbol_token_ref() {
121                RawSymbolTokenRef::SymbolId(symbol_id) => {
122                    // Get the text associated with this symbol ID
123                    match self.symbol_table.text_for(symbol_id) {
124                        Some(text) => RawSymbolTokenRef::Text(text),
125                        None => RawSymbolTokenRef::SymbolId(symbol_id),
126                    }
127                }
128                text_token => text_token,
129            };
130            self.raw_writer.add_annotation(raw_symbol_token_ref);
131        }
132    }
133
134    fn write_symbol<A: AsRawSymbolTokenRef>(&mut self, value: A) -> IonResult<()> {
135        let raw_symbol_token_ref = match value.as_raw_symbol_token_ref() {
136            RawSymbolTokenRef::SymbolId(symbol_id) => {
137                // Get the text associated with this symbol ID
138                match self.symbol_table.text_for(symbol_id) {
139                    Some(text) => RawSymbolTokenRef::Text(text),
140                    None => RawSymbolTokenRef::SymbolId(symbol_id),
141                }
142            }
143            text_token => text_token,
144        };
145        self.raw_writer.write_symbol(raw_symbol_token_ref)
146    }
147
148    fn set_field_name<A: AsRawSymbolTokenRef>(&mut self, name: A) {
149        let raw_symbol_token_ref = match name.as_raw_symbol_token_ref() {
150            RawSymbolTokenRef::SymbolId(symbol_id) => {
151                // Get the text associated with this symbol ID
152                match self.symbol_table.text_for(symbol_id) {
153                    Some(text) => RawSymbolTokenRef::Text(text),
154                    None => RawSymbolTokenRef::SymbolId(symbol_id),
155                }
156            }
157            text_token => text_token,
158        };
159        self.raw_writer.set_field_name(raw_symbol_token_ref);
160    }
161
162    delegate! {
163        to self.raw_writer {
164            fn ion_version(&self) -> (u8, u8);
165            fn write_ion_version_marker(&mut self, major: u8, minor: u8) -> IonResult<()>;
166            fn write_null(&mut self, ion_type: IonType) -> IonResult<()>;
167            fn write_bool(&mut self, value: bool) -> IonResult<()>;
168            fn write_i64(&mut self, value: i64) -> IonResult<()>;
169            fn write_int(&mut self, value: &Int) -> IonResult<()>;
170            fn write_f32(&mut self, value: f32) -> IonResult<()>;
171            fn write_f64(&mut self, value: f64) -> IonResult<()>;
172            fn write_decimal(&mut self, value: &Decimal) -> IonResult<()>;
173            fn write_timestamp(&mut self, value: &Timestamp) -> IonResult<()>;
174            fn write_string<A: AsRef<str>>(&mut self, value: A) -> IonResult<()>;
175            fn write_clob<A: AsRef<[u8]>>(&mut self, value: A) -> IonResult<()>;
176            fn write_blob<A: AsRef<[u8]>>(&mut self, value: A) -> IonResult<()>;
177            fn step_in(&mut self, container_type: IonType) -> IonResult<()>;
178            fn parent_type(&self) -> Option<IonType>;
179            fn depth(&self) -> usize;
180            fn step_out(&mut self) -> IonResult<()>;
181            fn flush(&mut self) -> IonResult<()>;
182            fn output(&self) -> &Self::Output;
183            fn output_mut(&mut self) -> &mut Self::Output;
184        }
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191    use crate::reader::ReaderBuilder;
192    use crate::IonReader;
193    use crate::StreamItem::Value;
194
195    #[test]
196    fn resolve_symbol_ids() -> IonResult<()> {
197        // Unlike the binary writer, the text writer won't add strings to the symbol table.
198        // However, if you ask it to write a symbol ID (e.g. $10) for which its initial symbol
199        // table has text, it will convert it to text before writing it.
200        let mut buffer = Vec::new();
201        let mut text_writer = TextWriterBuilder::default().build(&mut buffer)?;
202        // The following symbol IDs are in the system symbol table.
203        // https://amazon-ion.github.io/ion-docs/docs/symbols.html#system-symbols
204        text_writer.step_in(IonType::Struct)?;
205        text_writer.set_field_name(4);
206        text_writer.set_annotations([1]);
207        text_writer.write_symbol(5)?;
208        text_writer.step_out()?;
209        text_writer.flush()?;
210
211        let mut reader = ReaderBuilder::new().build(text_writer.output().as_slice())?;
212        assert_eq!(Value(IonType::Struct), reader.next()?);
213        reader.step_in()?;
214        assert_eq!(Value(IonType::Symbol), reader.next()?);
215        assert_eq!(1, reader.number_of_annotations());
216        // The reader returns text values for the symbol IDs it encountered in the stream
217        assert_eq!("$ion", reader.annotations().next().unwrap()?);
218        assert_eq!("name", reader.field_name()?);
219        assert_eq!("version", reader.read_symbol()?);
220
221        Ok(())
222    }
223}