Skip to content

Commit 851da45

Browse files
committed
refactor(sys): make byte string formatter reusable
1 parent 64ad750 commit 851da45

File tree

3 files changed

+133
-50
lines changed

3 files changed

+133
-50
lines changed

nginx-sys/src/detail.rs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
//! Implementation details shared between nginx-sys and ngx.
2+
#![allow(missing_docs)]
3+
4+
use core::fmt;
5+
6+
#[inline]
7+
pub fn debug_bytes(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result {
8+
if f.alternate() {
9+
match bytes.len() {
10+
0 => Ok(()),
11+
1 => write!(f, "{:02x}", bytes[0]),
12+
x => {
13+
for b in &bytes[..x - 1] {
14+
write!(f, "{b:02x},")?;
15+
}
16+
write!(f, "{:02x}", bytes[x - 1])
17+
}
18+
}
19+
} else {
20+
f.write_str("\"")?;
21+
display_bytes(f, bytes)?;
22+
f.write_str("\"")
23+
}
24+
}
25+
26+
#[inline]
27+
pub fn display_bytes(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result {
28+
// The implementation is similar to an inlined `String::from_utf8_lossy`, with two
29+
// important differences:
30+
//
31+
// - it writes directly to the Formatter instead of allocating a temporary String
32+
// - invalid sequences are represented as escaped individual bytes
33+
for chunk in bytes.utf8_chunks() {
34+
f.write_str(chunk.valid())?;
35+
for byte in chunk.invalid() {
36+
write!(f, "\\x{byte:02x}")?;
37+
}
38+
}
39+
40+
Ok(())
41+
}
42+
43+
#[cfg(test)]
44+
mod tests {
45+
extern crate alloc;
46+
use alloc::format;
47+
use alloc::string::ToString;
48+
49+
use super::*;
50+
51+
struct TestStr(&'static [u8]);
52+
53+
impl fmt::Debug for TestStr {
54+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55+
f.write_str("TestStr(")?;
56+
debug_bytes(f, self.0)?;
57+
f.write_str(")")
58+
}
59+
}
60+
61+
impl fmt::Display for TestStr {
62+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63+
display_bytes(f, self.0)
64+
}
65+
}
66+
67+
#[test]
68+
fn test_display() {
69+
let cases: &[(&[u8], &str)] = &[
70+
(b"", ""),
71+
(b"Ferris the \xf0\x9f\xa6\x80", "Ferris the 🦀"),
72+
(b"\xF0\x90\x80", "\\xf0\\x90\\x80"),
73+
(b"\xF0\x90\x80Hello World", "\\xf0\\x90\\x80Hello World"),
74+
(b"Hello \xF0\x90\x80World", "Hello \\xf0\\x90\\x80World"),
75+
(b"Hello World\xF0\x90\x80", "Hello World\\xf0\\x90\\x80"),
76+
];
77+
78+
for (bytes, expected) in cases {
79+
let str = TestStr(bytes);
80+
assert_eq!(str.to_string(), *expected);
81+
}
82+
83+
// Check that the formatter arguments are ignored correctly
84+
for (bytes, expected) in &cases[2..3] {
85+
let str = TestStr(bytes);
86+
assert_eq!(format!("{str:12.12}"), *expected);
87+
}
88+
}
89+
90+
#[test]
91+
fn test_debug() {
92+
let cases: &[(&[u8], &str, &str)] = &[
93+
(b"", "TestStr(\"\")", "TestStr()"),
94+
(b"a", "TestStr(\"a\")", "TestStr(61)"),
95+
(
96+
b"Ferris the \xf0\x9f\xa6\x80",
97+
"TestStr(\"Ferris the 🦀\")",
98+
"TestStr(46,65,72,72,69,73,20,74,68,65,20,f0,9f,a6,80)",
99+
),
100+
(
101+
b"\xF0\x90\x80",
102+
"TestStr(\"\\xf0\\x90\\x80\")",
103+
"TestStr(f0,90,80)",
104+
),
105+
];
106+
for (bytes, expected, alternate) in cases {
107+
let str = TestStr(bytes);
108+
assert_eq!(format!("{str:?}"), *expected);
109+
assert_eq!(format!("{str:#?}"), *alternate);
110+
}
111+
}
112+
}

nginx-sys/src/lib.rs

Lines changed: 3 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#![warn(missing_docs)]
33
#![no_std]
44

5+
pub mod detail;
56
mod event;
67
mod queue;
78

@@ -189,19 +190,9 @@ impl From<ngx_str_t> for &[u8] {
189190
}
190191

191192
impl fmt::Display for ngx_str_t {
193+
#[inline]
192194
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
193-
// The implementation is similar to an inlined `String::from_utf8_lossy`, with two
194-
// important differences:
195-
//
196-
// - it writes directly to the Formatter instead of allocating a temporary String
197-
// - invalid sequences are represented as escaped individual bytes
198-
for chunk in self.as_bytes().utf8_chunks() {
199-
f.write_str(chunk.valid())?;
200-
for byte in chunk.invalid() {
201-
write!(f, "\\x{byte:02x}")?;
202-
}
203-
}
204-
Ok(())
195+
detail::display_bytes(f, self.as_bytes())
205196
}
206197
}
207198

