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

Introduces a new writer API #680

Merged
merged 6 commits into from
Nov 17, 2023
Merged

Introduces a new writer API #680

merged 6 commits into from
Nov 17, 2023

Conversation

zslayton
Copy link
Contributor

Introduces a friendlier writer API that will supplant the existing IonWriter. For now, it's called the LazyWriter for symmetry with the LazyReader. However, I plan to revisit that naming scheme once these implementations are ready to become the primary (read: only) implementations in the library.

The new API uses traits instead of methods to handle writing different data types, which results less noisy code. For example, this code:

let mut writer = LazyRawTextWriter_1_0::new(&mut buffer);
writer
    .write(1)?
    .write(false)?
    .write(3f32)?
    .write("foo")?
    .write("bar".as_symbol_ref())?
    .write(Timestamp::with_ymd(2023, 11, 9).build()?)?
    .write([0xE0u8, 0x01, 0x00, 0xEA])?
    .write([1, 2, 3])?
    .flush()?;

produces this output:

1
false
3e0
"foo"
bar
2023-11-09T
{{4AEA6g==}}
[1, 2, 3]

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

Copy link
Contributor Author

@zslayton zslayton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest reading the files in this order:

  1. write_as_ion.rs
  2. annotate.rs
  3. encoder/mod.rs
  4. text/raw_text_writer.rs

and the rest in any order.

🗺️ PR tour

Comment on lines +37 to +40
fn annotate<'a, A: AsRawSymbolTokenRef>(
&'a self,
annotations: &'a [A],
) -> Annotated<'a, Self, A>;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗺️ This extension method allows values being passed into writer.write(...) to add annotations to themselves:

writer.write(30_000.annotate("lbs_bananas"))?;

Comment on lines +66 to +72
fn write_as_ion<'a, W: Write + 'a, E: LazyEncoder<W>, V: AnnotatedValueWriter<'a, W, E>>(
&self,
annotations_writer: V,
) -> IonResult<()> {
let value_writer = annotations_writer.write_annotations(self.annotations.iter())?;
self.value.write_as_ion_value(value_writer)
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗺️ AnnotatedValueWriter is a staged writer. You call a method to either write annotations or no_annotations() and upon success it returns a ValueWriter that can be used to write the value itself.

Comment on lines +74 to +75
SymbolType: AsRawSymbolTokenRef,
IterType: Iterator<Item = SymbolType> + Clone,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗺️ An earlier version of this used much more generics; I started spelling out what each generic was (e.g. SymbolType instead of A). I think I kind of like it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, please continue this. It gets hard to keep track sometimes even when there's only a few.

}
}

impl<'a, W: Write> ValueWriter<'a, W, TextEncoding_1_0> for TextValueWriter_1_0<'a, W> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗺️ The bodies of these methods are either copy/pasted from the existing raw text writer or invoke one of its methods.

Comment on lines +442 to +450
impl<'a, W: Write> Drop for TextListWriter_1_0<'a, W> {
fn drop(&mut self) {
// If the user didn't call `end`, the closing delimiter was not written to output.
// It's too late to call it here because we can't return a Result.
if !self.has_been_closed {
panic!("List writer was dropped without calling `end()`.");
}
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗺️ This is my compromise in the absence of linear types. We can't close() in Drop because there's no way for the application to handle a failure there. However, that also means we cannot force the writer to close automatically. This at least makes contract violations noisy -- it's not possible to forget to to close the container and keep going.

fn write_clob<A: AsRef<[u8]>>(self, value: A) -> IonResult<()>;
fn write_blob<A: AsRef<[u8]>>(self, value: A) -> IonResult<()>;
fn list_writer(self) -> IonResult<E::ListWriter<'a>>;
fn sexp_writer(self) -> IonResult<E::StructWriter<'a>>;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO for me: change the return type to SExpWriter.

Comment on lines +60 to +62
type SExpWriter<'a> = ();
type StructWriter<'a> = ();
type EExpressionWriter<'a> = ();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO for me: mark these as TODO.

.write("bar".as_symbol_ref())?
.write(Timestamp::with_ymd(2023, 11, 9).build()?)?
.write([0xE0u8, 0x01, 0x00, 0xEA])?
.write([1, 2, 3])?;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗺️ There are two ways to write a list: if you have a type that represents the list and your type implements WriteAsIon[Value], you can pass it to .write(...) as is done here. For a streaming list writer that can emit heterogeneous values, you can use ListWriter.

If WriteAsIon[Value] is implemented, it would be implemented using ListWriter.

}
}

// ===== WriteAsIonValue implementations for common types =====
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗺️ There are common types not covered here, like Vec<T: WriteAsIon> and several more integer types. I'd like to punt on those.

// This type allows us to define custom behavior for `null` via trait implementations.
// For an example, see the `WriteAsIonValue` trait.
#[derive(Debug, PartialEq, Copy, Clone)]
pub struct Null(pub IonType);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗺️ We have similar wrapper types in the Element API for Blob(Bytes), Clob(Bytes), List(Sequence) and SExp(Sequence).

Copy link
Contributor Author

@zslayton zslayton Nov 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗺️ Changes in this file make its inner workings crate-visible so the LazyRawTextWriter_1_0 can re-use them. Down the road, this type will be removed and I'll consolidate the logic.

@zslayton zslayton requested review from popematt and desaikd November 14, 2023 22:07
Copy link
Contributor

@popematt popematt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty good. I do wonder whether these traits will work for the binary writer, but that doesn't necessarily need to be solved right now.

src/lazy/encoder/mod.rs Show resolved Hide resolved
src/lazy/encoder/mod.rs Outdated Show resolved Hide resolved
Comment on lines +74 to +75
SymbolType: AsRawSymbolTokenRef,
IterType: Iterator<Item = SymbolType> + Clone,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, please continue this. It gets hard to keep track sometimes even when there's only a few.

src/lazy/encoder/annotate.rs Show resolved Hide resolved
src/lazy/encoder/mod.rs Show resolved Hide resolved
src/lazy/encoder/mod.rs Show resolved Hide resolved
src/lazy/encoder/write_as_ion.rs Show resolved Hide resolved
@zslayton zslayton merged commit 6113bc7 into main Nov 17, 2023
20 checks passed
@zslayton zslayton deleted the lazy-writer branch November 17, 2023 17:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants