use serde::{Deserialize, Deserializer, Serialize};
use smol_str::SmolStr;
use crate::{parser::err::ParseErrors, FromNormalizedStr};
#[derive(Serialize, Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord)]
pub struct Id(SmolStr);
impl Id {
pub(crate) fn new_unchecked(s: impl Into<SmolStr>) -> Id {
Id(s.into())
}
pub fn into_smolstr(self) -> SmolStr {
self.0
}
}
impl AsRef<str> for Id {
fn as_ref(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for Id {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", &self.0)
}
}
impl std::str::FromStr for Id {
type Err = ParseErrors;
fn from_str(s: &str) -> Result<Self, Self::Err> {
crate::parser::parse_ident(s)
}
}
impl FromNormalizedStr for Id {
fn describe_self() -> &'static str {
"Id"
}
}
struct IdVisitor;
impl<'de> serde::de::Visitor<'de> for IdVisitor {
type Value = Id;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("a valid id")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Id::from_normalized_str(value)
.map_err(|err| serde::de::Error::custom(format!("invalid id `{value}`: {err}")))
}
}
impl<'de> Deserialize<'de> for Id {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(IdVisitor)
}
}
#[cfg(feature = "arbitrary")]
impl<'a> arbitrary::Arbitrary<'a> for Id {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let construct_list = |s: &str| s.chars().collect::<Vec<char>>();
let list_concat = |s1: &[char], s2: &[char]| [s1, s2].concat();
let head_letters = construct_list("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_");
let tail_letters = list_concat(&construct_list("0123456789"), &head_letters);
let remaining_length = u.int_in_range(0..=16)?;
let mut cs = vec![*u.choose(&head_letters)?];
cs.extend(
(0..remaining_length)
.map(|_| u.choose(&tail_letters))
.collect::<Result<Vec<&char>, _>>()?,
);
let mut s: String = cs.into_iter().collect();
if crate::parser::parse_ident(&s).is_err() {
s.push('_');
}
Ok(Self::new_unchecked(s))
}
fn size_hint(depth: usize) -> (usize, Option<usize>) {
arbitrary::size_hint::and_all(&[
<usize as arbitrary::Arbitrary>::size_hint(depth),
<Vec<u8> as arbitrary::Arbitrary>::size_hint(depth),
])
}
}
#[derive(Serialize, Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord)]
pub struct AnyId(SmolStr);
impl AnyId {
pub(crate) fn new_unchecked(s: impl Into<SmolStr>) -> AnyId {
AnyId(s.into())
}
pub fn into_smolstr(self) -> SmolStr {
self.0
}
}
struct AnyIdVisitor;
impl<'de> serde::de::Visitor<'de> for AnyIdVisitor {
type Value = AnyId;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("any id")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
AnyId::from_normalized_str(value)
.map_err(|err| serde::de::Error::custom(format!("invalid id `{value}`: {err}")))
}
}
impl<'de> Deserialize<'de> for AnyId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(AnyIdVisitor)
}
}
impl AsRef<str> for AnyId {
fn as_ref(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for AnyId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", &self.0)
}
}
impl std::str::FromStr for AnyId {
type Err = ParseErrors;
fn from_str(s: &str) -> Result<Self, Self::Err> {
crate::parser::parse_anyid(s)
}
}
impl FromNormalizedStr for AnyId {
fn describe_self() -> &'static str {
"AnyId"
}
}
#[cfg(feature = "arbitrary")]
impl<'a> arbitrary::Arbitrary<'a> for AnyId {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let construct_list = |s: &str| s.chars().collect::<Vec<char>>();
let list_concat = |s1: &[char], s2: &[char]| [s1, s2].concat();
let head_letters = construct_list("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_");
let tail_letters = list_concat(&construct_list("0123456789"), &head_letters);
let remaining_length = u.int_in_range(0..=16)?;
let mut cs = vec![*u.choose(&head_letters)?];
cs.extend(
(0..remaining_length)
.map(|_| u.choose(&tail_letters))
.collect::<Result<Vec<&char>, _>>()?,
);
let s: String = cs.into_iter().collect();
debug_assert!(
crate::parser::parse_anyid(&s).is_ok(),
"all strings constructed this way should be valid AnyIds, but this one is not: {s:?}"
);
Ok(Self::new_unchecked(s))
}
fn size_hint(depth: usize) -> (usize, Option<usize>) {
arbitrary::size_hint::and_all(&[
<usize as arbitrary::Arbitrary>::size_hint(depth),
<Vec<u8> as arbitrary::Arbitrary>::size_hint(depth),
])
}
}
#[allow(clippy::panic)]
#[cfg(test)]
mod test {
use super::*;
#[test]
fn normalized_id() {
Id::from_normalized_str("foo").expect("should be OK");
Id::from_normalized_str("foo::bar").expect_err("shouldn't be OK");
Id::from_normalized_str(r#"foo::"bar""#).expect_err("shouldn't be OK");
Id::from_normalized_str(" foo").expect_err("shouldn't be OK");
Id::from_normalized_str("foo ").expect_err("shouldn't be OK");
Id::from_normalized_str("foo\n").expect_err("shouldn't be OK");
Id::from_normalized_str("foo//comment").expect_err("shouldn't be OK");
}
}