Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[serde] Implement serde Serializer/Deserializer::is_human_readable #791

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
[serde] Implement serde Serializer/Deserializer::is_human_readable
Will return true for text encodings, false for binary.
is_human_readable is used to determine whether Serialize implementations
should serialize in human-readable form.

Some types have a human-readable form that may be somewhat expensive
to construct, as well as a binary form that is compact and efficient.

Fixes #790
theli-ua committed Jun 19, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
commit c769c7db77446ef843bd321db1e507e1c00e4000
2 changes: 2 additions & 0 deletions src/lazy/encoder/binary/v1_0/container_writers.rs
Original file line number Diff line number Diff line change
@@ -335,6 +335,8 @@ impl<'value, 'top> MakeValueWriter for BinaryStructWriter_1_0<'value, 'top> {
}

impl<'value, 'top> StructWriter for BinaryStructWriter_1_0<'value, 'top> {
const IS_HUMAN_READABLE: bool = false;

fn close(self) -> IonResult<()> {
self.container_writer.end()
}
2 changes: 2 additions & 0 deletions src/lazy/encoder/binary/v1_0/value_writer.rs
Original file line number Diff line number Diff line change
@@ -288,6 +288,7 @@ impl<'value, 'top> ValueWriter for BinaryValueWriter_1_0<'value, 'top> {
type StructWriter = BinaryStructWriter_1_0<'value, 'top>;

type EExpWriter = Never;
const IS_HUMAN_READABLE: bool = false;

delegate_value_writer_to_self!();
}
@@ -411,6 +412,7 @@ impl<'value, 'top> ValueWriter for BinaryAnnotatedValueWriter_1_0<'value, 'top>
type SExpWriter = BinarySExpWriter_1_0<'value, 'top>;
type StructWriter = BinaryStructWriter_1_0<'value, 'top>;

const IS_HUMAN_READABLE: bool = false;
// Ion 1.0
type EExpWriter = Never;

1 change: 1 addition & 0 deletions src/lazy/encoder/binary/v1_1/container_writers.rs
Original file line number Diff line number Diff line change
@@ -345,6 +345,7 @@ impl<'value, 'top> MakeValueWriter for BinaryStructWriter_1_1<'value, 'top> {
}

impl<'value, 'top> StructWriter for BinaryStructWriter_1_1<'value, 'top> {
const IS_HUMAN_READABLE: bool = false;
fn close(mut self) -> IonResult<()> {
if let ContainerEncodingKind::Delimited(_) = &mut self.container_writer.encoder {
// Write the FlexSym escape (FlexUInt 0). The container writer can emit the closing
4 changes: 4 additions & 0 deletions src/lazy/encoder/binary/v1_1/value_writer.rs
Original file line number Diff line number Diff line change
@@ -664,6 +664,8 @@ impl<'value, 'top> ValueWriter for BinaryValueWriter_1_1<'value, 'top> {

type EExpWriter = BinaryEExpWriter_1_1<'value, 'top>;

const IS_HUMAN_READABLE: bool = false;

delegate_value_writer_to_self!();
}

@@ -767,6 +769,8 @@ impl<'value, 'top> ValueWriter for BinaryAnnotatedValueWriter_1_1<'value, 'top>
type StructWriter = BinaryStructWriter_1_1<'value, 'top>;
type EExpWriter = BinaryEExpWriter_1_1<'value, 'top>;

const IS_HUMAN_READABLE: bool = false;

annotate_and_delegate_1_1!(
IonType => write_null,
bool => write_bool,
5 changes: 5 additions & 0 deletions src/lazy/encoder/text/v1_0/value_writer.rs
Original file line number Diff line number Diff line change
@@ -443,6 +443,7 @@ impl<'value, W: Write> MakeValueWriter for TextStructWriter_1_0<'value, W> {
}

impl<'value, W: Write> StructWriter for TextStructWriter_1_0<'value, W> {
const IS_HUMAN_READABLE: bool = true;
fn close(self) -> IonResult<()> {
self.end()
}
@@ -473,6 +474,8 @@ impl<'value, W: Write + 'value> ValueWriter for TextAnnotatedValueWriter_1_0<'va
// Ion 1.0 does not support macros
type EExpWriter = Never;

const IS_HUMAN_READABLE: bool = true;

delegate_value_writer_to!(fallible closure |self_: Self| self_.encode_annotations());
}

@@ -498,6 +501,8 @@ impl<'value, W: Write> ValueWriter for TextValueWriter_1_0<'value, W> {
type SExpWriter = TextSExpWriter_1_0<'value, W>;
type StructWriter = TextStructWriter_1_0<'value, W>;

const IS_HUMAN_READABLE: bool = true;

// Ion 1.0 does not support macros
type EExpWriter = Never;
fn write_null(mut self, ion_type: IonType) -> IonResult<()> {
4 changes: 4 additions & 0 deletions src/lazy/encoder/text/v1_1/value_writer.rs
Original file line number Diff line number Diff line change
@@ -46,6 +46,8 @@ impl<'value, W: Write + 'value> ValueWriter for TextValueWriter_1_1<'value, W> {
type StructWriter = TextStructWriter_1_1<'value, W>;
type EExpWriter = TextEExpWriter_1_1<'value, W>;

const IS_HUMAN_READABLE: bool = true;

// For all of the scalars, delegate to the existing 1.0 writing logic.
delegate! {
to self.value_writer_1_0 {
@@ -121,6 +123,7 @@ impl<'value, W: Write + 'value> ValueWriter for TextAnnotatedValueWriter_1_1<'va
type SExpWriter = TextSExpWriter_1_1<'value, W>;
type StructWriter = TextStructWriter_1_1<'value, W>;
type EExpWriter = TextEExpWriter_1_1<'value, W>;
const IS_HUMAN_READABLE: bool = true;
// For all of the scalars, delegate to the existing 1.0 writing logic.
delegate! {
to self.value_writer_1_0 {
@@ -227,6 +230,7 @@ impl<'value, W: Write> MakeValueWriter for TextStructWriter_1_1<'value, W> {
}

impl<'value, W: Write> StructWriter for TextStructWriter_1_1<'value, W> {
const IS_HUMAN_READABLE: bool = true;
fn close(self) -> IonResult<()> {
self.writer_1_0.close()
}
5 changes: 5 additions & 0 deletions src/lazy/encoder/value_writer.rs
Original file line number Diff line number Diff line change
@@ -53,6 +53,7 @@ pub trait ValueWriter: AnnotatableWriter + Sized {
type SExpWriter: SequenceWriter<Resources = ()>;
type StructWriter: StructWriter;
type EExpWriter: EExpWriter<Resources = ()>;
const IS_HUMAN_READABLE: bool;

fn write_null(self, ion_type: IonType) -> IonResult<()>;
fn write_bool(self, value: bool) -> IonResult<()>;
@@ -236,6 +237,7 @@ impl<'field, StructWriterType: StructWriter> ValueWriter for FieldWriter<'field,
<<StructWriterType as MakeValueWriter>::ValueWriter<'field> as ValueWriter>::StructWriter;
type EExpWriter =
<<StructWriterType as MakeValueWriter>::ValueWriter<'field> as ValueWriter>::EExpWriter;
const IS_HUMAN_READABLE: bool = StructWriterType::IS_HUMAN_READABLE;

delegate_value_writer_to!(fallible closure |self_: Self| {
self_.struct_writer.encode_field_name(self_.name)?;
@@ -287,6 +289,8 @@ impl<'field, StructWriterType: StructWriter> AnnotatableWriter
impl<'field, StructWriterType: StructWriter> ValueWriter
for AnnotatedFieldWriter<'field, StructWriterType>
{
const IS_HUMAN_READABLE: bool = StructWriterType::IS_HUMAN_READABLE;

type ListWriter =
<<<StructWriterType as MakeValueWriter>::ValueWriter<'field> as AnnotatableWriter>::AnnotatedValueWriter<'field> as ValueWriter>::ListWriter;
type SExpWriter =
@@ -304,6 +308,7 @@ impl<'field, StructWriterType: StructWriter> ValueWriter
}

pub trait StructWriter: FieldEncoder + MakeValueWriter + Sized {
const IS_HUMAN_READABLE: bool;
/// Writes a struct field using the provided name/value pair.
fn write<A: AsRawSymbolRef, V: WriteAsIon>(
&mut self,
2 changes: 2 additions & 0 deletions src/lazy/encoder/writer.rs
Original file line number Diff line number Diff line change
@@ -251,6 +251,7 @@ impl<'value, V: ValueWriter> ValueWriter for ApplicationValueWriter<'value, V> {
type SExpWriter = ApplicationSExpWriter<'value, V>;
type StructWriter = ApplicationStructWriter<'value, V>;
type EExpWriter = ApplicationEExpWriter<'value, V>;
const IS_HUMAN_READABLE: bool = V::IS_HUMAN_READABLE;

delegate! {
to self.raw_value_writer {
@@ -403,6 +404,7 @@ impl<'value, V: ValueWriter> FieldEncoder for ApplicationStructWriter<'value, V>
}

impl<'value, V: ValueWriter> StructWriter for ApplicationStructWriter<'value, V> {
const IS_HUMAN_READABLE: bool = V::IS_HUMAN_READABLE;
fn close(self) -> IonResult<()> {
self.raw_struct_writer.close()
}
2 changes: 2 additions & 0 deletions src/lazy/never.rs
Original file line number Diff line number Diff line change
@@ -68,6 +68,7 @@ impl FieldEncoder for Never {
}

impl StructWriter for Never {
const IS_HUMAN_READABLE: bool = false;
fn close(self) -> IonResult<()> {
unreachable!("StructWriter::end in Never")
}
@@ -102,6 +103,7 @@ impl ValueWriter for Never {
type SExpWriter = Never;
type StructWriter = Never;
type EExpWriter = Never;
const IS_HUMAN_READABLE: bool = false;

delegate_value_writer_to_self!();
}
10 changes: 8 additions & 2 deletions src/lazy/reader.rs
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ use crate::lazy::text::raw::v1_1::reader::MacroAddress;
use crate::lazy::value::LazyValue;
use crate::read_config::ReadConfig;
use crate::result::IonFailure;
use crate::{IonError, IonResult};
use crate::{AnyEncoding, IonEncoding, IonError, IonResult};

/// A binary reader that only reads each value that it visits upon request (that is: lazily).
///
@@ -71,6 +71,12 @@ pub(crate) enum NextApplicationValue<'top, D: Decoder> {
EndOfStream,
}

impl<Input: IonInput> Reader<AnyEncoding, Input> {
pub fn detected_encoding(&self) -> IonEncoding {
self.system_reader.detected_encoding()
}
}

impl<Encoding: Decoder, Input: IonInput> Reader<Encoding, Input> {
/// Returns the next top-level value in the input stream as `Ok(Some(lazy_value))`.
/// If there are no more top-level values in the stream, returns `Ok(None)`.
@@ -121,7 +127,7 @@ impl<Encoding: Decoder, Input: IonInput> Reader<Encoding, Input> {
config: impl Into<ReadConfig<Encoding>>,
ion_data: Input,
) -> IonResult<Reader<Encoding, Input>> {
let system_reader = SystemReader::new(config, ion_data);
let system_reader = SystemReader::new(config, ion_data)?;
Ok(Reader { system_reader })
}
}
27 changes: 21 additions & 6 deletions src/lazy/streaming_raw_reader.rs
Original file line number Diff line number Diff line change
@@ -47,13 +47,28 @@ pub struct StreamingRawReader<Encoding: Decoder, Input: IonInput> {
const DEFAULT_IO_BUFFER_SIZE: usize = 4 * 1024;

impl<Encoding: Decoder, Input: IonInput> StreamingRawReader<Encoding, Input> {
pub fn new(encoding: Encoding, input: Input) -> StreamingRawReader<Encoding, Input> {
StreamingRawReader {
pub fn new(encoding: Encoding, input: Input) -> IonResult<StreamingRawReader<Encoding, Input>> {
let mut me = StreamingRawReader {
encoding,
input: input.into_data_source().into(),
saved_state: Default::default(),
stream_position: 0,
};
me.detect_encoding()?;
Ok(me)
}

fn detect_encoding<'top>(&'top mut self) -> IonResult<()> {
if self.buffer_is_empty() {
self.pull_more_data_from_source()?;
}

let available_bytes = unsafe { &*self.input.get() }.buffer();
let reader =
<Encoding::Reader<'top> as LazyRawReader<'top, Encoding>>::new(available_bytes);
self.saved_state = reader.save_state();

Ok(())
}

/// Gets a reference to the data source and tries to fill its buffer.
@@ -467,7 +482,7 @@ mod tests {
let empty_context = EncodingContext::empty();
let context = empty_context.get_ref();
let ion = "";
let mut reader = StreamingRawReader::new(AnyEncoding, ion.as_bytes());
let mut reader = StreamingRawReader::new(AnyEncoding, ion.as_bytes()).unwrap();
// We expect `Ok(EndOfStream)`, not `Err(Incomplete)`.
expect_end_of_stream(reader.next(context)?)?;
Ok(())
@@ -476,7 +491,7 @@ mod tests {
fn read_example_stream(input: impl IonInput) -> IonResult<()> {
let empty_context = EncodingContext::empty();
let context = empty_context.get_ref();
let mut reader = StreamingRawReader::new(AnyEncoding, input);
let mut reader = StreamingRawReader::new(AnyEncoding, input).unwrap();
expect_string(reader.next(context)?, "foo")?;
expect_string(reader.next(context)?, "bar")?;
expect_string(reader.next(context)?, "baz")?;
@@ -524,7 +539,7 @@ mod tests {
fn read_invalid_example_stream(input: impl IonInput) -> IonResult<()> {
let empty_context = EncodingContext::empty();
let context = empty_context.get_ref();
let mut reader = StreamingRawReader::new(AnyEncoding, input);
let mut reader = StreamingRawReader::new(AnyEncoding, input).unwrap();
let result = reader.next(context);
// Because the input stream is exhausted, the incomplete value is illegal data and raises
// a decoding error.
@@ -572,7 +587,7 @@ mod tests {
// contains incomplete data that could be misinterpreted by a reader.
let empty_context = EncodingContext::empty();
let context = empty_context.get_ref();
let mut reader = StreamingRawReader::new(v1_0::Text, IonStream::new(input));
let mut reader = StreamingRawReader::new(v1_0::Text, IonStream::new(input)).unwrap();

assert_eq!(reader.next(context)?.expect_ivm()?.version(), (1, 0));
assert_eq!(
16 changes: 8 additions & 8 deletions src/lazy/system_reader.rs
Original file line number Diff line number Diff line change
@@ -109,11 +109,11 @@ impl<Encoding: Decoder, Input: IonInput> SystemReader<Encoding, Input> {
pub fn new(
config: impl Into<ReadConfig<Encoding>>,
input: Input,
) -> SystemReader<Encoding, Input> {
) -> IonResult<SystemReader<Encoding, Input>> {
let config = config.into();
let raw_reader = StreamingRawReader::new(config.encoding(), input);
let raw_reader = StreamingRawReader::new(config.encoding(), input)?;
let expanding_reader = ExpandingReader::new(raw_reader, config.catalog);
SystemReader { expanding_reader }
Ok(SystemReader { expanding_reader })
}

// Returns `true` if the provided [`LazyRawValue`] is a struct whose first annotation is
@@ -368,7 +368,7 @@ mod tests {
hello
"#,
)?;
let mut system_reader = SystemReader::new(Binary, ion_data);
let mut system_reader = SystemReader::new(Binary, ion_data)?;
loop {
match system_reader.next_item()? {
SystemStreamItem::VersionMarker(marker) => {
@@ -393,7 +393,7 @@ mod tests {
)
"#,
)?;
let mut system_reader = SystemReader::new(Binary, ion_data);
let mut system_reader = SystemReader::new(Binary, ion_data)?;
loop {
match system_reader.next_item()? {
SystemStreamItem::Value(value) => {
@@ -420,7 +420,7 @@ mod tests {
}
"#,
)?;
let mut system_reader = SystemReader::new(Binary, ion_data);
let mut system_reader = SystemReader::new(Binary, ion_data)?;
loop {
match system_reader.next_item()? {
SystemStreamItem::Value(value) => {
@@ -441,14 +441,14 @@ mod tests {
use crate::{MapCatalog, SharedSymbolTable};

fn system_reader_for<I: IonInput>(ion: I) -> SystemReader<AnyEncoding, I> {
SystemReader::new(AnyEncoding, ion)
SystemReader::new(AnyEncoding, ion).unwrap()
}

fn system_reader_with_catalog_for<Input: IonInput>(
input: Input,
catalog: impl Catalog + 'static,
) -> SystemReader<AnyEncoding, Input> {
SystemReader::new(AnyEncoding.with_catalog(catalog), input)
SystemReader::new(AnyEncoding.with_catalog(catalog), input).unwrap()
}

#[test]
35 changes: 25 additions & 10 deletions src/serde/de.rs
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ use crate::lazy::value_ref::ValueRef;
use crate::result::IonFailure;
use crate::serde::decimal::TUNNELED_DECIMAL_TYPE_NAME;
use crate::serde::timestamp::TUNNELED_TIMESTAMP_TYPE_NAME;
use crate::{Decimal, IonError, IonResult, IonType, Timestamp};
use crate::{Decimal, IonEncoding, IonError, IonResult, IonType, Timestamp};

/// Generic method that can deserialize an object from any given type
/// that implements `IonInput`.
@@ -20,19 +20,27 @@ where
I: IonInput,
{
let mut reader = Reader::new(AnyEncoding, input)?;
let detected_encoding = reader.detected_encoding();
let value = reader.expect_next()?;
let value_deserializer = ValueDeserializer::new(&value);
let value_deserializer = ValueDeserializer::new(&value, detected_encoding);
T::deserialize(value_deserializer)
}

#[derive(Clone, Copy)]
pub struct ValueDeserializer<'a, 'de> {
detected_encoding: IonEncoding,
pub(crate) value: &'a LazyValue<'de, AnyEncoding>,
}

impl<'a, 'de> ValueDeserializer<'a, 'de> {
pub(crate) fn new(value: &'a LazyValue<'de, AnyEncoding>) -> Self {
Self { value }
pub(crate) fn new(
value: &'a LazyValue<'de, AnyEncoding>,
detected_encoding: IonEncoding,
) -> Self {
Self {
value,
detected_encoding,
}
}

fn deserialize_as_sequence<V: Visitor<'de>>(
@@ -41,8 +49,8 @@ impl<'a, 'de> ValueDeserializer<'a, 'de> {
) -> Result<V::Value, <Self as de::Deserializer<'de>>::Error> {
use ValueRef::*;
match self.value.read()? {
List(l) => visitor.visit_seq(SequenceIterator(l.iter())),
SExp(l) => visitor.visit_seq(SequenceIterator(l.iter())),
List(l) => visitor.visit_seq(SequenceIterator(l.iter(), self.detected_encoding)),
SExp(l) => visitor.visit_seq(SequenceIterator(l.iter(), self.detected_encoding)),
_ => IonResult::decoding_error("expected a list or sexp"),
}
}
@@ -51,7 +59,7 @@ impl<'a, 'de> ValueDeserializer<'a, 'de> {
visitor: V,
) -> Result<V::Value, <Self as de::Deserializer<'de>>::Error> {
let strukt = self.value.read()?.expect_struct()?;
let struct_as_map = StructAsMap::new(strukt.iter());
let struct_as_map = StructAsMap::new(strukt.iter(), self.detected_encoding);

visitor.visit_map(struct_as_map)
}
@@ -60,6 +68,10 @@ impl<'a, 'de> ValueDeserializer<'a, 'de> {
impl<'a, 'de> de::Deserializer<'de> for ValueDeserializer<'a, 'de> {
type Error = IonError;

fn is_human_readable(&self) -> bool {
self.detected_encoding.is_text()
}

fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
@@ -426,7 +438,7 @@ impl<'a, 'de> de::Deserializer<'de> for ValueDeserializer<'a, 'de> {
}
}

pub(crate) struct SequenceIterator<S>(pub(crate) S);
pub(crate) struct SequenceIterator<S>(pub(crate) S, IonEncoding);

impl<'de, S> SeqAccess<'de> for SequenceIterator<S>
where
@@ -441,21 +453,23 @@ where
let Some(lazy_value) = self.0.next().transpose()? else {
return Ok(None);
};
let deserializer = ValueDeserializer::new(&lazy_value);
let deserializer = ValueDeserializer::new(&lazy_value, self.1);
seed.deserialize(deserializer).map(Some)
}
}

struct StructAsMap<'de> {
iter: StructIterator<'de, AnyEncoding>,
current_field: Option<LazyField<'de, AnyEncoding>>,
detected_encoding: IonEncoding,
}

impl<'de> StructAsMap<'de> {
pub fn new(iter: StructIterator<'de, AnyEncoding>) -> Self {
pub fn new(iter: StructIterator<'de, AnyEncoding>, detected_encoding: IonEncoding) -> Self {
Self {
iter,
current_field: None,
detected_encoding,
}
}
}
@@ -490,6 +504,7 @@ impl<'de> MapAccess<'de> for StructAsMap<'de> {
// This method will only be called when `next_key_seed` reported another field,
// so we can unwrap this safely.
&self.current_field.as_ref().unwrap().value(),
self.detected_encoding,
))
}
}
22 changes: 22 additions & 0 deletions src/serde/mod.rs
Original file line number Diff line number Diff line change
@@ -204,13 +204,17 @@ pub use ser::{to_pretty, to_string};
#[cfg(test)]
#[cfg(feature = "experimental-serde")]
mod tests {
use std::net::IpAddr;

use crate::serde::{from_ion, to_pretty, to_string};

use crate::{Decimal, Element, Timestamp};
use chrono::{DateTime, FixedOffset, Utc};
use serde::{Deserialize, Serialize};
use serde_with::serde_as;

use super::ser::to_binary;

#[test]
fn test_struct() {
#[serde_as]
@@ -356,4 +360,22 @@ mod tests {
let expected = String::from("'embedded quotes'");
assert_eq!(expected, from_ion::<String, _>(i).unwrap());
}

#[test]
fn human_readable() {
// IpAddr has different repr based on if codec is considered
// human readable or not {true: string, false: byte array}
let ip: IpAddr = "127.0.0.1".parse().unwrap();
let expected_binary = [
224, 1, 0, 234, 235, 129, 131, 216, 134, 113, 3, 135, 179, 130, 86, 52, 233, 129, 138,
182, 33, 127, 32, 32, 33, 1,
];
let expected_s = "\"127.0.0.1\" ";
let binary = to_binary(&ip).unwrap();
assert_eq!(&binary[..], &expected_binary[..]);
let s = to_string(&ip).unwrap();
assert_eq!(s, expected_s);
assert_eq!(&from_ion::<IpAddr, _>(s).unwrap(), &ip);
assert_eq!(&from_ion::<IpAddr, _>(binary).unwrap(), &ip);
}
}
4 changes: 4 additions & 0 deletions src/serde/ser.rs
Original file line number Diff line number Diff line change
@@ -306,6 +306,10 @@ impl<'a, V: ValueWriter + 'a> ser::Serializer for ValueSerializer<'a, V> {
.struct_writer()?,
})
}

fn is_human_readable(&self) -> bool {
V::IS_HUMAN_READABLE
}
}

pub struct SeqWriter<V: ValueWriter> {