Skip to content

Commit

Permalink
Adds implementation of writer configuration
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
desaikd committed Nov 30, 2023
1 parent 4c4d262 commit de71366
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 141 deletions.
140 changes: 27 additions & 113 deletions src/element/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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<W: io::Write>(&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<u8> = 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<Item = &'a Element>>(
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<u8>`.
"##]
Expand Down Expand Up @@ -881,10 +785,15 @@ assert_eq!(element_before, element_after);
# }
```
"##]
#[cfg(feature = "experimental-writer")]
pub fn to_binary(&self) -> IonResult<Vec<u8>> {
let mut buffer = Vec::new();
self.write_as(Format::Binary, &mut buffer)?;
Ok(buffer)
let buffer = Vec::new();
let write_config: WriteConfig<BinaryEncoding_1_0> =
WriteConfig::<BinaryEncoding_1_0>::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##"

Check failure on line 799 in src/element/mod.rs

View workflow job for this annotation

GitHub Actions / Build and Test (ubuntu-latest, all)

unresolved link to `Element::write_as`
Expand Down Expand Up @@ -923,10 +832,15 @@ assert_eq!(element_before, element_after);
# }
```
"##]
#[cfg(feature = "experimental-writer")]
pub fn to_text(&self, text_kind: TextKind) -> IonResult<String> {
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<TextEncoding_1_0> =
WriteConfig::<TextEncoding_1_0>::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())
}
Expand Down
88 changes: 84 additions & 4 deletions src/element/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<E: Encoding> {
pub(crate) kind: WriteConfigKind,
phantom_data: PhantomData<E>,
}

#[cfg(feature = "experimental-writer")]
impl WriteConfig<TextEncoding_1_0> {
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<W: io::Write>(&self, output: W) -> IonResult<TextWriter<W>> {
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<BinaryEncoding_1_0> {
pub fn new() -> Self {
Self {
kind: WriteConfigKind::Binary(BinaryWriteConfig),
phantom_data: Default::default(),
}
}

/// Builds a binary writer based on writer configuration
pub fn build<W: io::Write>(&self, output: W) -> IonResult<BinaryWriter<W>> {
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<BinaryEncoding_1_0> {
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 {
Expand Down
17 changes: 16 additions & 1 deletion src/lazy/encoder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -50,6 +53,9 @@ pub trait LazyEncoder<W: Write>: 'static + Sized + Debug + Clone + Copy {
type SExpWriter<'a>;
type StructWriter<'a>;
type EExpressionWriter<'a>;

/// Build writer based on given writer configuration
fn build_writer<E: Encoding>(config: WriteConfig<E>, output: W) -> Self::Writer;
}

impl<W: Write> LazyEncoder<W> for TextEncoding_1_0 {
Expand All @@ -62,6 +68,15 @@ impl<W: Write> LazyEncoder<W> for TextEncoding_1_0 {
type SExpWriter<'a> = ();
type StructWriter<'a> = ();
type EExpressionWriter<'a> = ();

fn build_writer<E: Encoding>(config: WriteConfig<E>, 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.
Expand Down
23 changes: 0 additions & 23 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8> = 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
Expand Down

0 comments on commit de71366

Please sign in to comment.