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

Add support for async element writer #635

Merged
merged 1 commit into from
Sep 15, 2023

Conversation

tevoinea
Copy link
Contributor

@tevoinea tevoinea commented Aug 18, 2023

This PR adds support for writing to buffers implementing tokio's AsyncWrite. It mirrors the ElementWriter implementation, nearly identically. The only difference is in write_inner_content, the closure needs to return the Writer, I couldn't make the lifetimes line up any other way.

It also includes a full test suite, again mirrored from ElementWriter and docs.

@codecov-commenter
Copy link

codecov-commenter commented Aug 18, 2023

Codecov Report

Merging #635 (e2de822) into master (cced485) will decrease coverage by 0.08%.
The diff coverage is 13.84%.

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

@@            Coverage Diff             @@
##           master     #635      +/-   ##
==========================================
- Coverage   64.43%   64.36%   -0.08%     
==========================================
  Files          36       36              
  Lines       17288    17343      +55     
==========================================
+ Hits        11140    11162      +22     
- Misses       6148     6181      +33     
Flag Coverage Δ
unittests 64.36% <13.84%> (-0.08%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files Changed Coverage Δ
src/writer/async_tokio.rs 75.37% <0.00%> (-15.24%) ⬇️
src/writer.rs 91.46% <100.00%> (ø)

... and 1 file with indirect coverage changes

#[tokio::test]
async fn element_writer_nested() {
let mut buffer = Vec::new();
let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm fairly certain that the only reason this works is that you're using a buffer type which also implements Write rather than an exclusively-async type.

write_text_content and other methods aren't implemented for ElementWriter<'a, W: AsyncWrite + Unpin>

As a sidenote if you can avoid duplicating the definition of ElementWriter, please do.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm fairly certain that the only reason this works is that you're using a buffer type which also implements Write rather than an exclusively-async type.

Good catch! I'll update that

As a sidenote if you can avoid duplicating the definition of ElementWriter, please do.

AFAIK, the only way to do that would be to have an async-only implementation, then have the sync implementation be a wrapper around it. But since async is an optional feature in this crate (and perf is important), I don't think that's the best option.

If there's a better way, I'd love to know! :)

Copy link
Collaborator

Choose a reason for hiding this comment

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

You could remove Write requirement from ElementWriter struct and from impl block for attributes and put Write or AsyncWrite + Unpin to individual impl blocks. Then new functions should be named with _async suffix and no need separate method to create async element 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.

That's a good idea, I like that structure a lot more!

Copy link
Collaborator

@Mingun Mingun left a comment

Choose a reason for hiding this comment

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

Good work! Three issues:

  • documentation on feature incorrectly formatted
  • please use modules to grouping tests instead of common prefix
  • I would prefer to use one ElementWriter struct with sets of methods for sync and async writing

Cargo.toml Outdated
Comment on lines 56 to 57
##
## [writing events] to buffers implementing [`tokio::io::AsyncWrite`].
Copy link
Collaborator

Choose a reason for hiding this comment

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

The ## doc comments is usual rustdoc markdown comments, which is processed by document-features crate. So please rewrite that according to that. If you want to make hyperlink on writing events, please add hyperlink target

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I maybe misunderstood the docs there so I removed that change.

@@ -339,4 +484,93 @@ mod indentation_async {
</paired>"#
);
}

#[tokio::test]
async fn element_writer_empty() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Instead on using prefix element_writer_ please put all tests i a module element_writer:

mod element_writer {
    use super::*;
    use pretty_assertions::assert_eq;

    #[tokio::test]
    async fn empty() { ... }
    ...
}

#[tokio::test]
async fn element_writer_nested() {
let mut buffer = Vec::new();
let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4);
Copy link
Collaborator

Choose a reason for hiding this comment

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

You could remove Write requirement from ElementWriter struct and from impl block for attributes and put Write or AsyncWrite + Unpin to individual impl blocks. Then new functions should be named with _async suffix and no need separate method to create async element writer.

@@ -57,6 +60,58 @@ impl<W: AsyncWrite + Unpin> Writer<W> {
Ok(())
}

