Skip to content

Commit b0c6b93

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. Stop testing on 1.1.0 because the tests doesn't compile there. If we trust Rust to not silently break things, code that passes tests on stable should be correct on older versions as long as it compiles.
1 parent 42a5324 commit b0c6b93

File tree

3 files changed

+219
-11
lines changed

3 files changed

+219
-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

+217-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 represent a byte slice into an `AsciiStr` without validation.
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,204 @@ 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
365+
not_ascii: char,
366+
}
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+
374+
/// Get the byte that caused the error.
375+
///
376+
/// If a str 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+
// char::encode_utf8() is unstable
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 char that caused conversions 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 [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+
/// Trait for converting various slices into `AsciiStr`.
431+
pub trait AsAsciiStr : AsciiExt {
432+
/// Convert to `AsciiStr`, not doing any range asserts.
433+
unsafe fn as_ascii_unchecked(&self) -> &AsciiStr;
434+
/// Convert to `AsciiStr`.
435+
fn as_ascii(&self) -> Result<&AsciiStr,AsAsciiStrError>;
436+
}
437+
/// Trait for converting various slices into `AsciiStr`.
438+
pub trait AsMutAsciiStr : AsciiExt {
439+
/// Convert to `AsciiStr`, not doing any range asserts.
440+
unsafe fn as_mut_ascii_unchecked(&mut self) -> &mut AsciiStr;
441+
/// Convert to `AsciiStr`.
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+
impl AsAsciiStr for [u8] {
465+
fn as_ascii(&self) -> Result<&AsciiStr,AsAsciiStrError> {
466+
match self.iter().enumerate().find(|&(_,b)| *b > 127 ) {
467+
Some((index, &byte)) => Err(AsAsciiStrError{
468+
index: index,
469+
not_ascii: (byte - 128) as char,
470+
}),
471+
None => unsafe{ Ok(self.as_ascii_unchecked()) },
472+
}
473+
}
474+
unsafe fn as_ascii_unchecked(&self) -> &AsciiStr {
475+
AsciiStr::from_bytes_unchecked(self)
476+
}
477+
}
478+
impl AsMutAsciiStr for [u8] {
479+
fn as_mut_ascii(&mut self) -> Result<&mut AsciiStr,AsAsciiStrError> {
480+
match self.iter().enumerate().find(|&(_,b)| *b > 127 ) {
481+
Some((index, &byte)) => Err(AsAsciiStrError{
482+
index: index,
483+
not_ascii: (byte - 128) as char,
484+
}),
485+
None => unsafe{ Ok(self.as_mut_ascii_unchecked()) },
486+
}
487+
}
488+
unsafe fn as_mut_ascii_unchecked(&mut self) -> &mut AsciiStr {
489+
mem::transmute(self)
490+
}
491+
}
492+
493+
impl AsAsciiStr for str {
494+
fn as_ascii(&self) -> Result<&AsciiStr,AsAsciiStrError> {
495+
self.as_bytes().as_ascii().map_err(|err| AsAsciiStrError{
496+
not_ascii: self[err.index..].chars().next().unwrap(),
497+
index: err.index,
498+
})
499+
}
500+
unsafe fn as_ascii_unchecked(&self) -> &AsciiStr {
501+
mem::transmute(self)
502+
}
503+
}
504+
impl AsMutAsciiStr for str {
505+
fn as_mut_ascii(&mut self) -> Result<&mut AsciiStr,AsAsciiStrError> {
506+
match self.bytes().position(|b| b > 127 ) {
507+
Some(index) => Err(AsAsciiStrError{
508+
index: index,
509+
not_ascii: self[index..].chars().next().unwrap(),
510+
}),
511+
None => unsafe{ Ok(self.as_mut_ascii_unchecked()) },
512+
}
513+
}
514+
unsafe fn as_mut_ascii_unchecked(&mut self) -> &mut AsciiStr {
515+
mem::transmute(self)
516+
}
517+
}
518+
519+
345520
#[cfg(test)]
346521
mod tests {
347-
use AsciiCast;
348-
use super::AsciiStr;
522+
use {AsciiCast,Ascii};
523+
use super::{AsciiStr,AsAsciiStr,AsMutAsciiStr,AsAsciiStrError};
524+
525+
pub fn tuplify<T>(r: Result<T,AsAsciiStrError>) -> Result<T,(usize,char)> {
526+
r.map_err(|e| (e.index, e.not_ascii) )
527+
}
528+
529+
#[test]
530+
fn generic_as_ascii() {
531+
fn generic<C:AsAsciiStr+?Sized>(c: &C) -> Result<&AsciiStr,AsAsciiStrError> {
532+
c.as_ascii()
533+
}
534+
let arr = [Ascii::A];
535+
let ascii_str = arr.as_ref().into();
536+
assert_eq!(tuplify(generic("A")), Ok(ascii_str));
537+
assert_eq!(tuplify(generic(&b"A"[..])), Ok(ascii_str));
538+
//assert_eq!(generic(ascii_str), Ok(ascii_str));
539+
}
540+
541+
#[test]
542+
fn as_ascii() {
543+
let mut s: String = "abčd".to_string();
544+
let mut b: Vec<u8> = s.clone().into();
545+
assert_eq!(tuplify(s.as_str().as_ascii()), Err((2,'č')));
546+
assert_eq!(tuplify(s.as_mut_str().as_mut_ascii()), Err((2,'č')));
547+
let c = (b[2]-128) as char;
548+
assert_eq!(tuplify(b.as_slice().as_ascii()), Err((2,c)));
549+
assert_eq!(tuplify(b.as_mut_slice().as_mut_ascii()), Err((2,c)));
550+
let mut a = [Ascii::a, Ascii::b];
551+
assert_eq!(tuplify((&s[..2]).as_ascii()), Ok((&a[..]).into()));
552+
assert_eq!(tuplify((&b[..2]).as_ascii()), Ok((&a[..]).into()));
553+
let a = Ok((&mut a[..]).into());
554+
assert_eq!(tuplify((&mut s[..2]).as_mut_ascii()), a);
555+
assert_eq!(tuplify((&mut b[..2]).as_mut_ascii()), a);
556+
}
349557

350558
#[test]
351559
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)