@@ -313,41 +304,3 @@ pub unsafe fn add_to_ngx_table(
313304
}
314305
None
315306
}
316-
317-
#[cfg(test)]
318-
mod tests {
319-
extern crate alloc;
320-
use alloc::format;
321-
use alloc::string::ToString;
322-
323-
use super::*;
324-
325-
#[test]
326-
fn ngx_str_display() {
327-
let pairs: &[(&[u8], &str)] = &[
328-
(b"", ""),
329-
(b"Ferris the \xf0\x9f\xa6\x80", "Ferris the 🦀"),
330-
(b"\xF0\x90\x80", "\\xf0\\x90\\x80"),
331-
(b"\xF0\x90\x80Hello World", "\\xf0\\x90\\x80Hello World"),
332-
(b"Hello \xF0\x90\x80World", "Hello \\xf0\\x90\\x80World"),
333-
(b"Hello World\xF0\x90\x80", "Hello World\\xf0\\x90\\x80"),
334-
];
335-
336-
for (bytes, expected) in pairs {
337-
let str = ngx_str_t {
338-
data: bytes.as_ptr().cast_mut(),
339-
len: bytes.len(),
340-
};
341-
assert_eq!(str.to_string(), *expected);
342-
}
343-
344-
// Check that the formatter arguments are ignored correctly
345-
for (bytes, expected) in &pairs[2..3] {
346-
let str = ngx_str_t {
347-
data: bytes.as_ptr().cast_mut(),
348-
len: bytes.len(),
349-
};
350-
assert_eq!(format!("{str:12.12}"), *expected);
351-
}
352-
}
353-
}

src/core/string.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#[cfg(all(not(feature = "std"), feature = "alloc"))]
22
use alloc::{borrow::Cow, string::String};
3+
use core::fmt;
34
use core::str::{self, Utf8Error};
45
#[cfg(feature = "std")]
56
use std::{borrow::Cow, string::String};
@@ -91,12 +92,29 @@ impl AsRef<[u8]> for NgxStr {
9192
}
9293
}
9394

95+
impl fmt::Debug for NgxStr {
96+
#[inline]
97+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98+
// XXX: Use debug_tuple() and feature(debug_closure_helpers) once it's stabilized
99+
f.write_str("NgxStr(")?;
100+
nginx_sys::detail::debug_bytes(f, &self.0)?;
101+
f.write_str(")")
102+
}
103+
}
104+
94105
impl Default for &NgxStr {
95106
fn default() -> Self {
96107
NgxStr::from_bytes(&[])
97108
}
98109
}
99110

111+
impl fmt::Display for NgxStr {
112+
#[inline]
113+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114+
nginx_sys::detail::display_bytes(f, &self.0)
115+
}
116+
}
117+
100118
#[cfg(test)]
101119
mod tests {
102120
extern crate alloc;

0 commit comments

Comments
 (0)