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}