Skip to content

Commit 5820d9b

Browse files
Merge pull request #263 from marshallpierce/silverlyra-usage
Doc updates per #261
2 parents b64c624 + 97d9180 commit 5820d9b

File tree

4 files changed

+188
-95
lines changed

4 files changed

+188
-95
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ rand = { version = "0.8.5", features = ["small_rng"] }
4040
# Latest is 4.4.13 but specifies MSRV in Cargo.toml which means we can't depend
4141
# on it (even though we won't compile it in MSRV CI).
4242
clap = { version = "3.2.25", features = ["derive"] }
43+
strum = { version = "0.25", features = ["derive"] }
4344
# test fixtures for engine tests
4445
rstest = "0.13.0"
4546
rstest_reuse = "0.6.0"

examples/base64.rs

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,45 +2,35 @@ use std::fs::File;
22
use std::io::{self, Read};
33
use std::path::PathBuf;
44
use std::process;
5-
use std::str::FromStr;
65

76
use base64::{alphabet, engine, read, write};
87
use clap::Parser;
98

10-
#[derive(Clone, Debug, Parser)]
9+
#[derive(Clone, Debug, Parser, strum::EnumString, Default)]
10+
#[strum(serialize_all = "kebab-case")]
1111
enum Alphabet {
12+
#[default]
1213
Standard,
1314
UrlSafe,
1415
}
1516

16-
impl Default for Alphabet {
17-
fn default() -> Self {
18-
Self::Standard
19-
}
20-
}
21-
22-
impl FromStr for Alphabet {
23-
type Err = String;
24-
fn from_str(s: &str) -> Result<Self, String> {
25-
match s {
26-
"standard" => Ok(Self::Standard),
27-
"urlsafe" => Ok(Self::UrlSafe),
28-
_ => Err(format!("alphabet '{}' unrecognized", s)),
29-
}
30-
}
31-
}
32-
3317
/// Base64 encode or decode FILE (or standard input), to standard output.
3418
#[derive(Debug, Parser)]
3519
struct Opt {
36-
/// decode data
20+
/// Decode the base64-encoded input (default: encode the input as base64).
3721
#[structopt(short = 'd', long = "decode")]
3822
decode: bool,
39-
/// The alphabet to choose. Defaults to the standard base64 alphabet.
40-
/// Supported alphabets include "standard" and "urlsafe".
23+
24+
/// The encoding alphabet: "standard" (default) or "url-safe".
4125
#[structopt(long = "alphabet")]
4226
alphabet: Option<Alphabet>,
43-
/// The file to encode/decode.
27+
28+
/// Omit padding characters while encoding, and reject them while decoding.
29+
#[structopt(short = 'p', long = "no-padding")]
30+
no_padding: bool,
31+
32+
/// The file to encode or decode.
33+
#[structopt(name = "FILE", parse(from_os_str))]
4434
file: Option<PathBuf>,
4535
}
4636

@@ -65,7 +55,10 @@ fn main() {
6555
Alphabet::Standard => alphabet::STANDARD,
6656
Alphabet::UrlSafe => alphabet::URL_SAFE,
6757
},
68-
engine::general_purpose::PAD,
58+
match opt.no_padding {
59+
true => engine::general_purpose::NO_PAD,
60+
false => engine::general_purpose::PAD,
61+
},
6962
);
7063

7164
let stdout = io::stdout();

