use ::tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use serde::Serialize;
use serde::de::DeserializeOwned;
use crate::de::ReadDoc;
use crate::drive;
use crate::error::Error;
use crate::format::{FieldsLayout, Format};
use crate::markdown::Markdown;
use crate::ser::WriteDoc;
pub async fn to_string<T: Serialize + Markdown>(value: &T) -> Result<String, Error> {
to_string_with(value, Format::Yaml, FieldsLayout::Fenced).await
}
pub async fn to_string_with_format<T: Serialize + Markdown>(
value: &T,
format: Format,
) -> Result<String, Error> {
to_string_with(value, format, FieldsLayout::Fenced).await
}
pub async fn to_string_with<T: Serialize + Markdown>(
value: &T,
format: Format,
layout: FieldsLayout,
) -> Result<String, Error> {
drive::run_async(WriteDoc::new(value, format, layout)).await
}
pub async fn to_vec<T: Serialize + Markdown>(value: &T) -> Result<Vec<u8>, Error> {
to_string(value).await.map(String::into_bytes)
}
pub async fn to_writer<W, T>(mut writer: W, value: &T) -> Result<(), Error>
where
W: AsyncWrite + Unpin,
T: Serialize + Markdown,
{
let text = to_string(value).await?;
writer.write_all(text.as_bytes()).await?;
Ok(())
}
pub async fn from_str<T>(s: &str) -> Result<T, Error>
where
T: DeserializeOwned + Markdown,
{
drive::run_async(ReadDoc::new(s)).await
}
pub async fn from_slice<T>(bytes: &[u8]) -> Result<T, Error>
where
T: DeserializeOwned + Markdown,
{
match std::str::from_utf8(bytes) {
Ok(s) => from_str(s).await,
Err(err) => Err(Error::syntax(err.valid_up_to(), "invalid UTF-8")),
}
}
pub async fn from_reader<R, T>(mut reader: R) -> Result<T, Error>
where
R: AsyncRead + Unpin,
T: DeserializeOwned + Markdown,
{
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
from_slice(&bytes).await
}
#[cfg(all(test, feature = "yaml", feature = "json", feature = "toml"))]
mod tests {
use std::future::Future;
use std::io;
use std::pin::{Pin, pin};
use std::task::{Context, Poll, Waker};
use ::tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use super::{
from_reader, from_slice, from_str, to_string, to_string_with, to_string_with_format,
to_vec, to_writer,
};
use crate::error::ErrorKind;
use crate::format::{FieldsLayout, Format};
use crate::testdata::{goldens, types, values};
fn block_on<F: Future>(future: F) -> F::Output {
::tokio::runtime::Builder::new_current_thread()
.build()
.expect("runtime")
.block_on(future)
}
fn assert_send<F: Future + Send>(future: F) -> F {
future
}
#[test]
fn every_writer_gives_the_sync_document() {
let page = values::page();
block_on(async {
assert_eq!(
to_string(&page).await.expect("async"),
crate::to_string(&page).expect("sync")
);
assert_eq!(
to_vec(&page).await.expect("async"),
crate::to_vec(&page).expect("sync")
);
for format in [Format::Yaml, Format::Json, Format::Toml] {
assert_eq!(
to_string_with_format(&page, format).await.expect("async"),
crate::to_string_with_format(&page, format).expect("sync")
);
for layout in [FieldsLayout::Fenced, FieldsLayout::Bare] {
assert_eq!(
to_string_with(&page, format, layout).await.expect("async"),
crate::to_string_with(&page, format, layout).expect("sync"),
"{format} {layout:?}"
);
}
}
let mut written = Vec::new();
to_writer(&mut written, &page).await.expect("async");
assert_eq!(written, crate::to_vec(&page).expect("sync"));
});
}
#[test]
fn every_reader_gives_the_sync_value() {
block_on(async {
for (name, golden) in [
("yaml", goldens::PAGE_FENCED_YAML),
("json", goldens::PAGE_BARE_JSON),
("toml", goldens::PAGE_UNLABELED_TOML),
] {
let sync: types::Page = crate::from_str(golden).expect(name);
let back: types::Page = from_str(golden).await.expect(name);
assert_eq!(back, sync, "{name}");
let back: types::Page = from_slice(golden.as_bytes()).await.expect(name);
assert_eq!(back, sync, "{name}");
let back: types::Page = from_reader(golden.as_bytes()).await.expect(name);
assert_eq!(back, sync, "{name}");
}
let article: types::Article = from_str(goldens::STRUCTURED_FENCED_YAML)
.await
.expect("structured body");
assert_eq!(article, values::article());
});
}
#[test]
fn the_async_form_fails_as_the_sync_form_does() {
block_on(async {
let err = from_slice::<types::FieldsOnly>(&[0xff, b'a'])
.await
.expect_err("utf8");
assert_eq!(err.kind(), ErrorKind::Syntax);
assert_eq!(err.offset(), Some(0));
let err = from_str::<types::FieldsOnly>("```yaml\nname: only\n")
.await
.expect_err("unclosed");
assert_eq!(err.kind(), ErrorKind::Syntax);
let mut page = values::page();
page.text1 = "before\n---\nafter".into();
let err = to_string(&page).await.expect_err("middle value with ---");
assert_eq!(err.kind(), ErrorKind::Body);
});
}
struct Broken;
impl AsyncRead for Broken {
fn poll_read(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
_buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
Poll::Ready(Err(io::Error::other("boom")))
}
}
impl AsyncWrite for Broken {
fn poll_write(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
_buf: &[u8],
) -> Poll<io::Result<usize>> {
Poll::Ready(Err(io::Error::other("boom")))
}
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Poll::Ready(Ok(()))
}
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Poll::Ready(Ok(()))
}
}
#[test]
fn an_io_failure_is_io() {
block_on(async {
let err = from_reader::<_, types::FieldsOnly>(Broken)
.await
.expect_err("read");
assert_eq!(err.kind(), ErrorKind::Io);
assert!(std::error::Error::source(&err).is_some());
let err = to_writer(Broken, &values::page()).await.expect_err("write");
assert_eq!(err.kind(), ErrorKind::Io);
assert!(std::error::Error::source(&err).is_some());
});
}
#[test]
fn no_call_needs_a_thread_or_a_runtime() {
let page = values::page();
let mut write = pin!(assert_send(to_string(&page)));
let Poll::Ready(md) = write.as_mut().poll(&mut Context::from_waker(Waker::noop())) else {
panic!("outside a runtime the write finishes in one poll");
};
let md = md.expect("write");
let mut read = pin!(assert_send(from_str::<types::Page>(&md)));
let Poll::Ready(back) = read.as_mut().poll(&mut Context::from_waker(Waker::noop())) else {
panic!("outside a runtime the read finishes in one poll");
};
assert_eq!(back.expect("read"), page);
}
}