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

Writing to fmt::Formatter #1137

Open
asokol123 opened this issue May 26, 2024 · 1 comment
Open

Writing to fmt::Formatter #1137

asokol123 opened this issue May 26, 2024 · 1 comment

Comments

@asokol123
Copy link

asokol123 commented May 26, 2024

Is it possible to serialize a value directly to fmt::Formatter?
Usecase:

impl<T: Debug + serde::Serialize> Debug for Formattable<T> {
    fn fmt(&self, mut f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self.format_type {
            FormatType::Json => serde_json::to_writer(f, &self.data).map_err(|e| std::fmt::Error),
            FormatType::Plain => write!(f, "{:?}", self.data),
        }
    }
}

This doesn't work because to_writer requires std::io::Write, which we can't get from std::fmt::Formatter

@GREsau
Copy link
Contributor

GREsau commented Nov 26, 2024

I was curious so I did some playing around with this, and got a proof-of-concept working:

use serde::Serialize;
use std::{fmt, io};

struct FormatterAdapter<'a, 'b>(&'a mut fmt::Formatter<'b>);

impl io::Write for FormatterAdapter<'_, '_> {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        let s = unsafe {
            // serde_json does not emit invalid UTF-8
            std::str::from_utf8_unchecked(buf)
        };
        // Or if you can't guarantee that FormatterAdapter is only used with serde_json output:
        // let s = std::str::from_utf8(buf).map_err(io::Error::other)?;

        match self.0.write_str(s) {
            Ok(_) => Ok(s.len()),
            Err(_) => Err(io::ErrorKind::Other.into()),
        }
    }

    fn flush(&mut self) -> io::Result<()> {
        Ok(())
    }
}

pub fn to_formatter<T>(value: &T, f: &mut fmt::Formatter<'_>) -> serde_json::Result<()>
where
    T: ?Sized + serde::Serialize,
{
    let adapter = FormatterAdapter(f);
    serde_json::to_writer(adapter, value)
}

This would solve your use-case like so:

impl<T: Debug + serde::Serialize> Debug for Formattable<T> {
    fn fmt(&self, mut f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self.format_type {
            FormatType::Json => to_formatter(&self.data, f).or_else(|e| write!(f, "<Serialize error: {e}>")),
            FormatType::Plain => write!(f, "{:?}", self.data),
        }
    }
}

Note that if the inner value's Serialize impl returns an error then, this outputs the error as text. This is because rust considers string formatting to be infallible except for when the underlying stream fails (see https://doc.rust-lang.org/std/fmt/struct.Error.html) - so returning a std::fmt::Error from the Debug/Display impl actually causes a runtime panic with the message a formatting trait implementation returned an error when the underlying stream did not

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants