pub const STRING_NEWTYPE_MAX_BYTES: usize = 1024;
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum StringNewtypeError {
#[error("string newtype value cannot be empty")]
Empty,
#[error("string newtype value contains a NUL byte")]
Nul,
#[error("string newtype value contains a CR or LF byte")]
Newline,
#[error("string newtype value contains a non-tab control character")]
Control,
#[error("string newtype value exceeds {max} bytes (got {len})")]
#[non_exhaustive]
TooLong { len: usize, max: usize },
#[error("string newtype value contains a Unicode-tag codepoint (U+E0000..U+E007F)")]
UnicodeTag,
}
#[doc(hidden)]
pub fn __validate_string_newtype(value: &str) -> Result<(), StringNewtypeError> {
if value.is_empty() {
return Err(StringNewtypeError::Empty);
}
if value.len() > STRING_NEWTYPE_MAX_BYTES {
return Err(StringNewtypeError::TooLong {
len: value.len(),
max: STRING_NEWTYPE_MAX_BYTES,
});
}
for ch in value.chars() {
if ('\u{E0000}'..='\u{E007F}').contains(&ch) {
return Err(StringNewtypeError::UnicodeTag);
}
}
for byte in value.bytes() {
if byte == 0 {
return Err(StringNewtypeError::Nul);
}
if byte == b'\r' || byte == b'\n' {
return Err(StringNewtypeError::Newline);
}
if byte != b'\t' && byte.is_ascii_control() {
return Err(StringNewtypeError::Control);
}
}
Ok(())
}
#[macro_export]
macro_rules! string_newtype {
($(#[$meta:meta])* $name:ident) => {
$crate::__string_newtype_struct!($(#[$meta])* $name);
$crate::__string_newtype_impls!($name);
};
($(#[$meta:meta])* @unchecked $name:ident) => {
$crate::__string_newtype_struct!($(#[$meta])* $name);
$crate::__string_newtype_impls!($name);
$crate::__string_newtype_unchecked!($name);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __string_newtype_struct {
($(#[$meta:meta])* $name:ident) => {
$(#[$meta])*
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct $name(::std::string::String);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __string_newtype_impls {
($name:ident) => {
impl $name {
pub fn new(
value: impl ::std::convert::Into<::std::string::String>,
) -> ::std::result::Result<Self, $crate::StringNewtypeError> {
let value = value.into();
$crate::string_newtype::__validate_string_newtype(&value)?;
::std::result::Result::Ok(Self(value))
}
#[must_use]
pub fn as_str(&self) -> &str {
self.0.as_str()
}
#[must_use]
pub fn into_inner(self) -> ::std::string::String {
self.0
}
}
impl ::std::convert::AsRef<str> for $name {
fn as_ref(&self) -> &str {
self.0.as_str()
}
}
impl ::std::fmt::Display for $name {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
f.write_str(&self.0)
}
}
impl ::std::str::FromStr for $name {
type Err = $crate::StringNewtypeError;
fn from_str(s: &str) -> ::std::result::Result<Self, Self::Err> {
Self::new(s.to_owned())
}
}
impl ::std::convert::TryFrom<::std::string::String> for $name {
type Error = $crate::StringNewtypeError;
fn try_from(value: ::std::string::String) -> ::std::result::Result<Self, Self::Error> {
Self::new(value)
}
}
impl ::std::convert::TryFrom<&str> for $name {
type Error = $crate::StringNewtypeError;
fn try_from(value: &str) -> ::std::result::Result<Self, Self::Error> {
Self::new(value.to_owned())
}
}
impl ::std::convert::From<$name> for ::std::string::String {
fn from(value: $name) -> Self {
value.0
}
}
$crate::__string_newtype_serde_impls!($name);
$crate::__string_newtype_schemars_impls!($name);
};
}
#[cfg(feature = "serde")]
#[doc(hidden)]
#[macro_export]
macro_rules! __string_newtype_serde_impls {
($name:ident) => {
impl $crate::__macro_serde::Serialize for $name {
fn serialize<S>(
&self,
serializer: S,
) -> ::std::result::Result<S::Ok, S::Error>
where
S: $crate::__macro_serde::Serializer,
{
serializer.serialize_str(&self.0)
}
}
impl<'de> $crate::__macro_serde::Deserialize<'de> for $name {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: $crate::__macro_serde::Deserializer<'de>,
{
use $crate::__macro_serde::de::Error as _;
let value = <::std::string::String as $crate::__macro_serde::Deserialize<'de>>::deserialize(deserializer)?;
Self::new(value).map_err(D::Error::custom)
}
}
};
}
#[cfg(not(feature = "serde"))]
#[doc(hidden)]
#[macro_export]
macro_rules! __string_newtype_serde_impls {
($name:ident) => {};
}
#[cfg(feature = "schemars")]
#[doc(hidden)]
#[macro_export]
macro_rules! __string_newtype_schemars_impls {
($name:ident) => {
impl $crate::__macro_schemars::JsonSchema for $name {
fn inline_schema() -> bool {
true
}
fn schema_name() -> ::std::borrow::Cow<'static, str> {
stringify!($name).into()
}
fn schema_id() -> ::std::borrow::Cow<'static, str> {
concat!(module_path!(), "::", stringify!($name)).into()
}
fn json_schema(
_generator: &mut $crate::__macro_schemars::SchemaGenerator,
) -> $crate::__macro_schemars::Schema {
$crate::__macro_schemars::json_schema!({
"type": "string",
"minLength": 1,
"maxLength": $crate::STRING_NEWTYPE_MAX_BYTES,
})
}
}
};
}
#[cfg(not(feature = "schemars"))]
#[doc(hidden)]
#[macro_export]
macro_rules! __string_newtype_schemars_impls {
($name:ident) => {};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __string_newtype_unchecked {
($name:ident) => {
impl $name {
#[must_use]
pub fn new_unchecked(value: impl ::std::convert::Into<::std::string::String>) -> Self {
Self(value.into())
}
}
};
}