Skip to content

Commit 10e1b27

Browse files
authored
remove Display for EntityId in favor of explicit .escaped() and .as_ref() (#921)
Signed-off-by: Craig Disselkoen <[email protected]>
1 parent 10ca7ea commit 10e1b27

File tree

10 files changed

+55
-37
lines changed

10 files changed

+55
-37
lines changed

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

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ impl EntityUID {
196196

197197
impl std::fmt::Display for EntityUID {
198198
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199-
write!(f, "{}::\"{}\"", self.entity_type(), self.eid)
199+
write!(f, "{}::\"{}\"", self.entity_type(), self.eid.escaped())
200200
}
201201
}
202202

@@ -226,7 +226,15 @@ impl<'a> arbitrary::Arbitrary<'a> for EntityUID {
226226
}
227227
}
228228

229-
/// EID type is just a SmolStr for now
229+
/// The `Eid` type represents the id of an `Entity`, without the typename.
230+
/// Together with the typename it comprises an `EntityUID`.
231+
/// For example, in `User::"alice"`, the `Eid` is `alice`.
232+
///
233+
/// `Eid` does not implement `Display`, partly because it is unclear whether
234+
/// `Display` should produce an escaped representation or an unescaped representation
235+
/// (see [#884](https://github.com/cedar-policy/cedar/issues/884)).
236+
/// To get an escaped representation, use `.escaped()`.
237+
/// To get an unescaped representation, use `.as_ref()`.
230238
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Hash, PartialOrd, Ord)]
231239
pub struct Eid(SmolStr);
232240

@@ -235,6 +243,11 @@ impl Eid {
235243
pub fn new(eid: impl Into<SmolStr>) -> Self {
236244
Eid(eid.into())
237245
}
246+
247+
/// Get the contents of the `Eid` as an escaped string
248+
pub fn escaped(&self) -> SmolStr {
249+
self.0.escape_debug().collect()
250+
}
238251
}
239252

240253
impl AsRef<SmolStr> for Eid {
@@ -257,12 +270,6 @@ impl<'a> arbitrary::Arbitrary<'a> for Eid {
257270
}
258271
}
259272

