safe_decode/lib.rs
1#![no_std]
2#![forbid(unsafe_code)]
3#![cfg_attr(
4 test,
5 allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing)
6)]
7
8//! Panic-free, allocating byte→value transforms with no format knowledge.
9//!
10//! The companion to [`safe-read`](https://docs.rs/safe-read): that crate is `no_std` with
11//! *no* allocator and reads fixed-width integers; this one needs `alloc` because its
12//! outputs are `String`s and `Vec`s. That allocator boundary is the whole reason the two
13//! are separate crates.
14//!
15//! Membership is decidable — every function here is **panic-free, allocating,
16//! format-agnostic, and free of domain knowledge**. A transform that needs to know what
17//! the bytes *mean* belongs in the crate that owns that knowledge, not here.
18//!
19//! ## The UTF-16 family names its NUL policy
20//!
21//! A fleet audit found fourteen hand-rolled UTF-16 decoders disagreeing about NUL in four
22//! different ways. The disagreement was invisible because every one of them was called
23//! `decode_utf16le`. Here each policy is a separately named function, so picking the wrong
24//! one is a deliberate act rather than an accident:
25//!
26//! ```
27//! use safe_decode::{
28//! decode_utf16le_keep_nuls, decode_utf16le_trim_end_nuls, decode_utf16le_until_nul,
29//! split_utf16le_on_nul,
30//! };
31//!
32//! // UTF-16LE for 'A', NUL, 'B', NUL — the same bytes, four correct answers.
33//! let bytes = b"A\0\0\0B\0\0\0";
34//! assert_eq!(decode_utf16le_keep_nuls(bytes).text, "A\0B\0");
35//! assert_eq!(decode_utf16le_until_nul(bytes).text, "A");
36//! assert_eq!(decode_utf16le_trim_end_nuls(bytes).text, "A\0B");
37//! let parts = split_utf16le_on_nul(bytes);
38//! let texts: Vec<&str> = parts.iter().map(|d| d.text.as_str()).collect();
39//! assert_eq!(texts, ["A", "B", ""]);
40//! ```
41//!
42//! Each decode returns a [`DecodedUtf16`], which carries whether information was lost and
43//! why — an unpaired surrogate half, or an odd trailing byte that could not form a code
44//! unit. A caller that ignores it still gets well-formed text; a caller that cares can say
45//! so in a report.
46//!
47//! ```
48//! use safe_decode::decode_utf16le_keep_nuls;
49//!
50//! let lone_high_surrogate = decode_utf16le_keep_nuls(&[0x00, 0xD8]);
51//! assert_eq!(lone_high_surrogate.text, "\u{FFFD}");
52//! assert_eq!(lone_high_surrogate.unpaired_surrogates, 1);
53//! assert!(lone_high_surrogate.is_lossy());
54//! ```
55
56extern crate alloc;
57
58mod hex;
59mod rot13;
60mod utf16;
61
62pub use hex::{to_hex_lower, to_hex_upper};
63pub use rot13::rot13;
64pub use utf16::{
65 decode_utf16be_keep_nuls, decode_utf16be_trim_end_nuls, decode_utf16be_until_nul,
66 decode_utf16le_keep_nuls, decode_utf16le_trim_end_nuls, decode_utf16le_until_nul,
67 split_utf16be_on_nul, split_utf16le_on_nul, DecodedUtf16,
68};