estring/src/core.rs

329 lines
7.7 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Contains the ``EString`` type, as well as the basic implementation of conversions to
//! string types
//!
/// Format this type and wrap into ``EString``.
///
/// ``ToEString``s `to_estring` method is often used implicitly, through ``EString``s from.
///
/// # Examples
///
/// Basic implementation of ``ToEString`` on an example ``Point``.
///
/// ```rust
/// use std::fmt::Write;
/// use estring::{EString, ToEString};
///
/// #[derive(Debug, PartialEq)]
/// struct Point {
/// x: i32,
/// y: i32,
/// }
///
/// impl ToEString for Point {
/// fn to_estring(&self) -> EString {
/// let mut res = String::new();
/// write!(res, "({},{})", self.x, self.y)
/// .ok()
/// .expect("Cannot format Point into EString");
/// EString(res)
/// }
/// }
///
/// let point = Point { x: 1, y: 2 };
/// assert_eq!(point.to_estring(), EString::from("(1,2)"));
/// ```
///
pub trait ToEString {
/// Format this type and returns ``EString``.
///
/// # Examples
///
/// ```rust
/// use estring::{EString, ToEString};
///
/// let i = 5;
/// let five = EString::from(5);
/// assert_eq!(five, i.to_estring());
/// ```
fn to_estring(&self) -> EString;
}
/// Parse a value fragment from a ``EString``.
///
/// ``ParseFragment``s `parse_frag` method is often used implicitly, through ``EString``s parse.
/// See [parse](EString::parse)s documentation for examples.
///
/// # Examples
///
/// Basic implementation of ``ParseFragment`` on an example ``Point``.
///
/// ```rust
/// use estring::{EString, ParseFragment, Reason};
///
/// #[derive(Debug, PartialEq)]
/// struct Point {
/// x: i32,
/// y: i32,
/// }
///
/// impl ParseFragment for Point {
/// fn parse_frag(es: EString) -> estring::Result<Self> {
/// let orig = es.clone();
/// let (x, y) = es
/// .trim_matches(|p| p == '(' || p == ')')
/// .split_once(',')
/// .ok_or(estring::Error(orig, Reason::Split))?;
///
/// let (x, y) = (EString::from(x), EString::from(y));
/// let x = x.clone().parse::<i32>()
/// .map_err(|_| estring::Error(x, Reason::Parse))?;
/// let y = y.clone().parse::<i32>()
/// .map_err(|_| estring::Error(y, Reason::Parse))?;
///
/// Ok(Point { x, y })
/// }
/// }
///
/// let fragment = EString::from("(1,2)");
/// let res = Point::parse_frag(fragment).unwrap();
/// assert_eq!(res, Point { x: 1, y: 2 })
/// ```
///
pub trait ParseFragment: Sized {
/// Parses a ``EString`` fragment `es` to return a value of this type.
///
/// # Errors
///
/// If parsing is not succeeds, returns ``Error`` inside ``Err`` with original fragment `es`
/// and reason ``Reason``.
///
/// # Examples
///
/// ```rust
/// use estring::{EString, ParseFragment};
///
/// let fragment = EString::from("5");
/// let res = i32::parse_frag(fragment).unwrap();
/// assert_eq!(res, 5);
/// ```
fn parse_frag(es: EString) -> crate::Result<Self>;
}
// TODO: add example
/// Trait to represent structures that can act as aggregators.
///
/// The `agg` method works with data that has already been parsed. For this reason, **this trait
/// should never fail**.
pub trait Aggregate {
/// The resulting type after aggregation.
type Target: ?Sized;
/// Aggregates the value.
fn agg(self) -> Self::Target;
}
// TODO: add example
/// Trait to represent structures that can iterate values for the aggregator.
pub trait Aggregatable {
/// The type of the elements being iterated over.
type Item;
/// Returns Vec of aggregatable values
fn items(self) -> Vec<Self::Item>;
}
/// Wrapper under ``String`` type.
///
/// # Examples
///
/// You can create a ``EString`` from a any type that implement ``ToEString`` with ``EString::from``
///
/// ```rust
/// # use estring::EString;
/// let hello = EString::from("Hello, world");
/// let num = EString::from("999");
/// ```
///
/// You can use ``ToEString::to_estring`` directly on the type.
///
/// ```rust
/// # use estring::ToEString;
/// let some_opt = Some(999).to_estring();
/// let none_opt = None::<i32>.to_estring();
/// ```
///
#[derive(Debug, Default, PartialEq, Eq, Clone)]
pub struct EString(pub String);
impl std::fmt::Display for EString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl EString {
/// Creates a new empty ``EString``.
///
/// This will not allocate any inital buffer.
///
/// # Examples
///
/// Basic usage:
///
/// ```rust
/// # use estring::EString;
/// let s = EString::new();
/// ```
#[must_use]
#[inline]
pub fn new() -> Self {
Self(String::new())
}
/// Parses this inner string into another type.
///
/// `parse` can parse into any type that implements the ``ParseFragment`` trait.
///
/// # Errors
///
/// Will return `Err` if estring cannot parse inner fragment into the desired type.
///
/// # Examples
///
/// Basic usage
///
/// ```rust
/// # use estring::{EString, ParseFragment};
/// let fragment = EString::from("5");
/// let res = i32::parse_frag(fragment);
/// assert_eq!(res, Ok(5));
/// ```
///
/// Failing to parse:
///
/// ```rust
/// # use estring::{EString, ParseFragment, Error, Reason};
/// let fragment = EString::from("j");
/// let res = i32::parse_frag(fragment.clone());
/// assert_eq!(res, Err(Error(fragment, Reason::Parse)));
/// ```
#[inline]
pub fn parse<T: ParseFragment>(self) -> crate::Result<T> {
T::parse_frag(self)
}
}
impl<T> From<T> for EString
where
T: ToEString,
{
#[inline]
fn from(val: T) -> Self {
val.to_estring()
}
}
impl std::ops::Deref for EString {
type Target = String;
#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl ParseFragment for EString {
#[inline]
fn parse_frag(es: EString) -> crate::Result<Self> {
Ok(es)
}
}
#[cfg(feature = "aggs")]
impl Aggregatable for EString {
type Item = Self;
#[inline]
fn items(self) -> Vec<Self::Item> {
vec![self]
}
}
impl ParseFragment for String {
#[inline]
fn parse_frag(es: EString) -> crate::Result<Self> {
Ok(es.0)
}
}
impl ToEString for String {
#[inline]
fn to_estring(&self) -> EString {
EString(self.clone())
}
}
#[cfg(feature = "aggs")]
impl Aggregatable for String {
type Item = Self;
#[inline]
fn items(self) -> Vec<Self::Item> {
vec![self]
}
}
impl ParseFragment for &'static str {
#[inline]
fn parse_frag(es: EString) -> crate::Result<Self> {
Ok(Box::leak(es.0.into_boxed_str()))
}
}
impl<'a> ToEString for &'a str {
#[inline]
fn to_estring(&self) -> EString {
EString((*self).to_string())
}
}
#[cfg(feature = "aggs")]
impl<'a> Aggregatable for &'a str {
type Item = Self;
#[inline]
fn items(self) -> Vec<Self::Item> {
vec![self]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_deref_to_string() {
let estr = EString::from("hello");
assert_eq!(*estr, String::from("hello"));
}
#[test]
fn should_parse_into_itself() {
let estr = EString::from("hello");
match estr.parse::<EString>() {
Ok(res) => assert_eq!(res, EString::from("hello")),
_ => unreachable!(),
}
}
#[test]
fn should_parse_into_string() {
let estr = EString::from("hello");
match estr.parse::<String>() {
Ok(res) => assert_eq!(res, String::from("hello")),
_ => unreachable!(),
}
}
}