Skip to content

Commit

Permalink
Introduces Bytes, Blob, and Clob wrapper types (#506)
Browse files Browse the repository at this point in the history
This PR introduces new opaque wrappers for `Vec<u8>`, which was used
throughout APIs to represent owned blobs and clobs.

`Bytes` captures the core functionality of an owned byte array, but
does not include Ion type information.

`Blob` and `Clob` are both trivial tuple struct wrappers around
`Bytes`, adding an Ion type to the data.

The `Value::Blob` and `Value::Clob` enum variants (which already
provide an Ion type) now wrap an instance of `Bytes`.

This PR fixes #500.
  • Loading branch information
zslayton committed Apr 5, 2023
1 parent 6a05f30 commit 6e14332
Show file tree
Hide file tree
Showing 14 changed files with 246 additions and 49 deletions.
9 changes: 5 additions & 4 deletions src/binary/non_blocking/raw_binary_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::binary::non_blocking::type_descriptor::{Header, TypeDescriptor};
use crate::binary::uint::DecodedUInt;
use crate::binary::var_uint::VarUInt;
use crate::binary::IonTypeCode;
use crate::element::{Blob, Clob};
use crate::result::{
decoding_error, decoding_error_raw, illegal_operation, illegal_operation_raw,
incomplete_data_error,
Expand Down Expand Up @@ -769,8 +770,8 @@ impl<A: AsRef<[u8]>> IonReader for RawBinaryBufferReader<A> {
self.read_symbol_id().map(RawSymbolToken::SymbolId)
}

fn read_blob(&mut self) -> IonResult<Vec<u8>> {
self.read_blob_bytes().map(Vec::from)
fn read_blob(&mut self) -> IonResult<Blob> {
self.read_blob_bytes().map(Vec::from).map(Blob::from)
}

fn map_blob<F, U>(&mut self, f: F) -> IonResult<U>
Expand All @@ -781,8 +782,8 @@ impl<A: AsRef<[u8]>> IonReader for RawBinaryBufferReader<A> {
self.read_blob_bytes().map(f)
}

fn read_clob(&mut self) -> IonResult<Vec<u8>> {
self.read_clob_bytes().map(Vec::from)
fn read_clob(&mut self) -> IonResult<Clob> {
self.read_clob_bytes().map(Vec::from).map(Clob::from)
}

fn map_clob<F, U>(&mut self, f: F) -> IonResult<U>
Expand Down
16 changes: 9 additions & 7 deletions src/binary/raw_binary_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use crate::{
};
use std::io;

use crate::element::{Blob, Clob};
use crate::raw_symbol_token::RawSymbolToken;
use crate::result::{decoding_error_raw, IonError};
use crate::stream_reader::IonReader;
Expand Down Expand Up @@ -572,8 +573,8 @@ impl<R: IonDataSource> IonReader for RawBinaryReader<R> {
Ok(RawSymbolToken::SymbolId(symbol_id))
}

fn read_blob(&mut self) -> IonResult<Vec<u8>> {
self.map_blob(|b| Vec::from(b))
fn read_blob(&mut self) -> IonResult<Blob> {
self.map_blob(|b| Vec::from(b)).map(Blob::from)
}

fn map_blob<F, U>(&mut self, f: F) -> IonResult<U>
Expand All @@ -587,8 +588,8 @@ impl<R: IonDataSource> IonReader for RawBinaryReader<R> {
self.read_slice(number_of_bytes, |buffer: &[u8]| Ok(f(buffer)))
}

fn read_clob(&mut self) -> IonResult<Vec<u8>> {
self.map_clob(|c| Vec::from(c))
fn read_clob(&mut self) -> IonResult<Clob> {
self.map_clob(|c| Vec::from(c)).map(Clob::from)
}

fn map_clob<F, U>(&mut self, f: F) -> IonResult<U>
Expand Down Expand Up @@ -1181,6 +1182,7 @@ mod tests {

use crate::binary::constants::v1_0::IVM;
use crate::binary::raw_binary_reader::RawBinaryReader;
use crate::element::{Blob, Clob};
use crate::raw_reader::{RawStreamItem, RawStreamItem::*};
use crate::raw_symbol_token::local_sid_token;
use crate::result::{IonError, IonResult};
Expand Down Expand Up @@ -1446,7 +1448,7 @@ mod tests {
fn test_read_clob_empty() -> IonResult<()> {
let mut cursor = ion_cursor_for(&[0x90]);
assert_eq!(cursor.next()?, Value(IonType::Clob));
assert_eq!(cursor.read_clob()?, vec![]);
assert_eq!(cursor.read_clob()?, Clob::from(vec![]));
Ok(())
}

Expand All @@ -1462,15 +1464,15 @@ mod tests {
fn test_read_blob_empty() -> IonResult<()> {
let mut cursor = ion_cursor_for(&[0xA0]);
assert_eq!(cursor.next()?, Value(IonType::Blob));
assert_eq!(cursor.read_blob()?, vec![]);
assert_eq!(cursor.read_blob()?, Blob::from(vec![]));
Ok(())
}

#[test]
fn test_read_blob_123() -> IonResult<()> {
let mut cursor = ion_cursor_for(&[0xA3, 0x01, 0x02, 0x03]);
assert_eq!(cursor.next()?, Value(IonType::Blob));
assert_eq!(cursor.read_blob()?, vec![1u8, 2, 3]);
assert_eq!(cursor.read_blob()?, Blob::from(vec![1u8, 2, 3]));
Ok(())
}

Expand Down
12 changes: 8 additions & 4 deletions src/binary/raw_binary_writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,7 @@ mod writer_tests {
use rstest::*;

use super::*;
use crate::element::{Blob, Clob};
use crate::raw_symbol_token::{local_sid_token, RawSymbolToken};
use crate::reader::{Reader, ReaderBuilder};
use crate::symbol::Symbol;
Expand Down Expand Up @@ -1111,17 +1112,20 @@ mod writer_tests {
.map(|s| s.as_bytes())
.collect();

let clobs: Vec<Clob> = values.iter().map(|b| Clob::from(*b)).collect();
let blobs: Vec<Blob> = values.iter().map(|b| Blob::from(*b)).collect();

binary_writer_scalar_test(
&values,
clobs.as_slice(),
IonType::Clob,
|writer, v| writer.write_clob(*v),
|writer, v| writer.write_clob(v),
|reader| reader.read_clob(),
)?;

binary_writer_scalar_test(
&values,
blobs.as_slice(),
IonType::Blob,
|writer, v| writer.write_blob(*v),
|writer, v| writer.write_blob(v),
|reader| reader.read_blob(),
)
}
Expand Down
50 changes: 50 additions & 0 deletions src/element/bytes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/// An owned, immutable byte array.
/// ```rust
/// use ion_rs::element::Bytes;
/// let ivm: &[u8] = &[0xEA_u8, 0x01, 0x00, 0xE0]; // Ion 1.0 version marker
/// let bytes: Bytes = ivm.into();
/// assert_eq!(&bytes, ivm);
/// ```
/// ```rust
/// use ion_rs::element::Bytes;
/// let bytes: Bytes = "hello".into();
/// assert_eq!(&bytes, "hello".as_bytes());
/// ```
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Bytes {
data: Vec<u8>,
}

impl PartialEq<[u8]> for Bytes {
fn eq(&self, other: &[u8]) -> bool {
self.as_ref().eq(other)
}
}

impl From<Vec<u8>> for Bytes {
fn from(data: Vec<u8>) -> Self {
Bytes { data }
}
}

impl From<&[u8]> for Bytes {
fn from(data: &[u8]) -> Self {
Bytes {
data: data.to_vec(),
}
}
}

impl From<&str> for Bytes {
fn from(text: &str) -> Self {
Bytes {
data: text.as_bytes().into(),
}
}
}

impl AsRef<[u8]> for Bytes {
fn as_ref(&self) -> &[u8] {
&self.data
}
}
19 changes: 9 additions & 10 deletions src/element/element_stream_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::result::{decoding_error, illegal_operation, illegal_operation_raw};
use crate::text::parent_container::ParentContainer;

use crate::element::iterators::SymbolsIterator;
use crate::element::Element;
use crate::element::{Blob, Clob, Element};
use crate::{
Decimal, Int, IonError, IonReader, IonResult, IonType, Str, StreamItem, Symbol, Timestamp,
};
Expand Down Expand Up @@ -265,8 +265,8 @@ impl IonReader for ElementStreamReader {
self.current_value_as("symbol value", |v| v.as_symbol().map(|i| i.to_owned()))
}

fn read_blob(&mut self) -> IonResult<Vec<u8>> {
self.map_blob(|b| Vec::from(b))
fn read_blob(&mut self) -> IonResult<Blob> {
self.map_blob(|b| Vec::from(b)).map(Blob::from)
}

fn map_blob<F, U>(&mut self, f: F) -> IonResult<U>
Expand All @@ -280,8 +280,8 @@ impl IonReader for ElementStreamReader {
}
}

fn read_clob(&mut self) -> IonResult<Vec<u8>> {
self.map_clob(|c| Vec::from(c))
fn read_clob(&mut self) -> IonResult<Clob> {
self.map_clob(|c| Vec::from(c)).map(Clob::from)
}

fn map_clob<F, U>(&mut self, f: F) -> IonResult<U>
Expand Down Expand Up @@ -380,7 +380,6 @@ mod reader_tests {
use rstest::*;

use super::*;
use crate::element::Value;
use crate::result::IonResult;
use crate::stream_reader::IonReader;
use crate::types::decimal::Decimal;
Expand Down Expand Up @@ -504,9 +503,9 @@ mod reader_tests {
next_type(reader, IonType::List, false);
reader.step_in()?;
next_type(reader, IonType::Blob, false);
assert_eq!(&reader.read_blob()?, "encoded".as_bytes());
assert_eq!(reader.read_blob()?, Blob::from("encoded"));
next_type(reader, IonType::Clob, false);
assert_eq!(&reader.read_clob()?, "hello".as_bytes());
assert_eq!(reader.read_clob()?, Clob::from("hello"));
next_type(reader, IonType::Float, false);
assert_eq!(reader.read_f64()?, 4.5);
next_type(reader, IonType::Decimal, false);
Expand Down Expand Up @@ -535,8 +534,8 @@ mod reader_tests {
#[case(" 2007-07-12T ", Timestamp::with_ymd(2007, 7, 12).build().unwrap())]
#[case(" foo ", Symbol::owned("foo"))]
#[case(" \"hi!\" ", "hi!".to_owned())]
#[case(" {{ZW5jb2RlZA==}} ", Value::Blob("encoded".as_bytes().to_vec()))]
#[case(" {{\"hello\"}} ", Value::Clob("hello".as_bytes().to_vec()))]
#[case(" {{ZW5jb2RlZA==}} ", Blob::from("encoded"))]
#[case(" {{\"hello\"}} ", Clob::from("hello"))]
fn test_read_single_top_level_values<E: Into<Element>>(
#[case] text: &str,
#[case] expected_value: E,
Expand Down
116 changes: 116 additions & 0 deletions src/element/lob.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
use crate::element::Bytes;

/// An in-memory representation of an Ion blob.
///
/// ```rust
/// use ion_rs::element::Blob;
/// let ivm: &[u8] = &[0xEA_u8, 0x01, 0x00, 0xE0]; // Ion 1.0 version marker
/// let blob: Blob = ivm.into();
/// assert_eq!(&blob, ivm);
/// assert_eq!(blob.as_slice().len(), 4);
/// ```
/// ```rust
/// use ion_rs::element::Blob;
/// let blob: Blob = "hello".into();
/// assert_eq!(&blob, "hello".as_bytes());
/// assert_eq!(blob.as_slice().len(), 5);
/// ```
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Blob(pub Bytes);

impl Blob {
pub fn as_slice(&self) -> &[u8] {
self.as_ref()
}
}

/// An in-memory representation of an Ion clob.
///
/// ```rust
/// use ion_rs::element::Clob;
/// let clob: Clob = "hello".into();
/// assert_eq!(&clob, "hello".as_bytes());
/// assert_eq!(clob.as_slice().len(), 5);
/// ```
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Clob(pub Bytes);

impl Clob {
pub fn as_slice(&self) -> &[u8] {
self.as_ref()
}
}

impl AsRef<[u8]> for Blob {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}

impl AsRef<[u8]> for Clob {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}

impl PartialEq<[u8]> for Blob {
fn eq(&self, other: &[u8]) -> bool {
self.as_ref().eq(other)
}
}

impl PartialEq<[u8]> for Clob {
fn eq(&self, other: &[u8]) -> bool {
self.as_ref().eq(other)
}
}

impl From<Blob> for Bytes {
fn from(blob: Blob) -> Self {
blob.0
}
}

impl From<Clob> for Bytes {
fn from(clob: Clob) -> Self {
clob.0
}
}

impl From<Vec<u8>> for Blob {
fn from(data: Vec<u8>) -> Self {
let bytes: Bytes = data.into();
Blob(bytes)
}
}

impl From<Vec<u8>> for Clob {
fn from(data: Vec<u8>) -> Self {
let bytes: Bytes = data.into();
Clob(bytes)
}
}

impl From<&[u8]> for Blob {
fn from(data: &[u8]) -> Self {
Blob::from(data.to_vec())
}
}

impl From<&[u8]> for Clob {
fn from(data: &[u8]) -> Self {
Clob::from(data.to_vec())
}
}

impl From<&str> for Blob {
fn from(text: &str) -> Self {
text.as_bytes().into()
}
}

impl From<&str> for Clob {
fn from(text: &str) -> Self {
text.as_bytes().into()
}
}
Loading

0 comments on commit 6e14332

Please sign in to comment.