Skip to content

Commit 392d1fe

Browse files
Report source location for unknown entity type and action errors (#802)
Signed-off-by: John Kastner <[email protected]>
1 parent d016d23 commit 392d1fe

File tree

13 files changed

+278
-211
lines changed

13 files changed

+278
-211
lines changed

cedar-policy-core/src/ast/entity.rs

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use crate::ast::*;
1818
use crate::evaluator::{EvaluationError, RestrictedEvaluator};
1919
use crate::extensions::Extensions;
2020
use crate::parser::err::ParseErrors;
21+
use crate::parser::Loc;
2122
use crate::transitive_closure::TCNode;
2223
use crate::FromNormalizedStr;
2324
use itertools::Itertools;
@@ -62,13 +63,43 @@ impl std::fmt::Display for EntityType {
6263
}
6364

6465
/// Unique ID for an entity. These represent entities in the AST.
65-
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Hash, PartialOrd, Ord)]
66-
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
66+
#[derive(Serialize, Deserialize, Debug, Clone)]
6767
pub struct EntityUID {
6868
/// Typename of the entity
6969
ty: EntityType,
7070
/// EID of the entity
7171
eid: Eid,
72+
/// Location of the entity in policy source
73+
#[serde(skip)]
74+
loc: Option<Loc>,
75+
}
76+
77+
/// `PartialEq` implementation ignores the `loc`.
78+
impl PartialEq for EntityUID {
79+
fn eq(&self, other: &Self) -> bool {
80+
self.ty == other.ty && self.eid == other.eid
81+
}
82+
}
83+
impl Eq for EntityUID {}
84+
85+
impl std::hash::Hash for EntityUID {
86+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
87+
// hash the ty and eid, in line with the `PartialEq` impl which compares
88+
// the ty and eid.
89+
self.ty.hash(state);
90+
self.eid.hash(state);
91+
}
92+
}
93+
94+
impl PartialOrd for EntityUID {
95+
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
96+
Some(self.cmp(other))
97+
}
98+
}
99+
impl Ord for EntityUID {
100+
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
101+
self.ty.cmp(&other.ty).then(self.eid.cmp(&other.eid))
102+
}
72103
}
73104

74105
impl StaticallyTyped for EntityUID {
@@ -87,6 +118,7 @@ impl EntityUID {
87118
Self {
88119
ty: Self::test_entity_type(),
89120
eid: Eid(eid.into()),
121+
loc: None,
90122
}
91123
}
92124
// by default, Coverlay does not track coverage for lines after a line
@@ -113,6 +145,7 @@ impl EntityUID {
113145
Ok(Self {
114146
ty: EntityType::Specified(Name::parse_unqualified_name(typename)?),
115147
eid: Eid(eid.into()),
148+
loc: None,
116149
})
117150
}
118151

@@ -122,11 +155,17 @@ impl EntityUID {
122155
(self.ty, self.eid)
123156
}
124157

158+
/// Get the source location for this `EntityUID`.
159+
pub fn loc(&self) -> Option<&Loc> {
160+
self.loc.as_ref()
161+
}
162+
125163
/// Create a nominally-typed `EntityUID` with the given typename and EID
126-
pub fn from_components(name: Name, eid: Eid) -> Self {
164+
pub fn from_components(name: Name, eid: Eid, loc: Option<Loc>) -> Self {
127165
Self {
128166
ty: EntityType::Specified(name),
129167
eid,
168+
loc,
130169
}
131170
}
132171

@@ -135,6 +174,7 @@ impl EntityUID {
135174
Self {
136175
ty: EntityType::Unspecified,
137176
eid,
177+
loc: None,
138178
}
139179
}
140180

@@ -175,6 +215,17 @@ impl FromNormalizedStr for EntityUID {
175215
}
176216
}
177217

218+
#[cfg(feature = "arbitrary")]
219+
impl<'a> arbitrary::Arbitrary<'a> for EntityUID {
220+
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
221+
Ok(Self {
222+
ty: u.arbitrary()?,
223+
eid: u.arbitrary()?,
224+
loc: None,
225+
})
226+
}
227+
}
228+
178229
/// EID type is just a SmolStr for now
179230
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Hash, PartialOrd, Ord)]
180231
pub struct Eid(SmolStr);
@@ -497,12 +548,14 @@ mod test {
497548
let e2 = EntityUID::from_components(
498549
Name::parse_unqualified_name("test_entity_type").expect("should be a valid identifier"),
499550
Eid("foo".into()),
551+
None,
500552
);
501553
let e3 = EntityUID::unspecified_from_eid(Eid("foo".into()));
502554
let e4 = EntityUID::unspecified_from_eid(Eid("bar".into()));
503555
let e5 = EntityUID::from_components(
504556
Name::parse_unqualified_name("Unspecified").expect("should be a valid identifier"),
505557
Eid("foo".into()),
558+
None,
506559
);
507560

508561
// an EUID is equal to itself

cedar-policy-core/src/ast/name.rs

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,42 @@ use super::PrincipalOrResource;
2929
/// This is the `Name` type used to name types, functions, etc.
3030
/// The name can include namespaces.
3131
/// Clone is O(1).
32-
#[derive(Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord)]
33-
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
32+
#[derive(Debug, Clone)]
3433
pub struct Name {
3534
/// Basename
3635
pub(crate) id: Id,
3736
/// Namespaces
3837
pub(crate) path: Arc<Vec<Id>>,
38+
/// Location of the name in source
39+
pub(crate) loc: Option<Loc>,
40+
}
41+
42+
/// `PartialEq` implementation ignores the `loc`.
43+
impl PartialEq for Name {
44+
fn eq(&self, other: &Self) -> bool {
45+
self.id == other.id && self.path == other.path
46+
}
47+
}
48+
impl Eq for Name {}
49+
50+
impl std::hash::Hash for Name {
51+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
52+
// hash the ty and eid, in line with the `PartialEq` impl which compares
53+
// the ty and eid.
54+
self.id.hash(state);
55+
self.path.hash(state);
56+
}
57+
}
58+
59+
impl PartialOrd for Name {
60+
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
61+
Some(self.cmp(other))
62+
}
63+
}
64+
impl Ord for Name {
65+
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
66+
self.id.cmp(&other.id).then(self.path.cmp(&other.path))
67+
}
3968
}
4069

