cedar_policy_core/ast/
id.rsuse serde::{Deserialize, Deserializer, Serialize};
use smol_str::SmolStr;
use crate::{parser::err::ParseErrors, FromNormalizedStr};
use super::{InternalName, ReservedNameError};
const RESERVED_ID: &str = "__cedar";
#[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
}
pub fn is_reserved(&self) -> bool {
self.as_ref() == RESERVED_ID
}
}
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"
}
}
#[derive(Serialize, Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
pub struct UnreservedId(#[cfg_attr(feature = "wasm", tsify(type = "string"))] pub(crate) Id);
impl From<UnreservedId> for Id {
fn from(value: UnreservedId) -> Self {
value.0
}
}
impl TryFrom<Id> for UnreservedId {
type Error = ReservedNameError;
fn try_from(value: Id) -> Result<Self, Self::Error> {
if value.is_reserved() {
Err(ReservedNameError(InternalName::unqualified_name(value)))
} else {
Ok(Self(value))
}
}
}
impl AsRef<Id> for UnreservedId {
fn as_ref(&self) -> &Id {
&self.0
}
}
impl AsRef<str> for UnreservedId {
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}
impl std::fmt::Display for UnreservedId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl std::str::FromStr for UnreservedId {
type Err = ParseErrors;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Id::from_str(s).and_then(|id| id.try_into().map_err(ParseErrors::singleton))
}
}
impl FromNormalizedStr for UnreservedId {
fn describe_self() -> &'static str {
"Unreserved Id"
}
}
impl UnreservedId {
pub(crate) fn empty() -> Self {
#[allow(clippy::unwrap_used)]
Id("".into()).try_into().unwrap()
}
}
struct IdVisitor;
impl serde::de::Visitor<'_> 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)
}
}
impl<'de> Deserialize<'de> for UnreservedId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer
.deserialize_str(IdVisitor)
.and_then(|n| n.try_into().map_err(serde::de::Error::custom))
}
}
#[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),
])
}
}
#[cfg(feature = "arbitrary")]
impl<'a> arbitrary::Arbitrary<'a> for UnreservedId {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let id: Id = u.arbitrary()?;
match UnreservedId::try_from(id.clone()) {
Ok(id) => Ok(id),
Err(_) => {
#[allow(clippy::unwrap_used)]
let new_id = format!("_{id}").parse().unwrap();
Ok(new_id)
}
}
}
fn size_hint(depth: usize) -> (usize, Option<usize>) {
<Id as arbitrary::Arbitrary>::size_hint(depth)
}
}
#[derive(Serialize, Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
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 serde::de::Visitor<'_> 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");
}
}