use crate::ast::*;
use crate::entities::{err::EntitiesError, json::err::JsonSerializationError, EntityJson};
use crate::evaluator::{EvaluationError, RestrictedEvaluator};
use crate::extensions::Extensions;
use crate::parser::err::ParseErrors;
use crate::parser::Loc;
use crate::transitive_closure::TCNode;
use crate::FromNormalizedStr;
use educe::Educe;
use itertools::Itertools;
use miette::Diagnostic;
use ref_cast::RefCast;
use serde::{Deserialize, Serialize};
use serde_with::{serde_as, TryFromInto};
use smol_str::SmolStr;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::str::FromStr;
use std::sync::Arc;
use thiserror::Error;
pub static ACTION_ENTITY_TYPE: &str = "Action";
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Hash, PartialOrd, Ord, RefCast)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[serde(transparent)]
#[repr(transparent)]
pub struct EntityType(Name);
impl EntityType {
pub fn is_action(&self) -> bool {
self.0.as_ref().basename() == &Id::new_unchecked(ACTION_ENTITY_TYPE)
}
pub fn name(&self) -> &Name {
&self.0
}
pub fn loc(&self) -> Option<&Loc> {
self.0.as_ref().loc()
}
pub fn qualify_with(&self, namespace: Option<&Name>) -> Self {
Self(self.0.qualify_with_name(namespace))
}
pub fn from_normalized_str(src: &str) -> Result<Self, ParseErrors> {
Name::from_normalized_str(src).map(Into::into)
}
}
impl From<Name> for EntityType {
fn from(n: Name) -> Self {
Self(n)
}
}
impl From<EntityType> for Name {
fn from(ty: EntityType) -> Name {
ty.0
}
}
impl AsRef<Name> for EntityType {
fn as_ref(&self) -> &Name {
&self.0
}
}
impl FromStr for EntityType {
type Err = ParseErrors;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse().map(Self)
}
}
#[cfg(feature = "protobufs")]
impl From<&proto::EntityType> for EntityType {
#[allow(clippy::expect_used)]
fn from(v: &proto::EntityType) -> Self {
Self(Name::from(
v.name
.as_ref()
.expect("`as_ref()` for field that should exist"),
))
}
}
#[cfg(feature = "protobufs")]
impl From<&EntityType> for proto::EntityType {
fn from(v: &EntityType) -> Self {
Self {
name: Some(proto::Name::from(v.name())),
}
}
}
impl std::fmt::Display for EntityType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Educe, Serialize, Deserialize, Debug, Clone)]
#[educe(PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct EntityUID {
ty: EntityType,
eid: Eid,
#[serde(skip)]
#[educe(PartialEq(ignore))]
#[educe(Hash(ignore))]
#[educe(PartialOrd(ignore))]
loc: Option<Loc>,
}
impl StaticallyTyped for EntityUID {
fn type_of(&self) -> Type {
Type::Entity {
ty: self.ty.clone(),
}
}
}
impl EntityUID {
#[cfg(test)]
pub(crate) fn with_eid(eid: &str) -> Self {
Self {
ty: Self::test_entity_type(),
eid: Eid(eid.into()),
loc: None,
}
}
#[cfg(test)]
pub(crate) fn test_entity_type() -> EntityType {
let name = Name::parse_unqualified_name("test_entity_type")
.expect("test_entity_type should be a valid identifier");
EntityType(name)
}
pub fn with_eid_and_type(typename: &str, eid: &str) -> Result<Self, ParseErrors> {
Ok(Self {
ty: EntityType(Name::parse_unqualified_name(typename)?),
eid: Eid(eid.into()),
loc: None,
})
}
pub fn components(self) -> (EntityType, Eid) {
(self.ty, self.eid)
}
pub fn loc(&self) -> Option<&Loc> {
self.loc.as_ref()
}
pub fn from_components(ty: EntityType, eid: Eid, loc: Option<Loc>) -> Self {
Self { ty, eid, loc }
}
pub fn entity_type(&self) -> &EntityType {
&self.ty
}
pub fn eid(&self) -> &Eid {
&self.eid
}
pub fn is_action(&self) -> bool {
self.entity_type().is_action()
}
}
impl std::fmt::Display for EntityUID {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}::\"{}\"", self.entity_type(), self.eid.escaped())
}
}
impl std::str::FromStr for EntityUID {
type Err = ParseErrors;
fn from_str(s: &str) -> Result<Self, Self::Err> {
crate::parser::parse_euid(s)
}
}
impl FromNormalizedStr for EntityUID {
fn describe_self() -> &'static str {
"Entity UID"
}
}
#[cfg(feature = "arbitrary")]
impl<'a> arbitrary::Arbitrary<'a> for EntityUID {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
Ok(Self {
ty: u.arbitrary()?,
eid: u.arbitrary()?,
loc: None,
})
}
}
#[cfg(feature = "protobufs")]
impl From<&proto::EntityUid> for EntityUID {
#[allow(clippy::expect_used)]
fn from(v: &proto::EntityUid) -> Self {
Self {
ty: EntityType::from(
v.ty.as_ref()
.expect("`as_ref()` for field that should exist"),
),
eid: Eid::new(v.eid.clone()),
loc: None,
}
}
}
#[cfg(feature = "protobufs")]
impl From<&EntityUID> for proto::EntityUid {
fn from(v: &EntityUID) -> Self {
let eid_ref: &str = v.eid.as_ref();
Self {
ty: Some(proto::EntityType::from(&v.ty)),
eid: eid_ref.to_owned(),
}
}
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Hash, PartialOrd, Ord)]
pub struct Eid(SmolStr);
impl Eid {
pub fn new(eid: impl Into<SmolStr>) -> Self {
Eid(eid.into())
}
pub fn escaped(&self) -> SmolStr {
self.0.escape_debug().collect()
}
}
impl AsRef<SmolStr> for Eid {
fn as_ref(&self) -> &SmolStr {
&self.0
}
}
impl AsRef<str> for Eid {
fn as_ref(&self) -> &str {
&self.0
}
}
#[cfg(feature = "arbitrary")]
impl<'a> arbitrary::Arbitrary<'a> for Eid {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let x: String = u.arbitrary()?;
Ok(Self(x.into()))
}
}
#[derive(Debug, Clone, Serialize)]
pub struct Entity {
uid: EntityUID,
attrs: BTreeMap<SmolStr, PartialValueSerializedAsExpr>,
ancestors: HashSet<EntityUID>,
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
tags: BTreeMap<SmolStr, PartialValueSerializedAsExpr>,
}
impl std::hash::Hash for Entity {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.uid.hash(state);
}
}
impl Entity {
pub fn new(
uid: EntityUID,
attrs: impl IntoIterator<Item = (SmolStr, RestrictedExpr)>,
ancestors: HashSet<EntityUID>,
tags: impl IntoIterator<Item = (SmolStr, RestrictedExpr)>,
extensions: &Extensions<'_>,
) -> Result<Self, EntityAttrEvaluationError> {
let evaluator = RestrictedEvaluator::new(extensions);
let evaluate_kvs = |(k, v): (SmolStr, RestrictedExpr), was_attr: bool| {
let attr_val = evaluator
.partial_interpret(v.as_borrowed())
.map_err(|err| EntityAttrEvaluationError {
uid: uid.clone(),
attr_or_tag: k.clone(),
was_attr,
err,
})?;
Ok((k, attr_val.into()))
};
let evaluated_attrs = attrs
.into_iter()
.map(|kv| evaluate_kvs(kv, true))
.collect::<Result<_, EntityAttrEvaluationError>>()?;
let evaluated_tags = tags
.into_iter()
.map(|kv| evaluate_kvs(kv, false))
.collect::<Result<_, EntityAttrEvaluationError>>()?;
Ok(Entity {
uid,
attrs: evaluated_attrs,
ancestors,
tags: evaluated_tags,
})
}
pub fn new_with_attr_partial_value(
uid: EntityUID,
attrs: impl IntoIterator<Item = (SmolStr, PartialValue)>,
ancestors: HashSet<EntityUID>,
) -> Self {
Self::new_with_attr_partial_value_serialized_as_expr(
uid,
attrs.into_iter().map(|(k, v)| (k, v.into())).collect(),
ancestors,
)
}
pub fn new_with_attr_partial_value_serialized_as_expr(
uid: EntityUID,
attrs: BTreeMap<SmolStr, PartialValueSerializedAsExpr>,
ancestors: HashSet<EntityUID>,
) -> Self {
Entity {
uid,
attrs,
ancestors,
tags: BTreeMap::new(),
}
}
pub fn uid(&self) -> &EntityUID {
&self.uid
}
pub fn get(&self, attr: &str) -> Option<&PartialValue> {
self.attrs.get(attr).map(|v| v.as_ref())
}
pub fn get_tag(&self, tag: &str) -> Option<&PartialValue> {
self.tags.get(tag).map(|v| v.as_ref())
}
pub fn is_descendant_of(&self, e: &EntityUID) -> bool {
self.ancestors.contains(e)
}
pub fn ancestors(&self) -> impl Iterator<Item = &EntityUID> {
self.ancestors.iter()
}
pub fn attrs_len(&self) -> usize {
self.attrs.len()
}
pub fn tags_len(&self) -> usize {
self.tags.len()
}
pub fn keys(&self) -> impl Iterator<Item = &SmolStr> {
self.attrs.keys()
}
pub fn tag_keys(&self) -> impl Iterator<Item = &SmolStr> {
self.tags.keys()
}
pub fn attrs(&self) -> impl Iterator<Item = (&SmolStr, &PartialValue)> {
self.attrs.iter().map(|(k, v)| (k, v.as_ref()))
}
pub fn tags(&self) -> impl Iterator<Item = (&SmolStr, &PartialValue)> {
self.tags.iter().map(|(k, v)| (k, v.as_ref()))
}
pub fn with_uid(uid: EntityUID) -> Self {
Self {
uid,
attrs: BTreeMap::new(),
ancestors: HashSet::new(),
tags: BTreeMap::new(),
}
}
pub(crate) fn deep_eq(&self, other: &Self) -> bool {
self.uid == other.uid && self.attrs == other.attrs && self.ancestors == other.ancestors
}
#[cfg(test)]
pub fn set_uid(&mut self, uid: EntityUID) {
self.uid = uid;
}
#[cfg(any(test, fuzzing))]
pub fn set_attr(
&mut self,
attr: SmolStr,
val: BorrowedRestrictedExpr<'_>,
extensions: &Extensions<'_>,
) -> Result<(), EvaluationError> {
let val = RestrictedEvaluator::new(extensions).partial_interpret(val)?;
self.attrs.insert(attr, val.into());
Ok(())
}
#[cfg(any(test, fuzzing))]
pub fn set_tag(
&mut self,
tag: SmolStr,
val: BorrowedRestrictedExpr<'_>,
extensions: &Extensions<'_>,
) -> Result<(), EvaluationError> {
let val = RestrictedEvaluator::new(extensions).partial_interpret(val)?;
self.tags.insert(tag, val.into());
Ok(())
}
pub fn add_ancestor(&mut self, uid: EntityUID) {
self.ancestors.insert(uid);
}
pub fn into_inner(
self,
) -> (
EntityUID,
HashMap<SmolStr, PartialValue>,
HashSet<EntityUID>,
HashMap<SmolStr, PartialValue>,
) {
let Self {
uid,
attrs,
ancestors,
tags,
} = self;
(
uid,
attrs.into_iter().map(|(k, v)| (k, v.0)).collect(),
ancestors,
tags.into_iter().map(|(k, v)| (k, v.0)).collect(),
)
}
pub fn write_to_json(&self, f: impl std::io::Write) -> Result<(), EntitiesError> {
let ejson = EntityJson::from_entity(self)?;
serde_json::to_writer_pretty(f, &ejson).map_err(JsonSerializationError::from)?;
Ok(())
}
pub fn to_json_value(&self) -> Result<serde_json::Value, EntitiesError> {
let ejson = EntityJson::from_entity(self)?;
let v = serde_json::to_value(ejson).map_err(JsonSerializationError::from)?;
Ok(v)
}
pub fn to_json_string(&self) -> Result<String, EntitiesError> {
let ejson = EntityJson::from_entity(self)?;
let string = serde_json::to_string(&ejson).map_err(JsonSerializationError::from)?;
Ok(string)
}
}
impl PartialEq for Entity {
fn eq(&self, other: &Self) -> bool {
self.uid() == other.uid()
}
}
impl Eq for Entity {}
impl StaticallyTyped for Entity {
fn type_of(&self) -> Type {
self.uid.type_of()
}
}
impl TCNode<EntityUID> for Entity {
fn get_key(&self) -> EntityUID {
self.uid().clone()
}
fn add_edge_to(&mut self, k: EntityUID) {
self.add_ancestor(k)
}
fn out_edges(&self) -> Box<dyn Iterator<Item = &EntityUID> + '_> {
Box::new(self.ancestors())
}
fn has_edge_to(&self, e: &EntityUID) -> bool {
self.is_descendant_of(e)
}
}
impl TCNode<EntityUID> for Arc<Entity> {
fn get_key(&self) -> EntityUID {
self.uid().clone()
}
fn add_edge_to(&mut self, k: EntityUID) {
Arc::make_mut(self).add_ancestor(k)
}
fn out_edges(&self) -> Box<dyn Iterator<Item = &EntityUID> + '_> {
Box::new(self.ancestors())
}
fn has_edge_to(&self, e: &EntityUID) -> bool {
self.is_descendant_of(e)
}
}
impl std::fmt::Display for Entity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}:\n attrs:{}\n ancestors:{}",
self.uid,
self.attrs
.iter()
.map(|(k, v)| format!("{}: {}", k, v))
.join("; "),
self.ancestors.iter().join(", ")
)
}
}
#[cfg(feature = "protobufs")]
impl From<&proto::Entity> for Entity {
#[allow(clippy::expect_used)]
fn from(v: &proto::Entity) -> Self {
let eval = RestrictedEvaluator::new(Extensions::none());
let attrs: BTreeMap<SmolStr, PartialValueSerializedAsExpr> = v
.attrs
.iter()
.map(|(key, value)| {
let pval = eval
.partial_interpret(
BorrowedRestrictedExpr::new(&Expr::from(value)).expect("RestrictedExpr"),
)
.expect("interpret on RestrictedExpr");
(key.into(), pval.into())
})
.collect();
let ancestors: HashSet<EntityUID> = v.ancestors.iter().map(EntityUID::from).collect();
let tags: BTreeMap<SmolStr, PartialValueSerializedAsExpr> = v
.tags
.iter()
.map(|(key, value)| {
let pval = eval
.partial_interpret(
BorrowedRestrictedExpr::new(&Expr::from(value)).expect("RestrictedExpr"),
)
.expect("interpret on RestrictedExpr");
(key.into(), pval.into())
})
.collect();
Self {
uid: EntityUID::from(
v.uid
.as_ref()
.expect("`as_ref()` for field that should exist"),
),
attrs,
ancestors,
tags,
}
}
}
#[cfg(feature = "protobufs")]
impl From<&Entity> for proto::Entity {
fn from(v: &Entity) -> Self {
let mut attrs: HashMap<String, proto::Expr> = HashMap::with_capacity(v.attrs.len());
for (key, value) in &v.attrs {
attrs.insert(
key.to_string(),
proto::Expr::from(&Expr::from(PartialValue::from(value.to_owned()))),
);
}
let mut ancestors: Vec<proto::EntityUid> = Vec::with_capacity(v.ancestors.len());
for ancestor in &v.ancestors {
ancestors.push(proto::EntityUid::from(ancestor));
}
let mut tags: HashMap<String, proto::Expr> = HashMap::with_capacity(v.tags.len());
for (key, value) in &v.tags {
tags.insert(
key.to_string(),
proto::Expr::from(&Expr::from(PartialValue::from(value.to_owned()))),
);
}
Self {
uid: Some(proto::EntityUid::from(&v.uid)),
attrs,
ancestors,
tags,
}
}
}
#[cfg(feature = "protobufs")]
impl From<&Arc<Entity>> for proto::Entity {
fn from(v: &Arc<Entity>) -> Self {
Self::from(v.as_ref())
}
}
#[serde_as]
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct PartialValueSerializedAsExpr(
#[serde_as(as = "TryFromInto<RestrictedExpr>")] PartialValue,
);
impl AsRef<PartialValue> for PartialValueSerializedAsExpr {
fn as_ref(&self) -> &PartialValue {
&self.0
}
}
impl std::ops::Deref for PartialValueSerializedAsExpr {
type Target = PartialValue;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<PartialValue> for PartialValueSerializedAsExpr {
fn from(value: PartialValue) -> PartialValueSerializedAsExpr {
PartialValueSerializedAsExpr(value)
}
}
impl From<PartialValueSerializedAsExpr> for PartialValue {
fn from(value: PartialValueSerializedAsExpr) -> PartialValue {
value.0
}
}
impl std::fmt::Display for PartialValueSerializedAsExpr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Diagnostic, Error)]
#[error("failed to evaluate {} `{attr_or_tag}` of `{uid}`: {err}", if *.was_attr { "attribute" } else { "tag" })]
pub struct EntityAttrEvaluationError {
pub uid: EntityUID,
pub attr_or_tag: SmolStr,
pub was_attr: bool,
#[diagnostic(transparent)]
pub err: EvaluationError,
}
#[cfg(test)]
mod test {
use std::str::FromStr;
use super::*;
#[test]
fn display() {
let e = EntityUID::with_eid("eid");
assert_eq!(format!("{e}"), "test_entity_type::\"eid\"");
}
#[test]
fn test_euid_equality() {
let e1 = EntityUID::with_eid("foo");
let e2 = EntityUID::from_components(
Name::parse_unqualified_name("test_entity_type")
.expect("should be a valid identifier")
.into(),
Eid("foo".into()),
None,
);
let e3 = EntityUID::from_components(
Name::parse_unqualified_name("Unspecified")
.expect("should be a valid identifier")
.into(),
Eid("foo".into()),
None,
);
assert_eq!(e1, e1);
assert_eq!(e2, e2);
assert_eq!(e1, e2);
assert!(e1 != e3);
}
#[test]
fn action_checker() {
let euid = EntityUID::from_str("Action::\"view\"").unwrap();
assert!(euid.is_action());
let euid = EntityUID::from_str("Foo::Action::\"view\"").unwrap();
assert!(euid.is_action());
let euid = EntityUID::from_str("Foo::\"view\"").unwrap();
assert!(!euid.is_action());
let euid = EntityUID::from_str("Action::Foo::\"view\"").unwrap();
assert!(!euid.is_action());
}
#[cfg(feature = "protobufs")]
#[test]
fn round_trip_protobuf() {
let name = Name::from_normalized_str("B::C::D").unwrap();
let ety_specified = EntityType(name);
assert_eq!(
ety_specified,
EntityType::from(&proto::EntityType::from(&ety_specified))
);
let euid1 = EntityUID::with_eid("foo");
assert_eq!(euid1, EntityUID::from(&proto::EntityUid::from(&euid1)));
let euid2 = EntityUID::from_str("Foo::Action::\"view\"").unwrap();
assert_eq!(euid2, EntityUID::from(&proto::EntityUid::from(&euid2)));
let attrs = (1..=7)
.map(|id| (format!("{id}").into(), RestrictedExpr::val(true)))
.collect::<HashMap<SmolStr, _>>();
let entity = Entity::new(
r#"Foo::"bar""#.parse().unwrap(),
attrs,
HashSet::new(),
BTreeMap::new(),
Extensions::none(),
)
.unwrap();
assert_eq!(entity, Entity::from(&proto::Entity::from(&entity)));
}
#[test]
fn action_type_is_valid_id() {
assert!(Id::from_normalized_str(ACTION_ENTITY_TYPE).is_ok());
}
}