1use std::io::{BufWriter, Write};
2
3use bigdecimal::BigDecimal;
4use chrono::{DateTime, FixedOffset};
5
6use crate::element::writer::TextKind;
7use crate::raw_symbol_token_ref::{AsRawSymbolTokenRef, RawSymbolTokenRef};
8use crate::result::{illegal_operation, IonResult};
9use crate::text::text_formatter::STRING_ESCAPE_CODES;
10use crate::types::{ContainerType, Decimal, Timestamp};
11use crate::writer::IonWriter;
12use crate::{Int, IonType, RawSymbolToken};
13
14pub struct RawTextWriterBuilder {
15 whitespace_config: WhitespaceConfig,
16}
17
18impl RawTextWriterBuilder {
19 pub fn new(text_kind: TextKind) -> RawTextWriterBuilder {
21 match text_kind {
22 TextKind::Compact => Self::compact(),
23 TextKind::Lines => Self::lines(),
24 TextKind::Pretty => Self::pretty(),
25 }
26 }
27
28 pub fn compact() -> RawTextWriterBuilder {
35 RawTextWriterBuilder {
36 whitespace_config: COMPACT_WHITESPACE_CONFIG.clone(),
37 }
38 }
39
40 pub fn lines() -> RawTextWriterBuilder {
53 RawTextWriterBuilder {
54 whitespace_config: LINES_WHITESPACE_CONFIG.clone(),
55 }
56 }
57
58 pub fn pretty() -> RawTextWriterBuilder {
76 RawTextWriterBuilder {
77 whitespace_config: PRETTY_WHITESPACE_CONFIG.clone(),
78 }
79 }
80
81 pub fn with_space_between_top_level_values(
82 mut self,
83 space_between_top_level_values: &'static str,
84 ) -> RawTextWriterBuilder {
85 self.whitespace_config.space_between_top_level_values = space_between_top_level_values;
86 self
87 }
88
89 pub fn with_space_between_nested_values(
90 mut self,
91 space_between_values: &'static str,
92 ) -> RawTextWriterBuilder {
93 self.whitespace_config.space_between_nested_values = space_between_values;
94 self
95 }
96
97 pub fn with_indentation(mut self, indentation: &'static str) -> RawTextWriterBuilder {
98 self.whitespace_config.indentation = indentation;
99 self
100 }
101
102 pub fn with_space_after_field_name(
103 mut self,
104 space_after_field_name: &'static str,
105 ) -> RawTextWriterBuilder {
106 self.whitespace_config.space_after_field_name = space_after_field_name;
107 self
108 }
109
110 pub fn with_space_after_container_start(
111 mut self,
112 space_after_container_start: &'static str,
113 ) -> RawTextWriterBuilder {
114 self.whitespace_config.space_after_container_start = space_after_container_start;
115 self
116 }
117
118 pub fn build<W: Write>(self, sink: W) -> IonResult<RawTextWriter<W>> {
121 let raw_text_writer = RawTextWriter {
122 output: BufWriter::new(sink),
123 annotations: Vec::new(),
124 field_name: None,
125 containers: vec![EncodingLevel::default()],
126 whitespace_config: Box::new(self.whitespace_config),
130 };
131 Ok(raw_text_writer)
134 }
135}
136
137impl Default for RawTextWriterBuilder {
138 fn default() -> Self {
139 RawTextWriterBuilder::new(TextKind::Compact)
140 }
141}
142
143#[derive(Debug, PartialEq, Default)]
144struct EncodingLevel {
145 container_type: ContainerType,
146 child_count: usize,
147}
148
149#[derive(Clone)]
150struct WhitespaceConfig {
151 space_between_top_level_values: &'static str,
153 space_between_nested_values: &'static str,
155 indentation: &'static str,
157 space_after_field_name: &'static str,
159 space_after_container_start: &'static str,
161}
162
163static COMPACT_WHITESPACE_CONFIG: WhitespaceConfig = WhitespaceConfig {
164 space_between_top_level_values: " ",
166 space_between_nested_values: " ",
168 indentation: "",
170 space_after_field_name: " ",
172 space_after_container_start: "",
174};
175
176static LINES_WHITESPACE_CONFIG: WhitespaceConfig = WhitespaceConfig {
177 space_between_top_level_values: "\n",
179 ..COMPACT_WHITESPACE_CONFIG
181};
182
183static PRETTY_WHITESPACE_CONFIG: WhitespaceConfig = WhitespaceConfig {
184 space_between_top_level_values: "\n",
186 space_between_nested_values: "\n",
188 indentation: " ",
190 space_after_field_name: " ",
192 space_after_container_start: "\n",
194};
195
196pub struct RawTextWriter<W: Write> {
197 output: BufWriter<W>,
198 annotations: Vec<RawSymbolToken>,
199 field_name: Option<RawSymbolToken>,
200 containers: Vec<EncodingLevel>,
201 whitespace_config: Box<WhitespaceConfig>,
202}
203
204impl<W: Write> RawTextWriter<W> {
205 pub fn is_in_struct(&self) -> bool {
207 self.parent_level().container_type == ContainerType::Struct
208 }
209
210 fn index_within_parent(&self) -> usize {
214 self.parent_level().child_count
215 }
216
217 fn parent_level(&self) -> &EncodingLevel {
219 self.containers.last().unwrap()
221 }
222
223 fn increment_child_count(&mut self) {
225 let parent_level = self.containers.last_mut().unwrap();
226 parent_level.child_count += 1;
227 }
228
229 fn write_value_delimiter(&mut self) -> IonResult<()> {
231 use ContainerType::*;
232 let delimiter = match self.parent_level().container_type {
233 TopLevel => "",
234 Struct | List => ",",
235 SExpression => "",
236 };
237 write!(self.output, "{delimiter}")?;
238 Ok(())
239 }
240
241 fn write_space_before_value(&mut self) -> IonResult<()> {
246 if self.index_within_parent() == 0 {
248 if self.depth() > 0 {
250 write!(
253 &mut self.output,
254 "{}",
255 self.whitespace_config.space_after_container_start
256 )?;
257 }
258 } else {
259 self.write_value_delimiter()?;
262
263 let value_spacer = if self.depth() == 0 {
264 &self.whitespace_config.space_between_top_level_values
265 } else {
266 &self.whitespace_config.space_between_nested_values
267 };
268 write!(&mut self.output, "{value_spacer}")?;
269 }
270
271 if !self.whitespace_config.indentation.is_empty() {
272 for _ in 0..self.depth() {
274 write!(&mut self.output, "{}", self.whitespace_config.indentation)?;
275 }
276 }
277 Ok(())
278 }
279
280 fn token_is_identifier(token: &str) -> bool {
289 if token.is_empty() {
290 return false;
291 }
292 let mut chars = token.chars();
293 let first = chars.next().unwrap();
294 (first == '$' || first == '_' || first.is_ascii_alphabetic())
295 && chars.all(|c| c == '$' || c == '_' || c.is_ascii_alphanumeric())
296 }
297
298 fn token_is_keyword(token: &str) -> bool {
301 const KEYWORDS: &[&str] = &["true", "false", "nan", "null"];
302 KEYWORDS.contains(&token)
303 }
304
305 fn token_resembles_symbol_id(token: &str) -> bool {
309 if token.is_empty() {
310 return false;
311 }
312 let mut chars = token.chars();
313 let first = chars.next().unwrap();
314 first == '$' && chars.all(|c| c.is_numeric())
315 }
316
317 pub(crate) fn write_symbol_token<A: AsRawSymbolTokenRef>(
318 output: &mut BufWriter<W>,
319 token: A,
320 ) -> IonResult<()> {
321 match token.as_raw_symbol_token_ref() {
322 RawSymbolTokenRef::SymbolId(sid) => write!(output, "${sid}")?,
323 RawSymbolTokenRef::Text(text)
324 if Self::token_is_keyword(text) || Self::token_resembles_symbol_id(text) =>
325 {
326 write!(output, "'{text}'")?;
328 }
329 RawSymbolTokenRef::Text(text) if Self::token_is_identifier(text) => {
330 write!(output, "{text}")?
332 }
333 RawSymbolTokenRef::Text(text) => {
334 write!(output, "\'")?;
336 Self::write_escaped_text_body(output, text)?;
337 write!(output, "\'")?;
338 }
339 };
340 Ok(())
341 }
342
343 fn write_value_metadata(&mut self) -> IonResult<()> {
345 if let Some(field_name) = &self.field_name.take() {
346 Self::write_symbol_token(&mut self.output, field_name)?;
347 write!(
348 self.output,
349 ":{}",
350 self.whitespace_config.space_after_field_name
351 )?;
352 } else if self.is_in_struct() {
353 return illegal_operation("Values inside a struct must have a field name.");
354 }
355
356 if !self.annotations.is_empty() {
357 for annotation in &self.annotations {
358 Self::write_symbol_token(&mut self.output, annotation)?;
359 write!(self.output, "::")?;
360 }
361 self.annotations.clear();
362 }
363 Ok(())
364 }
365
366 fn write_scalar<F>(&mut self, scalar_writer: F) -> IonResult<()>
372 where
373 F: FnOnce(&mut BufWriter<W>) -> IonResult<()>,
374 {
375 self.write_space_before_value()?;
376 self.write_value_metadata()?;
377 scalar_writer(&mut self.output)?;
378 self.increment_child_count();
380 Ok(())
381 }
382
383 pub fn write_big_decimal(&mut self, value: &BigDecimal) -> IonResult<()> {
385 self.write_scalar(|output| {
386 write!(output, "{}", &value)?;
387 Ok(())
388 })
389 }
390
391 #[deprecated(
393 since = "0.6.1",
394 note = "Please use the `write_timestamp` method instead."
395 )]
396 pub fn write_datetime(&mut self, value: &DateTime<FixedOffset>) -> IonResult<()> {
397 self.write_scalar(|output| {
398 write!(output, "{}", value.to_rfc3339())?;
399 Ok(())
400 })
401 }
402
403 pub fn add_annotation<A: AsRawSymbolTokenRef>(&mut self, annotation: A) {
404 let token = match annotation.as_raw_symbol_token_ref() {
409 RawSymbolTokenRef::SymbolId(sid) => RawSymbolToken::SymbolId(sid),
410 RawSymbolTokenRef::Text(text) => RawSymbolToken::Text(text.to_string()),
411 };
412 self.annotations.push(token);
413 }
414
415 pub(crate) fn write_escaped_text_body<S: AsRef<str>>(
418 output: &mut BufWriter<W>,
419 value: S,
420 ) -> IonResult<()> {
421 let mut start = 0usize;
422 let text = value.as_ref();
423 for (byte_index, character) in text.char_indices() {
424 let escaped = match character {
425 '\n' => r"\n",
426 '\r' => r"\r",
427 '\t' => r"\t",
428 '\\' => r"\\",
429 '/' => r"\/",
430 '"' => r#"\""#,
431 '\'' => r"\'",
432 '?' => r"\?",
433 '\x00' => r"\0", '\x07' => r"\a", '\x08' => r"\b", '\x0B' => r"\v", '\x0C' => r"\f", _ => {
439 continue;
441 }
442 };
443 write!(output, "{}{}", &text[start..byte_index], escaped)?;
447 start = byte_index + character.len_utf8();
449 }
450 write!(output, "{}", &text[start..])?;
451 Ok(())
452 }
453}
454
455impl<W: Write> IonWriter for RawTextWriter<W> {
456 type Output = W;
457
458 fn ion_version(&self) -> (u8, u8) {
459 (1, 0)
460 }
461
462 fn write_ion_version_marker(&mut self, major: u8, minor: u8) -> IonResult<()> {
463 write!(self.output, "$ion_{major}_{minor}")?;
464 Ok(())
465 }
466
467 fn supports_text_symbol_tokens(&self) -> bool {
468 true
469 }
470
471 fn set_annotations<I, A>(&mut self, annotations: I)
473 where
474 A: AsRawSymbolTokenRef,
475 I: IntoIterator<Item = A>,
476 {
477 self.annotations.clear();
478 for annotation in annotations {
479 self.add_annotation(annotation)
480 }
481 }
482
483 fn write_null(&mut self, ion_type: IonType) -> IonResult<()> {
485 use IonType::*;
486 self.write_scalar(|output| {
487 let null_text = match ion_type {
488 Null => "null",
489 Bool => "null.bool",
490 Int => "null.int",
491 Float => "null.float",
492 Decimal => "null.decimal",
493 Timestamp => "null.timestamp",
494 Symbol => "null.symbol",
495 String => "null.string",
496 Blob => "null.blob",
497 Clob => "null.clob",
498 List => "null.list",
499 SExp => "null.sexp",
500 Struct => "null.struct",
501 };
502 write!(output, "{null_text}")?;
503 Ok(())
504 })
505 }
506
507 fn write_bool(&mut self, value: bool) -> IonResult<()> {
509 self.write_scalar(|output| {
510 let bool_text = match value {
511 true => "true",
512 false => "false",
513 };
514 write!(output, "{bool_text}")?;
515 Ok(())
516 })
517 }
518
519 fn write_i64(&mut self, value: i64) -> IonResult<()> {
521 self.write_scalar(|output| {
522 write!(output, "{value}")?;
523 Ok(())
524 })
525 }
526
527 fn write_int(&mut self, value: &Int) -> IonResult<()> {
529 self.write_scalar(|output| {
530 write!(output, "{value}")?;
531 Ok(())
532 })
533 }
534
535 fn write_f32(&mut self, value: f32) -> IonResult<()> {
537 self.write_f64(value as f64)
539 }
540
541 fn write_f64(&mut self, value: f64) -> IonResult<()> {
543 self.write_scalar(|output| {
544 if value.is_nan() {
545 write!(output, "nan")?;
546 return Ok(());
547 }
548
549 if value.is_infinite() {
550 if value.is_sign_positive() {
551 write!(output, "+inf")?;
552 } else {
553 write!(output, "-inf")?;
554 }
555 return Ok(());
556 }
557
558 if value == 0.0f64 && value.is_sign_negative() {
562 write!(output, "-0e0")?;
563 return Ok(());
564 }
565
566 write!(output, "{value:e}")?;
567 Ok(())
568 })
569 }
570
571 fn write_decimal(&mut self, value: &Decimal) -> IonResult<()> {
573 self.write_scalar(|output| {
574 write!(output, "{value}")?;
575 Ok(())
576 })
577 }
578
579 fn write_timestamp(&mut self, value: &Timestamp) -> IonResult<()> {
581 self.write_scalar(|output| {
582 write!(output, "{value}")?;
583 Ok(())
584 })
585 }
586
587 fn write_symbol<A: AsRawSymbolTokenRef>(&mut self, value: A) -> IonResult<()> {
589 self.write_scalar(|output| {
590 RawTextWriter::write_symbol_token(output, value)?;
591 Ok(())
592 })
593 }
594
595 fn write_string<S: AsRef<str>>(&mut self, value: S) -> IonResult<()> {
597 self.write_scalar(|output| {
598 write!(output, "\"")?;
599 RawTextWriter::write_escaped_text_body(output, value)?;
600 write!(output, "\"")?;
601 Ok(())
602 })
603 }
604
605 fn write_clob<A: AsRef<[u8]>>(&mut self, value: A) -> IonResult<()> {
607 const NUM_DELIMITER_BYTES: usize = 4; const NUM_HEX_BYTES_PER_BYTE: usize = 4; let value: &[u8] = value.as_ref();
612
613 let mut clob_value =
615 String::with_capacity((value.len() * NUM_HEX_BYTES_PER_BYTE) + NUM_DELIMITER_BYTES);
616
617 for byte in value.iter().copied() {
618 let c = byte as char;
619 let escaped_byte = STRING_ESCAPE_CODES[c as usize];
620 if !escaped_byte.is_empty() {
621 clob_value.push_str(escaped_byte);
622 } else {
623 clob_value.push(c);
624 }
625 }
626 self.write_scalar(|output| {
627 write!(output, "{{{{\"{clob_value}\"}}}}")?;
628 Ok(())
629 })
630 }
631
632 fn write_blob<A: AsRef<[u8]>>(&mut self, value: A) -> IonResult<()> {
634 self.write_scalar(|output| {
635 write!(output, "{{{{{}}}}}", base64::encode(value))?;
641 Ok(())
642 })
643 }
644
645 fn step_in(&mut self, ion_type: IonType) -> IonResult<()> {
648 use IonType::*;
649
650 self.write_space_before_value()?;
651 self.write_value_metadata()?;
652 let container_type = match ion_type {
653 Struct => {
654 write!(self.output, "{{")?;
655 ContainerType::Struct
656 }
657 List => {
658 write!(self.output, "[")?;
659 ContainerType::List
660 }
661 SExp => {
662 write!(self.output, "(")?;
663 ContainerType::SExpression
664 }
665 _ => return illegal_operation(format!("Cannot step into a(n) {ion_type:?}")),
666 };
667 self.containers.push(EncodingLevel {
668 container_type,
669 child_count: 0usize,
670 });
671
672 Ok(())
673 }
674
675 fn set_field_name<A: AsRawSymbolTokenRef>(&mut self, name: A) {
679 let token = match name.as_raw_symbol_token_ref() {
680 RawSymbolTokenRef::SymbolId(sid) => RawSymbolToken::SymbolId(sid),
681 RawSymbolTokenRef::Text(text) => RawSymbolToken::Text(text.to_string()),
682 };
683 self.field_name = Some(token);
684 }
685
686 fn parent_type(&self) -> Option<IonType> {
687 match self.parent_level().container_type {
688 ContainerType::TopLevel => None,
689 ContainerType::List => Some(IonType::List),
690 ContainerType::SExpression => Some(IonType::SExp),
691 ContainerType::Struct => Some(IonType::Struct),
692 }
693 }
694
695 fn depth(&self) -> usize {
696 self.containers.len() - 1
697 }
698
699 fn step_out(&mut self) -> IonResult<()> {
702 use ContainerType::*;
703 let end_delimiter = match self.parent_level().container_type {
704 Struct => "}",
705 List => "]",
706 SExpression => ")",
707 TopLevel => return illegal_operation("cannot step out of the top level"),
708 };
709 let popped_encoding_level = self.containers.pop().unwrap();
711 if popped_encoding_level.child_count > 0 {
712 if self
715 .whitespace_config
716 .space_between_nested_values
717 .contains(['\n', '\r'])
718 {
719 writeln!(&mut self.output)?;
720 for _ in 0..self.depth() {
721 write!(&mut self.output, "{}", self.whitespace_config.indentation)?;
722 }
723 }
724 }
725 write!(self.output, "{end_delimiter}")?;
726 self.increment_child_count();
727 Ok(())
728 }
729
730 fn flush(&mut self) -> IonResult<()> {
731 self.output.flush()?;
732 Ok(())
733 }
734
735 fn output(&self) -> &W {
736 self.output.get_ref()
737 }
738
739 fn output_mut(&mut self) -> &mut W {
740 self.output.get_mut()
741 }
742}
743
744#[cfg(test)]
745mod tests {
746 use std::str;
747 use std::str::FromStr;
748
749 use bigdecimal::BigDecimal;
750 use chrono::{FixedOffset, NaiveDate, TimeZone};
751
752 use crate::result::IonResult;
753 use crate::text::raw_text_writer::{RawTextWriter, RawTextWriterBuilder};
754 use crate::types::Timestamp;
755 use crate::writer::IonWriter;
756 use crate::IonType;
757
758 fn writer_test_with_builder<F>(builder: RawTextWriterBuilder, mut commands: F, expected: &str)
759 where
760 F: FnMut(&mut RawTextWriter<&mut Vec<u8>>) -> IonResult<()>,
761 {
762 let mut output = Vec::new();
763 let mut writer = builder
764 .build(&mut output)
765 .expect("could not create RawTextWriter");
766 commands(&mut writer).expect("Invalid TextWriter test commands.");
767 writer.flush().expect("Call to flush() failed.");
768 assert_eq!(
769 str::from_utf8(writer.output().as_slice()).unwrap(),
770 expected
771 );
772 }
773
774 fn writer_test<F>(
781 mut commands: F,
782 expected_default: &str,
783 expected_pretty: &str,
784 expected_lines: &str,
785 ) where
786 F: Fn(&mut RawTextWriter<&mut Vec<u8>>) -> IonResult<()>,
787 {
788 writer_test_with_builder(
789 RawTextWriterBuilder::default(),
790 &mut commands,
791 expected_default,
792 );
793 writer_test_with_builder(
794 RawTextWriterBuilder::pretty(),
795 &mut commands,
796 expected_pretty,
797 );
798 writer_test_with_builder(RawTextWriterBuilder::lines(), commands, expected_lines)
799 }
800
801 fn write_scalar_test<F>(mut commands: F, expected: &str)
805 where
806 F: Fn(&mut RawTextWriter<&mut Vec<u8>>) -> IonResult<()>,
807 {
808 writer_test_with_builder(RawTextWriterBuilder::default(), &mut commands, expected);
809 writer_test_with_builder(RawTextWriterBuilder::pretty(), &mut commands, expected);
810 writer_test_with_builder(RawTextWriterBuilder::lines(), commands, expected)
811 }
812
813 #[test]
814 fn write_null_null() {
815 write_scalar_test(|w| w.write_null(IonType::Null), "null");
816 }
817
818 #[test]
819 fn write_null_string() {
820 write_scalar_test(|w| w.write_null(IonType::String), "null.string");
821 }
822
823 #[test]
824 fn write_bool_true() {
825 write_scalar_test(|w| w.write_bool(true), "true");
826 }
827
828 #[test]
829 fn write_bool_false() {
830 write_scalar_test(|w| w.write_bool(false), "false");
831 }
832
833 #[test]
834 fn write_i64() {
835 write_scalar_test(|w| w.write_i64(7), "7");
836 }
837
838 #[test]
839 fn write_f32() {
840 write_scalar_test(|w| w.write_f32(700f32), "7e2");
841 }
842
843 #[test]
844 fn write_f64() {
845 write_scalar_test(|w| w.write_f64(700f64), "7e2");
846 }
847
848 #[test]
849 fn write_annotated_i64() {
850 write_scalar_test(
851 |w| {
852 w.set_annotations(["foo", "bar", "baz quux"]);
853 w.write_i64(7)
854 },
855 "foo::bar::'baz quux'::7",
856 );
857 }
858
859 #[test]
860 fn write_decimal() {
861 let decimal_text = "731221.9948";
862 write_scalar_test(
863 |w| w.write_big_decimal(&BigDecimal::from_str(decimal_text).unwrap()),
864 decimal_text,
865 );
866 }
867
868 #[test]
869 fn write_datetime_epoch() {
870 #![allow(deprecated)] let naive_datetime =
872 NaiveDate::from_ymd(2000_i32, 1_u32, 1_u32).and_hms(0_u32, 0_u32, 0_u32);
873 let offset = FixedOffset::west(0);
874 let datetime = offset.from_utc_datetime(&naive_datetime);
875 write_scalar_test(|w| w.write_datetime(&datetime), "2000-01-01T00:00:00+00:00");
876 }
877
878 #[test]
879 fn write_timestamp_with_year() {
880 let timestamp = Timestamp::with_year(2000)
881 .build()
882 .expect("building timestamp failed");
883 write_scalar_test(|w| w.write_timestamp(×tamp), "2000T");
884 }
885
886 #[test]
887 fn write_timestamp_with_month() {
888 let timestamp = Timestamp::with_year(2000)
889 .with_month(8)
890 .build()
891 .expect("building timestamp failed");
892 write_scalar_test(|w| w.write_timestamp(×tamp), "2000-08T");
893 }
894
895 #[test]
896 fn write_timestamp_with_ymd() {
897 let timestamp = Timestamp::with_ymd(2000, 8, 22)
898 .build()
899 .expect("building timestamp failed");
900 write_scalar_test(|w| w.write_timestamp(×tamp), "2000-08-22T");
901 }
902
903 #[test]
904 fn write_timestamp_with_ymd_hms() {
905 let timestamp = Timestamp::with_ymd(2000, 8, 22)
906 .with_hms(15, 45, 11)
907 .build_at_offset(2 * 60)
908 .expect("building timestamp failed");
909 write_scalar_test(
910 |w| w.write_timestamp(×tamp),
911 "2000-08-22T15:45:11+02:00",
912 );
913 }
914
915 #[test]
916 fn write_timestamp_with_ymd_hms_millis() {
917 let timestamp = Timestamp::with_ymd_hms_millis(2000, 8, 22, 15, 45, 11, 931)
918 .build_at_offset(-5 * 60)
919 .expect("building timestamp failed");
920 write_scalar_test(
921 |w| w.write_timestamp(×tamp),
922 "2000-08-22T15:45:11.931-05:00",
923 );
924 }
925
926 #[test]
927 fn write_timestamp_with_ymd_hms_millis_unknown_offset() {
928 let timestamp = Timestamp::with_ymd_hms_millis(2000, 8, 22, 15, 45, 11, 931)
929 .build_at_unknown_offset()
930 .expect("building timestamp failed");
931 write_scalar_test(
932 |w| w.write_timestamp(×tamp),
933 "2000-08-22T15:45:11.931-00:00",
934 );
935 }
936
937 #[test]
938 fn write_blob() {
939 write_scalar_test(|w| w.write_blob("hello".as_bytes()), "{{aGVsbG8=}}");
940 }
941
942 #[test]
943 fn write_clob() {
944 write_scalar_test(|w| w.write_clob("a\"\'\n".as_bytes()), "{{\"a\\\"'\\n\"}}");
945 write_scalar_test(
946 |w| w.write_clob("❤️".as_bytes()),
947 "{{\"\\xe2\\x9d\\xa4\\xef\\xb8\\x8f\"}}",
948 );
949 write_scalar_test(
950 |w| w.write_clob("hello world".as_bytes()),
951 "{{\"hello world\"}}",
952 );
953 }
954
955 #[test]
956 fn with_space_between_top_level_values() {
957 writer_test_with_builder(
958 RawTextWriterBuilder::default().with_space_between_top_level_values(" "),
959 |w| {
960 w.write_bool(true)?;
961 w.write_bool(false)
962 },
963 "true false",
964 );
965 }
966
967 #[test]
968 fn with_space_between_nested_values() {
969 writer_test_with_builder(
970 RawTextWriterBuilder::default().with_space_between_nested_values(" "),
971 |w| {
972 w.write_bool(true)?;
973 w.step_in(IonType::List)?;
974 w.write_string("foo")?;
975 w.write_i64(21)?;
976 w.write_symbol("bar")?;
977 w.step_out()
978 },
979 "true [\"foo\", 21, bar]", );
981 }
982
983 #[test]
984 fn with_indentation() {
985 writer_test_with_builder(
986 RawTextWriterBuilder::pretty().with_indentation(" "),
987 |w| {
988 w.step_in(IonType::List)?;
989 w.write_string("foo")?;
990 w.write_i64(21)?;
991 w.write_symbol("bar")?;
992 w.step_out()
993 },
994 "[\n \"foo\",\n 21,\n bar\n]", );
996 }
997
998 #[test]
999 fn with_space_after_field_name() {
1000 writer_test_with_builder(
1001 RawTextWriterBuilder::default().with_space_after_field_name(" "),
1002 |w| {
1003 w.step_in(IonType::Struct)?;
1004 w.set_field_name("a");
1005 w.write_string("foo")?;
1006 w.step_out()
1007 },
1008 "{a: \"foo\"}",
1009 );
1010 }
1011
1012 #[test]
1013 fn with_space_after_container_start() {
1014 writer_test_with_builder(
1015 RawTextWriterBuilder::default().with_space_after_container_start(" "),
1016 |w| {
1017 w.step_in(IonType::Struct)?;
1018 w.set_field_name("a");
1019 w.write_string("foo")?;
1020 w.step_out()
1021 },
1022 "{ a: \"foo\"}",
1023 );
1024 }
1025
1026 #[test]
1027 fn write_stream() {
1028 writer_test(
1029 |w| {
1030 w.write_string("foo")?;
1031 w.write_i64(21)?;
1032 w.write_symbol("bar")
1033 },
1034 "\"foo\" 21 bar",
1035 "\"foo\"\n21\nbar",
1036 "\"foo\"\n21\nbar",
1037 );
1038 }
1039
1040 #[test]
1041 fn write_list() {
1042 writer_test(
1043 |w| {
1044 w.step_in(IonType::List)?;
1045 w.write_string("foo")?;
1046 w.write_i64(21)?;
1047 w.write_symbol("bar")?;
1048 w.step_out()
1049 },
1050 "[\"foo\", 21, bar]",
1051 "[\n \"foo\",\n 21,\n bar\n]",
1052 "[\"foo\", 21, bar]",
1053 );
1054 }
1055
1056 #[test]
1057 fn write_nested_list() {
1058 writer_test(
1059 |w| {
1060 w.step_in(IonType::List)?;
1061 w.write_string("foo")?;
1062 w.write_i64(21)?;
1063 w.step_in(IonType::List)?;
1064 w.write_symbol("bar")?;
1065 w.step_out()?;
1066 w.step_out()
1067 },
1068 "[\"foo\", 21, [bar]]",
1069 "[\n \"foo\",\n 21,\n [\n bar\n ]\n]",
1070 "[\"foo\", 21, [bar]]",
1071 );
1072 }
1073
1074 #[test]
1075 fn write_s_expression() {
1076 writer_test(
1077 |w| {
1078 w.step_in(IonType::SExp)?;
1079 w.write_string("foo")?;
1080 w.write_i64(21)?;
1081 w.write_symbol("bar")?;
1082 w.step_out()
1083 },
1084 "(\"foo\" 21 bar)",
1085 "(\n \"foo\"\n 21\n bar\n)",
1086 "(\"foo\" 21 bar)",
1087 );
1088 }
1089
1090 #[test]
1091 fn write_struct() {
1092 writer_test(
1093 |w| {
1094 w.step_in(IonType::Struct)?;
1095 w.set_field_name("a");
1096 w.write_string("foo")?;
1097 w.set_field_name("b");
1098 w.write_i64(21)?;
1099 w.set_field_name("c");
1100 w.set_annotations(["quux"]);
1101 w.write_symbol("bar")?;
1102 w.step_out()
1103 },
1104 "{a: \"foo\", b: 21, c: quux::bar}",
1105 "{\n a: \"foo\",\n b: 21,\n c: quux::bar\n}",
1106 "{a: \"foo\", b: 21, c: quux::bar}",
1107 );
1108 }
1109}