Skip to content

Commit 2618d00

Browse files
shaobo-he-awscdisselkoen
authored andcommitted
Signed-off-by: Shaobo He <[email protected]> Co-authored-by: Craig Disselkoen <[email protected]>
1 parent e858194 commit 2618d00

File tree

7 files changed

+425
-32
lines changed

7 files changed

+425
-32
lines changed

cedar-policy-validator/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ entity-manifest = []
5353
similar-asserts = "1.5.0"
5454
cool_asserts = "2.0"
5555
cedar-policy-core = { version = "=4.0.0", path = "../cedar-policy-core", features = ["test-util"] }
56+
miette = { version = "7.1.0", features = ["fancy"] }
5657

5758
[build-dependencies]
5859
lalrpop = "0.20.0"

cedar-policy-validator/src/cedar_schema/fmt.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ pub fn json_schema_to_cedar_schema_str<N: Display>(
248248
.common_types
249249
.keys()
250250
.map(|ty_name| {
251-
RawName::new_from_unreserved(ty_name.clone())
251+
RawName::new_from_unreserved(ty_name.clone().into())
252252
.qualify_with_name(name.as_ref())
253253
.to_smolstr()
254254
})

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

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,11 @@ use super::{
3939
ToJsonSchemaError, ToJsonSchemaErrors,
4040
},
4141
};
42-
use crate::{cedar_schema, json_schema, RawName};
42+
use crate::{
43+
cedar_schema,
44+
json_schema::{self, is_reserved_schema_keyword, CommonTypeId},
45+
RawName,
46+
};
4347

4448
impl From<cedar_schema::Path> for RawName {
4549
fn from(p: cedar_schema::Path) -> Self {
@@ -234,12 +238,6 @@ fn convert_namespace(
234238
Ok((ns_name, def))
235239
}
236240

237-
// Test if this id is a reserved JSON schema keyword.
238-
// Issue: https://github.com/cedar-policy/cedar/issues/1070
239-
fn is_reserved_schema_keyword(id: &UnreservedId) -> bool {
240-
matches!(id.as_ref(), "Set" | "Record" | "Entity" | "Extension")
241-
}
242-
243241
impl TryFrom<Namespace> for json_schema::NamespaceDefinition<RawName> {
244242
type Error = ToJsonSchemaErrors;
245243

@@ -268,7 +266,7 @@ impl TryFrom<Namespace> for json_schema::NamespaceDefinition<RawName> {
268266
Err(ToJsonSchemaError::reserved_keyword(id, name_loc))
269267
} else {
270268
Ok((
271-
id,
269+
CommonTypeId::unchecked(id),
272270
cedar_type_to_json_type(decl.def).map_err(EAMapError::from)?,
273271
))
274272
}

cedar-policy-validator/src/json_schema.rs

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,81 @@ impl<N: Display> Fragment<N> {
172172
}
173173
}
174174

175+
/// An [`UnreservedId`] that cannot be reserved JSON schema keywords
176+
/// like `Set`, `Long`, and etc.
177+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
178+
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
179+
#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
180+
pub struct CommonTypeId(#[cfg_attr(feature = "wasm", tsify(type = "string"))] UnreservedId);
181+
182+
impl From<CommonTypeId> for UnreservedId {
183+
fn from(value: CommonTypeId) -> Self {
184+
value.0
185+
}
186+
}
187+
188+
impl CommonTypeId {
189+
/// Create a [`CommonTypeId`] based on an [`UnreservedId`] but do not check
190+
/// if the latter is valid or not
191+
pub fn unchecked(id: UnreservedId) -> Self {
192+
Self(id)
193+
}
194+
}
195+
196+
impl Display for CommonTypeId {
197+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
198+
self.0.fmt(f)
199+
}
200+
}
201+
202+
#[cfg(feature = "arbitrary")]
203+
impl<'a> arbitrary::Arbitrary<'a> for CommonTypeId {
204+
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
205+
let id: UnreservedId = u.arbitrary()?;
206+
if is_reserved_schema_keyword(&id) {
207+
// PANIC SAFETY: `_Bool`, `_Record`, and etc are valid common type names as well as valid unreserved names.
208+
#[allow(clippy::unwrap_used)]
209+
let new_id = format!("_{id}").parse().unwrap();
210+
Ok(CommonTypeId::unchecked(new_id))
211+
} else {
212+
Ok(CommonTypeId::unchecked(id))
213+
}
214+
}
215+
216+
fn size_hint(depth: usize) -> (usize, Option<usize>) {
217+
<UnreservedId as arbitrary::Arbitrary>::size_hint(depth)
218+
}
219+
}
220+
221+
// Test if this id is a reserved JSON schema keyword.
222+
// Issues:
223+
// https://github.com/cedar-policy/cedar/issues/1070
224+
// https://github.com/cedar-policy/cedar/issues/1139
225+
pub(crate) fn is_reserved_schema_keyword(id: &UnreservedId) -> bool {
226+
matches!(
227+
id.as_ref(),
228+
"Bool" | "Boolean" | "Entity" | "Extension" | "Long" | "Record" | "Set" | "String"
229+
)
230+
}
231+
232+
/// Deserialize a [`CommonTypeId`]
233+
impl<'de> Deserialize<'de> for CommonTypeId {
234+
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
235+
where
236+
D: Deserializer<'de>,
237+
{
238+
UnreservedId::deserialize(deserializer).and_then(|id| {
239+
if is_reserved_schema_keyword(&id) {
240+
Err(serde::de::Error::custom(format!(
241+
"Used reserved schema keyword: {id} "
242+
)))
243+
} else {
244+
Ok(Self(id))
245+
}
246+
})
247+
}
248+
}
249+
175250
/// A single namespace definition from a Fragment.
176251
/// This is composed of common types, entity types, and action definitions.
177252
///
@@ -194,7 +269,7 @@ pub struct NamespaceDefinition<N> {
194269
#[serde(default)]
195270
#[serde(skip_serializing_if = "HashMap::is_empty")]
196271
#[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
197-
pub common_types: HashMap<UnreservedId, Type<N>>,
272+
pub common_types: HashMap<CommonTypeId, Type<N>>,
198273
#[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
199274
pub entity_types: HashMap<UnreservedId, EntityType<N>>,
200275
#[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]

0 commit comments

Comments
 (0)