Skip to main content

ion_rs/element/
builders.rs

1use crate::Symbol;
2use crate::{Element, Sequence, Struct};
3
4/// Constructs [Sequence], [List], and [SExp] values incrementally.
5///
6/// Building a [Sequence]:
7/// ```
8/// use ion_rs::{Element, Sequence};
9/// let actual: Sequence = Sequence::builder()
10///     .push(1)
11///     .push(true)
12///     .push("foo")
13///     .build();
14/// let expected: Sequence = Sequence::new([
15///     Element::int(1),
16///     Element::boolean(true),
17///     Element::string("foo"),
18/// ]);
19/// assert_eq!(actual, expected);
20/// ```
21/// Building a [List]:
22/// ```
23/// use ion_rs::{Element, Sequence, List};
24/// let actual: List = Sequence::builder()
25///     .push(1)
26///     .push(true)
27///     .push("foo")
28///     .build_list();
29/// let expected: List = List(Sequence::new([
30///     Element::int(1),
31///     Element::boolean(true),
32///     Element::string("foo"),
33/// ]));
34/// assert_eq!(actual, expected);
35/// ```
36/// Building a [SExp]:
37/// ```
38/// use ion_rs::{Element, SExp, Sequence};
39/// let actual: SExp = Sequence::builder()
40///     .push(1)
41///     .push(true)
42///     .push("foo")
43///     .build_sexp();
44/// let expected: SExp = SExp(Sequence::new([
45///     Element::int(1),
46///     Element::boolean(true),
47///     Element::string("foo"),
48/// ]));
49/// assert_eq!(actual, expected);
50/// ```
51pub struct SequenceBuilder {
52    values: Vec<Element>,
53}
54
55impl SequenceBuilder {
56    /// Crate visible; users should call [`Sequence::builder()`] instead.
57    pub(crate) fn new() -> Self {
58        Self { values: Vec::new() }
59    }
60
61    /// Helper method for [`Sequence::clone_builder()`].
62    pub(crate) fn with_initial_elements(elements: &[Element]) -> Self {
63        let mut new_elements = Vec::with_capacity(elements.len());
64        new_elements.extend_from_slice(elements);
65        Self {
66            values: new_elements,
67        }
68    }
69
70    /// Adds the provided element to the end of the [`Sequence`] being constructed.
71    pub fn push<E: Into<Element>>(mut self, element: E) -> Self {
72        self.values.push(element.into());
73        self
74    }
75
76    /// Adds all of the provided elements to the end of the [`Sequence`] being constructed.
77    pub fn push_all<E: Into<Element>, I: IntoIterator<Item = E>>(mut self, elements: I) -> Self {
78        self.values.extend(elements.into_iter().map(|e| e.into()));
79        self
80    }
81
82    /// Removes the element at the specified position from the [`Sequence`] being constructed.
83    /// If the index is out of bounds, this method will panic.
84    pub fn remove(mut self, index: usize) -> Self {
85        // This has O(n) behavior; the removals could be
86        // buffered until the build() if needed.
87        self.values.remove(index);
88        self
89    }
90
91    /// Builds a [`Sequence`] with the previously specified elements.
92    pub fn build(self) -> Sequence {
93        self.values.into()
94    }
95
96    /// Builds a [`List`] with the previously specified elements.
97    pub fn build_list(self) -> List {
98        List(self.build())
99    }
100
101    /// Builds a [`SExp`] with the previously specified elements.
102    pub fn build_sexp(self) -> SExp {
103        SExp(self.build())
104    }
105}
106
107/// Constructs [Struct] values incrementally.
108///
109/// ```
110/// use ion_rs::{Element, ion_struct};
111/// let actual: Element = ion_struct! {
112///     "a": 1,
113///     "b": true,
114///     "c": "foo"
115/// }
116/// .into();
117/// let expected = Element::read_one(r#"{a: 1, b: true, c: "foo"}"#).unwrap();
118/// assert_eq!(actual, expected);
119/// ```
120///
121/// ```
122/// use ion_rs::{Element, ion_struct, Struct};
123/// let base_struct: Struct = ion_struct! {
124///     "foo": 1,
125///     "bar": 2,
126///     "baz": 3
127/// };
128///
129/// let modified_struct: Element = base_struct
130///     .clone_builder()
131///     .remove_field("bar")
132///     .with_field("quux", 4)
133///     .build()
134///     .into(); // Convert from `Struct` to `Element`
135///
136/// let expected = Element::read_one(r#"{foo: 1, baz: 3, quux: 4}"#).unwrap();
137/// assert_eq!(expected, modified_struct);
138/// ```
139pub struct StructBuilder {
140    fields: Vec<(Symbol, Element)>,
141}
142
143impl StructBuilder {
144    /// Crate visible; users should call [`Struct::builder()`] or [`Element::struct_builder`] instead.
145    pub(crate) fn new() -> Self {
146        StructBuilder { fields: Vec::new() }
147    }
148
149    /// Helper method for [`Struct::clone_builder()`].
150    pub(crate) fn with_initial_fields(elements: &[(Symbol, Element)]) -> Self {
151        let mut new_elements = Vec::with_capacity(elements.len());
152        new_elements.extend_from_slice(elements);
153        Self {
154            fields: new_elements,
155        }
156    }
157
158    /// Adds the provided `(name, value)` pair to the [`Struct`] being constructed.
159    pub fn with_field<S: Into<Symbol>, E: Into<Element>>(
160        mut self,
161        field_name: S,
162        field_value: E,
163    ) -> Self {
164        self.fields.push((field_name.into(), field_value.into()));
165        self
166    }
167
168    /// Adds all of the provided `(name, value)` pairs to the [`Struct`] being constructed.
169    ///
170    /// ```
171    /// use ion_rs::{Element, ion_struct};
172    ///
173    /// let struct1 = ion_struct! {
174    ///     "foo": 1,
175    ///     "bar": 2,
176    ///     "baz": 3
177    /// };
178    ///
179    /// let struct2 = ion_struct! {
180    ///     "a": 4,
181    ///     "b": 5,
182    ///     "c": 6
183    /// };
184    ///
185    /// let merged = struct1
186    ///     .clone_builder()
187    ///     .with_fields(struct2.fields())
188    ///     .build();
189    ///
190    /// let expected = Element::read_one("{foo: 1, bar: 2, baz: 3, a: 4, b: 5, c: 6}").unwrap();
191    /// ```
192    pub fn with_fields<S, E, I>(mut self, fields: I) -> Self
193    where
194        S: Into<Symbol>,
195        E: Into<Element>,
196        I: IntoIterator<Item = (S, E)>,
197    {
198        for (name, value) in fields.into_iter() {
199            let name: Symbol = name.into();
200            let value: Element = value.into();
201            self.fields.push((name, value));
202        }
203        self
204    }
205
206    /// Removes the first field with the specified name from the [`Struct`] being constructed.
207    pub fn remove_field<A: AsRef<str>>(mut self, field_to_remove: A) -> Self {
208        // TODO: This removes the first field with a matching name.
209        //       Do we need other versions for remove_all or remove_last?
210        // TODO: This has O(n) behavior; it could be optimized.
211        let field_to_remove: &str = field_to_remove.as_ref();
212        let _ = self
213            .fields
214            .iter()
215            .position(|(name, _)| name == &field_to_remove)
216            .map(|index| self.fields.remove(index));
217        self
218    }
219
220    /// Builds a [`Struct`] with the previously specified fields.
221    pub fn build(self) -> Struct {
222        Struct::from_iter(self.fields)
223    }
224}
225
226/// Constructs a list [`Element`] with the specified child values.
227///
228/// ```
229/// use ion_rs::{Element, ion_list};
230/// // Construct a list Element from Rust values
231/// let actual: Element = ion_list!["foo", 7, false, ion_list![1.5f64, -8.25f64]].into();
232/// // Construct an Element from serialized Ion data
233/// let expected = Element::read_one(r#"["foo", 7, false, [1.5e0, -8.25e0]]"#).unwrap();
234/// // Compare the two Elements
235/// assert_eq!(expected, actual);
236/// ```
237///
238/// Child values can be anything that implements `Into<Element>`, which
239/// includes existing [Element] values.
240///
241/// ```
242/// // Construct a list Element from existing Elements
243/// use ion_rs::{Element, ion_list, IntoAnnotatedElement};
244///
245/// let string_element: Element = "foo".into();
246/// let bool_element: Element = true.into();
247///
248/// let actual: Element = ion_list![
249///     string_element,
250///     bool_element,
251///     10i64.with_annotations(["bar"]), // .with_annotations() constructs an Element
252///     Element::clob("hello"),
253///     Element::symbol("world")
254/// ]
255/// .into();
256/// // Construct an Element from serialized Ion data
257/// let expected = Element::read_one(r#"["foo", true, bar::10, {{"hello"}}, world]"#).unwrap();
258/// // Compare the two Elements
259/// assert_eq!(expected, actual);
260/// ```
261#[macro_export]
262macro_rules! ion_list {
263    ($($element:expr),* $(,)?) => {{
264        use $crate::Sequence;
265        Sequence::builder()$(.push($element))*.build_list()
266    }};
267}
268
269/// Constructs an s-expression [`Element`] with the specified child values.
270///
271/// ```
272/// use ion_rs::{Element, ion_sexp};
273/// // Construct an s-expression Element from Rust values
274/// let actual: Element = ion_sexp!("foo" 7 false ion_sexp!(1.5f64 8.25f64)).into();
275/// // Construct an Element from serialized Ion data
276/// let expected = Element::read_one(r#"("foo" 7 false (1.5e0 8.25e0))"#).unwrap();
277/// // Compare the two Elements
278/// assert_eq!(expected, actual);
279/// ```
280///
281/// Child values can be anything that implements `Into<Element>`, which
282/// includes existing [Element] values.
283///
284/// ```
285/// // Construct a s-expression Element from existing Elements
286/// use ion_rs::{Element, ion_sexp, IntoAnnotatedElement};
287///
288/// let string_element: Element = "foo".into();
289/// let bool_element: Element = true.into();
290///
291/// let actual: Element = ion_sexp!(
292///     string_element
293///     bool_element
294///     10i64.with_annotations(["bar"]) // .with_annotations() constructs an Element
295///     Element::clob("hello")
296///     Element::symbol("world")
297/// ).into();
298/// // Construct an Element from serialized Ion data
299/// let expected = Element::read_one(r#"("foo" true bar::10 {{"hello"}} world)"#).unwrap();
300/// // Compare the two Elements
301/// assert_eq!(expected, actual);
302/// ```
303#[macro_export]
304macro_rules! ion_sexp {
305    ($($element:expr)*) => {{
306        use $crate::Sequence;
307        Sequence::builder()$(.push($element))*.build_sexp()
308    }};
309}
310
311/// Constructs an struct [`Element`] with the specified fields.
312///
313/// For each field, the name must implement `Into<Symbol>` and the value must implement
314/// `Into<Element>`.
315///
316/// ```
317/// use ion_rs::{Element, ion_struct, IonType};
318/// let field_name_2 = "x";
319/// let prefix = "abc";
320/// let suffix = "def";
321/// // Construct an s-expression Element from Rust values
322/// let actual = ion_struct! {
323///     "w": "foo",
324/// //   ^--- Quoted strings are field name literals
325/// //   v--- Unquoted field names are interpreted as variables
326///     field_name_2: 7,
327///     "y": false,
328///     "z": ion_struct!{ "a": 1.5f64, "b": -8.25f64},
329/// //        Arbitrary expressions are acceptable, though some may require
330/// //   v--- an extra scope (braces: `{}`) to be understood properly.
331///      {format!("{}_{}", prefix, suffix)}: IonType::Null
332/// }
333/// .into();
334/// // Construct an Element from serialized Ion data
335/// let expected = Element::read_one(
336///     r#"{w: "foo", x: 7, y: false, z: {a: 1.5e0, b: -8.25e0}, abc_def: null}"#,
337/// )
338/// .unwrap();
339/// // Compare the two Elements
340/// assert_eq!(expected, actual);
341/// ```
342#[macro_export]
343macro_rules! ion_struct {
344    ($($field_name:tt : $element:expr),* $(,)?) => {{
345        use $crate::Struct;
346        Struct::builder()$(.with_field($field_name, $element))*.build()
347    }};
348}
349
350/// Constructs a [`Sequence`] with the specified child values.
351///
352/// Note that a `Sequence` is NOT a type of `Element`. However, one can convert a `Sequence` into a
353/// `List` or `SExp`.
354///
355/// ```
356/// use ion_rs::{Element, ion_seq, ion_list, Sequence};
357/// // Construct a Sequence from serialized Ion data
358/// let expected: Sequence = Element::read_all(r#" "foo" 7 false [1.5e0, -8.25e0] "#).unwrap();
359/// // Construct a Sequence from Rust values
360/// let actual: Sequence = ion_seq!("foo" 7 false ion_list![1.5f64, -8.25f64]);
361/// // Compare the two Sequences
362/// assert_eq!(expected, actual);
363///
364/// // The ion_seq! macro can also accept a comma-delimited list of Rust values to convert
365/// let actual: Sequence = ion_seq!["foo", 7, false, ion_list![1.5f64, -8.25f64]];
366/// assert_eq!(expected, actual);
367/// ```
368#[macro_export]
369macro_rules! ion_seq {
370    ($($element:expr),* $(,)?) => {{
371        use $crate::Sequence;
372        Sequence::builder()$(.push($element))*.build()
373    }};
374    ($($element:expr)*) => {{
375        use $crate::Sequence;
376        Sequence::builder()$(.push($element))*.build()
377    }};
378}
379
380use crate::{List, SExp};
381
382#[cfg(test)]
383mod tests {
384    use crate::element::builders::{SequenceBuilder, StructBuilder};
385    use crate::element::Element;
386    use crate::Symbol;
387
388    #[test]
389    fn make_seq_with_macro() {
390        let expected = Element::read_all(r#"1 true "foo" "bar""#).unwrap();
391        // Sequences without commas (as found in SExps and at the top level) are ok
392        assert_eq!(ion_seq!(1 true "foo" "bar"), expected);
393        // Sequences with commas (as found in lists) are ok
394        assert_eq!(ion_seq![1, true, "foo", "bar"], expected);
395        // Trailing commas are allowed
396        assert_eq!(ion_seq![1, true, "foo", "bar",], expected);
397    }
398
399    #[test]
400    fn make_list_with_builder() {
401        let actual: Element = SequenceBuilder::new()
402            .push(1)
403            .push(true)
404            .push("foo")
405            .push(Symbol::owned("bar"))
406            .build_list()
407            .into();
408        let expected = Element::read_one(r#"[1, true, "foo", bar]"#).unwrap();
409        assert_eq!(actual, expected);
410    }
411
412    #[test]
413    fn make_list_with_macro() {
414        let actual: Element = ion_list![1, true, "foo", Symbol::owned("bar")].into();
415        let expected = Element::read_one(r#"[1, true, "foo", bar]"#).unwrap();
416        assert_eq!(actual, expected);
417        // Trailing commas are allowed
418        let actual: Element = ion_list![1, true, "foo", Symbol::owned("bar"),].into();
419        assert_eq!(actual, expected);
420    }
421
422    #[test]
423    fn make_list_with_builder_using_remove() {
424        let actual: Element = SequenceBuilder::new()
425            .push(1)
426            .push(true)
427            .push("foo")
428            .remove(1)
429            .remove(1)
430            .push(Symbol::owned("bar"))
431            .build_list()
432            .into();
433        let expected = Element::read_one("[1, bar]").unwrap();
434        assert_eq!(actual, expected);
435    }
436
437    #[test]
438    fn list_clone_builder() {
439        let original_list = ion_list![1, true, "foo", Symbol::owned("bar")];
440        let new_list: Element = original_list
441            .clone_builder()
442            .remove(1)
443            .push(88)
444            .build_list()
445            .into();
446        let expected_list = Element::read_one(r#"[1, "foo", bar, 88]"#).unwrap();
447        assert_eq!(new_list, expected_list);
448    }
449
450    #[test]
451    fn make_sexp_with_builder() {
452        let actual: Element = SequenceBuilder::new()
453            .push(1)
454            .push(true)
455            .push("foo")
456            .push(Symbol::owned("bar"))
457            .build_sexp()
458            .into();
459        let expected = Element::read_one(r#"(1 true "foo" bar)"#).unwrap();
460        assert_eq!(actual, expected);
461    }
462
463    #[test]
464    fn sexp_clone_builder() {
465        let original_sexp = ion_sexp!(1 true "foo" Symbol::owned("bar"));
466        let new_sexp: Element = original_sexp
467            .clone_builder()
468            .remove(1)
469            .push(88)
470            .build_sexp()
471            .into();
472        let expected_sexp = Element::read_one(r#"(1 "foo" bar 88)"#).unwrap();
473        assert_eq!(new_sexp, expected_sexp);
474    }
475
476    #[test]
477    fn make_sexp_with_builder_using_remove() {
478        let actual: Element = SequenceBuilder::new()
479            .push(1)
480            .push(true)
481            .push("foo")
482            .remove(1)
483            .remove(1)
484            .push(Symbol::owned("bar"))
485            .build_sexp()
486            .into();
487        let expected = Element::read_one("(1 bar)").unwrap();
488        assert_eq!(actual, expected);
489    }
490
491    #[test]
492    fn make_sexp_with_macro() {
493        let actual: Element = ion_sexp!(1 true "foo" Symbol::owned("bar")).into();
494        let expected = Element::read_one(r#"(1 true "foo" bar)"#).unwrap();
495        assert_eq!(actual, expected);
496    }
497
498    #[test]
499    fn make_struct_with_builder() {
500        let actual: Element = StructBuilder::new()
501            .with_field("a", 1)
502            .with_field("b", true)
503            .with_field("c", "foo")
504            .with_field("d", Symbol::owned("bar"))
505            .build()
506            .into();
507        let expected = Element::read_one(r#"{a: 1, b: true, c: "foo", d: bar}"#).unwrap();
508        assert_eq!(actual, expected);
509    }
510
511    #[test]
512    fn make_struct_with_macro() {
513        let actual: Element = ion_struct! {
514            "a": 1,
515            "b": true,
516            "c": "foo",
517            "d": Symbol::owned("bar")
518        }
519        .into();
520        let expected = Element::read_one(r#"{a: 1, b: true, c: "foo", d: bar}"#).unwrap();
521        assert_eq!(actual, expected);
522
523        // Trailing commas are allowed
524        let actual: Element = ion_struct! {
525            "a": 1,
526            "b": true,
527            "c": "foo",
528            "d": Symbol::owned("bar"), // <-- trailing comma
529        }
530        .into();
531        assert_eq!(actual, expected);
532    }
533
534    #[test]
535    fn make_struct_with_builder_using_remove_field() {
536        let actual: Element = StructBuilder::new()
537            .with_field("a", 1)
538            .with_field("b", true)
539            .with_field("c", "foo")
540            .with_field("d", Symbol::owned("bar"))
541            .with_field("d", Symbol::owned("baz"))
542            // Removes the only 'b' field
543            .remove_field("b")
544            // Removes the first 'd' field, leaves the second
545            .remove_field("d")
546            .build()
547            .into();
548        let expected = Element::read_one(r#"{a: 1, c: "foo", d: baz}"#).unwrap();
549        assert_eq!(actual, expected);
550    }
551}