// /// Provides a simple, high-level async API for writing XML elements.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Just remove the whole commented block. You always can restore the previous version by checkouting a commit that GitHub shows in a PR

Comment on lines 141 to 160
/// Adds an attribute to this element.
pub fn with_attribute_async<'b, I>(mut self, attr: I) -> Self
where
I: Into<Attribute<'b>>,
{
self.start_tag.push_attribute(attr);
self
}

/// Add additional attributes to this element using an iterator.
///
/// The yielded items must be convertible to [`Attribute`] using `Into`.
pub fn with_attributes_async<'b, I>(mut self, attributes: I) -> Self
where
I: IntoIterator,
I::Item: Into<Attribute<'b>>,
{
self.start_tag.extend_attributes(attributes);
self
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

This methods does not depend from writer, so remove them. Move existing with_attribute and with_attributes methods to the impl block that does not restrict W type parameter, then they will work for both sync and async writers

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since with_attribute is on the ElementWriter, the only way to do this is by removing the Write bound on the ElementWriter struct (in order to support a general impl. I still made the change.

Comment on lines 488 to 489
#[cfg(test)]
mod element_writer {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I supposed that this module will be inside the tests module, but that's also OK

use pretty_assertions::assert_eq;

#[tokio::test]
async fn empty() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Hm... maybe move all those tests to doc comments of corresponding methods? I think, this will be useful. This one for write_empty_async

}

#[tokio::test]
async fn text() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This for write_text_content_async

}

#[tokio::test]
async fn nested() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

And this for write_inner_content_async

Copy link
Collaborator

@Mingun Mingun left a comment

Choose a reason for hiding this comment

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

Nice! Only several small nits remained

/// std::str::from_utf8(&buffer).unwrap(),
/// r#"<paired attr1="value1" attr2="value2">text</paired>"#
/// );
/// }
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
/// }
/// # }

/// std::str::from_utf8(&buffer).unwrap(),
/// r#"<empty attr1="value1" attr2="value2"/>"#
/// );
/// }
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
/// }
/// # }

And also please dedent the visible code in example

/// </inner>
/// </outer>"#
/// );
/// }
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
/// }
/// # }

And also please dedent the visible code in example and check that indent is correct (the first visible line has different indent that the others)

/// Write some text inside the current element.
/// # Example
///
/// ```rust
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
/// ```rust
/// ```

The code blocks already have rust type by default

/// Write an empty (self-closing) tag.
/// # Example
///
/// ```rust
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
/// ```rust
/// ```

The code blocks already have rust type by default

/// Create a new scope for writing XML inside the current element.
/// # Example
///
/// ```rust
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
/// ```rust
/// ```

The code blocks already have rust type by default

@Mingun
Copy link
Collaborator

Mingun commented Sep 14, 2023

Also, please add entry to Changelog.md:

  • Short description here from line 19:

    quick-xml/Changelog.md

    Lines 17 to 19 in cced485

    - [#545]: Resolve well-known namespaces (`xml` and `xmlns`) to their appropriate URIs.
    Also, enforce namespace constraints related to these well-known namespaces.
  • A link to this PR here on line 29 (i. e. between two existing links, we keep list of links sorted):

    quick-xml/Changelog.md

    Lines 28 to 29 in cced485

    [#545]: https://github.com/tafia/quick-xml/pull/545
    [#643]: https://github.com/tafia/quick-xml/pull/643
  • (keep two lines between list of links and the previous release section)

@Mingun Mingun merged commit efb2dc0 into tafia:master Sep 15, 2023
6 checks passed
@Mingun
Copy link
Collaborator

Mingun commented Sep 15, 2023

Thanks for your work!

Mingun added a commit to Mingun/quick-xml that referenced this pull request Sep 18, 2023
Sometimes TortoiseGit loses commits during rebase... What a dangerous bug
Mingun added a commit to Mingun/quick-xml that referenced this pull request Sep 18, 2023
Sometimes TortoiseGit loses commits during rebase... What a dangerous bug
Mingun added a commit to Mingun/quick-xml that referenced this pull request Sep 24, 2023
Sometimes TortoiseGit loses commits during rebase... What a dangerous bug
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.

4 participants