src/alphabet.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -160,21 +160,21 @@ impl fmt::Display for ParseAlphabetError {
160160
#[cfg(any(feature = "std", test))]
161161
impl error::Error for ParseAlphabetError {}
162162

163-
/// The standard alphabet (uses `+` and `/`).
163+
/// The standard alphabet (with `+` and `/`) specified in [RFC 4648][].
164164
///
165-
/// See [RFC 3548](https://tools.ietf.org/html/rfc3548#section-3).
165+
/// [RFC 4648]: https://datatracker.ietf.org/doc/html/rfc4648#section-4
166166
pub const STANDARD: Alphabet = Alphabet::from_str_unchecked(
167167
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
168168
);
169169

170-
/// The URL safe alphabet (uses `-` and `_`).
170+
/// The URL-safe alphabet (with `-` and `_`) specified in [RFC 4648][].
171171
///
172-
/// See [RFC 3548](https://tools.ietf.org/html/rfc3548#section-4).
172+
/// [RFC 4648]: https://datatracker.ietf.org/doc/html/rfc4648#section-5
173173
pub const URL_SAFE: Alphabet = Alphabet::from_str_unchecked(
174174
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",
175175
);
176176

177-
/// The `crypt(3)` alphabet (uses `.` and `/` as the first two values).
177+
/// The `crypt(3)` alphabet (with `.` and `/` as the _first_ two characters).
178178
///
179179
/// Not standardized, but folk wisdom on the net asserts that this alphabet is what crypt uses.
180180
pub const CRYPT: Alphabet = Alphabet::from_str_unchecked(
@@ -186,7 +186,7 @@ pub const BCRYPT: Alphabet = Alphabet::from_str_unchecked(
186186
"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
187187
);
188188

189-
/// The alphabet used in IMAP-modified UTF-7 (uses `+` and `,`).
189+
/// The alphabet used in IMAP-modified UTF-7 (with `+` and `,`).
190190
///
191191
/// See [RFC 3501](https://tools.ietf.org/html/rfc3501#section-5.1.3)
192192
pub const IMAP_MUTF7: Alphabet = Alphabet::from_str_unchecked(

src/lib.rs

Lines changed: 164 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,124 @@
1-
//! # Getting started
1+
//! Correct, fast, and configurable [base64][] decoding and encoding. Base64
2+
//! transports binary data efficiently in contexts where only plain text is
3+
//! allowed.
24
//!
3-
//! 1. Perhaps one of the preconfigured engines in [engine::general_purpose] will suit, e.g.
4-
//! [engine::general_purpose::STANDARD_NO_PAD].
5-
//! - These are re-exported in [prelude] with a `BASE64_` prefix for those who prefer to
6-
//! `use base64::prelude::*` or equivalent, e.g. [prelude::BASE64_STANDARD_NO_PAD]
7-
//! 1. If not, choose which alphabet you want. Most usage will want [alphabet::STANDARD] or [alphabet::URL_SAFE].
8-
//! 1. Choose which [Engine] implementation you want. For the moment there is only one: [engine::GeneralPurpose].
9-
//! 1. Configure the engine appropriately using the engine's `Config` type.
10-
//! - This is where you'll select whether to add padding (when encoding) or expect it (when
11-
//! decoding). If given the choice, prefer no padding.
12-
//! 1. Build the engine using the selected alphabet and config.
5+
//! [base64]: https://developer.mozilla.org/en-US/docs/Glossary/Base64
136
//!
14-
//! For more detail, see below.
7+
//! # Usage
158
//!
16-
//! ## Alphabets
9+
//! Use an [`Engine`] to decode or encode base64, configured with the base64
10+
//! alphabet and padding behavior best suited to your application.
1711
//!
18-
//! An [alphabet::Alphabet] defines what ASCII symbols are used to encode to or decode from.
12+
//! ## Engine setup
1913
//!
20-
//! Constants in [alphabet] like [alphabet::STANDARD] or [alphabet::URL_SAFE] provide commonly used
21-
//! alphabets, but you can also build your own custom [alphabet::Alphabet] if needed.
14+
//! There is more than one way to encode a stream of bytes as “base64”.
15+
//! Different applications use different encoding
16+
//! [alphabets][alphabet::Alphabet] and
17+
//! [padding behaviors][engine::general_purpose::GeneralPurposeConfig].
2218
//!
23-
//! ## Engines
19+
//! ### Encoding alphabet
2420
//!
25-
//! Once you have an `Alphabet`, you can pick which `Engine` you want. A few parts of the public
26-
//! API provide a default, but otherwise the user must provide an `Engine` to use.
21+
//! Almost all base64 [alphabets][alphabet::Alphabet] use `A-Z`, `a-z`, and
22+
//! `0-9`, which gives nearly 64 characters (26 + 26 + 10 = 62), but they differ
23+
//! in their choice of their final 2.
2724
//!
28-
//! See [Engine] for more.
25+
//! Most applications use the [standard][alphabet::STANDARD] alphabet specified
26+
//! in [RFC 4648][rfc-alphabet]. If that’s all you need, you can get started
27+
//! quickly by using the pre-configured
28+
//! [`STANDARD`][engine::general_purpose::STANDARD] engine, which is also available
29+
//! in the [`prelude`] module as shown here, if you prefer a minimal `use`
30+
//! footprint.
2931
//!
30-
//! ## Config
32+
#![cfg_attr(feature = "alloc", doc = "```")]
33+
#![cfg_attr(not(feature = "alloc"), doc = "```ignore")]
34+
//! use base64::prelude::*;
3135
//!
32-
//! In addition to an `Alphabet`, constructing an `Engine` also requires an [engine::Config]. Each
33-
//! `Engine` has a corresponding `Config` implementation since different `Engine`s may offer different
34-
//! levels of configurability.
36+
//! # fn main() -> Result<(), base64::DecodeError> {
37+
//! assert_eq!(BASE64_STANDARD.decode(b"+uwgVQA=")?, b"\xFA\xEC\x20\x55\0");
38+
//! assert_eq!(BASE64_STANDARD.encode(b"\xFF\xEC\x20\x55\0"), "/+wgVQA=");
39+
//! # Ok(())
40+
//! # }
41+
//! ```
3542
//!
36-
//! # Encoding
43+
//! [rfc-alphabet]: https://datatracker.ietf.org/doc/html/rfc4648#section-4
3744
//!
38-
//! Several different encoding methods on [Engine] are available to you depending on your desire for
39-
//! convenience vs performance.
45+
//! Other common alphabets are available in the [`alphabet`] module.
4046
//!
41-
//! | Method | Output | Allocates |
42-
//! | ------------------------ | ---------------------------- | ------------------------------ |
43-
//! | [Engine::encode] | Returns a new `String` | Always |
44-
//! | [Engine::encode_string] | Appends to provided `String` | Only if `String` needs to grow |
45-
//! | [Engine::encode_slice] | Writes to provided `&[u8]` | Never - fastest |
47+
//! #### URL-safe alphabet
4648
//!
47-
//! All of the encoding methods will pad as per the engine's config.
49+
//! The standard alphabet uses `+` and `/` as its two non-alphanumeric tokens,
50+
//! which cannot be safely used in URL’s without encoding them as `%2B` and
51+
//! `%2F`.
4852
//!
49-
//! # Decoding
53+
//! To avoid that, some applications use a [“URL-safe” alphabet][alphabet::URL_SAFE],
54+
//! which uses `-` and `_` instead. To use that alternative alphabet, use the
55+
//! [`URL_SAFE`][engine::general_purpose::URL_SAFE] engine. This example doesn't
56+
//! use [`prelude`] to show what a more explicit `use` would look like.
5057
//!
51-
//! Just as for encoding, there are different decoding methods available.
58+
#![cfg_attr(feature = "alloc", doc = "```")]
59+
#![cfg_attr(not(feature = "alloc"), doc = "```ignore")]
60+
//! use base64::{engine::general_purpose::URL_SAFE, Engine as _};
5261
//!
53-
//! | Method | Output | Allocates |
54-
//! | ------------------------ | ----------------------------- | ------------------------------ |
55-
//! | [Engine::decode] | Returns a new `Vec<u8>` | Always |
56-
//! | [Engine::decode_vec] | Appends to provided `Vec<u8>` | Only if `Vec` needs to grow |
57-
//! | [Engine::decode_slice] | Writes to provided `&[u8]` | Never - fastest |
62+
//! # fn main() -> Result<(), base64::DecodeError> {
63+
//! assert_eq!(URL_SAFE.decode(b"-uwgVQA=")?, b"\xFA\xEC\x20\x55\0");
64+
//! assert_eq!(URL_SAFE.encode(b"\xFF\xEC\x20\x55\0"), "_-wgVQA=");
65+
//! # Ok(())
66+
//! # }
67+
//! ```
5868
//!
59-
//! Unlike encoding, where all possible input is valid, decoding can fail (see [DecodeError]).
69+
//! ### Padding characters
6070
//!
61-
//! Input can be invalid because it has invalid characters or invalid padding. The nature of how
62-
//! padding is checked depends on the engine's config.
63-
//! Whitespace in the input is invalid, just like any other non-base64 byte.
71+
//! Each base64 character represents 6 bits (2⁶ = 64) of the original binary
72+
//! data, and every 3 bytes of input binary data will encode to 4 base64
73+
//! characters (8 bits × 3 = 6 bits × 4 = 24 bits).
6474
//!
65-
//! # `Read` and `Write`
75+
//! When the input is not an even multiple of 3 bytes in length, [canonical][]
76+
//! base64 encoders insert padding characters at the end, so that the output
77+
//! length is always a multiple of 4:
6678
//!
67-
//! To decode a [std::io::Read] of b64 bytes, wrap a reader (file, network socket, etc) with
68-
//! [read::DecoderReader].
79+
//! [canonical]: https://datatracker.ietf.org/doc/html/rfc4648#section-3.5
6980
//!
70-
//! To write raw bytes and have them b64 encoded on the fly, wrap a [std::io::Write] with
71-
//! [write::EncoderWriter].
81+
#![cfg_attr(feature = "alloc", doc = "```")]
82+
#![cfg_attr(not(feature = "alloc"), doc = "```ignore")]
83+
//! use base64::{engine::general_purpose::STANDARD, Engine as _};
7284
//!
73-
//! There is some performance overhead (15% or so) because of the necessary buffer shuffling --
74-
//! still fast enough that almost nobody cares. Also, these implementations do not heap allocate.
85+
//! assert_eq!(STANDARD.encode(b""), "");
86+
//! assert_eq!(STANDARD.encode(b"f"), "Zg==");
87+
//! assert_eq!(STANDARD.encode(b"fo"), "Zm8=");
88+
//! assert_eq!(STANDARD.encode(b"foo"), "Zm9v");
89+
//! ```
7590
//!
76-
//! # `Display`
91+
//! Canonical encoding ensures that base64 encodings will be exactly the same,
92+
//! byte-for-byte, regardless of input length. But the `=` padding characters
93+
//! aren’t necessary for decoding, and they may be omitted by using a
94+
//! [`NO_PAD`][engine::general_purpose::NO_PAD] configuration:
7795
//!
78-
//! See [display] for how to transparently base64-encode data via a `Display` implementation.
96+
#![cfg_attr(feature = "alloc", doc = "```")]
97+
#![cfg_attr(not(feature = "alloc"), doc = "```ignore")]
98+
//! use base64::{engine::general_purpose::STANDARD_NO_PAD, Engine as _};
7999
//!
80-
//! # Examples
100+
//! assert_eq!(STANDARD_NO_PAD.encode(b""), "");
101+
//! assert_eq!(STANDARD_NO_PAD.encode(b"f"), "Zg");
102+
//! assert_eq!(STANDARD_NO_PAD.encode(b"fo"), "Zm8");
103+
//! assert_eq!(STANDARD_NO_PAD.encode(b"foo"), "Zm9v");
104+
//! ```
81105
//!
82-
//! ## Using predefined engines
106+
//! The pre-configured `NO_PAD` engines will reject inputs containing padding
107+
//! `=` characters. To encode without padding and still accept padding while
108+
//! decoding, create an [engine][engine::general_purpose::GeneralPurpose] with
109+
//! that [padding mode][engine::DecodePaddingMode].
83110
//!
84111
#![cfg_attr(feature = "alloc", doc = "```")]
85112
#![cfg_attr(not(feature = "alloc"), doc = "```ignore")]
86-
//! use base64::{Engine as _, engine::general_purpose};
87-
//!
88-
//! let orig = b"data";
89-
//! let encoded: String = general_purpose::STANDARD_NO_PAD.encode(orig);
90-
//! assert_eq!("ZGF0YQ", encoded);
91-
//! assert_eq!(orig.as_slice(), &general_purpose::STANDARD_NO_PAD.decode(encoded).unwrap());
92-
//!
93-
//! // or, URL-safe
94-
//! let encoded_url = general_purpose::URL_SAFE_NO_PAD.encode(orig);
113+
//! # use base64::{engine::general_purpose::STANDARD_NO_PAD, Engine as _};
114+
//! assert_eq!(STANDARD_NO_PAD.decode(b"Zm8="), Err(base64::DecodeError::InvalidPadding));
95115
//! ```
96116
//!
97-
//! ## Custom alphabet, config, and engine
117+
//! ### Further customization
118+
//!
119+
//! Decoding and encoding behavior can be customized by creating an
120+
//! [engine][engine::GeneralPurpose] with an [alphabet][alphabet::Alphabet] and
121+
//! [padding configuration][engine::GeneralPurposeConfig]:
98122
//!
99123
#![cfg_attr(feature = "alloc", doc = "```")]
100124
#![cfg_attr(not(feature = "alloc"), doc = "```ignore")]
@@ -117,6 +141,81 @@
117141
//!
118142
//! ```
119143
//!
144+
//! ## Memory allocation
145+
//!
146+
//! The [decode][Engine::decode()] and [encode][Engine::encode()] engine methods
147+
//! allocate memory for their results – `decode` returns a `Vec<u8>` and
148+
//! `encode` returns a `String`. To instead decode or encode into a buffer that
149+
//! you allocated, use one of the alternative methods:
150+
//!
151+
//! #### Decoding
152+
//!
153+
//! | Method | Output | Allocates memory |
154+
//! | -------------------------- | ----------------------------- | ----------------------------- |
155+
//! | [`Engine::decode`] | returns a new `Vec<u8>` | always |
156+
//! | [`Engine::decode_vec`] | appends to provided `Vec<u8>` | if `Vec` lacks capacity |
157+
//! | [`Engine::decode_slice`] | writes to provided `&[u8]` | never
158+
//!
159+
//! #### Encoding
160+
//!
161+
//! | Method | Output | Allocates memory |
162+
//! | -------------------------- | ---------------------------- | ------------------------------ |
163+
//! | [`Engine::encode`] | returns a new `String` | always |
164+
//! | [`Engine::encode_string`] | appends to provided `String` | if `String` lacks capacity |
165+
//! | [`Engine::encode_slice`] | writes to provided `&[u8]` | never |
166+
//!
167+
//! ## Input and output
168+
//!
169+
//! The `base64` crate can [decode][Engine::decode()] and
170+
//! [encode][Engine::encode()] values in memory, or
171+
//! [`DecoderReader`][read::DecoderReader] and
172+
//! [`EncoderWriter`][write::EncoderWriter] provide streaming decoding and
173+
//! encoding for any [readable][std::io::Read] or [writable][std::io::Write]
174+
//! byte stream.
175+
//!
176+
//! #### Decoding
177+
//!
178+
#![cfg_attr(feature = "std", doc = "```")]
179+
#![cfg_attr(not(feature = "std"), doc = "```ignore")]
180+
//! # use std::io;
181+
//! use base64::{engine::general_purpose::STANDARD, read::DecoderReader};
182+
//!
183+
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
184+
//! let mut input = io::stdin();
185+
//! let mut decoder = DecoderReader::new(&mut input, &STANDARD);
186+
//! io::copy(&mut decoder, &mut io::stdout())?;
187+
//! # Ok(())
188+
//! # }
189+
//! ```
190+
//!
191+
//! #### Encoding
192+
//!
193+
#![cfg_attr(feature = "std", doc = "```")]
194+
#![cfg_attr(not(feature = "std"), doc = "```ignore")]
195+
//! # use std::io;
196+
//! use base64::{engine::general_purpose::STANDARD, write::EncoderWriter};
197+
//!
198+
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
199+
//! let mut output = io::stdout();
200+
//! let mut encoder = EncoderWriter::new(&mut output, &STANDARD);
201+
//! io::copy(&mut io::stdin(), &mut encoder)?;
202+
//! # Ok(())
203+
//! # }
204+
//! ```
205+
//!
206+
//! #### Display
207+
//!
208+
//! If you only need a base64 representation for implementing the
209+
//! [`Display`][std::fmt::Display] trait, use
210+
//! [`Base64Display`][display::Base64Display]:
211+
//!
212+
//! ```
213+
//! use base64::{display::Base64Display, engine::general_purpose::STANDARD};
214+
//!
215+
//! let value = Base64Display::new(b"\0\x01\x02\x03", &STANDARD);
216+
//! assert_eq!("base64: AAECAw==", format!("base64: {}", value));
217+
//! ```
218+
//!
120219
//! # Panics
121220
//!
122221
//! If length calculations result in overflowing `usize`, a panic will result.

0 commit comments

Comments
 (0)