Skip to content

Commit 5248fa7

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. Stops testing on 1.1.0 since 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 0b722d0 commit 5248fa7

File tree

3 files changed

+214
-11
lines changed

3 files changed

+214
-11
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
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

Lines changed: 212 additions & 9 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())
@@ -178,12 +194,6 @@ impl PartialOrd<AsciiString> for AsciiStr {
178194
}
179195
*/
180196

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

@@ -213,6 +223,11 @@ impl AsMut<[Ascii]> for AsciiStr {
213223
}
214224
}
215225

226+
impl Default for &'static AsciiStr {
227+
fn default() -> &'static AsciiStr {
228+
unsafe{ "".as_ascii_unchecked() }
229+
}
230+
}
216231
impl<'a> From<&'a[Ascii]> for &'a AsciiStr {
217232
fn from(slice: &[Ascii]) -> &AsciiStr {
218233
unsafe{ mem::transmute(slice) }
@@ -342,10 +357,198 @@ impl<'a> AsciiCast<'a> for str {
342357
}
343358
}
344359

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+
345516
#[cfg(test)]
346517
mod tests {
347-
use AsciiCast;
348-
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+
}
349552

350553
#[test]
351554
fn default() {

src/lib.rs

Lines changed: 1 addition & 1 deletion
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)