| 1 | //! Enables base64'd output anywhere you might use a `Display` implementation, like a format string. |
| 2 | //! |
| 3 | //! ``` |
| 4 | //! use base64::{display::Base64Display, engine::general_purpose::STANDARD}; |
| 5 | //! |
| 6 | //! let data = vec![0x0, 0x1, 0x2, 0x3]; |
| 7 | //! let wrapper = Base64Display::new(&data, &STANDARD); |
| 8 | //! |
| 9 | //! assert_eq!("base64: AAECAw==" , format!("base64: {}" , wrapper)); |
| 10 | //! ``` |
| 11 | |
| 12 | use super::chunked_encoder::ChunkedEncoder; |
| 13 | use crate::engine::Engine; |
| 14 | use core::fmt::{Display, Formatter}; |
| 15 | use core::{fmt, str}; |
| 16 | |
| 17 | /// A convenience wrapper for base64'ing bytes into a format string without heap allocation. |
| 18 | pub struct Base64Display<'a, 'e, E: Engine> { |
| 19 | bytes: &'a [u8], |
| 20 | chunked_encoder: ChunkedEncoder<'e, E>, |
| 21 | } |
| 22 | |
| 23 | impl<'a, 'e, E: Engine> Base64Display<'a, 'e, E> { |
| 24 | /// Create a `Base64Display` with the provided engine. |
| 25 | pub fn new(bytes: &'a [u8], engine: &'e E) -> Base64Display<'a, 'e, E> { |
| 26 | Base64Display { |
| 27 | bytes, |
| 28 | chunked_encoder: ChunkedEncoder::new(engine), |
| 29 | } |
| 30 | } |
| 31 | } |
| 32 | |
| 33 | impl<'a, 'e, E: Engine> Display for Base64Display<'a, 'e, E> { |
| 34 | fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> { |
| 35 | let mut sink: FormatterSink<'_, '_> = FormatterSink { f: formatter }; |
| 36 | self.chunked_encoder.encode(self.bytes, &mut sink) |
| 37 | } |
| 38 | } |
| 39 | |
| 40 | struct FormatterSink<'a, 'b: 'a> { |
| 41 | f: &'a mut Formatter<'b>, |
| 42 | } |
| 43 | |
| 44 | impl<'a, 'b: 'a> super::chunked_encoder::Sink for FormatterSink<'a, 'b> { |
| 45 | type Error = fmt::Error; |
| 46 | |
| 47 | fn write_encoded_bytes(&mut self, encoded: &[u8]) -> Result<(), Self::Error> { |
| 48 | // Avoid unsafe. If max performance is needed, write your own display wrapper that uses |
| 49 | // unsafe here to gain about 10-15%. |
| 50 | self.f |
| 51 | .write_str(data:str::from_utf8(encoded).expect(msg:"base64 data was not utf8" )) |
| 52 | } |
| 53 | } |
| 54 | |
| 55 | #[cfg (test)] |
| 56 | mod tests { |
| 57 | use super::super::chunked_encoder::tests::{ |
| 58 | chunked_encode_matches_normal_encode_random, SinkTestHelper, |
| 59 | }; |
| 60 | use super::*; |
| 61 | use crate::engine::general_purpose::STANDARD; |
| 62 | |
| 63 | #[test ] |
| 64 | fn basic_display() { |
| 65 | assert_eq!( |
| 66 | "~$Zm9vYmFy#*" , |
| 67 | format!("~${}#*" , Base64Display::new(b"foobar" , &STANDARD)) |
| 68 | ); |
| 69 | assert_eq!( |
| 70 | "~$Zm9vYmFyZg==#*" , |
| 71 | format!("~${}#*" , Base64Display::new(b"foobarf" , &STANDARD)) |
| 72 | ); |
| 73 | } |
| 74 | |
| 75 | #[test ] |
| 76 | fn display_encode_matches_normal_encode() { |
| 77 | let helper = DisplaySinkTestHelper; |
| 78 | chunked_encode_matches_normal_encode_random(&helper); |
| 79 | } |
| 80 | |
| 81 | struct DisplaySinkTestHelper; |
| 82 | |
| 83 | impl SinkTestHelper for DisplaySinkTestHelper { |
| 84 | fn encode_to_string<E: Engine>(&self, engine: &E, bytes: &[u8]) -> String { |
| 85 | format!("{}" , Base64Display::new(bytes, engine)) |
| 86 | } |
| 87 | } |
| 88 | } |
| 89 | |