Skip to content

Commit eeebd00

Browse files
committed
Add trait As(Mut)AsciiStr to replace AsciiCast<Target=AsciiStr>
The error type says where and why it failed. Separate trait to follow std convention.
1 parent ea1565c commit eeebd00

File tree

2 files changed

+209
-5
lines changed

2 files changed

+209
-5
lines changed

src/ascii_str.rs

+208-4
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())
@@ -209,7 +225,7 @@ impl AsMut<[Ascii]> for AsciiStr {
209225

210226
impl Default for &'static AsciiStr {
211227
fn default() -> &'static AsciiStr {
212-
unsafe{ "".into_ascii_unchecked() }
228+
unsafe{ "".as_ascii_unchecked() }
213229
}
214230
}
215231
impl<'a> From<&'a[Ascii]> for &'a AsciiStr {
@@ -341,10 +357,198 @@ impl<'a> AsciiCast<'a> for str {
341357
}
342358
}
343359

360+
361+
/// Error returned by AsAsciiStr
362+
#[derive(Clone,Copy)]
363+
pub struct AsAsciiStrError {
364+
index: usize,
365+
/// If less than 128, it was a byte >= 128
366+
not_ascii: char,
367+
}
368+
impl AsAsciiStrError {
369+
/// The index of the first non-ASCII byte or character.
370+
pub fn index(self) -> usize {
371+
self.index
372+
}
373+
/// Get the byte that caused the error.
374+
///
375+
/// If a str was being converted, the first byte in the utf8 encoding is returned.
376+
pub fn byte(self) -> u8 {
377+
if (self.not_ascii as u32) < 128 {
378+
self.not_ascii as u8 + 128
379+
} else {
380+
// char::encode_utf8() is unstable
381+
let mut s = String::with_capacity(4);
382+
s.push(self.not_ascii);
383+
s.bytes().next().unwrap()
384+
}
385+
}
386+
/// Get the char that caused conversions from a `str` to fail.
387+
///
388+
/// Returns `None` if the error was caused by a byte in a `[u8]`
389+
pub fn char(self) -> Option<char> {
390+
match self.not_ascii as u32 {
391+
0...127 => None, // byte in [u8]
392+
_ => Some(self.not_ascii),
393+
}
394+
}
395+
}
396+
impl fmt::Debug for AsAsciiStrError {
397+
fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result {
398+
if (self.not_ascii as u32) < 128 {
399+
write!(fmtr, "b'\\x{:x}' at index {}", self.not_ascii as u8 + 128, self.index)
400+
} else {
401+
write!(fmtr, "'{}' at index {}", self.not_ascii, self.index)
402+
}
403+
}
404+
}
405+
impl fmt::Display for AsAsciiStrError {
406+
fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result {
407+
if (self.not_ascii as u32) < 128 {
408+
write!(fmtr, "the byte \\x{:x} at index {} is not ASCII", self.not_ascii as u8 + 128, self.index)
409+
} else {
410+
write!(fmtr, "the character {} at index {} is not ASCII", self.not_ascii, self.index)
411+
}
412+
}
413+
}
414+
impl Error for AsAsciiStrError {
415+
fn description(&self) -> &'static str {
416+
if (self.not_ascii as u32) < 128 {
417+
"one or more bytes are not ASCII"
418+
} else {
419+
"one or more characters are not ASCII"
420+
}
421+
}
422+
}
423+
424+
425+
/// Trait for converting various slices into `AsciiStr`.
426+
pub trait AsAsciiStr : AsciiExt {
427+
/// Convert to `AsciiStr`, not doing any range asserts.
428+
unsafe fn as_ascii_unchecked(&self) -> &AsciiStr;
429+
/// Convert to `AsciiStr`.
430+
fn as_ascii(&self) -> Result<&AsciiStr,AsAsciiStrError>;
431+
}
432+
/// Trait for converting various slices into `AsciiStr`.
433+
pub trait AsMutAsciiStr : AsciiExt {
434+
/// Convert to `AsciiStr`, not doing any range asserts.
435+
unsafe fn as_mut_ascii_unchecked(&mut self) -> &mut AsciiStr;
436+
/// Convert to `AsciiStr`.
437+
fn as_mut_ascii(&mut self) -> Result<&mut AsciiStr,AsAsciiStrError>;
438+
}
439+
440+
#[cfg(feature = "unstable")]
441+
impl AsAsciiStr for AsciiStr {
442+
fn as_ascii(&self) -> Result<&AsciiStr,AsAsciiStrError> {
443+
Ok(self)
444+
}
445+
unsafe fn as_ascii_unchecked(&self) -> &AsciiStr {
446+
self
447+
}
448+
}
449+
#[cfg(feature = "unstable")]
450+
impl AsMutAsciiStr for AsciiStr {
451+
fn as_mut_ascii(&mut self) -> Result<&mut AsciiStr,AsAsciiStrError> {
452+
Ok(self)
453+
}
454+
unsafe fn as_mut_ascii_unchecked(&mut self) -> &mut AsciiStr {
455+
self
456+
}
457+
}
458+
459+
impl AsAsciiStr for [u8] {
460+
fn as_ascii(&self) -> Result<&AsciiStr,AsAsciiStrError> {
461+
match self.iter().enumerate().find(|&(_,b)| *b > 127 ) {
462+
Some((index, &byte)) => Err(AsAsciiStrError{
463+
index: index,
464+
not_ascii: (byte - 128) as char,
465+
}),
466+
None => unsafe{ Ok(self.as_ascii_unchecked()) },
467+
}
468+
}
469+
unsafe fn as_ascii_unchecked(&self) -> &AsciiStr {
470+
AsciiStr::from_bytes_unchecked(self)
471+
}
472+
}
473+
impl AsMutAsciiStr for [u8] {
474+
fn as_mut_ascii(&mut self) -> Result<&mut AsciiStr,AsAsciiStrError> {
475+
match self.iter().enumerate().find(|&(_,b)| *b > 127 ) {
476+
Some((index, &byte)) => Err(AsAsciiStrError{
477+
index: index,
478+
not_ascii: (byte - 128) as char,
479+
}),
480+
None => unsafe{ Ok(self.as_mut_ascii_unchecked()) },
481+
}
482+
}
483+
unsafe fn as_mut_ascii_unchecked(&mut self) -> &mut AsciiStr {
484+
mem::transmute(self)
485+
}
486+
}
487+
488+
impl AsAsciiStr for str {
489+
fn as_ascii(&self) -> Result<&AsciiStr,AsAsciiStrError> {
490+
self.as_bytes().as_ascii().map_err(|err| AsAsciiStrError{
491+
not_ascii: self[err.index..].chars().next().unwrap(),
492+
index: err.index,
493+
})
494+
}
495+
unsafe fn as_ascii_unchecked(&self) -> &AsciiStr {
496+
mem::transmute(self)
497+
}
498+
}
499+
impl AsMutAsciiStr for str {
500+
fn as_mut_ascii(&mut self) -> Result<&mut AsciiStr,AsAsciiStrError> {
501+
match self.bytes().position(|b| b > 127 ) {
502+
Some(index) => Err(AsAsciiStrError{
503+
index: index,
504+
not_ascii: self[index..].chars().next().unwrap(),
505+
}),
506+
None => unsafe{ Ok(self.as_mut_ascii_unchecked()) },
507+
}
508+
}
509+
unsafe fn as_mut_ascii_unchecked(&mut self) -> &mut AsciiStr {
510+
mem::transmute(self)
511+
}
512+
}
513+
514+
515+
344516
#[cfg(test)]
345517
mod tests {
346-
use AsciiCast;
347-
use super::AsciiStr;
518+
use {AsciiCast,Ascii};
519+
use super::{AsciiStr,AsAsciiStr,AsMutAsciiStr,AsAsciiStrError};
520+
521+
pub fn tuplify<T>(r: Result<T,AsAsciiStrError>) -> Result<T,(usize,char)> {
522+
r.map_err(|e| (e.index, e.not_ascii) )
523+
}
524+
525+
#[test]
526+
fn generic_as_ascii() {
527+
fn generic<C:AsAsciiStr+?Sized>(c: &C) -> Result<&AsciiStr,AsAsciiStrError> {
528+
c.as_ascii()
529+
}
530+
let arr = [Ascii::A];
531+
let ascii_str = arr.as_ref().into();
532+
assert_eq!(tuplify(generic("A")), Ok(ascii_str));
533+
assert_eq!(tuplify(generic(&b"A"[..])), Ok(ascii_str));
534+
//assert_eq!(generic(ascii_str), Ok(ascii_str));
535+
}
536+
#[test]
537+
fn as_ascii() {
538+
let mut s: String = "abčd".to_string();
539+
let mut b: Vec<u8> = s.clone().into();
540+
assert_eq!(tuplify(s.as_str().as_ascii()), Err((2,'č')));
541+
assert_eq!(tuplify(s.as_mut_str().as_mut_ascii()), Err((2,'č')));
542+
let c = (b[2]-128) as char;
543+
assert_eq!(tuplify(b.as_slice().as_ascii()), Err((2,c)));
544+
assert_eq!(tuplify(b.as_mut_slice().as_mut_ascii()), Err((2,c)));
545+
let mut a = [Ascii::a, Ascii::b];
546+
assert_eq!(tuplify((&s[..2]).as_ascii()), Ok((&a[..]).into()));
547+
assert_eq!(tuplify((&b[..2]).as_ascii()), Ok((&a[..]).into()));
548+
let a = Ok((&mut a[..]).into());
549+
assert_eq!(tuplify((&mut s[..2]).as_mut_ascii()), a);
550+
assert_eq!(tuplify((&mut b[..2]).as_mut_ascii()), a);
551+
}
348552

349553
#[test]
350554
fn as_str() {

src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use std::ascii::AsciiExt;
2121

2222
pub use ascii::{Ascii, IntoAscii, IntoAsciiError};
2323
pub use ascii_string::{AsciiString, IntoAsciiString};
24-
pub use ascii_str::AsciiStr;
24+
pub use ascii_str::{AsciiStr, AsAsciiStr, AsMutAsciiStr, AsAsciiStrError};
2525

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

0 commit comments

Comments
 (0)