Skip to main content

format_bytes/
lib.rs

1/*!
2This crate exposes procedural macros that allow you to format bytestrings.
3For more background on why you would want to do that,
4[read this article](https://octobus.net/blog/2020-06-05-not-everything-is-utf8.html).
5
6## Usage
7
8Add this to your `Cargo.toml`:
9
10```toml
11[dependencies]
12format-bytes = "0.4"
13```
14
15Requires Rust 1.85 or later. See the [changelog](https://foss.heptapod.net/octobus/rust/format-bytes/-/blob/branch/default/CHANGELOG.md).
16
17Then use the macro like so:
18
19```rust
20use format_bytes::format_bytes;
21
22fn main() {
23    assert_eq!(
24        format_bytes!(b"look at those {} bytes", &[0u8, 1, 2]),
25        b"look at those \x00\x01\x02 bytes"
26    );
27
28    let path = b"caf\xe9";
29    assert_eq!(
30        format_bytes!("{{{path}}} is {len} bytes long", len = path.len()),
31        b"{caf\xe9} is 4 bytes long"
32    );
33}
34```
35
36See more examples of how it works on the documentation of
37[`format_bytes!` itself][`format_bytes!`].
38
39Arguments can be of any type that implements [`DisplayBytes`]: bytestrings,
40integers and floats, [`Utf8`] for [`std::fmt::Display`] types, [`join()`] for
41sequences, and references and smart pointers to those.
42
43### Writing to a stream: `write_bytes!`
44
45[`write_bytes!`] takes a `&mut` to the stream to write to, followed by the same
46arguments as [`format_bytes!`]:
47
48```rust
49use format_bytes::write_bytes;
50use std::io;
51
52fn write_entry(out: &mut dyn io::Write, path: &[u8]) -> io::Result<()> {
53    write_bytes!(out, b"{}: {} bytes\n", path, path.len())
54}
55
56fn main() {
57    let mut out = vec![];
58    write_entry(&mut out, b"caf\xe9").unwrap();
59    assert_eq!(out, b"caf\xe9: 4 bytes\n");
60}
61```
62
63The stream can be any [`std::io::Write`], or any [`Sink`].
64
65### Formatting later: `message!`
66
67[`message!`] takes the same arguments as [`format_bytes!`], but returns a
68[`Message`] that keeps the format string and the arguments, to be written
69later, for example by an error handler. Arguments are moved into the message,
70so they must be `'static`, [`Send`] and [`Sync`].
71
72```rust
73use format_bytes::{Message, format_bytes, message};
74
75fn unknown_revision(rev: u32) -> Message {
76    message!("unknown revision {rev}")
77}
78
79fn main() {
80    let msg = unknown_revision(42);
81    assert_eq!(format_bytes!("abort: {msg}"), b"abort: unknown revision 42");
82    assert_eq!(msg.template().source, "unknown revision {rev}");
83}
84```
85
86A [`Message`] is itself a [`DisplayBytes`] argument. [`Message::write_to`]
87writes it to a [`Sink`], and [`Message::template`] gives the format string,
88parsed at compile time.
89
90### Custom output: `Sink`
91
92A [`Sink`] receives the format string separately from each argument and its
93name ([`ArgName`]). Every [`std::io::Write`] is a [`Sink`] that writes both
94as-is. Other sinks can, for example, tell user data apart from the message
95around it:
96
97```rust
98use format_bytes::{ArgName, DisplayBytes, Sink, write_bytes};
99use std::io;
100
101/// Quotes arguments
102struct Quoting(Vec<u8>);
103
104impl Sink for Quoting {
105    fn literal(&mut self, text: &str) -> io::Result<()> {
106        self.0.literal(text)
107    }
108
109    fn arg(&mut self, _: ArgName, value: &dyn DisplayBytes) -> io::Result<()> {
110        write_bytes!(&mut self.0, "'{}'", value)
111    }
112}
113
114fn main() {
115    let mut sink = Quoting(vec![]);
116    write_bytes!(&mut sink, "cannot open {path}", path = b"caf\xe9").unwrap();
117    assert_eq!(sink.0, b"cannot open 'caf\xe9'");
118}
119```
120
121A sink that cares about types for structured output, can call
122[`DisplayBytes::visit`] with a [`Visit`], which implements methods for
123integers, UTF-8 text, bytes, a [`join()`] sequence or a nested [`Message`].
124
125## Missing features
126
127* [`format!`]-like explicit indices (`{0}`) and format specs (`{:?}`, padding):
128if the need manifests itself, they might appear
129* ``#[no_std]`` support
130
131
132## Why not 1.0?
133
134Not until the macro gets a bit of mileage (it
135will be used in [Mercurial](https://www.mercurial-scm.org)).
136*/
137
138use std::fmt;
139use std::io;
140
141/// Creates a `Vec<u8>` using interpolation of runtime expressions.
142///
143/// The first argument `format_bytes!` receives is a format string.
144/// This must be a string literal, or a bytestring literal holding valid UTF-8.
145/// The power of the formatting string
146/// is in the `{}`s contained.
147///
148/// Literal braces are written `{{` and `}}`.
149///
150/// Additional arguments passed to `format_bytes!` replace the `{}`s
151/// within the formatting string in the order given. Named arguments, given
152/// after positional ones as `name = value`, replace each `{name}`. A `{name}`
153/// with no such argument uses the variable `name` from the caller's scope.
154/// Each argument is evaluated once.
155///
156/// These additional arguments may have any type that implements
157/// the [`DisplayBytes`] trait.
158///
159/// # Examples
160///
161/// ```
162/// use format_bytes::format_bytes;
163///
164/// assert_eq!(format_bytes!(b""), b"");
165/// assert_eq!(format_bytes!(b"here"), b"here");
166/// assert_eq!(format_bytes!("é{}", b"\xe9"), b"\xc3\xa9\xe9");
167/// assert_eq!(format_bytes!(b"this {{ escapes }}"), b"this { escapes }");
168/// assert_eq!(format_bytes!(b"also this {{}}"), b"also this {}");
169/// assert_eq!(format_bytes!(b"this works {{{}}}", b"a"), b"this works {a}");
170/// assert_eq!(
171///     format_bytes!(b"look at those {} bytes", &[0u8, 1, 2]),
172///     b"look at those \x00\x01\x02 bytes"
173/// );
174///
175/// let bytes = vec![0u8, 1, 2];
176///
177/// assert_eq!(
178///     format_bytes!(b"look at those {} bytes", bytes),
179///     b"look at those \x00\x01\x02 bytes"
180/// );
181/// assert_eq!(
182///     format_bytes!(b"{}.{}.{}.{}", 1_i32, 2_u8, 3_f32, &4),
183///     b"1.2.3.4"
184/// );
185/// assert_eq!(
186///     format_bytes!(b"what about this very very long message {}?", "here".as_bytes()),
187///     b"what about this very very long message here?".to_vec()
188/// );
189/// assert_eq!(format_bytes!(b"{}", std::borrow::Cow::Borrowed("cow".as_bytes())), b"cow");
190/// ```
191#[macro_export]
192macro_rules! format_bytes {
193    ($($args: tt)*) => {{
194        let mut vec = Vec::<u8>::new();
195        $crate::write_bytes!(&mut vec, $($args)*)
196            // Never panics since `impl std::fmt::Write for Vec<u8>` never errors:
197            .unwrap();
198        vec
199    }}
200}
201
202#[doc(hidden)]
203pub use format_bytes_macros::{_message, _write_bytes};
204
205/// Like [`format_bytes!`], but writes to a stream given as an additional first argument.
206///
207/// The stream is a `&mut` reference to a [`Sink`], such as any [`std::io::Write`].
208/// The macro returns [`std::io::Result<()>`](std::io::Result).
209///
210/// # Examples
211///
212/// ```
213/// use format_bytes::write_bytes;
214///
215/// const BUFFER_LEN: usize = 20;
216/// let mut buffer = [0_u8; BUFFER_LEN];
217/// let mut slice = &mut buffer[..];
218///
219/// write_bytes!(&mut slice, b"{}", 3.14).unwrap();
220///
221/// // `impl std::io::Write for &mut [u8]` reassigns the slice to the unwritten remainder:
222/// let written = BUFFER_LEN - slice.len();
223/// assert_eq!(&buffer[..written], b"3.14");
224/// ```
225#[macro_export]
226macro_rules! write_bytes {
227    ($($args: tt)*) => {
228        $crate::_write_bytes!([$crate] $($args)*)
229    };
230}
231
232/// Let types decide how to format themselves for presentation to users in a byte-stream output.
233///
234/// Similar to `std::fmt::Display`, but the output stream is bytes instead of Unicode.
235///
236/// When output is presented to users, it is decoded with an unspecified character encoding
237/// that is presumed to be ASCII-compatible.
238///
239/// Implementers should return any error from `output` (e.g. with the `?` operator),
240/// and not emit other errors.
241///
242/// # Example
243///
244/// A typical `impl` for a struct with multiple fields might use the `write_bytes` macro:
245///
246/// ```
247/// use format_bytes::{DisplayBytes, write_bytes};
248///
249/// struct Point2D { x: f32, y: f32 }
250///
251/// impl DisplayBytes for Point2D {
252///     fn display_bytes(
253///         &self,
254///         out: &mut dyn std::io::Write,
255///     ) -> std::io::Result<()> {
256///         write_bytes!(out, b"x = {}, y = {}", self.x, self.y)
257///     }
258/// }
259/// ```
260pub trait DisplayBytes {
261    fn display_bytes(&self, output: &mut dyn io::Write) -> io::Result<()>;
262
263    /// Describes the value with its type, for sinks that care about types,
264    /// such as structured output.
265    ///
266    /// Defaults to the output of `display_bytes`, as bytes.
267    fn visit(&self, visitor: &mut dyn Visit) -> io::Result<()> {
268        let mut bytes = vec![];
269        self.display_bytes(&mut bytes)?;
270        visitor.bytes(&bytes)
271    }
272}
273
274/// Receives a value described by [`DisplayBytes::visit`].
275///
276/// Only `bytes` and `str` are required: other methods default to them, the
277/// way `display_bytes` would write the value.
278pub trait Visit {
279    /// Bytes in an unknown encoding
280    fn bytes(&mut self, value: &[u8]) -> io::Result<()>;
281
282    /// Text known to be valid UTF-8
283    fn str(&mut self, value: &str) -> io::Result<()>;
284
285    fn int(&mut self, value: i128) -> io::Result<()> {
286        self.str(&value.to_string())
287    }
288
289    fn uint(&mut self, value: u128) -> io::Result<()> {
290        self.str(&value.to_string())
291    }
292
293    /// Items of [`join`]. Defaults to them joined, as bytes.
294    fn seq(
295        &mut self,
296        items: &[&dyn DisplayBytes],
297        separator: &dyn DisplayBytes,
298    ) -> io::Result<()> {
299        let mut bytes = vec![];
300        join(items, separator).display_bytes(&mut bytes)?;
301        self.bytes(&bytes)
302    }
303
304    /// A message within another, e.g. a cause within its context. Defaults to
305    /// it written, as bytes: a [`Sink`] would rather write it through itself,
306    /// with [`Message::write_to`].
307    fn message(&mut self, message: &Message) -> io::Result<()> {
308        let mut bytes = vec![];
309        message.display_bytes(&mut bytes)?;
310        self.bytes(&bytes)
311    }
312}
313
314/// How a format string refers to an argument.
315#[derive(Debug, Clone, Copy, PartialEq, Eq)]
316pub enum ArgName {
317    /// The n-th positional argument
318    Index(usize),
319    /// A named argument, or a variable captured from scope
320    Name(&'static str),
321}
322
323/// Destination of formatting, which keeps the format string's text apart from
324/// its arguments.
325///
326/// Every [`std::io::Write`] is a `Sink` that writes both as-is.
327pub trait Sink {
328    /// Text from the format string: written by the programmer, valid UTF-8
329    fn literal(&mut self, text: &str) -> io::Result<()>;
330
331    /// A value interpolated into the format string
332    fn arg(
333        &mut self,
334        name: ArgName,
335        value: &dyn DisplayBytes,
336    ) -> io::Result<()>;
337}
338
339impl<W: io::Write + ?Sized> Sink for W {
340    fn literal(&mut self, text: &str) -> io::Result<()> {
341        self.write_all(text.as_bytes())
342    }
343
344    fn arg(&mut self, _: ArgName, value: &dyn DisplayBytes) -> io::Result<()> {
345        // `&mut Self` is `Sized`, unlike `Self`
346        value.display_bytes(&mut &mut *self)
347    }
348}
349
350/// Creates a [`Message`]: like [`format_bytes!`], but the arguments are kept,
351/// for a [`Sink`] to write later.
352///
353/// Arguments are moved into the message, so they must be `'static`, `Send`
354/// and `Sync`.
355///
356/// # Example
357///
358/// ```
359/// use format_bytes::message;
360///
361/// let rev = 42;
362/// let msg = message!("unknown revision {rev} in {path}", path = b"caf\xe9".to_vec());
363///
364/// let mut out = vec![];
365/// msg.write_to(&mut out).unwrap();
366/// assert_eq!(out, b"unknown revision 42 in caf\xe9");
367/// assert_eq!(msg.template().source, "unknown revision {rev} in {path}");
368/// ```
369#[macro_export]
370macro_rules! message {
371    ($($args: tt)*) => {
372        $crate::_message!([$crate] $($args)*)
373    };
374}
375
376/// A format string, parsed at compile time.
377#[derive(Debug)]
378pub struct Template {
379    /// The format string as written
380    pub source: &'static str,
381    pub pieces: &'static [Piece],
382    /// Names of the arguments, in order
383    pub args: &'static [ArgName],
384}
385
386/// A part of a [`Template`].
387#[derive(Debug, Clone, Copy, PartialEq, Eq)]
388pub enum Piece {
389    /// Text, with `{{` and `}}` unescaped
390    Literal(&'static str),
391    /// Index of an argument
392    Arg(usize),
393}
394
395/// A format string with its arguments, made by [`message!`].
396pub struct Message {
397    template: &'static Template,
398    args: Vec<Box<dyn DisplayBytes + Send + Sync>>,
399}
400
401impl Message {
402    #[doc(hidden)]
403    pub fn new(
404        template: &'static Template,
405        args: Vec<Box<dyn DisplayBytes + Send + Sync>>,
406    ) -> Self {
407        Self { template, args }
408    }
409
410    pub fn template(&self) -> &'static Template {
411        self.template
412    }
413
414    pub fn arg(&self, index: usize) -> &dyn DisplayBytes {
415        &*self.args[index]
416    }
417
418    /// Writes the message as [`write_bytes!`] would.
419    pub fn write_to(&self, sink: &mut dyn Sink) -> io::Result<()> {
420        for piece in self.template.pieces {
421            match *piece {
422                Piece::Literal(text) => sink.literal(text)?,
423                Piece::Arg(index) => {
424                    sink.arg(self.template.args[index], self.arg(index))?
425                }
426            }
427        }
428        Ok(())
429    }
430}
431
432impl DisplayBytes for Message {
433    fn display_bytes(&self, mut output: &mut dyn io::Write) -> io::Result<()> {
434        self.write_to(&mut output)
435    }
436
437    fn visit(&self, visitor: &mut dyn Visit) -> io::Result<()> {
438        visitor.message(self)
439    }
440}
441
442impl fmt::Debug for Message {
443    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
444        let mut bytes = vec![];
445        self.write_to(&mut bytes).map_err(|_| fmt::Error)?;
446        f.debug_tuple("Message")
447            .field(&String::from_utf8_lossy(&bytes))
448            .finish()
449    }
450}
451
452macro_rules! impl_through_deref {
453    // Macro hygiene requires the `$Inner` ident to be an input
454    // so it matches corresponding idents in `$Wrapper` types:
455    ($Inner: ident => $( $Wrapper: ty, )*) => {
456        $(
457            /// Forward to the inner type.
458            impl<$Inner: ?Sized + DisplayBytes> DisplayBytes for $Wrapper {
459                #[inline]
460                fn display_bytes(&self, output: &mut dyn io::Write) -> io::Result<()> {
461                    (**self).display_bytes(output)
462                }
463
464                #[inline]
465                fn visit(&self, visitor: &mut dyn Visit) -> io::Result<()> {
466                    (**self).visit(visitor)
467                }
468            }
469        )*
470    };
471}
472
473impl_through_deref! {
474    Inner =>
475    &'_ Inner,
476    &'_ mut Inner,
477    Box<Inner>,
478    std::rc::Rc<Inner>,
479    std::sync::Arc<Inner>,
480}
481
482/// Forward to the inner type.
483impl<Inner> DisplayBytes for std::borrow::Cow<'_, Inner>
484where
485    Inner: ?Sized + ToOwned + DisplayBytes,
486{
487    #[inline]
488    fn display_bytes(&self, output: &mut dyn io::Write) -> io::Result<()> {
489        (**self).display_bytes(output)
490    }
491
492    #[inline]
493    fn visit(&self, visitor: &mut dyn Visit) -> io::Result<()> {
494        (**self).visit(visitor)
495    }
496}
497
498macro_rules! impl_for_byte_string {
499    ($($Ty: ty),+) => {
500        $(
501            /// Byte strings are "formatted" as-is.
502            impl DisplayBytes for $Ty {
503                #[inline]
504                fn display_bytes(&self, output: &mut dyn io::Write) -> io::Result<()> {
505                    output.write_all(self)
506                }
507            }
508        )+
509    };
510}
511
512impl_for_byte_string!([u8], Vec<u8>);
513
514macro_rules! impl_for_arrays {
515    ($( $LEN: expr )+) => {
516        impl_for_byte_string! {
517            $( [u8; $LEN] ),+
518        }
519    };
520}
521
522impl_for_arrays! {
523    0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
524    17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
525    33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
526    49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
527}
528
529/// Adaptor for types that implement `std::fmt::Display`. The Unicode output is encoded as UTF-8.
530///
531/// # Example
532///
533/// ```rust
534/// use format_bytes::{format_bytes, Utf8};
535///
536/// assert_eq!(format_bytes!(b"{}", Utf8("è_é")), b"\xc3\xa8_\xc3\xa9");
537/// ```
538pub struct Utf8<Inner>(pub Inner);
539
540impl<Inner: fmt::Display> DisplayBytes for Utf8<Inner> {
541    fn display_bytes(&self, output: &mut dyn io::Write) -> io::Result<()> {
542        // Calling `Display::fmt` requires a `Formatter` which we can’t create directly,
543        // so let’s go through `write!` with an adaptor type.
544        struct Adapter<'a> {
545            output: &'a mut dyn io::Write,
546            result: io::Result<()>,
547        }
548
549        impl fmt::Write for Adapter<'_> {
550            fn write_str(&mut self, s: &str) -> fmt::Result {
551                if self.result.is_err() {
552                    return Err(fmt::Error);
553                }
554                let utf8 = s.as_bytes();
555                match self.output.write_all(utf8) {
556                    Ok(()) => Ok(()),
557                    Err(error) => {
558                        // `fmt::Error` cannot carry any data so stash the error
559                        self.result = Err(error);
560                        Err(fmt::Error)
561                    }
562                }
563            }
564        }
565
566        let mut adapter = Adapter {
567            output,
568            result: Ok(()),
569        };
570        {
571            // `write!` requires this import: https://github.com/rust-lang/rust/issues/21826
572            use fmt::Write;
573            write!(adapter, "{}", self.0)
574                // Recover stashed error
575                .map_err(|fmt::Error| adapter.result.unwrap_err())
576        }
577    }
578
579    fn visit(&self, visitor: &mut dyn Visit) -> io::Result<()> {
580        visitor.str(&self.0.to_string())
581    }
582}
583
584macro_rules! impl_ascii_only {
585    ($( $Ty: ident )* => |$value: ident, $visitor: ident| $visit: expr) => {
586        $(
587            /// Format to ASCII bytes with `std::fmt::Display`.
588            ///
589            /// The `Display` impl for this type only emits ASCII characters,
590            /// so it’s less useful than in the general case
591            /// to make users explicitly opt-in to UTF-8 encoding.
592            impl DisplayBytes for $Ty {
593                #[inline]
594                fn display_bytes(&self, output: &mut dyn io::Write) -> io::Result<()> {
595                    Utf8(self).display_bytes(output)
596                }
597
598                #[inline]
599                fn visit(&self, $visitor: &mut dyn Visit) -> io::Result<()> {
600                    let $value = *self;
601                    $visit
602                }
603            }
604        )*
605    };
606}
607
608impl_ascii_only! {
609    u8 u16 u32 u64 u128 usize => |value, visitor| visitor.uint(value as u128)
610}
611
612impl_ascii_only! {
613    i8 i16 i32 i64 i128 isize => |value, visitor| visitor.int(value as i128)
614}
615
616impl_ascii_only! {
617    f32 f64 => |value, visitor| visitor.str(&value.to_string())
618}
619
620/// Format a sequence of values with the given separator repeated
621/// between any two consecutive values, but not at the start or end of the sequence.
622///
623/// It is visited with [`Visit::seq`].
624///
625/// # Example
626///
627/// ```
628/// use format_bytes::{format_bytes, join};
629///
630/// let formatted = format_bytes!(b"Got {}.", join(&[4, 3, 2], b" and "));
631/// assert_eq!(formatted, b"Got 4 and 3 and 2.");
632/// ```
633pub fn join(
634    iterable: impl IntoIterator<Item = impl DisplayBytes>,
635    separator: impl DisplayBytes,
636) -> impl DisplayBytes {
637    Join {
638        items: iterable.into_iter().collect::<Vec<_>>(),
639        separator,
640    }
641}
642
643struct Join<T, S> {
644    items: Vec<T>,
645    separator: S,
646}
647
648impl<T: DisplayBytes, S: DisplayBytes> DisplayBytes for Join<T, S> {
649    fn display_bytes(&self, output: &mut dyn io::Write) -> io::Result<()> {
650        for (i, item) in self.items.iter().enumerate() {
651            if i > 0 {
652                self.separator.display_bytes(output)?;
653            }
654            item.display_bytes(output)?;
655        }
656        Ok(())
657    }
658
659    fn visit(&self, visitor: &mut dyn Visit) -> io::Result<()> {
660        let items: Vec<&dyn DisplayBytes> =
661            self.items.iter().map(|item| item as _).collect();
662        visitor.seq(&items, &self.separator)
663    }
664}