Skip to content

Commit

Permalink
adds suggested changes
Browse files Browse the repository at this point in the history
* Modifies element APIs to use raw text/bianry writers instead of lazy
writer
* Adds implementation of `WriteAsIon` for `Element` and implementation
of `WriteAsIonValue` for `Value`
* adds build API implementation for binary and text lazy writers
* Puts `WriteConfig` under `experimental-lazy-reader` as it uses
`Encoding`
  • Loading branch information
desaikd committed Dec 14, 2023
1 parent fdc54cf commit 06fe5f8
Show file tree
Hide file tree
Showing 7 changed files with 219 additions and 69 deletions.
140 changes: 113 additions & 27 deletions src/element/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,19 @@
//! [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::{ion_data, Decimal, Int, IonError, IonResult, IonType, Str, Symbol, Timestamp};

#[cfg(feature = "experimental-writer")]
use crate::TextKind;
use crate::text::text_writer::TextWriterBuilder;
use crate::{
ion_data, Decimal, Format, Int, IonError, IonResult, IonType, Str, Symbol, TextKind, Timestamp,
};
use std::cmp::Ordering;
use std::fmt::{Display, Formatter};
use std::io;

mod annotations;
pub(crate) mod iterators;
Expand All @@ -40,14 +41,11 @@ mod sequence;

// Re-export the Value variant types and traits so they can be accessed directly from this module.
use crate::data_source::IonDataSource;
#[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::element::writer::ElementWriter;
use crate::reader::ReaderBuilder;
use crate::result::IonFailure;
use crate::ElementWriter;
use crate::{Blob, Bytes, Clob, List, SExp, Struct};

use crate::result::IonFailure;
pub use annotations::{Annotations, IntoAnnotations};
pub use sequence::Sequence;

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

