From de7136688b4da4cd43332832b43c446cd66e55fa Mon Sep 17 00:00:00 2001 From: Khushboo Desai Date: Thu, 30 Nov 2023 09:08:52 -0800 Subject: [PATCH] Adds implementation of writer configuration * Adds a writer configuration implementation that supports configuring both binary and text writer * Adds build_writer() with LazyEncoder which can be used to be build a writer based on given writer configuration * Removes write_as and write_all_as methods from element. Instead now LazyTextWriter can be used to write a Rust value as Ion. * Uses WriteConfig within to_binary and to_text methods of element API --- src/element/mod.rs | 140 ++++++++-------------------------------- src/element/writer.rs | 88 +++++++++++++++++++++++-- src/lazy/encoder/mod.rs | 17 ++++- src/lib.rs | 23 ------- 4 files changed, 127 insertions(+), 141 deletions(-) diff --git a/src/element/mod.rs b/src/element/mod.rs index cb7d0f41..0dfcf6fc 100644 --- a/src/element/mod.rs +++ b/src/element/mod.rs @@ -12,19 +12,18 @@ //! [simd-json-value]: https://docs.rs/simd-json/latest/simd_json/value/index.html //! [serde-json-value]: https://docs.serde.rs/serde_json/value/enum.Value.html -use crate::binary::binary_writer::BinaryWriterBuilder; use crate::element::builders::{SequenceBuilder, StructBuilder}; use crate::element::reader::ElementReader; use crate::ion_data::{IonEq, IonOrd}; +#[cfg(feature = "experimental-writer")] use crate::ion_writer::IonWriter; use crate::text::text_formatter::IonValueFormatter; -use crate::text::text_writer::TextWriterBuilder; -use crate::{ - ion_data, Decimal, Format, Int, IonError, IonResult, IonType, Str, Symbol, TextKind, Timestamp, -}; +use crate::{ion_data, Decimal, Int, IonError, IonResult, IonType, Str, Symbol, Timestamp}; + +#[cfg(feature = "experimental-writer")] +use crate::TextKind; use std::cmp::Ordering; use std::fmt::{Display, Formatter}; -use std::io; mod annotations; pub(crate) mod iterators; @@ -41,11 +40,14 @@ mod sequence; // Re-export the Value variant types and traits so they can be accessed directly from this module. use crate::data_source::IonDataSource; -use crate::element::writer::ElementWriter; +#[cfg(feature = "experimental-writer")] +use crate::element::writer::WriteConfig; +#[cfg(feature = "experimental-writer")] +use crate::lazy::encoding::{BinaryEncoding_1_0, TextEncoding_1_0}; use crate::reader::ReaderBuilder; -use crate::{Blob, Bytes, Clob, List, SExp, Struct}; - use crate::result::IonFailure; +use crate::ElementWriter; +use crate::{Blob, Bytes, Clob, List, SExp, Struct}; pub use annotations::{Annotations, IntoAnnotations}; pub use sequence::Sequence; @@ -749,104 +751,6 @@ impl Element { Ok(()) } - #[doc = r##" -Serializes this [`Element`] as Ion, writing the resulting bytes to the provided [`io::Write`]. -The caller must verify that `output` is either empty or only contains Ion of the same -format (text or binary) before writing begins. - -This method constructs a new writer for each invocation, which means that there will only be a single -top level value in the output stream. Writing several values to the same stream is preferable to -maximize encoding efficiency. See [`write_all_as`](Self::write_all_as) for details. -"##] - #[cfg_attr( - feature = "experimental-writer", - doc = r##" -To reuse a writer and have greater control over resource -management, see [`Element::write_to`]. -"## - )] - #[doc = r##" -``` -# use ion_rs::{Format, IonResult, TextKind}; -# fn main() -> IonResult<()> { -use ion_rs::Element; -use ion_rs::ion_list; - -// Construct an Element -let element_before: Element = ion_list! [1, 2, 3].into(); - -// Write the Element to a buffer using a specified format -let mut buffer = Vec::new(); -element_before.write_as(Format::Text(TextKind::Pretty), &mut buffer)?; - -// Read the Element back from the serialized form -let element_after = Element::read_one(&buffer)?; - -// Confirm that no data was lost -assert_eq!(element_before, element_after); -# Ok(()) -# } -``` -"##] - pub fn write_as(&self, format: Format, output: W) -> IonResult<()> { - match format { - Format::Text(text_kind) => { - let mut text_writer = TextWriterBuilder::new(text_kind).build(output)?; - Element::write_element_to(self, &mut text_writer)?; - text_writer.flush() - } - Format::Binary => { - let mut binary_writer = BinaryWriterBuilder::default().build(output)?; - Element::write_element_to(self, &mut binary_writer)?; - binary_writer.flush() - } - } - } - - /// Serializes each of the provided [`Element`]s as Ion, writing the resulting bytes to the - /// provided [`io::Write`]. The caller must verify that `output` is either empty or only - /// contains Ion of the same format (text or binary) before writing begins. - /// - /// This method is preferable to [`write_as`](Self::write_as) when writing streams consisting - /// of more than one top-level value; the writer can re-use the same symbol table definition - /// to encode each value, resulting in a more compact representation. - /// - /// ``` - ///# use ion_rs::IonResult; - ///# fn main() -> IonResult<()> { - /// use ion_rs::{Element, Format, ion_seq}; - /// - /// let elements = ion_seq!("foo", "bar", "baz"); - /// let mut buffer: Vec = Vec::new(); - /// Element::write_all_as(&elements, Format::Binary, &mut buffer)?; - /// let roundtrip_elements = Element::read_all(buffer)?; - /// assert_eq!(elements, roundtrip_elements); - ///# Ok(()) - ///# } - /// ``` - pub fn write_all_as<'a, W: io::Write, I: IntoIterator>( - elements: I, - format: Format, - output: W, - ) -> IonResult<()> { - match format { - Format::Text(text_kind) => { - let mut text_writer = TextWriterBuilder::new(text_kind).build(output)?; - for element in elements { - Element::write_element_to(element, &mut text_writer)?; - } - text_writer.flush() - } - Format::Binary => { - let mut binary_writer = BinaryWriterBuilder::default().build(output)?; - for element in elements { - Element::write_element_to(element, &mut binary_writer)?; - } - binary_writer.flush() - } - } - } - #[doc = r##" Serializes this [`Element`] as binary Ion, returning the output as a `Vec`. "##] @@ -881,10 +785,15 @@ assert_eq!(element_before, element_after); # } ``` "##] + #[cfg(feature = "experimental-writer")] pub fn to_binary(&self) -> IonResult> { - let mut buffer = Vec::new(); - self.write_as(Format::Binary, &mut buffer)?; - Ok(buffer) + let buffer = Vec::new(); + let write_config: WriteConfig = + WriteConfig::::new(); + let mut writer = write_config.build(buffer)?; + Element::write_element_to(self, &mut writer)?; + writer.flush()?; + Ok(writer.output().to_owned().to_owned()) } #[doc = r##" @@ -923,10 +832,15 @@ assert_eq!(element_before, element_after); # } ``` "##] + #[cfg(feature = "experimental-writer")] pub fn to_text(&self, text_kind: TextKind) -> IonResult { - let mut buffer = Vec::new(); - self.write_as(Format::Text(text_kind), &mut buffer)?; - Ok(std::str::from_utf8(&buffer) + let buffer = Vec::new(); + let write_config: WriteConfig = + WriteConfig::::new(text_kind); + let mut writer = write_config.build(buffer)?; + Element::write_element_to(self, &mut writer)?; + writer.flush()?; + Ok(std::str::from_utf8(writer.output()) .expect("writer produced invalid utf-8") .to_string()) } diff --git a/src/element/writer.rs b/src/element/writer.rs index 546d77b9..d6544a4b 100644 --- a/src/element/writer.rs +++ b/src/element/writer.rs @@ -3,13 +3,93 @@ //! Provides utility to serialize Ion data from [`Element`] into common targets //! such as byte buffers or files. -use crate::result::IonResult; - +#[cfg(feature = "experimental-writer")] +use crate::binary::binary_writer::{BinaryWriter, BinaryWriterBuilder}; use crate::ion_writer::IonWriter; +#[cfg(feature = "experimental-writer")] +use crate::lazy::encoding::{BinaryEncoding_1_0, Encoding, TextEncoding_1_0}; +use crate::result::IonResult; +#[cfg(feature = "experimental-writer")] +use crate::text::text_writer::{TextWriter, TextWriterBuilder}; pub use crate::Format::*; -use crate::IonType; pub use crate::TextKind::*; -use crate::{Element, Value}; +use crate::{Element, IonType, TextKind, Value}; +#[cfg(feature = "experimental-writer")] +use std::io; +#[cfg(feature = "experimental-writer")] +use std::marker::PhantomData; + +/// Writer configuration to provide format and Ion version details to writer through encoding +/// This will be used to create a writer without specifying which writer methods to use +#[cfg(feature = "experimental-writer")] +pub struct WriteConfig { + pub(crate) kind: WriteConfigKind, + phantom_data: PhantomData, +} + +#[cfg(feature = "experimental-writer")] +impl WriteConfig { + pub fn new(text_kind: TextKind) -> Self { + Self { + kind: WriteConfigKind::Text(TextWriteConfig { text_kind }), + phantom_data: Default::default(), + } + } + + /// Builds a text writer based on writer configuration + pub fn build(&self, output: W) -> IonResult> { + match &self.kind { + WriteConfigKind::Text(text_writer_config) => { + TextWriterBuilder::new(text_writer_config.text_kind).build(output) + } + WriteConfigKind::Binary(_) => { + unreachable!("Binary writer can not be created from text encoding") + } + } + } +} + +#[cfg(feature = "experimental-writer")] +impl WriteConfig { + pub fn new() -> Self { + Self { + kind: WriteConfigKind::Binary(BinaryWriteConfig), + phantom_data: Default::default(), + } + } + + /// Builds a binary writer based on writer configuration + pub fn build(&self, output: W) -> IonResult> { + match &self.kind { + WriteConfigKind::Text(_) => { + unreachable!("Text writer can not be created from binary encoding") + } + WriteConfigKind::Binary(_) => BinaryWriterBuilder::default().build(output), + } + } +} + +#[cfg(feature = "experimental-writer")] +impl Default for WriteConfig { + fn default() -> Self { + Self::new() + } +} + +/// Writer configuration type enum for text and binary configuration +pub(crate) enum WriteConfigKind { + Text(TextWriteConfig), + Binary(BinaryWriteConfig), +} + +/// Text writer configuration with text kind to be sued to create a writer +pub(crate) struct TextWriteConfig { + text_kind: TextKind, +} + +/// Binary writer configuration to be sued to create a writer +// TODO: Add appropriate binary configuration if required for 1.1 +pub(crate) struct BinaryWriteConfig; /// Serializes [`Element`] instances into some kind of output sink. pub trait ElementWriter { diff --git a/src/lazy/encoder/mod.rs b/src/lazy/encoder/mod.rs index 56796fcc..5e489a65 100644 --- a/src/lazy/encoder/mod.rs +++ b/src/lazy/encoder/mod.rs @@ -8,7 +8,10 @@ use std::fmt::Debug; use std::io::Write; use write_as_ion::WriteAsIon; -use crate::lazy::encoding::TextEncoding_1_0; +use crate::element::writer::WriteConfig; +use crate::element::writer::WriteConfigKind; + +use crate::lazy::encoding::{Encoding, TextEncoding_1_0}; use crate::raw_symbol_token_ref::AsRawSymbolTokenRef; use crate::text::raw_text_writer::{WhitespaceConfig, PRETTY_WHITESPACE_CONFIG}; use crate::{Decimal, Int, IonResult, IonType, RawSymbolTokenRef, RawTextWriter, Timestamp}; @@ -50,6 +53,9 @@ pub trait LazyEncoder: 'static + Sized + Debug + Clone + Copy { type SExpWriter<'a>; type StructWriter<'a>; type EExpressionWriter<'a>; + + /// Build writer based on given writer configuration + fn build_writer(config: WriteConfig, output: W) -> Self::Writer; } impl LazyEncoder for TextEncoding_1_0 { @@ -62,6 +68,15 @@ impl LazyEncoder for TextEncoding_1_0 { type SExpWriter<'a> = (); type StructWriter<'a> = (); type EExpressionWriter<'a> = (); + + fn build_writer(config: WriteConfig, output: W) -> Self::Writer { + match &config.kind { + WriteConfigKind::Text(_) => LazyRawTextWriter_1_0::new(output), + WriteConfigKind::Binary(_) => { + unreachable!("Binary writer can not be created from text encoding") + } + } + } } /// One-shot methods that take a (possibly empty) sequence of annotations to encode and return a ValueWriter. diff --git a/src/lib.rs b/src/lib.rs index ed7c2cce..ac97ff86 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -130,29 +130,6 @@ //! # Ok(()) //! # } //! ``` -//! -//! ## Writing an `Element` to an `io::Write` -//! -//! ``` -//! # use ion_rs::IonResult; -//! # fn main() -> IonResult<()> { -//! use ion_rs::{Element, Format, TextKind, ion_list, ion_struct}; -//! let element: Element = ion_struct! { -//! "foo": "hello", -//! "bar": true, -//! "baz": ion_list! [4, 5, 6] -//! } -//! .into(); -//! -//! let mut buffer: Vec = Vec::new(); -//! element.write_as(Format::Text(TextKind::Compact), &mut buffer)?; -//! assert_eq!( -//! "{foo: \"hello\", bar: true, baz: [4, 5, 6]}".as_bytes(), -//! buffer.as_slice() -//! ); -//! # Ok(()) -//! # } -//! ``` // XXX this top-level import is required because of the macro factoring of rstest_reuse // XXX Clippy incorrectly indicates that this is redundant