Skip to content

Commit a6c80f7

Browse files
committed
Add As(Mut)AsciiStr to replace AsciiCast<Target=AsciiStr>
The error type says where and why conversion failed. Two separate traits to follow std convention. Stops testing on 1.1.0 because the tests doesn't compile there: If we trust Rust to not introduce silent breaking changes, code that passes tests on stable should be correct on older versions as long as it compiles.
1 parent ee1e38c commit a6c80f7

File tree

3 files changed

+231
-11
lines changed

3 files changed

+231
-11
lines changed

.travis.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ before_script:
1515
script:
1616
- |
1717
travis-cargo build &&
18-
travis-cargo test &&
18+
travis-cargo --skip 1.1.0 test &&
1919
travis-cargo --only stable doc
2020
2121
after_success:

src/ascii_str.rs

+229-9
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,27 @@ 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 be represented as a byte slice to an `AsciiStr` without checking for non-ASCII characters..
89+
///
90+
/// # Examples
91+
/// ```
92+
/// # use ascii::AsciiStr;
93+
/// let foo = unsafe{ AsciiStr::from_bytes_unchecked("foo") };
94+
/// assert_eq!(foo.as_str(), "foo");
95+
/// ```
96+
pub unsafe fn from_bytes_unchecked<'a, B: ?Sized>(bytes: &'a B) -> &'a AsciiStr
97+
where B: AsRef<[u8]>
98+
{
99+
mem::transmute(bytes.as_ref())
100+
}
101+
87102
/// Converts a borrowed string to a borrows ascii string.
88103
pub fn from_str<'a>(s: &'a str) -> Result<&'a AsciiStr, ()> {
89104
AsciiStr::from_bytes(s.as_bytes())
@@ -178,12 +193,6 @@ impl PartialOrd<AsciiString> for AsciiStr {
178193
}
179194
*/
180195

181-
impl Default for &'static AsciiStr {
182-
fn default() -> &'static AsciiStr {
183-
unsafe { mem::transmute("") }
184-
}
185-
}
186-
187196
impl ToOwned for AsciiStr {
188197
type Owned = AsciiString;
189198

@@ -213,6 +222,11 @@ impl AsMut<[Ascii]> for AsciiStr {
213222
}
214223
}
215224

225+
impl Default for &'static AsciiStr {
226+
fn default() -> &'static AsciiStr {
227+
unsafe{ "".as_ascii_unchecked() }
228+
}
229+
}
216230
impl<'a> From<&'a[Ascii]> for &'a AsciiStr {
217231
fn from(slice: &[Ascii]) -> &AsciiStr {
218232
unsafe{ mem::transmute(slice) }
@@ -342,10 +356,216 @@ impl<'a> AsciiCast<'a> for str {
342356
}
343357
}
344358

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

350570
#[test]
351571
fn default() {

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)