Skip to content

Commit eabe452

Browse files
Fix 1176 (#1177) (#1191)
Signed-off-by: Andrew Wells <[email protected]>
1 parent 9aafdfe commit eabe452

File tree

3 files changed

+436
-27
lines changed

3 files changed

+436
-27
lines changed

cedar-policy-core/src/entities/conformance.rs

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
* limitations under the License.
1515
*/
1616

17+
use std::collections::BTreeMap;
18+
1719
use super::{
1820
schematype_of_restricted_expr, EntityTypeDescription, GetSchemaTypeError,
1921
HeterogeneousSetError, Schema, SchemaType, TypeMismatchError,
@@ -291,6 +293,60 @@ pub fn typecheck_value_against_schematype(
291293
}
292294
}
293295

296+
/// Check whether the given `RestrictedExpr` is a valid instance of `SchemaType`
297+
pub fn does_restricted_expr_implement_schematype(
298+
expr: BorrowedRestrictedExpr<'_>,
299+
expected_ty: &SchemaType,
300+
) -> bool {
301+
use SchemaType::*;
302+
303+
match expected_ty {
304+
Bool => expr.as_bool().is_some(),
305+
Long => expr.as_long().is_some(),
306+
String => expr.as_string().is_some(),
307+
EmptySet => expr.as_set_elements().is_some_and(|e| e.count() == 0),
308+
Set { .. } if expr.as_set_elements().is_some_and(|e| e.count() == 0) => true,
309+
Set { element_ty: elty } => match expr.as_set_elements() {
310+
Some(mut els) => els.all(|e| does_restricted_expr_implement_schematype(e, elty)),
311+
None => false,
312+
},
313+
Record { attrs, open_attrs } => match expr.as_record_pairs() {
314+
Some(pairs) => {
315+
let pairs_map: BTreeMap<&SmolStr, BorrowedRestrictedExpr<'_>> = pairs.collect();
316+
let all_req_schema_attrs_in_record = attrs.iter().all(|(k, v)| {
317+
!v.required
318+
|| match pairs_map.get(k) {
319+
Some(inner_e) => {
320+
does_restricted_expr_implement_schematype(*inner_e, &v.attr_type)
321+
}
322+
None => false,
323+
}
324+
});
325+
let all_rec_attrs_match_schema =
326+
pairs_map.iter().all(|(k, inner_e)| match attrs.get(*k) {
327+
Some(sch_ty) => {
328+
does_restricted_expr_implement_schematype(*inner_e, &sch_ty.attr_type)
329+
}
330+
None => *open_attrs,
331+
});
332+
all_rec_attrs_match_schema && all_req_schema_attrs_in_record
333+
}
334+
None => false,
335+
},
336+
Extension { name } => match expr.as_extn_fn_call() {
337+
Some((actual_name, _)) => match name.id.as_ref() {
338+
"ipaddr" => actual_name.id.as_ref() == "ip",
339+
_ => name == actual_name,
340+
},
341+
None => false,
342+
},
343+
Entity { ty } => match expr.as_euid() {
344+
Some(actual_euid) => actual_euid.entity_type() == ty,
345+
None => false,
346+
},
347+
}
348+
}
349+
294350
/// Check whether the given `RestrictedExpr` typechecks with the given `SchemaType`.
295351
/// If the typecheck passes, return `Ok(())`.
296352
/// If the typecheck fails, return an appropriate `Err`.
@@ -299,23 +355,15 @@ pub fn typecheck_restricted_expr_against_schematype(
299355
expected_ty: &SchemaType,
300356
extensions: Extensions<'_>,
301357
) -> Result<(), TypecheckError> {
302-
// TODO(#440): instead of computing the `SchemaType` of `expr` and then
303-
// checking whether the schematypes are "consistent", wouldn't it be less
304-
// confusing, more efficient, and maybe even more precise to just typecheck
305-
// directly?
358+
if does_restricted_expr_implement_schematype(expr, expected_ty) {
359+
return Ok(());
360+
}
306361
match schematype_of_restricted_expr(expr, extensions) {
307-
Ok(actual_ty) => {
308-
if actual_ty.is_consistent_with(expected_ty) {
309-
// typecheck passes
310-
Ok(())
311-
} else {
312-
Err(TypecheckError::TypeMismatch(TypeMismatchError {
313-
expected: Box::new(expected_ty.clone()),
314-
actual_ty: Some(Box::new(actual_ty)),
315-
actual_val: Either::Right(Box::new(expr.to_owned())),
316-
}))
317-
}
318-
}
362+
Ok(actual_ty) => Err(TypecheckError::TypeMismatch(TypeMismatchError {
363+
expected: Box::new(expected_ty.clone()),
364+
actual_ty: Some(Box::new(actual_ty)),
365+
actual_val: Either::Right(Box::new(expr.to_owned())),
366+
})),
319367
Err(GetSchemaTypeError::UnknownInsufficientTypeInfo { .. }) => {
320368
// in this case we just don't have the information to know whether
321369
// the attribute value (an unknown) matches the expected type.

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

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@ pub enum SchemaType {
6767
#[derive(Debug, PartialEq, Eq, Clone)]
6868
pub struct AttributeType {
6969
/// Type of the attribute
70-
attr_type: SchemaType,
70+
pub(crate) attr_type: SchemaType,
7171
/// Is the attribute required
72-
required: bool,
72+
pub(crate) required: bool,
7373
}
7474

7575
impl SchemaType {
@@ -313,11 +313,8 @@ pub struct HeterogeneousSetError {
313313
/// required or optional.
314314
/// This function, when given a record that has keys A, B, and C, will return a
315315
/// `SchemaType` where A, B, and C are all marked as optional attributes, but no
316-
/// other attributes are possible.
317-
/// That is, this assumes that all existing attributes are optional, but that no
318-
/// other optional attributes are possible.
319-
/// Compared to marking A, B, and C as required, this allows the returned
320-
/// `SchemaType` to `is_consistent_with()` more types.
316+
/// other attributes are possible. This maximized flexibility while avoiding
317+
/// heterogeneous sets.
321318
///
322319
/// This function may return `GetSchemaTypeError`, but should never return
323320
/// `NontrivialResidual`, because `RestrictedExpr`s can't contain nontrivial
@@ -341,9 +338,8 @@ pub fn schematype_of_restricted_expr(
341338
BorrowedRestrictedExpr::new_unchecked(v), // assuming the invariant holds for the record as a whole, it will also hold for each attribute value
342339
extensions,
343340
)?;
344-
// we can't know if the attribute is required or optional,
345-
// but marking it optional is more flexible -- allows the
346-
// attribute type to `is_consistent_with()` more types
341+
// We can't know if the attribute is required or optional.
342+
// Keep as optional to minimize heterogeneous sets.
347343
Ok((k.clone(), AttributeType::optional(attr_type)))
348344
}).collect::<Result<HashMap<_,_>, GetSchemaTypeError>>()?,
349345
open_attrs: false,

0 commit comments

Comments
 (0)