from-string 1.0.0

A crate that provides a FromString trait with a String to String noop.
Documentation
//! A crate that provides a [`FromString`] trait that works similar to
//! [`FromStr`] but operates on owned strings.  It's implemented for all types
//! that implement [`FromStr`] with compatible errors and it has a fast-path
//! for the no-op string to string conversion.
//!
//! For convenience reasons it is constrained to conversions that return a
//! [`Box<dyn Error + Send + Sync>`](std::error::Error).
//!
//! ```
//! use from_string::from_string;
//!
//! let s: String = from_string("Hello World!").unwrap();
//! let i: u64 = from_string("42").unwrap();
//! ```
use std::any::TypeId;
use std::mem::{ManuallyDrop, transmute_copy};
use std::str::FromStr;

/// The error type for all conversions.
pub type Error = Box<dyn std::error::Error + Send + Sync + 'static>;

/// Parse a value from an owned string
///
/// [`FromString`] is implemented for all compatible [`FromStr`] types.  Those are
/// all types with an implementation that fails with an error that can be converted
/// into a `Box<dyn Error + Send + Sync>`.
///
/// This trait cannot be be implemented for custom types because of the blanket
/// implementation.  A more ergonomic way to call this is the [`from_string`]
/// function.
pub trait FromString: Sized {
    /// Parses a string `s`` to return a value of this type.
    fn from_string(s: String) -> Result<Self, Error>;
}

impl<T> FromString for T
where
    T: FromStr<Err: Into<Error>> + 'static,
{
    fn from_string(s: String) -> Result<Self, Error> {
        if TypeId::of::<T>() == TypeId::of::<String>() {
            Ok(unsafe { transmute_copy(&ManuallyDrop::new(s)) })
        } else {
            T::from_str(&s).map_err(Into::into)
        }
    }
}

/// Utility function to call [`FromString::from_string`].
///
/// ```
/// # use from_string::from_string;
/// let s: String = from_string("Hello World").unwrap();
/// let i: i64 = from_string("42").unwrap();
/// ```
pub fn from_string<T, S>(s: S) -> Result<T, Error>
where
    T: FromString,
    S: Into<String>,
{
    FromString::from_string(s.into())
}

#[test]
fn test_conversion() {
    let s: String = from_string("Hello World").unwrap();
    let i: i64 = from_string("42").unwrap();
    assert_eq!(s, "Hello World");
    assert_eq!(i, 42);
}

#[test]
fn test_failed_conversion() {
    let err = from_string::<i64, _>("42aaaa").unwrap_err();
    assert_eq!(err.to_string(), "invalid digit found in string");

    #[derive(Debug)]
    struct X;

    impl FromStr for X {
        type Err = String;

        fn from_str(_: &str) -> Result<Self, Self::Err> {
            Err("i hate this".into())
        }
    }

    let err = from_string::<X, _>("42aaaa").unwrap_err();
    assert_eq!(err.to_string(), "i hate this");
}