260-
impl std::fmt::Display for Eid {
261-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
262-
write!(f, "{}", self.0.escape_debug())
263-
}
264-
}
265-
266273
/// Entity datatype
267274
#[derive(Debug, Clone, Serialize)]
268275
pub struct Entity {

cedar-policy-core/src/entities.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ impl Entities {
294294
))?;
295295
for entity in entities {
296296
let euid = to_dot_id(&entity.uid());
297-
let label = format!(r#"[label={}]"#, to_dot_id(&entity.uid().eid()));
297+
let label = format!(r#"[label={}]"#, to_dot_id(&entity.uid().eid().escaped()));
298298
dot_str.write_str(&format!("\t\t{euid} {label}\n"))?;
299299
}
300300
dot_str.write_str("\t}\n")?;

cedar-policy-core/src/parser.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -747,8 +747,7 @@ mod parse_tests {
747747
fn test_parse_string() {
748748
// test idempotence
749749
assert_eq!(
750-
ast::Eid::new(parse_internal_string(r"a\nblock\nid").expect("should parse"))
751-
.to_string(),
750+
ast::Eid::new(parse_internal_string(r"a\nblock\nid").expect("should parse")).escaped(),
752751
r"a\nblock\nid",
753752
);
754753
parse_internal_string(r#"oh, no, a '! "#).expect("single quote should be fine");

cedar-policy-validator/src/diagnostics.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use thiserror::Error;
2222

2323
use std::collections::BTreeSet;
2424

25-
use cedar_policy_core::ast::{Expr, Name, PolicyID};
25+
use cedar_policy_core::ast::{Eid, Expr, Name, PolicyID};
2626
use cedar_policy_core::parser::Loc;
2727

2828
use crate::types::Type;
@@ -209,7 +209,7 @@ impl ValidationError {
209209
pub(crate) fn unspecified_entity(
210210
source_loc: Option<Loc>,
211211
policy_id: PolicyID,
212-
entity_id: String,
212+
entity_id: Eid,
213213
) -> Self {
214214
validation_errors::UnspecifiedEntity {
215215
source_loc,

cedar-policy-validator/src/diagnostics/validation_errors.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use cedar_policy_core::parser::Loc;
2727
use std::collections::BTreeSet;
2828

2929
use cedar_policy_core::ast::{
30-
CallStyle, EntityUID, Expr, ExprKind, ExprShapeOnly, Name, PolicyID, Var,
30+
CallStyle, Eid, EntityUID, Expr, ExprKind, ExprShapeOnly, Name, PolicyID, Var,
3131
};
3232
use cedar_policy_core::parser::join_with_conjunction;
3333

@@ -141,12 +141,12 @@ impl Diagnostic for InvalidActionApplication {
141141

142142
/// Structure containing details about an unspecified entity error.
143143
#[derive(Debug, Clone, Error, Hash, Eq, PartialEq)]
144-
#[error("for policy `{policy_id}`, unspecified entity with id `{entity_id}`")]
144+
#[error("for policy `{policy_id}`, unspecified entity with id `{}`", .entity_id.escaped())]
145145
pub struct UnspecifiedEntity {
146146
pub source_loc: Option<Loc>,
147147
pub policy_id: PolicyID,
148-
/// EID of the unspecified entity.
149-
pub entity_id: String,
148+
/// EID of the unspecified entity
149+
pub entity_id: Eid,
150150
}
151151

152152
impl Diagnostic for UnspecifiedEntity {

cedar-policy-validator/src/rbac.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ impl Validator {
7474
Some(ValidationError::unspecified_entity(
7575
euid.loc().cloned(),
7676
template.id().clone(),
77-
euid.eid().to_string(),
77+
euid.eid().clone(),
7878
))
7979
}
8080
cedar_policy_core::ast::EntityType::Specified(_) => None,
@@ -102,7 +102,7 @@ impl Validator {
102102
ast::EntityType::Unspecified => Some(ValidationError::unspecified_entity(
103103
euid.loc().cloned(),
104104
template.id().clone(),
105-
euid.eid().to_string(),
105+
euid.eid().clone(),
106106
)),
107107
ast::EntityType::Specified(name) => {
108108
let is_known_action_entity_id = self.schema.is_known_action_id(euid);
@@ -146,7 +146,7 @@ impl Validator {
146146
Some(ValidationError::unspecified_entity(
147147
None,
148148
policy_id.clone(),
149-
euid.eid().to_string(),
149+
euid.eid().clone(),
150150
))
151151
}
152152
cedar_policy_core::ast::EntityType::Specified(name) => {
@@ -1572,7 +1572,8 @@ mod test {
15721572
assert_eq!(1, notes.len());
15731573
assert_matches!(notes.first(),
15741574
Some(ValidationError::UnspecifiedEntity(UnspecifiedEntity { entity_id, .. })) => {
1575-
assert_eq!("foo", entity_id);
1575+
let eid: &str = entity_id.as_ref();
1576+
assert_eq!("foo", eid);
15761577
}
15771578
);
15781579

@@ -1591,7 +1592,8 @@ mod test {
15911592
assert_eq!(1, notes.len());
15921593
assert_matches!(notes.first(),
15931594
Some(ValidationError::UnspecifiedEntity(UnspecifiedEntity { entity_id, .. })) => {
1594-
assert_eq!("foo", entity_id);
1595+
let eid: &str = entity_id.as_ref();
1596+
assert_eq!("foo", eid);
15951597
}
15961598
);
15971599

@@ -1622,7 +1624,8 @@ mod test {
16221624
assert_eq!(1, notes.len());
16231625
assert_matches!(notes.first(),
16241626
Some(ValidationError::UnspecifiedEntity(UnspecifiedEntity { entity_id, .. })) => {
1625-
assert_eq!("foo", entity_id);
1627+
let eid: &str = entity_id.as_ref();
1628+
assert_eq!("foo", eid);
16261629
}
16271630
);
16281631

cedar-policy-validator/src/schema.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2105,7 +2105,8 @@ mod test {
21052105
.unwrap();
21062106
let ancestors = view_photo.ancestors().collect::<Vec<_>>();
21072107
let read = ancestors[0];
2108-
assert_eq!(read.eid().to_string(), "read");
2108+
let read_eid: &str = read.eid().as_ref();
2109+
assert_eq!(read_eid, "read");
21092110
assert_eq!(read.entity_type().to_string(), "Foo::Action");
21102111
}
21112112

@@ -2165,7 +2166,8 @@ mod test {
21652166
.unwrap();
21662167
let ancestors = view_photo.ancestors().collect::<Vec<_>>();
21672168
let read = ancestors[0];
2168-
assert_eq!(read.eid().to_string(), "read");
2169+
let read_eid: &str = read.eid().as_ref();
2170+
assert_eq!(read_eid, "read");
21692171
assert_eq!(
21702172
read.entity_type().to_string(),
21712173
"ExampleCo::Personnel::Action"

cedar-policy/CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2222
- Removed unnecessary lifetimes from some validation related structs (#715)
2323
- Changed policy validation to reject comparisons and conditionals between
2424
record types that differ in whether an attribute is required or optional.
25-
- Fixed a performance issue when constructing an error for accessing
25+
- Fixed a performance issue when constructing an error for accessing
2626
a non-existent attribute on sufficiently large records/entities
2727

2828
### Removed
@@ -37,6 +37,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3737
the rich data provided by `miette::Diagnostic`, for instance `.help()` and
3838
`labels()`. Callers can continue using the same behavior by calling
3939
`.iter().map(ToString::to_string)`. (#882, resolving #543)
40+
- Removed `Display` impl for `EntityId` in favor of explicit `.escaped()` and
41+
`.as_ref()` for escaped and unescaped representations (respectively) of the
42+
`EntityId`; see note there (#921, resolving #884)
4043

4144
### Fixed
4245

cedar-policy/src/api/id.rs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use cedar_policy_core::entities::json::err::JsonDeserializationErrorContext;
2424
use cedar_policy_core::FromNormalizedStr;
2525
use ref_cast::RefCast;
2626
use serde::{Deserialize, Serialize};
27+
use smol_str::SmolStr;
2728
use std::convert::Infallible;
2829
use std::str::FromStr;
2930

@@ -39,6 +40,12 @@ use std::str::FromStr;
3940
/// let id : EntityId = "my-id".parse().unwrap_or_else(|never| match never {});
4041
/// # assert_eq!(id.as_ref(), "my-id");
4142
/// ```
43+
///
44+
/// `EntityId` does not implement `Display`, partly because it is unclear
45+
/// whether `Display` should produce an escaped representation or an unescaped
46+
/// representation (see [#884](https://github.com/cedar-policy/cedar/issues/884)).
47+
/// To get an escaped representation, use `.escaped()`.
48+
/// To get an unescaped representation, use `.as_ref()`.
4249
#[repr(transparent)]
4350
#[allow(clippy::module_name_repetitions)]
4451
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, RefCast)]
@@ -52,6 +59,11 @@ impl EntityId {
5259
Err(infallible) => match infallible {},
5360
}
5461
}
62+
63+
/// Get the contents of the `EntityId` as an escaped string
64+
pub fn escaped(&self) -> SmolStr {
65+
self.0.escaped()
66+
}
5567
}
5668

5769
impl FromStr for EntityId {
@@ -67,15 +79,6 @@ impl AsRef<str> for EntityId {
6779
}
6880
}
6981

70-
// Note that this Display formatter will format the EntityId as it would be expected
71-
// in the EntityUid string form. For instance, the `"alice"` in `User::"alice"`.
72-
// This means it adds quotes and potentially performs some escaping.
73-
impl std::fmt::Display for EntityId {
74-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75-
write!(f, "{}", self.0)
76-
}
77-
}
78-
7982
/// Represents an entity type name. Consists of a namespace and the type name.
8083
///
8184
/// An `EntityTypeName` can can be constructed using

cedar-policy/src/tests.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3740,6 +3740,7 @@ mod decimal_ip_constructors {
37403740

37413741
mod into_iter_entities {
37423742
use super::*;
3743+
use smol_str::SmolStr;
37433744

37443745
#[test]
37453746
fn into_iter_entities() {
@@ -3762,9 +3763,9 @@ mod into_iter_entities {
37623763
"#;
37633764

37643765
let list = Entities::from_json_str(test_data, None).unwrap();
3765-
let mut list_out: Vec<String> = list
3766+
let mut list_out: Vec<SmolStr> = list
37663767
.into_iter()
3767-
.map(|entity| entity.uid().id().to_string())
3768+
.map(|entity| entity.uid().id().escaped())
37683769
.collect();
37693770
list_out.sort();
37703771
assert_eq!(list_out, &["admin", "alice"]);

0 commit comments

Comments
 (0)