Skip to content

Commit a9a25fa

Browse files
committed
Add trait IntoAsciiStr to replace and AsciiCast<Target=AsciiStr>
Supports converting partially, and the error type says where and why it wen't wrong. Untested. Should also be implemented for &mut, but that's too much code ;)
1 parent 5f2971f commit a9a25fa

File tree

2 files changed

+127
-4
lines changed

2 files changed

+127
-4
lines changed

src/ascii_str.rs

Lines changed: 126 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::{fmt, mem};
22
use std::ops::{Index, IndexMut, Range, RangeTo, RangeFrom, RangeFull};
3+
use std::error::Error;
34
use std::ascii::AsciiExt;
45

56
use AsciiCast;
@@ -77,13 +78,28 @@ impl AsciiStr {
7778
{
7879
unsafe {
7980
if bytes.as_ref().is_ascii() {
80-
Ok( mem::transmute(bytes.as_ref()) )
81+
Ok( Self::from_bytes_unchecked(bytes) )
8182
} else {
8283
Err(())
8384
}
8485
}
8586
}
8687

88+
/// Converts anything that can represent a byte slice into an `AsciiStr` without validation.
89+
///
90+
/// # Examples
91+
///
92+
/// ```
93+
/// # use ascii::AsciiStr;
94+
/// let foo = unsafe{ AsciiStr::from_bytes_unchecked("foo") };
95+
/// assert_eq!(foo.as_str(), "foo");
96+
/// ```
97+
pub unsafe fn from_bytes_unchecked<'a, B: ?Sized>(bytes: &'a B) -> &'a AsciiStr
98+
where B: AsRef<[u8]>
99+
{
100+
mem::transmute(bytes.as_ref())
101+
}
102+
87103
/// Converts a borrowed string to a borrows ascii string.
88104
pub fn from_str<'a>(s: &'a str) -> Result<&'a AsciiStr, ()> {
89105
AsciiStr::from_bytes(s.as_bytes())
@@ -293,10 +309,117 @@ impl<'a> AsciiCast<'a> for str {
293309
}
294310
}
295311

312+
313+
/// Error returned by IntoAsciiStr
314+
#[derive(PartialEq)]// allows assert_eq!
315+
pub struct IntoAsciiStrError {
316+
index: usize,
317+
/// If less than 128, it was a byte >= 128
318+
not_ascii: char,
319+
}
320+
321+
impl fmt::Debug for IntoAsciiStrError {
322+
fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result {
323+
if (self.not_ascii as u32) < 128 {
324+
write!(fmtr, "b'\\x{:x}' at index {}", self.not_ascii as u8 + 128, self.index)
325+
} else {
326+
write!(fmtr, "'{}' at index {}", self.not_ascii, self.index)
327+
}
328+
}
329+
}
330+
impl fmt::Display for IntoAsciiStrError {
331+
fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result {
332+
if (self.not_ascii as u32) < 128 {
333+
write!(fmtr, "the byte \\x{:x} at index {} is not ASCII", self.not_ascii as u8 + 128, self.index)
334+
} else {
335+
write!(fmtr, "the character {} at index {} is not ASCII", self.not_ascii, self.index)
336+
}
337+
}
338+
}
339+
impl Error for IntoAsciiStrError {
340+
fn description(&self) -> &'static str {
341+
"one or more bytes (or chars if str) are not ASCII"
342+
}
343+
}
344+
345+
346+
/// Trait for converting various slices into `AsciiStr`.
347+
pub trait IntoAsciiStr : AsciiExt {
348+
/// Convert to `AsciiStr`, not doing any range asserts.
349+
unsafe fn into_ascii_unchecked(&self) -> &AsciiStr;
350+
/// Convert the portion from start of self that is valid ASCII to `AsciiStr`.
351+
fn ascii_part(&self) -> &AsciiStr;
352+
/// Convert to `AsciiStr`.
353+
fn into_ascii(&self) -> Result<&AsciiStr,IntoAsciiStrError>;
354+
}
355+
356+
#[cfg(feature = "unstable")]
357+
impl IntoAsciiStr for AsciiStr {
358+
fn into_ascii(&self) -> Result<&AsciiStr,IntoAsciiStrError> {
359+
Ok(self)
360+
}
361+
fn ascii_part(&self) -> &AsciiStr {
362+
self
363+
}
364+
unsafe fn into_ascii_unchecked(&self) -> &AsciiStr {
365+
self
366+
}
367+
}
368+
impl IntoAsciiStr for [u8] {
369+
fn into_ascii(&self) -> Result<&AsciiStr,IntoAsciiStrError> {
370+
match self.iter().enumerate().find(|&(_,b)| *b > 127 ) {
371+
Some((index, &byte)) => Err(IntoAsciiStrError{
372+
index: index,
373+
not_ascii: (byte - 128) as char,
374+
}),
375+
None => unsafe{ Ok(self.into_ascii_unchecked()) },
376+
}
377+
}
378+
fn ascii_part(&self) -> &AsciiStr {
379+
let ascii = self.iter().position(|b| *b > 127 )
380+
.map(|l| &self[..l] )
381+
.unwrap_or(self);
382+
unsafe{ ascii.into_ascii_unchecked() }
383+
}
384+
unsafe fn into_ascii_unchecked(&self) -> &AsciiStr {
385+
AsciiStr::from_bytes_unchecked(self)
386+
}
387+
}
388+
impl IntoAsciiStr for str {
389+
fn into_ascii(&self) -> Result<&AsciiStr,IntoAsciiStrError> {
390+
let s = self.ascii_part();
391+
if s.len() == self.len() {
392+
Ok(s)
393+
} else {
394+
Err(IntoAsciiStrError{
395+
index: s.len(),
396+
not_ascii: self[s.len()..].chars().next().unwrap(),
397+
})
398+
}
399+
}
400+
fn ascii_part(&self) -> &AsciiStr {
401+
self.as_bytes().ascii_part()
402+
}
403+
unsafe fn into_ascii_unchecked(&self) -> &AsciiStr {
404+
mem::transmute(self)
405+
}
406+
}
407+
408+
409+
296410
#[cfg(test)]
297411
mod tests {
298-
use AsciiCast;
299-
use super::AsciiStr;
412+
use {AsciiCast,Ascii};
413+
use super::{AsciiStr,IntoAsciiStr,IntoAsciiStrError};
414+
415+
#[test]
416+
fn into_ascii() {
417+
fn generic<C:IntoAsciiStr+?Sized>(c: &C) -> Result<&AsciiStr,IntoAsciiStrError> {
418+
c.into_ascii()
419+
}
420+
assert_eq!(generic("A"), Ok([Ascii::A].as_ref().into()));
421+
assert_eq!(generic(&b"A"[..]), Ok([Ascii::A].as_ref().into()));
422+
}
300423

301424
#[test]
302425
fn as_str() {

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use std::ascii::AsciiExt;
2323

2424
pub use ascii::{Ascii, IntoAscii, IntoAsciiError};
2525
pub use ascii_string::{AsciiString, IntoAsciiString};
26-
pub use ascii_str::AsciiStr;
26+
pub use ascii_str::{AsciiStr, IntoAsciiStr, IntoAsciiStrError};
2727

2828
/// Trait for converting into an ascii type.
2929
pub trait AsciiCast<'a>: AsciiExt {

0 commit comments

Comments
 (0)