4170
/// A shortcut for `Name::unqualified_name`
@@ -61,10 +90,11 @@ impl TryFrom<Name> for Id {
6190

6291
impl Name {
6392
/// A full constructor for `Name`
64-
pub fn new(basename: Id, path: impl IntoIterator<Item = Id>) -> Self {
93+
pub fn new(basename: Id, path: impl IntoIterator<Item = Id>, loc: Option<Loc>) -> Self {
6594
Self {
6695
id: basename,
6796
path: Arc::new(path.into_iter().collect()),
97+
loc,
6898
}
6999
}
70100

@@ -73,6 +103,7 @@ impl Name {
73103
Self {
74104
id,
75105
path: Arc::new(vec![]),
106+
loc: None,
76107
}
77108
}
78109

@@ -82,15 +113,21 @@ impl Name {
82113
Ok(Self {
83114
id: s.parse()?,
84115
path: Arc::new(vec![]),
116+
loc: None,
85117
})
86118
}
87119

88120
/// Given a type basename and a namespace (as a `Name` itself),
89121
/// return a `Name` representing the type's fully qualified name
90-
pub fn type_in_namespace(basename: Id, namespace: Name) -> Name {
122+
pub fn type_in_namespace(basename: Id, namespace: Name, loc: Option<Loc>) -> Name {
91123
let mut path = Arc::unwrap_or_clone(namespace.path);
92124
path.push(namespace.id);
93-
Name::new(basename, path)
125+
Name::new(basename, path, loc)
126+
}
127+
128+
/// Get the source location
129+
pub fn loc(&self) -> Option<&Loc> {
130+
self.loc.as_ref()
94131
}
95132

96133
/// Get the basename of the `Name` (ie, with namespaces stripped).
@@ -129,6 +166,7 @@ impl Name {
129166
.namespace_components()
130167
.chain(std::iter::once(namespace.basename()))
131168
.cloned(),
169+
self.loc.clone(),
132170
),
133171
None => self.clone(),
134172
}
@@ -179,6 +217,17 @@ impl FromNormalizedStr for Name {
179217
}
180218
}
181219

220+
#[cfg(feature = "arbitrary")]
221+
impl<'a> arbitrary::Arbitrary<'a> for Name {
222+
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
223+
Ok(Self {
224+
id: u.arbitrary()?,
225+
path: u.arbitrary()?,
226+
loc: None,
227+
})
228+
}
229+
}
230+
182231
struct NameVisitor;
183232

184233
impl<'de> serde::de::Visitor<'de> for NameVisitor {

cedar-policy-core/src/ast/policy.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2142,6 +2142,7 @@ mod test {
21422142
let id = EntityUID::from_components(
21432143
name::Name::unqualified_name(id::Id::new_unchecked("s")),
21442144
entity::Eid::new("eid"),
2145+
None,
21452146
);
21462147
let mut i = EntityIterator::One(&id);
21472148
assert_eq!(i.next(), Some(&id));
@@ -2153,10 +2154,12 @@ mod test {
21532154
let id1 = EntityUID::from_components(
21542155
name::Name::unqualified_name(id::Id::new_unchecked("s")),
21552156
entity::Eid::new("eid1"),
2157+
None,
21562158
);
21572159
let id2 = EntityUID::from_components(
21582160
name::Name::unqualified_name(id::Id::new_unchecked("s")),
21592161
entity::Eid::new("eid2"),
2162+
None,
21602163
);
21612164
let v = vec![&id1, &id2];
21622165
let mut i = EntityIterator::Bunch(v);

cedar-policy-core/src/entities/json/value.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ impl TryFrom<TypeAndId> for EntityUID {
198198
Ok(EntityUID::from_components(
199199
Name::from_normalized_str(&e.entity_type)?,
200200
Eid::new(e.id),
201+
None,
201202
))
202203
}
203204
}

cedar-policy-core/src/est/expr.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1108,6 +1108,7 @@ fn interpret_primary(
11081108
__entity: TypeAndId::from(ast::EntityUID::from_components(
11091109
name,
11101110
ast::Eid::new(eid),
1111+
None,
11111112
)),
11121113
}))),
11131114
Err(unescape_errs) => {
@@ -1143,6 +1144,7 @@ fn interpret_primary(
11431144
.and_then(|id| id.to_string().parse().map_err(Into::into))
11441145
})
11451146
.collect::<Result<Vec<ast::Id>, ParseErrors>>()?,
1147+
Some(node.loc.clone()),
11461148
))),
11471149
(path, id) => {
11481150
let (l, r, src) = match (path.first(), path.last()) {

cedar-policy-core/src/parser/cst_to_ast.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2118,7 +2118,9 @@ impl Node<Option<cst::Name>> {
21182118

21192119
// computation and error generation is complete, so fail or construct
21202120
match (maybe_name, path.len()) {
2121-
(Some(r), len) if len == name.path.len() => Some(construct_name(path, r)),
2121+
(Some(r), len) if len == name.path.len() => {
2122+
Some(construct_name(path, r, self.loc.clone()))
2123+
}
21222124
_ => None,
21232125
}
21242126
}
@@ -2227,7 +2229,7 @@ impl Node<Option<cst::Ref>> {
22272229
};
22282230

22292231
match (maybe_path, maybe_eid) {
2230-
(Some(p), Some(e)) => Some(construct_refr(p, e)),
2232+
(Some(p), Some(e)) => Some(construct_refr(p, e, self.loc.clone())),
22312233
_ => None,
22322234
}
22332235
}
@@ -2343,15 +2345,16 @@ fn construct_string_from_var(v: ast::Var) -> SmolStr {
23432345
ast::Var::Context => "context".into(),
23442346
}
23452347
}
2346-
fn construct_name(path: Vec<ast::Id>, id: ast::Id) -> ast::Name {
2348+
fn construct_name(path: Vec<ast::Id>, id: ast::Id, loc: Loc) -> ast::Name {
23472349
ast::Name {
23482350
id,
23492351
path: Arc::new(path),
2352+
loc: Some(loc),
23502353
}
23512354
}
2352-
fn construct_refr(p: ast::Name, n: SmolStr) -> ast::EntityUID {
2355+
fn construct_refr(p: ast::Name, n: SmolStr, loc: Loc) -> ast::EntityUID {
23532356
let eid = ast::Eid::new(n);
2354-
ast::EntityUID::from_components(p, eid)
2357+
ast::EntityUID::from_components(p, eid, Some(loc))
23552358
}
23562359
fn construct_expr_ref(r: ast::EntityUID, loc: Loc) -> ast::Expr {
23572360
ast::ExprBuilder::new().with_source_loc(loc).val(r)

cedar-policy-validator/src/human_schema/ast.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,11 @@ impl std::fmt::Display for Path {
113113

114114
impl From<Path> for Name {
115115
fn from(value: Path) -> Self {
116-
value.0.node.into()
116+
Self::new(
117+
value.0.node.basename,
118+
value.0.node.namespace,
119+
Some(value.0.loc),
120+
)
117121
}
118122
}
119123

@@ -123,12 +127,6 @@ struct PathInternal {
123127
namespace: Vec<Id>,
124128
}
125129

126-
impl From<PathInternal> for Name {
127-
fn from(value: PathInternal) -> Self {
128-
Self::new(value.basename, value.namespace)
129-
}
130-
}
131-
132130
impl PathInternal {
133131
fn iter(&self) -> impl Iterator<Item = &Id> {
134132
self.namespace.iter().chain(once(&self.basename))

cedar-policy-validator/src/human_schema/to_json_schema.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,7 @@ impl<'a> ConversionContext<'a> {
434434
&Some(Name::new(
435435
prefix_base.clone(),
436436
prefix_prefix.into_iter().cloned(),
437+
None,
437438
)),
438439
),
439440
None =>

0 commit comments

Comments
 (0)