|
|
|
use super::*;
|
|
|
|
use std::marker::PhantomData;
|
|
|
|
use std::{fmt,error};
|
|
|
|
use std::ops::Deref;
|
|
|
|
use std::borrow::{Borrow, ToOwned};
|
|
|
|
|
|
|
|
/// A spec to validate the formatting of a string for.
|
|
|
|
pub trait FormatSpec
|
|
|
|
{
|
|
|
|
type Error: Into<eyre::Report>;
|
|
|
|
fn validate(s: &str) -> Result<(), Self::Error>;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// A strongly validated string slice
|
|
|
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
|
|
#[repr(transparent)]
|
|
|
|
pub struct FormattedStr<F: FormatSpec + ?Sized>(PhantomData<*const F>, str);
|
|
|
|
|
|
|
|
impl<F: FormatSpec + ?Sized> std::marker::Unpin for FormattedStr<F>{}
|
|
|
|
unsafe impl<F: FormatSpec + ?Sized> std::marker::Send for FormattedStr<F>{}
|
|
|
|
unsafe impl<F: FormatSpec + ?Sized> std::marker::Sync for FormattedStr<F>{}
|
|
|
|
|
|
|
|
/// A strongly validated string
|
|
|
|
// TODO: How to perform validation on deserialise? Custom `Deserialize` impl? might have to.
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
|
|
pub struct FormattedString<F: FormatSpec + ?Sized>(String, PhantomData<F>);
|
|
|
|
|
|
|
|
// Deserialising
|
|
|
|
const _:() = {
|
|
|
|
use serde::{
|
|
|
|
de::Visitor,
|
|
|
|
de::Error,
|
|
|
|
de,
|
|
|
|
Serialize,
|
|
|
|
ser::Serializer,
|
|
|
|
};
|
|
|
|
|
|
|
|
impl<F: FormatSpec + ?Sized> Serialize for FormattedStr<F>
|
|
|
|
{
|
|
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
|
|
where
|
|
|
|
S: Serializer,
|
|
|
|
{
|
|
|
|
serializer.serialize_str(&self.1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
impl<F: FormatSpec + ?Sized> Serialize for FormattedString<F>
|
|
|
|
{
|
|
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
|
|
where
|
|
|
|
S: Serializer,
|
|
|
|
{
|
|
|
|
serializer.serialize_str(&self.0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
struct FormattedStringVisitor<F: FormatSpec + ?Sized>(PhantomData<F>);
|
|
|
|
|
|
|
|
impl<'de, F: FormatSpec + ?Sized> Visitor<'de> for FormattedStringVisitor<F>
|
|
|
|
{
|
|
|
|
type Value = FormattedString<F>;
|
|
|
|
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
|
|
|
{
|
|
|
|
write!(f, "a string satisfying the requirements of {}", std::any::type_name::<F>())
|
|
|
|
}
|
|
|
|
fn visit_str<E: Error>(self, s: &str) -> Result<Self::Value, E>
|
|
|
|
{
|
|
|
|
FormattedStr::<F>::new(s).map_err(|x| E::custom(x.into())).map(ToOwned::to_owned)
|
|
|
|
}
|
|
|
|
fn visit_string<E: Error>(self, s: String) -> Result<Self::Value, E>
|
|
|
|
{
|
|
|
|
FormattedString::<F>::new(s).map_err(|x| E::custom(x.into()))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'de, F: FormatSpec + ?Sized> de::Deserialize<'de> for FormattedString<F> {
|
|
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
|
|
where
|
|
|
|
D: de::Deserializer<'de>,
|
|
|
|
{
|
|
|
|
let vis = FormattedStringVisitor::<F>(PhantomData);
|
|
|
|
deserializer.deserialize_string(vis)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
impl<F: FormatSpec + ?Sized> FormattedStr<F>
|
|
|
|
{
|
|
|
|
/// Create a new instance without validating the input.
|
|
|
|
///
|
|
|
|
/// # Safety
|
|
|
|
/// You must be sure the input is of a valid state to `F`.
|
|
|
|
#[inline(always)] pub unsafe fn new_unchecked<'a>(s: &'a str) -> &'a Self
|
|
|
|
{
|
|
|
|
std::mem::transmute(s)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Create and validate a the format of a new instance.
|
|
|
|
pub fn new<'a>(s: &'a str) -> Result<&'a Self, F::Error>
|
|
|
|
{
|
|
|
|
F::validate(s).map(move |_| unsafe {Self::new_unchecked(s)})
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the inner str
|
|
|
|
#[inline] pub fn as_str<'a>(&'a self) -> &'a str
|
|
|
|
{
|
|
|
|
&self.1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<F: FormatSpec + ?Sized> FormattedString<F>
|
|
|
|
{
|
|
|
|
/// Create a new instance without validating the input.
|
|
|
|
///
|
|
|
|
/// # Safety
|
|
|
|
/// You must be sure the input is of a valid state to `F`.
|
|
|
|
#[inline(always)] pub unsafe fn new_unchecked(s: String) -> Self
|
|
|
|
{
|
|
|
|
std::mem::transmute(s)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Create and validate a the format of a new instance.
|
|
|
|
pub fn new(s: String) -> Result<Self, F::Error>
|
|
|
|
{
|
|
|
|
F::validate(&s)
|
|
|
|
.map(move |_| unsafe {Self::new_unchecked(s)})
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<F: FormatSpec + ?Sized> FormattedString<F>
|
|
|
|
{
|
|
|
|
/// As a formatted str
|
|
|
|
#[inline] pub fn as_ref<'a>(&'a self) -> &'a FormattedStr<F>
|
|
|
|
{
|
|
|
|
unsafe { FormattedStr::new_unchecked(&self.0) }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<F: FormatSpec + ?Sized> AsRef<str> for FormattedStr<F>
|
|
|
|
{
|
|
|
|
#[inline] fn as_ref(&self) -> &str
|
|
|
|
{
|
|
|
|
self.as_str()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<F: FormatSpec + ?Sized> Borrow<FormattedStr<F>> for FormattedString<F>
|
|
|
|
{
|
|
|
|
#[inline] fn borrow(&self) -> &FormattedStr<F> {
|
|
|
|
self.as_ref()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<F: FormatSpec + ?Sized> ToOwned for FormattedStr<F>
|
|
|
|
{
|
|
|
|
type Owned = FormattedString<F>;
|
|
|
|
#[inline] fn to_owned(&self) -> Self::Owned {
|
|
|
|
FormattedString(String::from(&self.1), PhantomData)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<F: FormatSpec + ?Sized> AsRef<FormattedStr<F>> for FormattedString<F>
|
|
|
|
{
|
|
|
|
#[inline] fn as_ref(&self) -> &FormattedStr<F> {
|
|
|
|
self.as_ref()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<F: FormatSpec + ?Sized> Deref for FormattedString<F>
|
|
|
|
{
|
|
|
|
type Target = FormattedStr<F>;
|
|
|
|
#[inline] fn deref(&self) -> &Self::Target {
|
|
|
|
self.as_ref()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a,F: FormatSpec + ?Sized> From<&'a FormattedStr<F>> for &'a str
|
|
|
|
{
|
|
|
|
#[inline] fn from(from: &'a FormattedStr<F>) -> Self
|
|
|
|
{
|
|
|
|
&from.1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
impl<'a,F: FormatSpec + ?Sized> TryFrom<&'a str> for &'a FormattedStr<F>
|
|
|
|
{
|
|
|
|
type Error = F::Error;
|
|
|
|
|
|
|
|
#[inline] fn try_from(from: &'a str) -> Result<Self, Self::Error>
|
|
|
|
{
|
|
|
|
FormattedStr::new(from)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, F: FormatSpec + ?Sized> From<FormattedString<F>> for String
|
|
|
|
{
|
|
|
|
#[inline] fn from(from: FormattedString<F>) -> Self
|
|
|
|
{
|
|
|
|
from.0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, F: FormatSpec + ?Sized> TryFrom<String> for FormattedString<F>
|
|
|
|
{
|
|
|
|
type Error = F::Error;
|
|
|
|
|
|
|
|
fn try_from(from: String) -> Result<Self, Self::Error>
|
|
|
|
{
|
|
|
|
Self::new(from)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub mod formats
|
|
|
|
{
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
cfg_if! {
|
|
|
|
if #[cfg(feature="nightly")] {
|
|
|
|
/// A format valid for any string
|
|
|
|
pub type AnyFormat = !;
|
|
|
|
} else {
|
|
|
|
/// A format valid for any string
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
|
|
pub enum AnyFormat{}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FormatSpec for AnyFormat
|
|
|
|
{
|
|
|
|
type Error = std::convert::Infallible;
|
|
|
|
|
|
|
|
#[inline] fn validate(_: &str) -> Result<(), Self::Error>
|
|
|
|
{
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A format spec that must satisfy both these format specs in order
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
|
|
pub struct BothFormat<T, U = AnyFormat>(PhantomData<(T, U)>)
|
|
|
|
where T: FormatSpec, U: FormatSpec;
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum MultiFormatError<T,U>
|
|
|
|
{
|
|
|
|
First(T),
|
|
|
|
Second(U),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T, U> FormatSpec for BothFormat<T,U>
|
|
|
|
where T: FormatSpec,
|
|
|
|
U: FormatSpec,
|
|
|
|
T::Error : error::Error + 'static + Send + Sync,
|
|
|
|
U::Error : error::Error + 'static + Send + Sync,
|
|
|
|
{
|
|
|
|
type Error = MultiFormatError<T::Error, U::Error>;
|
|
|
|
|
|
|
|
fn validate(s: &str) -> Result<(), Self::Error> {
|
|
|
|
T::validate(s).map_err(MultiFormatError::First)?;
|
|
|
|
U::validate(s).map_err(MultiFormatError::Second)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T: 'static, U: 'static> error::Error for MultiFormatError<T,U>
|
|
|
|
where T: error::Error,
|
|
|
|
U: error::Error
|
|
|
|
{
|
|
|
|
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
|
|
|
match &self {
|
|
|
|
Self::First(f) => Some(f),
|
|
|
|
Self::Second(n) => Some(n),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
impl<T, U> fmt::Display for MultiFormatError<T,U>
|
|
|
|
where T: fmt::Display,
|
|
|
|
U: fmt::Display
|
|
|
|
{
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
|
|
|
{
|
|
|
|
match self {
|
|
|
|
Self::First(_) => write!(f, "the first condition failed"),
|
|
|
|
Self::Second(_) => write!(f, "the second condition failed"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// A hex string format specifier
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
|
|
pub enum HexFormat{}
|
|
|
|
/// A string with a constant max length
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
|
|
pub enum MaxLenFormat<const MAX: usize>{}
|
|
|
|
|
|
|
|
impl<const MAX: usize> FormatSpec for MaxLenFormat<MAX>
|
|
|
|
{
|
|
|
|
type Error = MaxLenExceededError<MAX>;
|
|
|
|
|
|
|
|
#[inline] fn validate(s: &str) -> Result<(), Self::Error> {
|
|
|
|
if s.len() > MAX {
|
|
|
|
Err(MaxLenExceededError)
|
|
|
|
} else {
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct MaxLenExceededError<const MAX: usize>;
|
|
|
|
|
|
|
|
impl<const MAX: usize> error::Error for MaxLenExceededError<MAX>{}
|
|
|
|
impl<const MAX: usize> fmt::Display for MaxLenExceededError<MAX>
|
|
|
|
{
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
|
|
|
{
|
|
|
|
write!(f, "string length exceeds {}", MAX)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl HexFormat
|
|
|
|
{
|
|
|
|
const HEX_MAP: &'static [u8] = b"1234567890abcdefABCDEF";
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FormatSpec for HexFormat
|
|
|
|
{
|
|
|
|
type Error = HexFormatError;
|
|
|
|
fn validate(s: &str) -> Result<(), Self::Error> {
|
|
|
|
|
|
|
|
for (i, chr) in s.char_indices()
|
|
|
|
{
|
|
|
|
if !chr.is_ascii_alphanumeric() || !Self::HEX_MAP.contains(&(chr as u8)) {
|
|
|
|
return Err(HexFormatError(chr, i));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Error for an invalid hex string.
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct HexFormatError(char, usize);
|
|
|
|
|
|
|
|
impl error::Error for HexFormatError{}
|
|
|
|
impl fmt::Display for HexFormatError
|
|
|
|
{
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
|
|
|
{
|
|
|
|
write!(f, "invalid hex char {:?} at index {}", self.0, self.1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// A PEM formatted string.
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
|
|
pub enum PEMFormat{}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct PEMFormatError;
|
|
|
|
|
|
|
|
impl error::Error for PEMFormatError{}
|
|
|
|
impl fmt::Display for PEMFormatError
|
|
|
|
{
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
|
|
|
{
|
|
|
|
write!(f, "invalid PEM format")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FormatSpec for PEMFormat
|
|
|
|
{
|
|
|
|
type Error = PEMFormatError;
|
|
|
|
fn validate(_s: &str) -> Result<(), Self::Error> {
|
|
|
|
todo!("Learn the PEM ciphertext format");
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub type MaxLenStr<const MAX: usize> = FormattedStr<MaxLenFormat<MAX>>;
|
|
|
|
pub type MaxLenString<const MAX: usize> = FormattedString<MaxLenFormat<MAX>>;
|
|
|
|
|
|
|
|
pub type HexFormattedStr = FormattedStr<HexFormat>;
|
|
|
|
pub type HexFormattedString = FormattedString<HexFormat>;
|
|
|
|
|
|
|
|
pub type PEMFormattedStr = FormattedStr<PEMFormat>;
|
|
|
|
pub type PEMFormattedString = FormattedString<PEMFormat>;
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests
|
|
|
|
{
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
|
|
fn hex_format()
|
|
|
|
{
|
|
|
|
let _invalid = HexFormattedStr::new("ab120982039840i ").expect_err("Invalidation");
|
|
|
|
let _valid = HexFormattedStr::new("abc123982095830495adcfDD").expect("Validation");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|