#[doc = r##"
Expand Down Expand Up @@ -832,15 +923,10 @@ assert_eq!(element_before, element_after);
# }
```
"##]
#[cfg(feature = "experimental-writer")]
pub fn to_text(&self, text_kind: TextKind) -> IonResult<String> {
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())
let mut buffer = Vec::new();
self.write_as(Format::Text(text_kind), &mut buffer)?;
Ok(std::str::from_utf8(&buffer)
.expect("writer produced invalid utf-8")
.to_string())
}
Expand Down
55 changes: 19 additions & 36 deletions src/element/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,73 +3,56 @@
//! Provides utility to serialize Ion data from [`Element`] into common targets
//! such as byte buffers or files.
#[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::*;
pub use crate::TextKind::*;
use crate::{Element, IonType, TextKind, Value};
#[cfg(feature = "experimental-writer")]
use std::io;

Check warning on line 11 in src/element/writer.rs

View workflow job for this annotation

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

unused import: `std::io`

Check warning on line 11 in src/element/writer.rs

View workflow job for this annotation

GitHub Actions / Build and Test (ubuntu-latest, experimental-ion-hash)

unused import: `std::io`

Check warning on line 11 in src/element/writer.rs

View workflow job for this annotation

GitHub Actions / Build and Test (macos-latest, default)

unused import: `std::io`

Check warning on line 11 in src/element/writer.rs

View workflow job for this annotation

GitHub Actions / Build and Test (macos-latest, experimental-ion-hash)

unused import: `std::io`

Check warning on line 11 in src/element/writer.rs

View workflow job for this annotation

GitHub Actions / Build and Test (windows-2019, experimental-ion-hash)

unused import: `std::io`

Check warning on line 11 in src/element/writer.rs

View workflow job for this annotation

GitHub Actions / Build and Test (windows-2019, default)

unused import: `std::io`
#[cfg(feature = "experimental-writer")]
use std::marker::PhantomData;

Check warning on line 12 in src/element/writer.rs

View workflow job for this annotation

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

unused import: `std::marker::PhantomData`

Check warning on line 12 in src/element/writer.rs

View workflow job for this annotation

GitHub Actions / Build and Test (ubuntu-latest, experimental-ion-hash)

unused import: `std::marker::PhantomData`

Check warning on line 12 in src/element/writer.rs

View workflow job for this annotation

GitHub Actions / Build and Test (macos-latest, default)

unused import: `std::marker::PhantomData`

Check warning on line 12 in src/element/writer.rs

View workflow job for this annotation

GitHub Actions / Build and Test (macos-latest, experimental-ion-hash)

unused import: `std::marker::PhantomData`

Check warning on line 12 in src/element/writer.rs

View workflow job for this annotation

GitHub Actions / Build and Test (windows-2019, experimental-ion-hash)

unused import: `std::marker::PhantomData`

Check warning on line 12 in src/element/writer.rs

View workflow job for this annotation

GitHub Actions / Build and Test (windows-2019, default)

unused import: `std::marker::PhantomData`
#[cfg(feature = "experimental-lazy-reader")]
use {
crate::lazy::encoder::{LazyEncoder, LazyRawWriter},
crate::lazy::encoding::{BinaryEncoding_1_0, Encoding, TextEncoding_1_0},
};

/// 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")]
#[cfg(feature = "experimental-lazy-reader")]
pub struct WriteConfig<E: Encoding> {
pub(crate) kind: WriteConfigKind,
phantom_data: PhantomData<E>,
}

#[cfg(feature = "experimental-writer")]
#[cfg(feature = "experimental-lazy-reader")]
impl<E: Encoding + LazyEncoder> WriteConfig<E> {
/// Builds a writer based on writer configuration
pub fn build<W: io::Write>(self, output: W) -> IonResult<<E as LazyEncoder>::Writer<W>> {
E::Writer::build(self, output)
}
}

#[cfg(feature = "experimental-lazy-reader")]
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")]
#[cfg(feature = "experimental-lazy-reader")]
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")]
#[cfg(feature = "experimental-lazy-reader")]
impl Default for WriteConfig<BinaryEncoding_1_0> {
fn default() -> Self {
Self::new()
Expand All @@ -82,12 +65,12 @@ pub(crate) enum WriteConfigKind {
Binary(BinaryWriteConfig),
}

/// Text writer configuration with text kind to be sued to create a writer
/// Text writer configuration with text kind to be used to create a writer
pub(crate) struct TextWriteConfig {
text_kind: TextKind,
}

/// Binary writer configuration to be sued to create a writer
/// Binary writer configuration to be used to create a writer
// TODO: Add appropriate binary configuration if required for 1.1
pub(crate) struct BinaryWriteConfig;

Expand Down
13 changes: 12 additions & 1 deletion src/lazy/encoder/binary/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use bumpalo::Bump as BumpAllocator;
use delegate::delegate;

use crate::binary::var_uint::VarUInt;
use crate::element::writer::{WriteConfig, WriteConfigKind};
use crate::lazy::encoder::binary::value_writer::{
BinaryAnnotatableValueWriter_1_0, MAX_INLINE_LENGTH,
};
Expand All @@ -13,7 +14,7 @@ use crate::lazy::encoder::value_writer::internal::MakeValueWriter;
use crate::lazy::encoder::value_writer::{SequenceWriter, StructWriter};
use crate::lazy::encoder::write_as_ion::WriteAsIon;
use crate::lazy::encoder::{LazyEncoder, LazyRawWriter};
use crate::lazy::encoding::BinaryEncoding_1_0;
use crate::lazy::encoding::{BinaryEncoding_1_0, Encoding};
use crate::raw_symbol_token_ref::AsRawSymbolTokenRef;
use crate::result::EncodingError;
use crate::{IonError, IonResult, RawSymbolTokenRef};
Expand Down Expand Up @@ -132,6 +133,16 @@ impl<W: Write> LazyRawWriter<W> for LazyRawBinaryWriter_1_0<W> {
Self::new(output)
}

/// Build binary writer based on given writer configuration
fn build<E: Encoding>(config: WriteConfig<E>, output: W) -> IonResult<Self> {
match &config.kind {
WriteConfigKind::Text(_) => {
unreachable!("Text writer can not be created from binary encoding")
}
WriteConfigKind::Binary(_) => LazyRawBinaryWriter_1_0::new(output),
}
}

delegate! {
to self {
fn flush(&mut self) -> IonResult<()>;
Expand Down
8 changes: 7 additions & 1 deletion src/lazy/encoder/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
#![allow(non_camel_case_types)]

use crate::IonResult;
use std::fmt::Debug;
use std::io::Write;

use crate::element::writer::WriteConfig;
use crate::lazy::encoding::Encoding;
use crate::IonResult;
use value_writer::SequenceWriter;

pub mod annotate;
Expand Down Expand Up @@ -36,6 +39,9 @@ pub(crate) mod private {
/// An Ion writer without an encoding context (that is: symbol/macro tables).
pub trait LazyRawWriter<W: Write>: SequenceWriter {
fn new(output: W) -> IonResult<Self>
where
Self: Sized;
fn build<E: Encoding>(config: WriteConfig<E>, output: W) -> IonResult<Self>
where
Self: Sized;
fn flush(&mut self) -> IonResult<()>;
Expand Down
13 changes: 12 additions & 1 deletion src/lazy/encoder/text/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use crate::element::writer::{WriteConfig, WriteConfigKind};
use crate::lazy::encoder::text::value_writer::{
TextAnnotatableValueWriter_1_0, TextValueWriter_1_0,
};
use crate::lazy::encoder::value_writer::internal::MakeValueWriter;
use crate::lazy::encoder::value_writer::SequenceWriter;
use crate::lazy::encoder::write_as_ion::WriteAsIon;
use crate::lazy::encoder::{LazyEncoder, LazyRawWriter};
use crate::lazy::encoding::TextEncoding_1_0;
use crate::lazy::encoding::{Encoding, TextEncoding_1_0};
use crate::text::raw_text_writer::{WhitespaceConfig, PRETTY_WHITESPACE_CONFIG};
use crate::IonResult;
use delegate::delegate;
Expand Down Expand Up @@ -78,6 +79,16 @@ impl<W: Write> LazyRawWriter<W> for LazyRawTextWriter_1_0<W> {
Ok(LazyRawTextWriter_1_0::new(output))
}

/// Build text writer based on given writer configuration
fn build<E: Encoding>(config: WriteConfig<E>, output: W) -> IonResult<Self> {
match &config.kind {
WriteConfigKind::Text(_) => Ok(LazyRawTextWriter_1_0::new(output)),
WriteConfigKind::Binary(_) => {
unreachable!("Binary writer can not be created from text encoding")
}
}
}

// Delegate the trait methods to the inherent methods; this allows a version of these
// methods to be called on the concrete type even when the trait is not in scope.
delegate! {
Expand Down
Loading

0 comments on commit 06fe5f8

Please sign in to comment.