1#![allow(
19 clippy::missing_panics_doc,
20 clippy::missing_errors_doc,
21 clippy::similar_names,
22 clippy::result_large_err, reason = "this module doesn't currently comply with these lints"
24)]
25
26mod id;
27use cedar_policy_core::pst::error_body;
28#[cfg(feature = "entity-manifest")]
29use cedar_policy_core::validator::entity_manifest;
30#[cfg(feature = "entity-manifest")]
32pub use cedar_policy_core::validator::entity_manifest::{
33 AccessTrie, EntityManifest, EntityRoot, Fields, RootAccessTrie,
34};
35use cedar_policy_core::validator::json_schema;
36use cedar_policy_core::validator::typecheck::{PolicyCheck, Typechecker};
37pub use id::*;
38
39#[cfg(feature = "deprecated-schema-compat")]
40mod deprecated_schema_compat;
41
42mod err;
43pub use err::*;
44
45#[cfg(feature = "tpe")]
46mod tpe;
47#[cfg(feature = "tpe")]
48pub use tpe::*;
49
50pub use ast::Effect;
51pub use authorizer::Decision;
52#[cfg(feature = "partial-eval")]
53use cedar_policy_core::ast::BorrowedRestrictedExpr;
54use cedar_policy_core::ast::{self, RequestSchema, RestrictedExpr};
55use cedar_policy_core::authorizer::{self};
56use cedar_policy_core::entities::{ContextSchema, Dereference};
57use cedar_policy_core::est::{self, TemplateLink};
58use cedar_policy_core::evaluator::Evaluator;
59#[cfg(feature = "partial-eval")]
60use cedar_policy_core::evaluator::RestrictedEvaluator;
61use cedar_policy_core::extensions::Extensions;
62use cedar_policy_core::parser;
63pub use cedar_policy_core::pst;
64use cedar_policy_core::FromNormalizedStr;
65use itertools::{Either, Itertools};
66use linked_hash_map::LinkedHashMap;
67use miette::Diagnostic;
68use ref_cast::RefCast;
69use serde::{Deserialize, Serialize};
70use smol_str::SmolStr;
71use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
72use std::io::Read;
73use std::str::FromStr;
74use std::sync::Arc;
75
76#[expect(
77 clippy::unwrap_used,
78 reason = "`CARGO_PKG_VERSION` should return a valid SemVer version string"
79)]
80pub(crate) mod version {
81 use semver::Version;
82 use std::sync::LazyLock;
83
84 static SDK_VERSION: LazyLock<Version> =
86 LazyLock::new(|| env!("CARGO_PKG_VERSION").parse().unwrap());
87 static LANG_VERSION: LazyLock<Version> = LazyLock::new(|| Version::new(4, 5, 0));
90
91 pub fn get_sdk_version() -> Version {
93 SDK_VERSION.clone()
94 }
95 pub fn get_lang_version() -> Version {
97 LANG_VERSION.clone()
98 }
99}
100
101#[repr(transparent)]
103#[derive(Debug, Clone, PartialEq, Eq, RefCast, Hash)]
104pub struct Entity(pub(crate) ast::Entity);
105
106#[doc(hidden)] impl AsRef<ast::Entity> for Entity {
108 fn as_ref(&self) -> &ast::Entity {
109 &self.0
110 }
111}
112
113#[doc(hidden)]
114impl From<ast::Entity> for Entity {
115 fn from(entity: ast::Entity) -> Self {
116 Self(entity)
117 }
118}
119
120impl Entity {
121 pub fn new(
143 uid: EntityUid,
144 attrs: HashMap<String, RestrictedExpression>,
145 parents: HashSet<EntityUid>,
146 ) -> Result<Self, EntityAttrEvaluationError> {
147 Self::new_with_tags(uid, attrs, parents, [])
148 }
149
150 pub fn new_no_attrs(uid: EntityUid, parents: HashSet<EntityUid>) -> Self {
155 Self(ast::Entity::new_with_attr_partial_value(
158 uid.into(),
159 [],
160 HashSet::new(),
161 parents.into_iter().map(EntityUid::into).collect(),
162 [],
163 ))
164 }
165
166 pub fn new_with_tags(
171 uid: EntityUid,
172 attrs: impl IntoIterator<Item = (String, RestrictedExpression)>,
173 parents: impl IntoIterator<Item = EntityUid>,
174 tags: impl IntoIterator<Item = (String, RestrictedExpression)>,
175 ) -> Result<Self, EntityAttrEvaluationError> {
176 Ok(Self(ast::Entity::new(
179 uid.into(),
180 attrs.into_iter().map(|(k, v)| (k.into(), v.0)),
181 HashSet::new(),
182 parents.into_iter().map(EntityUid::into).collect(),
183 tags.into_iter().map(|(k, v)| (k.into(), v.0)),
184 Extensions::all_available(),
185 )?))
186 }
187
188 pub fn with_uid(uid: EntityUid) -> Self {
199 Self(ast::Entity::with_uid(uid.into()))
200 }
201
202 pub fn deep_eq(&self, other: &Self) -> bool {
211 self.0.deep_eq(&other.0)
212 }
213
214 pub fn uid(&self) -> EntityUid {
225 self.0.uid().clone().into()
226 }
227
228 pub fn attr(&self, attr: &str) -> Option<Result<EvalResult, PartialValueToValueError>> {
249 match ast::Value::try_from(self.0.get(attr)?.clone()) {
250 Ok(v) => Some(Ok(EvalResult::from(v))),
251 Err(e) => Some(Err(e)),
252 }
253 }
254
255 pub fn attrs(
260 &self,
261 ) -> impl Iterator<Item = (&str, Result<EvalResult, PartialValueToValueError>)> {
262 self.0.attrs().map(|(k, v)| {
263 (
264 k.as_ref(),
265 ast::Value::try_from(v.clone()).map(EvalResult::from),
266 )
267 })
268 }
269
270 pub fn tag(&self, tag: &str) -> Option<Result<EvalResult, PartialValueToValueError>> {
275 match ast::Value::try_from(self.0.get_tag(tag)?.clone()) {
276 Ok(v) => Some(Ok(EvalResult::from(v))),
277 Err(e) => Some(Err(e)),
278 }
279 }
280
281 pub fn tags(
286 &self,
287 ) -> impl Iterator<Item = (&str, Result<EvalResult, PartialValueToValueError>)> {
288 self.0.tags().map(|(k, v)| {
289 (
290 k.as_ref(),
291 ast::Value::try_from(v.clone()).map(EvalResult::from),
292 )
293 })
294 }
295
296 pub fn into_inner(
298 self,
299 ) -> (
300 EntityUid,
301 HashMap<String, RestrictedExpression>,
302 HashSet<EntityUid>,
303 ) {
304 let (uid, attrs, ancestors, mut parents, _) = self.0.into_inner();
305 parents.extend(ancestors);
306
307 let attrs = attrs
308 .into_iter()
309 .map(|(k, v)| {
310 (
311 k.to_string(),
312 match v {
313 ast::PartialValue::Value(val) => {
314 RestrictedExpression(ast::RestrictedExpr::from(val))
315 }
316 ast::PartialValue::Residual(exp) => {
317 RestrictedExpression(ast::RestrictedExpr::new_unchecked(exp))
318 }
319 },
320 )
321 })
322 .collect();
323
324 (
325 uid.into(),
326 attrs,
327 parents.into_iter().map(Into::into).collect(),
328 )
329 }
330
331 pub fn from_json_value(
334 value: serde_json::Value,
335 schema: Option<&Schema>,
336 ) -> Result<Self, EntitiesError> {
337 let schema = schema.map(|s| cedar_policy_core::validator::CoreSchema::new(&s.0));
338 let eparser = cedar_policy_core::entities::EntityJsonParser::new(
339 schema.as_ref(),
340 Extensions::all_available(),
341 cedar_policy_core::entities::TCComputation::ComputeNow,
342 );
343 eparser.single_from_json_value(value).map(Self)
344 }
345
346 pub fn from_json_str(
349 src: impl AsRef<str>,
350 schema: Option<&Schema>,
351 ) -> Result<Self, EntitiesError> {
352 let schema = schema.map(|s| cedar_policy_core::validator::CoreSchema::new(&s.0));
353 let eparser = cedar_policy_core::entities::EntityJsonParser::new(
354 schema.as_ref(),
355 Extensions::all_available(),
356 cedar_policy_core::entities::TCComputation::ComputeNow,
357 );
358 eparser.single_from_json_str(src).map(Self)
359 }
360
361 pub fn from_json_file(f: impl Read, schema: Option<&Schema>) -> Result<Self, EntitiesError> {
364 let schema = schema.map(|s| cedar_policy_core::validator::CoreSchema::new(&s.0));
365 let eparser = cedar_policy_core::entities::EntityJsonParser::new(
366 schema.as_ref(),
367 Extensions::all_available(),
368 cedar_policy_core::entities::TCComputation::ComputeNow,
369 );
370 eparser.single_from_json_file(f).map(Self)
371 }
372
373 pub fn write_to_json(&self, f: impl std::io::Write) -> Result<(), EntitiesError> {
381 self.0.write_to_json(f)
382 }
383
384 pub fn to_json_value(&self) -> Result<serde_json::Value, EntitiesError> {
392 self.0.to_json_value()
393 }
394
395 pub fn to_json_string(&self) -> Result<String, EntitiesError> {
403 self.0.to_json_string()
404 }
405}
406
407impl std::fmt::Display for Entity {
408 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
409 write!(f, "{}", self.0)
410 }
411}
412
413#[repr(transparent)]
416#[derive(Debug, Clone, Default, PartialEq, Eq, RefCast)]
417pub struct Entities(pub(crate) cedar_policy_core::entities::Entities);
418
419#[doc(hidden)] impl AsRef<cedar_policy_core::entities::Entities> for Entities {
421 fn as_ref(&self) -> &cedar_policy_core::entities::Entities {
422 &self.0
423 }
424}
425
426#[doc(hidden)]
427impl From<cedar_policy_core::entities::Entities> for Entities {
428 fn from(entities: cedar_policy_core::entities::Entities) -> Self {
429 Self(entities)
430 }
431}
432
433use entities_errors::EntitiesError;
434
435impl Entities {
436 pub fn empty() -> Self {
443 Self(cedar_policy_core::entities::Entities::new())
444 }
445
446 pub fn get(&self, uid: &EntityUid) -> Option<&Entity> {
448 match self.0.entity(uid.as_ref()) {
449 Dereference::Residual(_) | Dereference::NoSuchEntity => None,
450 Dereference::Data(e) => Some(Entity::ref_cast(e)),
451 }
452 }
453
454 #[doc = include_str!("../experimental_warning.md")]
458 #[must_use]
459 #[cfg(feature = "partial-eval")]
460 pub fn partial(self) -> Self {
461 Self(self.0.partial())
462 }
463
464 pub fn iter(&self) -> impl Iterator<Item = &Entity> {
466 self.0.iter().map(Entity::ref_cast)
467 }
468
469 pub fn deep_eq(&self, other: &Self) -> bool {
475 self.0.deep_eq(&other.0)
476 }
477
478 pub fn from_entities(
497 entities: impl IntoIterator<Item = Entity>,
498 schema: Option<&Schema>,
499 ) -> Result<Self, EntitiesError> {
500 cedar_policy_core::entities::Entities::from_entities(
501 entities.into_iter().map(|e| e.0),
502 schema
503 .map(|s| cedar_policy_core::validator::CoreSchema::new(&s.0))
504 .as_ref(),
505 cedar_policy_core::entities::TCComputation::ComputeNow,
506 Extensions::all_available(),
507 )
508 .map(Entities)
509 }
510
511 pub fn add_entities(
528 self,
529 entities: impl IntoIterator<Item = Entity>,
530 schema: Option<&Schema>,
531 ) -> Result<Self, EntitiesError> {
532 Ok(Self(
533 self.0.add_entities(
534 entities.into_iter().map(|e| Arc::new(e.0)),
535 schema
536 .map(|s| cedar_policy_core::validator::CoreSchema::new(&s.0))
537 .as_ref(),
538 cedar_policy_core::entities::TCComputation::ComputeNow,
539 Extensions::all_available(),
540 )?,
541 ))
542 }
543
544 pub fn remove_entities(
551 self,
552 entity_ids: impl IntoIterator<Item = EntityUid>,
553 ) -> Result<Self, EntitiesError> {
554 Ok(Self(self.0.remove_entities(
555 entity_ids.into_iter().map(|euid| euid.0),
556 cedar_policy_core::entities::TCComputation::ComputeNow,
557 )?))
558 }
559
560 pub fn upsert_entities(
575 self,
576 entities: impl IntoIterator<Item = Entity>,
577 schema: Option<&Schema>,
578 ) -> Result<Self, EntitiesError> {
579 Ok(Self(
580 self.0.upsert_entities(
581 entities.into_iter().map(|e| Arc::new(e.0)),
582 schema
583 .map(|s| cedar_policy_core::validator::CoreSchema::new(&s.0))
584 .as_ref(),
585 cedar_policy_core::entities::TCComputation::ComputeNow,
586 Extensions::all_available(),
587 )?,
588 ))
589 }
590
591 pub fn add_entities_from_json_str(
612 self,
613 json: &str,
614 schema: Option<&Schema>,
615 ) -> Result<Self, EntitiesError> {
616 let schema = schema.map(|s| cedar_policy_core::validator::CoreSchema::new(&s.0));
617 let eparser = cedar_policy_core::entities::EntityJsonParser::new(
618 schema.as_ref(),
619 Extensions::all_available(),
620 cedar_policy_core::entities::TCComputation::ComputeNow,
621 );
622 let new_entities = eparser.iter_from_json_str(json)?.map(Arc::new);
623 Ok(Self(self.0.add_entities(
624 new_entities,
625 schema.as_ref(),
626 cedar_policy_core::entities::TCComputation::ComputeNow,
627 Extensions::all_available(),
628 )?))
629 }
630
631 pub fn add_entities_from_json_value(
652 self,
653 json: serde_json::Value,
654 schema: Option<&Schema>,
655 ) -> Result<Self, EntitiesError> {
656 let schema = schema.map(|s| cedar_policy_core::validator::CoreSchema::new(&s.0));
657 let eparser = cedar_policy_core::entities::EntityJsonParser::new(
658 schema.as_ref(),
659 Extensions::all_available(),
660 cedar_policy_core::entities::TCComputation::ComputeNow,
661 );
662 let new_entities = eparser.iter_from_json_value(json)?.map(Arc::new);
663 Ok(Self(self.0.add_entities(
664 new_entities,
665 schema.as_ref(),
666 cedar_policy_core::entities::TCComputation::ComputeNow,
667 Extensions::all_available(),
668 )?))
669 }
670
671 pub fn add_entities_from_json_file(
693 self,
694 json: impl std::io::Read,
695 schema: Option<&Schema>,
696 ) -> Result<Self, EntitiesError> {
697 let schema = schema.map(|s| cedar_policy_core::validator::CoreSchema::new(&s.0));
698 let eparser = cedar_policy_core::entities::EntityJsonParser::new(
699 schema.as_ref(),
700 Extensions::all_available(),
701 cedar_policy_core::entities::TCComputation::ComputeNow,
702 );
703 let new_entities = eparser.iter_from_json_file(json)?.map(Arc::new);
704 Ok(Self(self.0.add_entities(
705 new_entities,
706 schema.as_ref(),
707 cedar_policy_core::entities::TCComputation::ComputeNow,
708 Extensions::all_available(),
709 )?))
710 }
711
712 pub fn from_json_str(json: &str, schema: Option<&Schema>) -> Result<Self, EntitiesError> {
763 let schema = schema.map(|s| cedar_policy_core::validator::CoreSchema::new(&s.0));
764 let eparser = cedar_policy_core::entities::EntityJsonParser::new(
765 schema.as_ref(),
766 Extensions::all_available(),
767 cedar_policy_core::entities::TCComputation::ComputeNow,
768 );
769 eparser.from_json_str(json).map(Entities)
770 }
771
772 pub fn from_json_value(
818 json: serde_json::Value,
819 schema: Option<&Schema>,
820 ) -> Result<Self, EntitiesError> {
821 let schema = schema.map(|s| cedar_policy_core::validator::CoreSchema::new(&s.0));
822 let eparser = cedar_policy_core::entities::EntityJsonParser::new(
823 schema.as_ref(),
824 Extensions::all_available(),
825 cedar_policy_core::entities::TCComputation::ComputeNow,
826 );
827 eparser.from_json_value(json).map(Entities)
828 }
829
830 pub fn from_json_file(
854 json: impl std::io::Read,
855 schema: Option<&Schema>,
856 ) -> Result<Self, EntitiesError> {
857 let schema = schema.map(|s| cedar_policy_core::validator::CoreSchema::new(&s.0));
858 let eparser = cedar_policy_core::entities::EntityJsonParser::new(
859 schema.as_ref(),
860 Extensions::all_available(),
861 cedar_policy_core::entities::TCComputation::ComputeNow,
862 );
863 eparser.from_json_file(json).map(Entities)
864 }
865
866 pub fn is_ancestor_of(&self, a: &EntityUid, b: &EntityUid) -> bool {
869 match self.0.entity(b.as_ref()) {
870 Dereference::Data(b) => b.is_descendant_of(a.as_ref()),
871 _ => a == b, }
873 }
874
875 pub fn ancestors<'a>(
878 &'a self,
879 euid: &EntityUid,
880 ) -> Option<impl Iterator<Item = &'a EntityUid>> {
881 let entity = match self.0.entity(euid.as_ref()) {
882 Dereference::Residual(_) | Dereference::NoSuchEntity => None,
883 Dereference::Data(e) => Some(e),
884 }?;
885 Some(entity.ancestors().map(EntityUid::ref_cast))
886 }
887
888 pub fn len(&self) -> usize {
890 self.0.len()
891 }
892
893 pub fn is_empty(&self) -> bool {
895 self.0.is_empty()
896 }
897
898 pub fn write_to_json(&self, f: impl std::io::Write) -> std::result::Result<(), EntitiesError> {
906 self.0.write_to_json(f)
907 }
908
909 pub fn to_json_value(&self) -> Result<serde_json::Value, EntitiesError> {
917 self.0.to_json_value()
918 }
919
920 #[doc = include_str!("../experimental_warning.md")]
921 pub fn to_dot_str(&self) -> String {
925 let mut dot_str = String::new();
926 #[expect(clippy::unwrap_used, reason = "writing to a String cannot fail")]
927 self.0.to_dot_str(&mut dot_str).unwrap();
928 dot_str
929 }
930}
931
932pub fn validate_scope_variables(
939 principal: &EntityUid,
940 action: &EntityUid,
941 resource: &EntityUid,
942 schema: &Schema,
943) -> std::result::Result<(), RequestValidationError> {
944 Ok(RequestSchema::validate_scope_variables(
945 &schema.0,
946 Some(&principal.0),
947 Some(&action.0),
948 Some(&resource.0),
949 )?)
950}
951
952pub mod entities {
954
955 #[derive(Debug)]
957 pub struct IntoIter {
958 pub(super) inner: <cedar_policy_core::entities::Entities as IntoIterator>::IntoIter,
959 }
960
961 impl Iterator for IntoIter {
962 type Item = super::Entity;
963
964 fn next(&mut self) -> Option<Self::Item> {
965 self.inner.next().map(super::Entity)
966 }
967 fn size_hint(&self) -> (usize, Option<usize>) {
968 self.inner.size_hint()
969 }
970 }
971}
972
973impl IntoIterator for Entities {
974 type Item = Entity;
975 type IntoIter = entities::IntoIter;
976
977 fn into_iter(self) -> Self::IntoIter {
978 Self::IntoIter {
979 inner: self.0.into_iter(),
980 }
981 }
982}
983
984#[repr(transparent)]
986#[derive(Debug, Clone, RefCast)]
987pub struct Authorizer(authorizer::Authorizer);
988
989#[doc(hidden)] impl AsRef<authorizer::Authorizer> for Authorizer {
991 fn as_ref(&self) -> &authorizer::Authorizer {
992 &self.0
993 }
994}
995
996impl Default for Authorizer {
997 fn default() -> Self {
998 Self::new()
999 }
1000}
1001
1002impl Authorizer {
1003 pub fn new() -> Self {
1060 Self(authorizer::Authorizer::new())
1061 }
1062
1063 pub fn is_authorized(&self, r: &Request, p: &PolicySet, e: &Entities) -> Response {
1117 self.0.is_authorized(r.0.clone(), &p.ast, &e.0).into()
1118 }
1119
1120 #[doc = include_str!("../experimental_warning.md")]
1125 #[cfg(feature = "partial-eval")]
1126 pub fn is_authorized_partial(
1127 &self,
1128 query: &Request,
1129 policy_set: &PolicySet,
1130 entities: &Entities,
1131 ) -> PartialResponse {
1132 let response = self
1133 .0
1134 .is_authorized_core(query.0.clone(), &policy_set.ast, &entities.0);
1135 PartialResponse(response)
1136 }
1137}
1138
1139#[derive(Debug, PartialEq, Eq, Clone)]
1141pub struct Response {
1142 pub(crate) decision: Decision,
1144 pub(crate) diagnostics: Diagnostics,
1146}
1147
1148#[doc = include_str!("../experimental_warning.md")]
1153#[cfg(feature = "partial-eval")]
1154#[repr(transparent)]
1155#[derive(Debug, Clone, RefCast)]
1156pub struct PartialResponse(cedar_policy_core::authorizer::PartialResponse);
1157
1158#[cfg(feature = "partial-eval")]
1159impl PartialResponse {
1160 pub fn decision(&self) -> Option<Decision> {
1163 self.0.decision()
1164 }
1165
1166 pub fn concretize(self) -> Response {
1169 self.0.concretize().into()
1170 }
1171
1172 pub fn definitely_satisfied(&self) -> impl Iterator<Item = Policy> + '_ {
1175 self.0.definitely_satisfied().map(Policy::from_ast)
1176 }
1177
1178 pub fn definitely_errored(&self) -> impl Iterator<Item = &PolicyId> {
1180 self.0.definitely_errored().map(PolicyId::ref_cast)
1181 }
1182
1183 pub fn may_be_determining(&self) -> impl Iterator<Item = Policy> + '_ {
1191 self.0.may_be_determining().map(Policy::from_ast)
1192 }
1193
1194 pub fn must_be_determining(&self) -> impl Iterator<Item = Policy> + '_ {
1202 self.0.must_be_determining().map(Policy::from_ast)
1203 }
1204
1205 pub fn nontrivial_residuals(&'_ self) -> impl Iterator<Item = Policy> + '_ {
1210 self.0.nontrivial_residuals().map(Policy::from_ast)
1211 }
1212
1213 pub fn all_residuals(&'_ self) -> impl Iterator<Item = Policy> + '_ {
1218 self.0.all_residuals().map(Policy::from_ast)
1219 }
1220
1221 pub fn unknown_entities(&self) -> HashSet<EntityUid> {
1223 let mut entity_uids = HashSet::new();
1224 for policy in self.0.all_residuals() {
1225 entity_uids.extend(policy.unknown_entities().into_iter().map(Into::into));
1226 }
1227 entity_uids
1228 }
1229
1230 pub fn get(&self, id: &PolicyId) -> Option<Policy> {
1232 self.0.get(id.as_ref()).map(Policy::from_ast)
1233 }
1234
1235 #[expect(
1237 clippy::needless_pass_by_value,
1238 reason = "don't want to change signature of deprecated public function"
1239 )]
1240 #[deprecated = "use reauthorize_with_bindings"]
1241 pub fn reauthorize(
1242 &self,
1243 mapping: HashMap<SmolStr, RestrictedExpression>,
1244 auth: &Authorizer,
1245 es: &Entities,
1246 ) -> Result<Self, ReauthorizationError> {
1247 self.reauthorize_with_bindings(mapping.iter().map(|(k, v)| (k.as_str(), v)), auth, es)
1248 }
1249
1250 pub fn reauthorize_with_bindings<'m>(
1253 &self,
1254 mapping: impl IntoIterator<Item = (&'m str, &'m RestrictedExpression)>,
1255 auth: &Authorizer,
1256 es: &Entities,
1257 ) -> Result<Self, ReauthorizationError> {
1258 let exts = Extensions::all_available();
1259 let evaluator = RestrictedEvaluator::new(exts);
1260 let mapping = mapping
1261 .into_iter()
1262 .map(|(name, expr)| {
1263 evaluator
1264 .interpret(BorrowedRestrictedExpr::new_unchecked(expr.0.as_ref()))
1265 .map(|v| (name.into(), v))
1266 })
1267 .collect::<Result<HashMap<_, _>, EvaluationError>>()?;
1268 let r = self.0.reauthorize(&mapping, &auth.0, &es.0)?;
1269 Ok(Self(r))
1270 }
1271}
1272
1273#[cfg(feature = "partial-eval")]
1274#[doc(hidden)]
1275impl From<cedar_policy_core::authorizer::PartialResponse> for PartialResponse {
1276 fn from(pr: cedar_policy_core::authorizer::PartialResponse) -> Self {
1277 Self(pr)
1278 }
1279}
1280
1281#[derive(Debug, PartialEq, Eq, Clone)]
1283pub struct Diagnostics {
1284 reason: HashSet<PolicyId>,
1287 errors: Vec<AuthorizationError>,
1290}
1291
1292#[doc(hidden)]
1293impl From<authorizer::Diagnostics> for Diagnostics {
1294 fn from(diagnostics: authorizer::Diagnostics) -> Self {
1295 Self {
1296 reason: diagnostics.reason.into_iter().map(PolicyId::new).collect(),
1297 errors: diagnostics.errors.into_iter().map(Into::into).collect(),
1298 }
1299 }
1300}
1301
1302impl Diagnostics {
1303 pub fn reason(&self) -> impl Iterator<Item = &PolicyId> {
1360 self.reason.iter()
1361 }
1362
1363 pub fn errors(&self) -> impl Iterator<Item = &AuthorizationError> + '_ {
1419 self.errors.iter()
1420 }
1421
1422 pub(crate) fn into_components(
1424 self,
1425 ) -> (
1426 impl Iterator<Item = PolicyId>,
1427 impl Iterator<Item = AuthorizationError>,
1428 ) {
1429 (self.reason.into_iter(), self.errors.into_iter())
1430 }
1431}
1432
1433impl Response {
1434 pub fn new(
1436 decision: Decision,
1437 reason: HashSet<PolicyId>,
1438 errors: Vec<AuthorizationError>,
1439 ) -> Self {
1440 Self {
1441 decision,
1442 diagnostics: Diagnostics { reason, errors },
1443 }
1444 }
1445
1446 pub fn decision(&self) -> Decision {
1448 self.decision
1449 }
1450
1451 pub fn diagnostics(&self) -> &Diagnostics {
1453 &self.diagnostics
1454 }
1455}
1456
1457#[doc(hidden)]
1458impl From<authorizer::Response> for Response {
1459 fn from(a: authorizer::Response) -> Self {
1460 Self {
1461 decision: a.decision,
1462 diagnostics: a.diagnostics.into(),
1463 }
1464 }
1465}
1466
1467#[derive(Default, Eq, PartialEq, Copy, Clone, Debug, Serialize, Deserialize)]
1469#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
1470#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
1471#[serde(rename_all = "camelCase")]
1472#[non_exhaustive]
1473pub enum ValidationMode {
1474 #[default]
1477 Strict,
1478 #[doc = include_str!("../experimental_warning.md")]
1480 #[cfg(feature = "permissive-validate")]
1481 Permissive,
1482 #[doc = include_str!("../experimental_warning.md")]
1484 #[cfg(feature = "partial-validate")]
1485 Partial,
1486}
1487
1488#[doc(hidden)]
1489impl From<ValidationMode> for cedar_policy_core::validator::ValidationMode {
1490 fn from(mode: ValidationMode) -> Self {
1491 match mode {
1492 ValidationMode::Strict => Self::Strict,
1493 #[cfg(feature = "permissive-validate")]
1494 ValidationMode::Permissive => Self::Permissive,
1495 #[cfg(feature = "partial-validate")]
1496 ValidationMode::Partial => Self::Partial,
1497 }
1498 }
1499}
1500
1501#[repr(transparent)]
1503#[derive(Debug, Clone, RefCast)]
1504pub struct Validator(cedar_policy_core::validator::Validator);
1505
1506#[doc(hidden)] impl AsRef<cedar_policy_core::validator::Validator> for Validator {
1508 fn as_ref(&self) -> &cedar_policy_core::validator::Validator {
1509 &self.0
1510 }
1511}
1512
1513impl Validator {
1514 pub fn new(schema: Schema) -> Self {
1517 Self(cedar_policy_core::validator::Validator::new(schema.0))
1518 }
1519
1520 pub fn schema(&self) -> &Schema {
1522 RefCast::ref_cast(self.0.schema())
1523 }
1524
1525 pub fn validate(&self, pset: &PolicySet, mode: ValidationMode) -> ValidationResult {
1533 ValidationResult::from(self.0.validate(&pset.ast, mode.into()))
1534 }
1535
1536 pub fn validate_with_level(
1544 &self,
1545 pset: &PolicySet,
1546 mode: ValidationMode,
1547 max_deref_level: u32,
1548 ) -> ValidationResult {
1549 ValidationResult::from(
1550 self.0
1551 .validate_with_level(&pset.ast, mode.into(), max_deref_level),
1552 )
1553 }
1554}
1555
1556#[derive(Debug, Clone)]
1559pub struct SchemaFragment {
1560 value: cedar_policy_core::validator::ValidatorSchemaFragment<
1561 cedar_policy_core::validator::ConditionalName,
1562 cedar_policy_core::validator::ConditionalName,
1563 >,
1564 lossless:
1565 cedar_policy_core::validator::json_schema::Fragment<cedar_policy_core::validator::RawName>,
1566}
1567
1568#[doc(hidden)] impl
1570 AsRef<
1571 cedar_policy_core::validator::ValidatorSchemaFragment<
1572 cedar_policy_core::validator::ConditionalName,
1573 cedar_policy_core::validator::ConditionalName,
1574 >,
1575 > for SchemaFragment
1576{
1577 fn as_ref(
1578 &self,
1579 ) -> &cedar_policy_core::validator::ValidatorSchemaFragment<
1580 cedar_policy_core::validator::ConditionalName,
1581 cedar_policy_core::validator::ConditionalName,
1582 > {
1583 &self.value
1584 }
1585}
1586
1587#[doc(hidden)] impl
1589 TryFrom<
1590 cedar_policy_core::validator::json_schema::Fragment<cedar_policy_core::validator::RawName>,
1591 > for SchemaFragment
1592{
1593 type Error = SchemaError;
1594 fn try_from(
1595 json_frag: cedar_policy_core::validator::json_schema::Fragment<
1596 cedar_policy_core::validator::RawName,
1597 >,
1598 ) -> Result<Self, Self::Error> {
1599 Ok(Self {
1600 value: json_frag.clone().try_into()?,
1601 lossless: json_frag,
1602 })
1603 }
1604}
1605
1606fn get_annotation_by_key(
1607 annotations: &est::Annotations,
1608 annotation_key: impl AsRef<str>,
1609) -> Option<&str> {
1610 annotations
1611 .0
1612 .get(&annotation_key.as_ref().parse().ok()?)
1613 .map(|value| annotation_value_to_str_ref(value.as_ref()))
1614}
1615
1616fn annotation_value_to_str_ref(value: Option<&ast::Annotation>) -> &str {
1617 value.map_or("", |a| a.as_ref())
1618}
1619
1620fn annotations_to_pairs(annotations: &est::Annotations) -> impl Iterator<Item = (&str, &str)> {
1621 annotations
1622 .0
1623 .iter()
1624 .map(|(key, value)| (key.as_ref(), annotation_value_to_str_ref(value.as_ref())))
1625}
1626
1627impl SchemaFragment {
1628 pub fn namespace_annotations(
1634 &self,
1635 namespace: EntityNamespace,
1636 ) -> Option<impl Iterator<Item = (&str, &str)>> {
1637 self.lossless
1638 .0
1639 .get(&Some(namespace.0))
1640 .map(|ns_def| annotations_to_pairs(&ns_def.annotations))
1641 }
1642
1643 pub fn namespace_annotation(
1652 &self,
1653 namespace: EntityNamespace,
1654 annotation_key: impl AsRef<str>,
1655 ) -> Option<&str> {
1656 let ns = self.lossless.0.get(&Some(namespace.0))?;
1657 get_annotation_by_key(&ns.annotations, annotation_key)
1658 }
1659
1660 pub fn common_type_annotations(
1666 &self,
1667 namespace: Option<EntityNamespace>,
1668 ty: &str,
1669 ) -> Option<impl Iterator<Item = (&str, &str)>> {
1670 let ns_def = self.lossless.0.get(&namespace.map(|n| n.0))?;
1671 let ty = json_schema::CommonTypeId::new(ast::UnreservedId::from_normalized_str(ty).ok()?)
1672 .ok()?;
1673 ns_def
1674 .common_types
1675 .get(&ty)
1676 .map(|ty| annotations_to_pairs(&ty.annotations))
1677 }
1678
1679 pub fn common_type_annotation(
1688 &self,
1689 namespace: Option<EntityNamespace>,
1690 ty: &str,
1691 annotation_key: impl AsRef<str>,
1692 ) -> Option<&str> {
1693 let ns_def = self.lossless.0.get(&namespace.map(|n| n.0))?;
1694 let ty = json_schema::CommonTypeId::new(ast::UnreservedId::from_normalized_str(ty).ok()?)
1695 .ok()?;
1696 get_annotation_by_key(&ns_def.common_types.get(&ty)?.annotations, annotation_key)
1697 }
1698
1699 pub fn entity_type_annotations(
1705 &self,
1706 namespace: Option<EntityNamespace>,
1707 ty: &str,
1708 ) -> Option<impl Iterator<Item = (&str, &str)>> {
1709 let ns_def = self.lossless.0.get(&namespace.map(|n| n.0))?;
1710 let ty = ast::UnreservedId::from_normalized_str(ty).ok()?;
1711 ns_def
1712 .entity_types
1713 .get(&ty)
1714 .map(|ty| annotations_to_pairs(&ty.annotations))
1715 }
1716
1717 pub fn entity_type_annotation(
1726 &self,
1727 namespace: Option<EntityNamespace>,
1728 ty: &str,
1729 annotation_key: impl AsRef<str>,
1730 ) -> Option<&str> {
1731 let ns_def = self.lossless.0.get(&namespace.map(|n| n.0))?;
1732 let ty = ast::UnreservedId::from_normalized_str(ty).ok()?;
1733 get_annotation_by_key(&ns_def.entity_types.get(&ty)?.annotations, annotation_key)
1734 }
1735
1736 pub fn action_annotations(
1741 &self,
1742 namespace: Option<EntityNamespace>,
1743 id: &EntityId,
1744 ) -> Option<impl Iterator<Item = (&str, &str)>> {
1745 let ns_def = self.lossless.0.get(&namespace.map(|n| n.0))?;
1746 ns_def
1747 .actions
1748 .get(id.unescaped())
1749 .map(|a| annotations_to_pairs(&a.annotations))
1750 }
1751
1752 pub fn action_annotation(
1760 &self,
1761 namespace: Option<EntityNamespace>,
1762 id: &EntityId,
1763 annotation_key: impl AsRef<str>,
1764 ) -> Option<&str> {
1765 let ns_def = self.lossless.0.get(&namespace.map(|n| n.0))?;
1766 get_annotation_by_key(
1767 &ns_def.actions.get(id.unescaped())?.annotations,
1768 annotation_key,
1769 )
1770 }
1771
1772 pub fn namespaces(&self) -> impl Iterator<Item = Option<EntityNamespace>> + '_ {
1776 self.value.namespaces().filter_map(|ns| {
1777 match ns.map(|ns| ast::Name::try_from(ns.clone())) {
1778 Some(Ok(n)) => Some(Some(EntityNamespace(n))),
1779 None => Some(None), Some(Err(_)) => {
1781 None
1788 }
1789 }
1790 })
1791 }
1792
1793 pub fn from_json_str(src: &str) -> Result<Self, SchemaError> {
1796 let lossless = cedar_policy_core::validator::json_schema::Fragment::from_json_str(src)?;
1797 Ok(Self {
1798 value: lossless.clone().try_into()?,
1799 lossless,
1800 })
1801 }
1802
1803 pub fn from_json_value(json: serde_json::Value) -> Result<Self, SchemaError> {
1806 let lossless = cedar_policy_core::validator::json_schema::Fragment::from_json_value(json)?;
1807 Ok(Self {
1808 value: lossless.clone().try_into()?,
1809 lossless,
1810 })
1811 }
1812
1813 pub fn from_cedarschema_file(
1815 r: impl std::io::Read,
1816 ) -> Result<(Self, impl Iterator<Item = SchemaWarning>), CedarSchemaError> {
1817 let (lossless, warnings) =
1818 cedar_policy_core::validator::json_schema::Fragment::from_cedarschema_file(
1819 r,
1820 Extensions::all_available(),
1821 )?;
1822 Ok((
1823 Self {
1824 value: lossless.clone().try_into()?,
1825 lossless,
1826 },
1827 warnings,
1828 ))
1829 }
1830
1831 pub fn from_cedarschema_str(
1833 src: &str,
1834 ) -> Result<(Self, impl Iterator<Item = SchemaWarning>), CedarSchemaError> {
1835 let (lossless, warnings) =
1836 cedar_policy_core::validator::json_schema::Fragment::from_cedarschema_str(
1837 src,
1838 Extensions::all_available(),
1839 )?;
1840 Ok((
1841 Self {
1842 value: lossless.clone().try_into()?,
1843 lossless,
1844 },
1845 warnings,
1846 ))
1847 }
1848
1849 pub fn from_json_file(file: impl std::io::Read) -> Result<Self, SchemaError> {
1852 let lossless = cedar_policy_core::validator::json_schema::Fragment::from_json_file(file)?;
1853 Ok(Self {
1854 value: lossless.clone().try_into()?,
1855 lossless,
1856 })
1857 }
1858
1859 pub fn to_json_value(self) -> Result<serde_json::Value, SchemaError> {
1861 serde_json::to_value(self.lossless).map_err(|e| SchemaError::JsonSerialization(e.into()))
1862 }
1863
1864 pub fn to_json_string(&self) -> Result<String, SchemaError> {
1866 serde_json::to_string(&self.lossless).map_err(|e| SchemaError::JsonSerialization(e.into()))
1867 }
1868
1869 pub fn to_cedarschema(&self) -> Result<String, ToCedarSchemaError> {
1872 let str = self.lossless.to_cedarschema()?;
1873 Ok(str)
1874 }
1875}
1876
1877impl TryInto<Schema> for SchemaFragment {
1878 type Error = SchemaError;
1879
1880 fn try_into(self) -> Result<Schema, Self::Error> {
1884 Ok(Schema(
1885 cedar_policy_core::validator::ValidatorSchema::from_schema_fragments(
1886 [self.value],
1887 Extensions::all_available(),
1888 )?,
1889 ))
1890 }
1891}
1892
1893impl FromStr for SchemaFragment {
1894 type Err = CedarSchemaError;
1895 fn from_str(src: &str) -> Result<Self, Self::Err> {
1901 Self::from_cedarschema_str(src).map(|(frag, _)| frag)
1902 }
1903}
1904
1905#[repr(transparent)]
1907#[derive(Debug, Clone, RefCast)]
1908pub struct Schema(pub(crate) cedar_policy_core::validator::ValidatorSchema);
1909
1910#[doc(hidden)] impl AsRef<cedar_policy_core::validator::ValidatorSchema> for Schema {
1912 fn as_ref(&self) -> &cedar_policy_core::validator::ValidatorSchema {
1913 &self.0
1914 }
1915}
1916
1917#[doc(hidden)]
1918impl From<cedar_policy_core::validator::ValidatorSchema> for Schema {
1919 fn from(schema: cedar_policy_core::validator::ValidatorSchema) -> Self {
1920 Self(schema)
1921 }
1922}
1923
1924impl FromStr for Schema {
1925 type Err = CedarSchemaError;
1926
1927 fn from_str(schema_src: &str) -> Result<Self, Self::Err> {
1934 Self::from_cedarschema_str(schema_src).map(|(schema, _)| schema)
1935 }
1936}
1937
1938impl Schema {
1939 pub fn from_schema_fragments(
1944 fragments: impl IntoIterator<Item = SchemaFragment>,
1945 ) -> Result<Self, SchemaError> {
1946 Ok(Self(
1947 cedar_policy_core::validator::ValidatorSchema::from_schema_fragments(
1948 fragments.into_iter().map(|f| f.value),
1949 Extensions::all_available(),
1950 )?,
1951 ))
1952 }
1953
1954 pub fn from_json_value(json: serde_json::Value) -> Result<Self, SchemaError> {
1957 Ok(Self(
1958 cedar_policy_core::validator::ValidatorSchema::from_json_value(
1959 json,
1960 Extensions::all_available(),
1961 )?,
1962 ))
1963 }
1964
1965 pub fn from_json_str(json: &str) -> Result<Self, SchemaError> {
1968 Ok(Self(
1969 cedar_policy_core::validator::ValidatorSchema::from_json_str(
1970 json,
1971 Extensions::all_available(),
1972 )?,
1973 ))
1974 }
1975
1976 pub fn from_json_file(file: impl std::io::Read) -> Result<Self, SchemaError> {
1979 Ok(Self(
1980 cedar_policy_core::validator::ValidatorSchema::from_json_file(
1981 file,
1982 Extensions::all_available(),
1983 )?,
1984 ))
1985 }
1986
1987 pub fn from_cedarschema_file(
1989 file: impl std::io::Read,
1990 ) -> Result<(Self, impl Iterator<Item = SchemaWarning> + 'static), CedarSchemaError> {
1991 let (schema, warnings) =
1992 cedar_policy_core::validator::ValidatorSchema::from_cedarschema_file(
1993 file,
1994 Extensions::all_available(),
1995 )?;
1996 Ok((Self(schema), warnings))
1997 }
1998
1999 pub fn from_cedarschema_str(
2001 src: &str,
2002 ) -> Result<(Self, impl Iterator<Item = SchemaWarning>), CedarSchemaError> {
2003 let (schema, warnings) =
2004 cedar_policy_core::validator::ValidatorSchema::from_cedarschema_str(
2005 src,
2006 Extensions::all_available(),
2007 )?;
2008 Ok((Self(schema), warnings))
2009 }
2010
2011 pub fn action_entities(&self) -> Result<Entities, EntitiesError> {
2014 Ok(Entities(self.0.action_entities()?))
2015 }
2016
2017 pub fn principals(&self) -> impl Iterator<Item = &EntityTypeName> {
2042 self.0.principals().map(RefCast::ref_cast)
2043 }
2044
2045 pub fn resources(&self) -> impl Iterator<Item = &EntityTypeName> {
2069 self.0.resources().map(RefCast::ref_cast)
2070 }
2071
2072 pub fn principals_for_action(
2078 &self,
2079 action: &EntityUid,
2080 ) -> Option<impl Iterator<Item = &EntityTypeName>> {
2081 self.0
2082 .principals_for_action(&action.0)
2083 .map(|iter| iter.map(RefCast::ref_cast))
2084 }
2085
2086 pub fn resources_for_action(
2092 &self,
2093 action: &EntityUid,
2094 ) -> Option<impl Iterator<Item = &EntityTypeName>> {
2095 self.0
2096 .resources_for_action(&action.0)
2097 .map(|iter| iter.map(RefCast::ref_cast))
2098 }
2099
2100 pub fn request_envs(&self) -> impl Iterator<Item = RequestEnv> + '_ {
2103 self.0
2104 .unlinked_request_envs(cedar_policy_core::validator::ValidationMode::Strict)
2105 .map(Into::into)
2106 }
2107
2108 pub fn ancestors<'a>(
2114 &'a self,
2115 ty: &'a EntityTypeName,
2116 ) -> Option<impl Iterator<Item = &'a EntityTypeName> + 'a> {
2117 self.0
2118 .ancestors(&ty.0)
2119 .map(|iter| iter.map(RefCast::ref_cast))
2120 }
2121
2122 pub fn action_groups(&self) -> impl Iterator<Item = &EntityUid> {
2124 self.0.action_groups().map(RefCast::ref_cast)
2125 }
2126
2127 pub fn entity_types(&self) -> impl Iterator<Item = &EntityTypeName> {
2129 self.0
2130 .entity_types()
2131 .map(|ety| RefCast::ref_cast(ety.name()))
2132 }
2133
2134 pub fn actions(&self) -> impl Iterator<Item = &EntityUid> {
2136 self.0.actions().map(RefCast::ref_cast)
2137 }
2138
2139 pub fn actions_for_principal_and_resource<'a: 'b, 'b>(
2143 &'a self,
2144 principal_type: &'b EntityTypeName,
2145 resource_type: &'b EntityTypeName,
2146 ) -> impl Iterator<Item = &'a EntityUid> + 'b {
2147 self.0
2148 .actions_for_principal_and_resource(&principal_type.0, &resource_type.0)
2149 .map(RefCast::ref_cast)
2150 }
2151}
2152
2153pub fn schema_str_to_json_with_resolved_types(
2163 schema_str: &str,
2164) -> Result<(serde_json::Value, Vec<SchemaWarning>), CedarSchemaError> {
2165 let (json_schema_fragment, warnings) =
2167 json_schema::Fragment::from_cedarschema_str(schema_str, Extensions::all_available())
2168 .map_err(
2169 |e: cedar_policy_core::validator::CedarSchemaError| -> CedarSchemaError {
2170 e.into()
2171 },
2172 )?;
2173
2174 let warnings_as_schema_warnings: Vec<SchemaWarning> = warnings.collect();
2175
2176 let fully_resolved_fragment =
2178 match json_schema_fragment.to_internal_name_fragment_with_resolved_types() {
2179 Ok(fragment) => fragment,
2180 Err(e) => {
2181 return Err(e.into());
2183 }
2184 };
2185
2186 let json_value = serde_json::to_value(&fully_resolved_fragment).map_err(|e| {
2188 let schema_error = SchemaError::JsonSerialization(
2189 cedar_policy_core::validator::schema_errors::JsonSerializationError::from(e),
2190 );
2191 CedarSchemaError::Schema(schema_error)
2192 })?;
2193
2194 Ok((json_value, warnings_as_schema_warnings))
2195}
2196
2197#[cfg(test)]
2202mod test_schema_str_to_json_with_resolved_types {
2203 use super::*;
2204
2205 #[test]
2206 fn test_unresolved_type_error() {
2207 let schema_str = r#"entity User = { "name": MyName };"#;
2208
2209 let result = schema_str_to_json_with_resolved_types(schema_str);
2210
2211 match result {
2213 Ok(_) => panic!("Expected error but got success - MyName should not be resolved"),
2214 Err(CedarSchemaError::Schema(SchemaError::TypeNotDefined(type_not_defined_error))) => {
2215 let error_message = format!("{}", type_not_defined_error);
2217 assert!(
2218 error_message.contains("MyName"),
2219 "Expected error message to contain 'MyName', but got: {}",
2220 error_message
2221 );
2222
2223 assert!(
2225 error_message.contains("failed to resolve type"),
2226 "Expected error message to mention 'failed to resolve type', but got: {}",
2227 error_message
2228 );
2229 }
2230 Err(CedarSchemaError::Schema(other_schema_error)) => {
2231 panic!(
2232 "Expected TypeNotDefined error, but got different SchemaError: {:?}",
2233 other_schema_error
2234 );
2235 }
2236 Err(CedarSchemaError::Parse(parse_error)) => {
2237 panic!(
2238 "Expected TypeNotDefined error, but got parse error: {:?}",
2239 parse_error
2240 );
2241 }
2242 Err(CedarSchemaError::Io(io_error)) => {
2243 panic!(
2244 "Expected TypeNotDefined error, but got IO error: {:?}",
2245 io_error
2246 );
2247 }
2248 }
2249 }
2250
2251 #[test]
2252 fn test_successful_resolution() {
2253 let schema_str = r#"
2254 type MyName = String;
2255 entity User = { "name": MyName };
2256 "#;
2257
2258 let result = schema_str_to_json_with_resolved_types(schema_str);
2259
2260 match result {
2261 Ok((json_value, warnings)) => {
2262 assert!(json_value.is_object(), "Expected JSON object");
2264
2265 let json_str = serde_json::to_string(&json_value).unwrap();
2267 assert!(
2268 !json_str.contains("EntityOrCommon"),
2269 "JSON should not contain unresolved EntityOrCommon types: {}",
2270 json_str
2271 );
2272
2273 assert!(
2275 json_str.contains("MyName"),
2276 "JSON should contain resolved MyName type reference: {}",
2277 json_str
2278 );
2279
2280 assert_eq!(warnings.len(), 0, "Expected no warnings for valid schema");
2282 }
2283 Err(e) => panic!("Expected success but got error: {:?}", e),
2284 }
2285 }
2286}
2287#[derive(Debug, Clone)]
2290pub struct ValidationResult {
2291 validation_errors: Vec<ValidationError>,
2292 validation_warnings: Vec<ValidationWarning>,
2293}
2294
2295impl ValidationResult {
2296 pub fn validation_passed(&self) -> bool {
2300 self.validation_errors.is_empty()
2301 }
2302
2303 pub fn validation_passed_without_warnings(&self) -> bool {
2306 self.validation_errors.is_empty() && self.validation_warnings.is_empty()
2307 }
2308
2309 pub fn validation_errors(&self) -> impl Iterator<Item = &ValidationError> {
2311 self.validation_errors.iter()
2312 }
2313
2314 pub fn validation_warnings(&self) -> impl Iterator<Item = &ValidationWarning> {
2316 self.validation_warnings.iter()
2317 }
2318
2319 fn first_error_or_warning(&self) -> Option<&dyn Diagnostic> {
2320 self.validation_errors
2321 .first()
2322 .map(|e| e as &dyn Diagnostic)
2323 .or_else(|| {
2324 self.validation_warnings
2325 .first()
2326 .map(|w| w as &dyn Diagnostic)
2327 })
2328 }
2329
2330 pub(crate) fn into_errors_and_warnings(
2331 self,
2332 ) -> (
2333 impl Iterator<Item = ValidationError>,
2334 impl Iterator<Item = ValidationWarning>,
2335 ) {
2336 (
2337 self.validation_errors.into_iter(),
2338 self.validation_warnings.into_iter(),
2339 )
2340 }
2341}
2342
2343#[doc(hidden)]
2344impl From<cedar_policy_core::validator::ValidationResult> for ValidationResult {
2345 fn from(r: cedar_policy_core::validator::ValidationResult) -> Self {
2346 let (errors, warnings) = r.into_errors_and_warnings();
2347 Self {
2348 validation_errors: errors.map(ValidationError::from).collect(),
2349 validation_warnings: warnings.map(ValidationWarning::from).collect(),
2350 }
2351 }
2352}
2353
2354impl std::fmt::Display for ValidationResult {
2355 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2356 match self.first_error_or_warning() {
2357 Some(diagnostic) => write!(f, "{diagnostic}"),
2358 None => write!(f, "no errors or warnings"),
2359 }
2360 }
2361}
2362
2363impl std::error::Error for ValidationResult {
2364 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
2365 self.first_error_or_warning()
2366 .and_then(std::error::Error::source)
2367 }
2368
2369 fn description(&self) -> &str {
2370 #[expect(
2371 deprecated,
2372 reason = "description() is deprecated but we still want to forward it"
2373 )]
2374 self.first_error_or_warning()
2375 .map_or("no errors or warnings", std::error::Error::description)
2376 }
2377
2378 fn cause(&self) -> Option<&dyn std::error::Error> {
2379 #[expect(
2380 deprecated,
2381 reason = "cause() is deprecated but we still want to forward it"
2382 )]
2383 self.first_error_or_warning()
2384 .and_then(std::error::Error::cause)
2385 }
2386}
2387
2388impl Diagnostic for ValidationResult {
2392 fn related(&self) -> Option<Box<dyn Iterator<Item = &dyn Diagnostic> + '_>> {
2393 let mut related = self
2394 .validation_errors
2395 .iter()
2396 .map(|err| err as &dyn Diagnostic)
2397 .chain(
2398 self.validation_warnings
2399 .iter()
2400 .map(|warn| warn as &dyn Diagnostic),
2401 );
2402 related.next().map(move |first| match first.related() {
2403 Some(first_related) => Box::new(first_related.chain(related)),
2404 None => Box::new(related) as Box<dyn Iterator<Item = _>>,
2405 })
2406 }
2407
2408 fn severity(&self) -> Option<miette::Severity> {
2409 self.first_error_or_warning()
2410 .map_or(Some(miette::Severity::Advice), Diagnostic::severity)
2411 }
2412
2413 fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
2414 self.first_error_or_warning().and_then(Diagnostic::labels)
2415 }
2416
2417 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
2418 self.first_error_or_warning()
2419 .and_then(Diagnostic::source_code)
2420 }
2421
2422 fn code(&self) -> Option<Box<dyn std::fmt::Display + '_>> {
2423 self.first_error_or_warning().and_then(Diagnostic::code)
2424 }
2425
2426 fn url(&self) -> Option<Box<dyn std::fmt::Display + '_>> {
2427 self.first_error_or_warning().and_then(Diagnostic::url)
2428 }
2429
2430 fn help(&self) -> Option<Box<dyn std::fmt::Display + '_>> {
2431 self.first_error_or_warning().and_then(Diagnostic::help)
2432 }
2433
2434 fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
2435 self.first_error_or_warning()
2436 .and_then(Diagnostic::diagnostic_source)
2437 }
2438}
2439
2440pub fn confusable_string_checker<'a>(
2446 templates: impl Iterator<Item = &'a Template> + 'a,
2447) -> impl Iterator<Item = ValidationWarning> + 'a {
2448 cedar_policy_core::validator::confusable_string_checks(templates.map(|t| &t.ast))
2449 .map(std::convert::Into::into)
2450}
2451
2452#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
2464pub struct EntityNamespace(pub(crate) ast::Name);
2465
2466#[doc(hidden)] impl AsRef<ast::Name> for EntityNamespace {
2468 fn as_ref(&self) -> &ast::Name {
2469 &self.0
2470 }
2471}
2472
2473impl FromStr for EntityNamespace {
2476 type Err = ParseErrors;
2477
2478 fn from_str(namespace_str: &str) -> Result<Self, Self::Err> {
2479 ast::Name::from_normalized_str(namespace_str)
2480 .map(EntityNamespace)
2481 .map_err(Into::into)
2482 }
2483}
2484
2485impl std::fmt::Display for EntityNamespace {
2486 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2487 write!(f, "{}", self.0)
2488 }
2489}
2490
2491#[derive(Debug, Clone, Default)]
2492pub(crate) struct StringifiedPolicySet {
2496 pub policies: Vec<String>,
2498 pub policy_templates: Vec<String>,
2500}
2501
2502#[derive(Debug, Clone, Default)]
2504pub struct PolicySet {
2505 pub(crate) ast: ast::PolicySet,
2508 policies: LinkedHashMap<PolicyId, Policy>,
2510 templates: LinkedHashMap<PolicyId, Template>,
2512}
2513
2514impl PartialEq for PolicySet {
2515 fn eq(&self, other: &Self) -> bool {
2516 self.ast.eq(&other.ast)
2518 }
2519}
2520impl Eq for PolicySet {}
2521
2522#[doc(hidden)] impl AsRef<ast::PolicySet> for PolicySet {
2524 fn as_ref(&self) -> &ast::PolicySet {
2525 &self.ast
2526 }
2527}
2528
2529#[doc(hidden)]
2530impl From<ast::PolicySet> for PolicySet {
2531 fn from(pset: ast::PolicySet) -> Self {
2532 Self::from_ast(pset)
2533 }
2534}
2535
2536impl FromStr for PolicySet {
2537 type Err = ParseErrors;
2538
2539 fn from_str(policies: &str) -> Result<Self, Self::Err> {
2546 let (texts, pset) = parser::parse_policyset_and_also_return_policy_text(policies)?;
2547 #[expect(clippy::expect_used, reason = "By the invariant on `parse_policyset_and_also_return_policy_text(policies)`, every `PolicyId` in `pset.policies()` occurs as a key in `text`.")]
2548 let policies = pset.policies().map(|p|
2549 (
2550 PolicyId::new(p.id().clone()),
2551 Policy { lossless: LosslessPolicy::policy_or_template_text(*texts.get(p.id()).expect("internal invariant violation: policy id exists in asts but not texts")), ast: p.clone() }
2552 )
2553 ).collect();
2554 #[expect(
2555 clippy::expect_used,
2556 reason = "By the invariant on `parse_policyset_and_also_return_policy_text(policies)`, every `PolicyId` in `pset.templates()` also occurs as a key in `text`."
2557 )]
2558 let templates = pset
2559 .templates()
2560 .map(|t| {
2561 (
2562 PolicyId::new(t.id().clone()),
2563 Template {
2564 lossless: LosslessTemplate::from_text(*texts.get(t.id()).expect(
2565 "internal invariant violation: template id exists in asts but not ests",
2566 )),
2567 ast: t.clone(),
2568 },
2569 )
2570 })
2571 .collect();
2572 Ok(Self {
2573 ast: pset,
2574 policies,
2575 templates,
2576 })
2577 }
2578}
2579
2580impl PolicySet {
2581 fn from_est(est: &est::PolicySet) -> Result<Self, PolicySetError> {
2583 let ast: ast::PolicySet = est.clone().try_into()?;
2584 #[expect(
2585 clippy::expect_used,
2586 reason = "Since conversion from EST to AST succeeded, every `PolicyId` in `ast.policies()` occurs in `est`"
2587 )]
2588 let policies = ast
2589 .policies()
2590 .map(|p| {
2591 (
2592 PolicyId::new(p.id().clone()),
2593 Policy {
2594 lossless: LosslessPolicy::Est(est.get_policy(p.id()).expect(
2595 "internal invariant violation: policy id exists in asts but not ests",
2596 )),
2597 ast: p.clone(),
2598 },
2599 )
2600 })
2601 .collect();
2602 #[expect(
2603 clippy::expect_used,
2604 reason = "Since conversion from EST to AST succeeded, every `PolicyId` in `ast.templates()` occurs in `est`"
2605 )]
2606 let templates = ast
2607 .templates()
2608 .map(|t| {
2609 (
2610 PolicyId::new(t.id().clone()),
2611 Template {
2612 lossless: LosslessTemplate::Est(est.get_template(t.id()).expect(
2613 "internal invariant violation: template id exists in asts but not ests",
2614 )),
2615 ast: t.clone(),
2616 },
2617 )
2618 })
2619 .collect();
2620 Ok(Self {
2621 ast,
2622 policies,
2623 templates,
2624 })
2625 }
2626
2627 pub(crate) fn from_ast(ast: ast::PolicySet) -> Self {
2629 let templates = ast
2630 .templates()
2631 .cloned()
2632 .map(|t| (PolicyId::new(t.id().clone()), t.into()))
2633 .collect();
2634 let policies = ast
2635 .policies()
2636 .cloned()
2637 .map(|p| (PolicyId::new(p.id().clone()), p.into()))
2638 .collect();
2639 Self {
2640 ast,
2641 policies,
2642 templates,
2643 }
2644 }
2645
2646 pub fn from_pst(pst_set: pst::PolicySet) -> Result<Self, PolicySetError> {
2653 let mut set = Self::new();
2654 for (id, template) in pst_set.templates {
2655 if id != template.id {
2656 return Err(policy_set_errors::InconsistentPolicyId {
2657 map_key: id.into(),
2658 inner_id: template.id.into(),
2659 }
2660 .into());
2661 }
2662 let ast_template: ast::Template = template.clone().try_into()?;
2663 set.ast.add_template(ast_template.clone())?;
2664 set.templates.insert(
2665 id.into(),
2666 Template {
2667 ast: ast_template,
2668 lossless: LosslessTemplate::Pst(template),
2669 },
2670 );
2671 }
2672 for (id, static_policy) in pst_set.policies {
2673 if &id != static_policy.id() {
2674 return Err(policy_set_errors::InconsistentPolicyId {
2675 map_key: id.into(),
2676 inner_id: static_policy.id().clone().into(),
2677 }
2678 .into());
2679 }
2680 let pst_policy = pst::Policy::Static(static_policy);
2681 let ast_policy: ast::Policy = pst_policy.clone().try_into()?;
2682 set.ast.add(ast_policy.clone())?;
2683 set.policies.insert(
2684 id.into(),
2685 Policy {
2686 ast: ast_policy,
2687 lossless: LosslessPolicy::Pst(pst_policy),
2688 },
2689 );
2690 }
2691 for link in pst_set.template_links {
2692 let vals: HashMap<SlotId, EntityUid> = link
2693 .values
2694 .into_iter()
2695 .map(|(k, v)| {
2696 let ast_uid = ast::EntityUID::from(v);
2697 (k.into(), EntityUid(ast_uid))
2698 })
2699 .collect();
2700 set.link(link.template_id.into(), link.new_id.into(), vals)?;
2701 }
2702 Ok(set)
2703 }
2704
2705 pub fn from_json_str(src: impl AsRef<str>) -> Result<Self, PolicySetError> {
2707 let est: est::PolicySet = serde_json::from_str(src.as_ref())
2708 .map_err(|e| policy_set_errors::JsonPolicySetError { inner: e })?;
2709 Self::from_est(&est)
2710 }
2711
2712 pub fn from_json_value(src: serde_json::Value) -> Result<Self, PolicySetError> {
2714 let est: est::PolicySet = serde_json::from_value(src)
2715 .map_err(|e| policy_set_errors::JsonPolicySetError { inner: e })?;
2716 Self::from_est(&est)
2717 }
2718
2719 pub fn from_json_file(r: impl std::io::Read) -> Result<Self, PolicySetError> {
2721 let est: est::PolicySet = serde_json::from_reader(r)
2722 .map_err(|e| policy_set_errors::JsonPolicySetError { inner: e })?;
2723 Self::from_est(&est)
2724 }
2725
2726 pub fn to_json(self) -> Result<serde_json::Value, PolicySetError> {
2728 let est = self.est()?;
2729 let value = serde_json::to_value(est)
2730 .map_err(|e| policy_set_errors::JsonPolicySetError { inner: e })?;
2731 Ok(value)
2732 }
2733
2734 pub fn to_pst(&self) -> Result<pst::PolicySet, PolicySetError> {
2744 let templates = self
2745 .templates
2746 .iter()
2747 .map(|(id, t)| Ok((id.clone().into(), t.to_pst()?)))
2748 .collect::<Result<_, pst::PstConstructionError>>()?;
2749 let mut policies = LinkedHashMap::new();
2750 let mut template_links = Vec::new();
2751 for (id, policy) in &self.policies {
2752 if policy.is_static() {
2753 if let pst::Policy::Static(sp) = policy.to_pst()? {
2754 policies.insert(id.clone().into(), sp);
2755 }
2756 } else {
2757 template_links.push(pst::TemplateLink {
2758 template_id: policy.ast.template().id().clone().into(),
2759 new_id: id.clone().into(),
2760 values: policy
2761 .ast
2762 .env()
2763 .iter()
2764 .map(|(k, v)| ((*k).into(), v.clone().into()))
2765 .collect(),
2766 });
2767 }
2768 }
2769 Ok(pst::PolicySet {
2770 templates,
2771 policies,
2772 template_links,
2773 })
2774 }
2775
2776 pub fn try_into_pst(self) -> Result<pst::PolicySet, PolicySetError> {
2781 let templates = self
2782 .templates
2783 .into_iter()
2784 .map(|(id, t)| Ok((id.into(), t.try_into_pst()?)))
2785 .collect::<Result<_, pst::PstConstructionError>>()?;
2786 let mut policies = LinkedHashMap::new();
2787 let mut template_links = Vec::new();
2788 for (id, policy) in self.policies {
2789 if policy.is_static() {
2790 if let pst::Policy::Static(sp) = policy.try_into_pst()? {
2791 policies.insert(id.into(), sp);
2792 }
2793 } else {
2794 template_links.push(pst::TemplateLink {
2795 template_id: policy.ast.template().id().clone().into(),
2796 new_id: id.into(),
2797 values: policy
2798 .ast
2799 .env()
2800 .iter()
2801 .map(|(k, v)| ((*k).into(), v.clone().into()))
2802 .collect(),
2803 });
2804 }
2805 }
2806 Ok(pst::PolicySet {
2807 templates,
2808 policies,
2809 template_links,
2810 })
2811 }
2812
2813 fn est(self) -> Result<est::PolicySet, PolicyToJsonError> {
2815 let (static_policies, template_links): (Vec<_>, Vec<_>) =
2816 fold_partition(self.policies, is_static_or_link)?;
2817 let static_policies = static_policies.into_iter().collect::<LinkedHashMap<_, _>>();
2818 let templates = self
2819 .templates
2820 .into_iter()
2821 .map(|(id, template)| {
2822 template
2823 .lossless
2824 .est(|| template.ast.clone().into())
2825 .map(|est| (id.into(), est))
2826 })
2827 .collect::<Result<LinkedHashMap<_, _>, _>>()?;
2828 let est = est::PolicySet {
2829 templates,
2830 static_policies,
2831 template_links,
2832 };
2833
2834 Ok(est)
2835 }
2836
2837 pub fn to_cedar(&self) -> Option<String> {
2856 match self.stringify() {
2857 Some(StringifiedPolicySet {
2858 policies,
2859 policy_templates,
2860 }) => {
2861 let policies_as_vec = policies
2862 .into_iter()
2863 .chain(policy_templates)
2864 .collect::<Vec<_>>();
2865 Some(policies_as_vec.join("\n\n"))
2866 }
2867 None => None,
2868 }
2869 }
2870
2871 pub(crate) fn stringify(&self) -> Option<StringifiedPolicySet> {
2888 let policies = self
2889 .policies
2890 .values()
2891 .sorted_by_key(|p| AsRef::<str>::as_ref(p.id()))
2895 .map(Policy::to_cedar)
2896 .collect::<Option<Vec<_>>>()?;
2897 let policy_templates = self
2898 .templates
2899 .values()
2900 .sorted_by_key(|t| AsRef::<str>::as_ref(t.id()))
2901 .map(Template::to_cedar)
2902 .collect_vec();
2903
2904 Some(StringifiedPolicySet {
2905 policies,
2906 policy_templates,
2907 })
2908 }
2909
2910 pub fn new() -> Self {
2912 Self {
2913 ast: ast::PolicySet::new(),
2914 policies: LinkedHashMap::new(),
2915 templates: LinkedHashMap::new(),
2916 }
2917 }
2918
2919 pub fn from_policies(
2921 policies: impl IntoIterator<Item = Policy>,
2922 ) -> Result<Self, PolicySetError> {
2923 let mut set = Self::new();
2924 for policy in policies {
2925 set.add(policy)?;
2926 }
2927 Ok(set)
2928 }
2929
2930 pub fn merge(
2945 &mut self,
2946 other: &Self,
2947 rename_duplicates: bool,
2948 ) -> Result<HashMap<PolicyId, PolicyId>, PolicySetError> {
2949 match self.ast.merge_policyset(&other.ast, rename_duplicates) {
2950 Ok(renaming) => {
2951 let renaming: HashMap<PolicyId, PolicyId> = renaming
2952 .into_iter()
2953 .map(|(old_pid, new_pid)| (PolicyId::new(old_pid), PolicyId::new(new_pid)))
2954 .collect();
2955
2956 for (old_pid, op) in &other.policies {
2957 let pid = renaming.get(old_pid).unwrap_or(old_pid);
2958 if !self.policies.contains_key(pid) {
2959 let lossless = if renaming.contains_key(old_pid) {
2960 op.lossless.new_id(pid.clone())
2961 } else {
2962 op.lossless.clone()
2963 };
2964 #[expect(
2965 clippy::unwrap_used,
2966 reason = "`pid` is the new id of a policy from `other`, so it will be in `self` after merging"
2967 )]
2968 let new_p = Policy {
2969 ast: self.ast.get(pid.as_ref()).unwrap().clone(),
2972 lossless,
2973 };
2974 self.policies.insert(pid.clone(), new_p);
2975 }
2976 }
2977 for (old_pid, ot) in &other.templates {
2978 let pid = renaming.get(old_pid).unwrap_or(old_pid);
2979 if !self.templates.contains_key(pid) {
2980 let lossless = if renaming.contains_key(old_pid) {
2981 ot.lossless.new_id(pid.clone())
2982 } else {
2983 ot.lossless.clone()
2984 };
2985 #[expect(
2986 clippy::unwrap_used,
2987 reason = "`pid` is the new id of a template from `other`, so it will be in `self` after merging"
2988 )]
2989 let new_t = Template {
2990 ast: self.ast.get_template(pid.as_ref()).unwrap().clone(),
2991 lossless,
2992 };
2993 self.templates.insert(pid.clone(), new_t);
2994 }
2995 }
2996
2997 Ok(renaming)
2998 }
2999 Err(ast::PolicySetError::Occupied { id }) => Err(PolicySetError::AlreadyDefined(
3000 policy_set_errors::AlreadyDefined {
3001 id: PolicyId::new(id),
3002 },
3003 )),
3004 }
3005 }
3006
3007 pub fn add(&mut self, policy: Policy) -> Result<(), PolicySetError> {
3011 if policy.is_static() {
3012 let id = PolicyId::new(policy.ast.id().clone());
3013 self.ast.add(policy.ast.clone())?;
3014 self.policies.insert(id, policy);
3015 Ok(())
3016 } else {
3017 Err(PolicySetError::ExpectedStatic(
3018 policy_set_errors::ExpectedStatic::new(),
3019 ))
3020 }
3021 }
3022
3023 pub fn remove_static(&mut self, policy_id: PolicyId) -> Result<Policy, PolicySetError> {
3027 let Some(policy) = self.policies.remove(&policy_id) else {
3028 return Err(PolicySetError::PolicyNonexistent(
3029 policy_set_errors::PolicyNonexistentError { policy_id },
3030 ));
3031 };
3032 if self
3033 .ast
3034 .remove_static(&ast::PolicyID::from_string(&policy_id))
3035 .is_ok()
3036 {
3037 Ok(policy)
3038 } else {
3039 self.policies.insert(policy_id.clone(), policy);
3041 Err(PolicySetError::PolicyNonexistent(
3042 policy_set_errors::PolicyNonexistentError { policy_id },
3043 ))
3044 }
3045 }
3046
3047 pub fn add_template(&mut self, template: Template) -> Result<(), PolicySetError> {
3049 let id = PolicyId::new(template.ast.id().clone());
3050 self.ast.add_template(template.ast.clone())?;
3051 self.templates.insert(id, template);
3052 Ok(())
3053 }
3054
3055 pub fn remove_template(&mut self, template_id: PolicyId) -> Result<Template, PolicySetError> {
3060 let Some(template) = self.templates.remove(&template_id) else {
3061 return Err(PolicySetError::TemplateNonexistent(
3062 policy_set_errors::TemplateNonexistentError { template_id },
3063 ));
3064 };
3065 #[expect(clippy::panic, reason = "We just found the policy in self.templates")]
3067 match self
3068 .ast
3069 .remove_template(&ast::PolicyID::from_string(&template_id))
3070 {
3071 Ok(_) => Ok(template),
3072 Err(ast::PolicySetTemplateRemovalError::RemoveTemplateWithLinksError(_)) => {
3073 self.templates.insert(template_id.clone(), template);
3074 Err(PolicySetError::RemoveTemplateWithActiveLinks(
3075 policy_set_errors::RemoveTemplateWithActiveLinksError { template_id },
3076 ))
3077 }
3078 Err(ast::PolicySetTemplateRemovalError::NotTemplateError(_)) => {
3079 self.templates.insert(template_id.clone(), template);
3080 Err(PolicySetError::RemoveTemplateNotTemplate(
3081 policy_set_errors::RemoveTemplateNotTemplateError { template_id },
3082 ))
3083 }
3084 Err(ast::PolicySetTemplateRemovalError::RemovePolicyNoTemplateError(_)) => {
3085 panic!("Found template policy in self.templates but not in self.ast");
3086 }
3087 }
3088 }
3089
3090 pub fn get_linked_policies(
3093 &self,
3094 template_id: PolicyId,
3095 ) -> Result<impl Iterator<Item = &PolicyId>, PolicySetError> {
3096 self.ast
3097 .get_linked_policies(&ast::PolicyID::from_string(&template_id))
3098 .map_or_else(
3099 |_| {
3100 Err(PolicySetError::TemplateNonexistent(
3101 policy_set_errors::TemplateNonexistentError { template_id },
3102 ))
3103 },
3104 |v| Ok(v.map(PolicyId::ref_cast)),
3105 )
3106 }
3107
3108 pub fn policies(&self) -> impl Iterator<Item = &Policy> {
3112 self.policies.values()
3113 }
3114
3115 pub fn templates(&self) -> impl Iterator<Item = &Template> {
3117 self.templates.values()
3118 }
3119
3120 pub fn template(&self, id: &PolicyId) -> Option<&Template> {
3122 self.templates.get(id)
3123 }
3124
3125 pub fn policy(&self, id: &PolicyId) -> Option<&Policy> {
3127 self.policies.get(id)
3128 }
3129
3130 pub fn annotation(&self, id: &PolicyId, key: impl AsRef<str>) -> Option<&str> {
3135 self.ast
3136 .get(id.as_ref())?
3137 .annotation(&key.as_ref().parse().ok()?)
3138 .map(AsRef::as_ref)
3139 }
3140
3141 pub fn template_annotation(&self, id: &PolicyId, key: impl AsRef<str>) -> Option<&str> {
3146 self.ast
3147 .get_template(id.as_ref())?
3148 .annotation(&key.as_ref().parse().ok()?)
3149 .map(AsRef::as_ref)
3150 }
3151
3152 pub fn is_empty(&self) -> bool {
3154 debug_assert_eq!(
3155 self.ast.is_empty(),
3156 self.policies.is_empty() && self.templates.is_empty()
3157 );
3158 self.ast.is_empty()
3159 }
3160
3161 pub fn num_of_policies(&self) -> usize {
3165 self.policies.len()
3166 }
3167
3168 pub fn num_of_templates(&self) -> usize {
3170 self.templates.len()
3171 }
3172
3173 pub fn link(
3182 &mut self,
3183 template_id: PolicyId,
3184 new_id: PolicyId,
3185 vals: HashMap<SlotId, EntityUid>,
3186 ) -> Result<(), PolicySetError> {
3187 let unwrapped_vals: HashMap<ast::SlotId, ast::EntityUID> = vals
3188 .into_iter()
3189 .map(|(key, value)| (key.into(), value.into()))
3190 .collect();
3191
3192 let Some(template) = self.templates.get(&template_id) else {
3197 return Err(if self.policies.contains_key(&template_id) {
3198 policy_set_errors::ExpectedTemplate::new().into()
3199 } else {
3200 policy_set_errors::LinkingError {
3201 inner: ast::LinkingError::NoSuchTemplate {
3202 id: template_id.into(),
3203 },
3204 }
3205 .into()
3206 });
3207 };
3208
3209 let linked_ast = self.ast.link(
3210 template_id.into(),
3211 new_id.clone().into(),
3212 unwrapped_vals.clone(),
3213 )?;
3214
3215 #[expect(
3216 clippy::expect_used,
3217 reason = "`lossless.link()` will not fail after `ast.link()` succeeds"
3218 )]
3219 let linked_lossless = template
3220 .lossless
3221 .clone()
3222 .link(
3223 new_id.clone().into(),
3224 unwrapped_vals.iter().map(|(k, v)| (*k, v)),
3225 )
3226 .expect("ast.link() didn't fail above, so this shouldn't fail");
3231 self.policies.insert(
3232 new_id,
3233 Policy {
3234 ast: linked_ast.clone(),
3235 lossless: linked_lossless,
3236 },
3237 );
3238 Ok(())
3239 }
3240
3241 #[doc = include_str!("../experimental_warning.md")]
3243 #[cfg(feature = "partial-eval")]
3244 pub fn unknown_entities(&self) -> HashSet<EntityUid> {
3245 let mut entity_uids = HashSet::new();
3246 for policy in self.policies.values() {
3247 entity_uids.extend(policy.unknown_entities());
3248 }
3249 entity_uids
3250 }
3251
3252 pub fn unlink(&mut self, policy_id: PolicyId) -> Result<Policy, PolicySetError> {
3255 let Some(policy) = self.policies.remove(&policy_id) else {
3256 return Err(PolicySetError::LinkNonexistent(
3257 policy_set_errors::LinkNonexistentError { policy_id },
3258 ));
3259 };
3260 #[expect(clippy::panic, reason = "We just found the policy in self.policies")]
3262 match self.ast.unlink(&ast::PolicyID::from_string(&policy_id)) {
3263 Ok(_) => Ok(policy),
3264 Err(ast::PolicySetUnlinkError::NotLinkError(_)) => {
3265 self.policies.insert(policy_id.clone(), policy);
3267 Err(PolicySetError::UnlinkLinkNotLink(
3268 policy_set_errors::UnlinkLinkNotLinkError { policy_id },
3269 ))
3270 }
3271 Err(ast::PolicySetUnlinkError::UnlinkingError(_)) => {
3272 panic!("Found linked policy in self.policies but not in self.ast")
3273 }
3274 }
3275 }
3276}
3277
3278impl std::fmt::Display for PolicySet {
3279 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3280 let mut policies = self.policies().peekable();
3282 while let Some(policy) = policies.next() {
3283 policy.lossless.fmt(|| policy.ast.clone().into(), f)?;
3284 if policies.peek().is_some() {
3285 writeln!(f)?;
3286 }
3287 }
3288 Ok(())
3289 }
3290}
3291
3292fn is_static_or_link(
3295 (id, policy): (PolicyId, Policy),
3296) -> Result<Either<(ast::PolicyID, est::Policy), TemplateLink>, PolicyToJsonError> {
3297 match policy.template_id() {
3298 Some(template_id) => {
3299 let values = policy
3300 .ast
3301 .env()
3302 .iter()
3303 .map(|(id, euid)| (*id, euid.clone()))
3304 .collect();
3305 Ok(Either::Right(TemplateLink {
3306 new_id: id.into(),
3307 template_id: template_id.clone().into(),
3308 values,
3309 }))
3310 }
3311 None => policy
3312 .lossless
3313 .est(|| policy.ast.clone().into())
3314 .map(|est| Either::Left((id.into(), est))),
3315 }
3316}
3317
3318#[expect(
3321 clippy::redundant_pub_crate,
3322 reason = "can't be private because it's used in tests"
3323)]
3324pub(crate) fn fold_partition<T, A, B, E>(
3325 i: impl IntoIterator<Item = T>,
3326 f: impl Fn(T) -> Result<Either<A, B>, E>,
3327) -> Result<(Vec<A>, Vec<B>), E> {
3328 let mut lefts = vec![];
3329 let mut rights = vec![];
3330
3331 for item in i {
3332 match f(item)? {
3333 Either::Left(left) => lefts.push(left),
3334 Either::Right(right) => rights.push(right),
3335 }
3336 }
3337
3338 Ok((lefts, rights))
3339}
3340
3341#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
3345pub struct RequestEnv {
3346 pub(crate) principal: EntityTypeName,
3347 pub(crate) action: EntityUid,
3348 pub(crate) resource: EntityTypeName,
3349 pub(crate) principal_slot: Option<EntityTypeName>,
3350 pub(crate) resource_slot: Option<EntityTypeName>,
3351}
3352
3353impl RequestEnv {
3354 pub fn new(principal: EntityTypeName, action: EntityUid, resource: EntityTypeName) -> Self {
3356 Self {
3357 principal,
3358 action,
3359 resource,
3360 principal_slot: None,
3361 resource_slot: None,
3362 }
3363 }
3364
3365 pub fn new_request_env_with_slots(
3367 principal: EntityTypeName,
3368 action: EntityUid,
3369 resource: EntityTypeName,
3370 principal_slot: Option<EntityTypeName>,
3371 resource_slot: Option<EntityTypeName>,
3372 ) -> Self {
3373 Self {
3374 principal,
3375 action,
3376 resource,
3377 principal_slot,
3378 resource_slot,
3379 }
3380 }
3381
3382 pub fn principal(&self) -> &EntityTypeName {
3384 &self.principal
3385 }
3386
3387 pub fn action(&self) -> &EntityUid {
3389 &self.action
3390 }
3391
3392 pub fn resource(&self) -> &EntityTypeName {
3394 &self.resource
3395 }
3396
3397 pub fn principal_slot(&self) -> Option<&EntityTypeName> {
3399 self.principal_slot.as_ref()
3400 }
3401
3402 pub fn resource_slot(&self) -> Option<&EntityTypeName> {
3404 self.resource_slot.as_ref()
3405 }
3406}
3407
3408#[doc(hidden)]
3409impl From<cedar_policy_core::validator::types::RequestEnv<'_>> for RequestEnv {
3410 fn from(renv: cedar_policy_core::validator::types::RequestEnv<'_>) -> Self {
3411 match renv {
3412 cedar_policy_core::validator::types::RequestEnv::DeclaredAction {
3413 principal,
3414 action,
3415 resource,
3416 principal_slot,
3417 resource_slot,
3418 ..
3419 } => Self {
3420 principal: principal.clone().into(),
3421 action: action.clone().into(),
3422 resource: resource.clone().into(),
3423 principal_slot: principal_slot.map(EntityTypeName::from),
3424 resource_slot: resource_slot.map(EntityTypeName::from),
3425 },
3426 #[expect(
3427 clippy::unreachable,
3428 reason = "partial validation is not enabled and hence `RequestEnv::UndeclaredAction` should not show up"
3429 )]
3430 cedar_policy_core::validator::types::RequestEnv::UndeclaredAction => {
3431 unreachable!("used unsupported feature")
3432 }
3433 }
3434 }
3435}
3436
3437fn get_valid_request_envs(ast: &ast::Template, s: &Schema) -> impl Iterator<Item = RequestEnv> {
3442 let tc = Typechecker::new(
3443 &s.0,
3444 cedar_policy_core::validator::ValidationMode::default(),
3445 );
3446 tc.typecheck_by_request_env(ast)
3447 .filter_map(|(env, pc)| {
3448 if matches!(pc, PolicyCheck::Success(_)) {
3449 Some(env.into())
3450 } else {
3451 None
3452 }
3453 })
3454 .collect::<BTreeSet<_>>()
3455 .into_iter()
3456}
3457
3458#[derive(Debug, Clone)]
3464pub struct Template {
3465 pub(crate) ast: ast::Template,
3468
3469 pub(crate) lossless: LosslessTemplate,
3477}
3478
3479impl PartialEq for Template {
3480 fn eq(&self, other: &Self) -> bool {
3481 self.ast.eq(&other.ast)
3483 }
3484}
3485impl Eq for Template {}
3486
3487#[doc(hidden)] impl AsRef<ast::Template> for Template {
3489 fn as_ref(&self) -> &ast::Template {
3490 &self.ast
3491 }
3492}
3493
3494#[doc(hidden)]
3495impl From<ast::Template> for Template {
3496 fn from(template: ast::Template) -> Self {
3497 Self::from_ast(template)
3498 }
3499}
3500
3501impl Template {
3502 pub fn from_pst(pst_template: pst::Template) -> Result<Self, pst::PstConstructionError> {
3505 let ast: ast::Template = pst_template.clone().try_into()?;
3506 if ast.slots().count() == 0 {
3507 return Err(error_body::ExpectedTemplateWithSlotsError.into());
3508 }
3509 Ok(Self {
3510 ast,
3511 lossless: LosslessTemplate::Pst(pst_template),
3512 })
3513 }
3514
3515 pub fn to_pst(&self) -> Result<pst::Template, pst::PstConstructionError> {
3517 Self::pst_with_id(
3518 self.ast.id().clone(),
3519 self.lossless
3520 .pst(|| pst::Template::try_from(self.ast.clone())),
3521 )
3522 }
3523
3524 pub fn try_into_pst(self) -> Result<pst::Template, pst::PstConstructionError> {
3528 let id = self.ast.id().clone();
3529 Self::pst_with_id(
3530 id,
3531 self.lossless
3532 .try_into_pst(|| pst::Template::try_from(self.ast)),
3533 )
3534 }
3535
3536 fn pst_with_id(
3538 id: ast::PolicyID,
3539 template: Result<pst::Template, pst::PstConstructionError>,
3540 ) -> Result<pst::Template, pst::PstConstructionError> {
3541 template.map(|template| template.with_id(id.into()))
3542 }
3543
3544 pub fn parse(id: Option<PolicyId>, src: impl AsRef<str>) -> Result<Self, ParseErrors> {
3550 let ast = parser::parse_template(id.map(Into::into), src.as_ref())?;
3551 Ok(Self {
3552 ast,
3553 lossless: LosslessTemplate::from_text(Some(src.as_ref())),
3554 })
3555 }
3556
3557 pub fn id(&self) -> &PolicyId {
3559 PolicyId::ref_cast(self.ast.id())
3560 }
3561
3562 #[must_use]
3564 pub fn new_id(&self, id: PolicyId) -> Self {
3565 Self {
3566 ast: self.ast.new_id(id.clone().into()),
3567 lossless: self.lossless.new_id(id),
3568 }
3569 }
3570
3571 pub fn effect(&self) -> Effect {
3573 self.ast.effect()
3574 }
3575
3576 pub fn has_non_scope_constraint(&self) -> bool {
3578 self.ast.non_scope_constraints().is_some()
3579 }
3580
3581 pub fn annotation(&self, key: impl AsRef<str>) -> Option<&str> {
3586 self.ast
3587 .annotation(&key.as_ref().parse().ok()?)
3588 .map(AsRef::as_ref)
3589 }
3590
3591 pub fn annotations(&self) -> impl Iterator<Item = (&str, &str)> {
3595 self.ast
3596 .annotations()
3597 .map(|(k, v)| (k.as_ref(), v.as_ref()))
3598 }
3599
3600 pub fn slots(&self) -> impl Iterator<Item = &SlotId> {
3602 self.ast.slots().map(|slot| SlotId::ref_cast(&slot.id))
3603 }
3604
3605 pub fn principal_constraint(&self) -> TemplatePrincipalConstraint {
3607 match self.ast.principal_constraint().as_inner() {
3608 ast::PrincipalOrResourceConstraint::Any => TemplatePrincipalConstraint::Any,
3609 ast::PrincipalOrResourceConstraint::In(eref) => {
3610 TemplatePrincipalConstraint::In(match eref {
3611 ast::EntityReference::EUID(e) => Some(e.as_ref().clone().into()),
3612 ast::EntityReference::Slot(_) => None,
3613 })
3614 }
3615 ast::PrincipalOrResourceConstraint::Eq(eref) => {
3616 TemplatePrincipalConstraint::Eq(match eref {
3617 ast::EntityReference::EUID(e) => Some(e.as_ref().clone().into()),
3618 ast::EntityReference::Slot(_) => None,
3619 })
3620 }
3621 ast::PrincipalOrResourceConstraint::Is(entity_type) => {
3622 TemplatePrincipalConstraint::Is(entity_type.as_ref().clone().into())
3623 }
3624 ast::PrincipalOrResourceConstraint::IsIn(entity_type, eref) => {
3625 TemplatePrincipalConstraint::IsIn(
3626 entity_type.as_ref().clone().into(),
3627 match eref {
3628 ast::EntityReference::EUID(e) => Some(e.as_ref().clone().into()),
3629 ast::EntityReference::Slot(_) => None,
3630 },
3631 )
3632 }
3633 }
3634 }
3635
3636 pub fn action_constraint(&self) -> ActionConstraint {
3638 match self.ast.action_constraint() {
3640 ast::ActionConstraint::Any => ActionConstraint::Any,
3641 ast::ActionConstraint::In(ids) => {
3642 ActionConstraint::In(ids.iter().map(|id| id.as_ref().clone().into()).collect())
3643 }
3644 ast::ActionConstraint::Eq(id) => ActionConstraint::Eq(id.as_ref().clone().into()),
3645 #[cfg(feature = "tolerant-ast")]
3646 #[expect(clippy::unimplemented, reason = "experimental feature")]
3647 ast::ActionConstraint::ErrorConstraint => {
3648 unimplemented!("internal ErrorConstraint cannot be represented in the public API")
3649 }
3650 }
3651 }
3652
3653 pub fn resource_constraint(&self) -> TemplateResourceConstraint {
3655 match self.ast.resource_constraint().as_inner() {
3656 ast::PrincipalOrResourceConstraint::Any => TemplateResourceConstraint::Any,
3657 ast::PrincipalOrResourceConstraint::In(eref) => {
3658 TemplateResourceConstraint::In(match eref {
3659 ast::EntityReference::EUID(e) => Some(e.as_ref().clone().into()),
3660 ast::EntityReference::Slot(_) => None,
3661 })
3662 }
3663 ast::PrincipalOrResourceConstraint::Eq(eref) => {
3664 TemplateResourceConstraint::Eq(match eref {
3665 ast::EntityReference::EUID(e) => Some(e.as_ref().clone().into()),
3666 ast::EntityReference::Slot(_) => None,
3667 })
3668 }
3669 ast::PrincipalOrResourceConstraint::Is(entity_type) => {
3670 TemplateResourceConstraint::Is(entity_type.as_ref().clone().into())
3671 }
3672 ast::PrincipalOrResourceConstraint::IsIn(entity_type, eref) => {
3673 TemplateResourceConstraint::IsIn(
3674 entity_type.as_ref().clone().into(),
3675 match eref {
3676 ast::EntityReference::EUID(e) => Some(e.as_ref().clone().into()),
3677 ast::EntityReference::Slot(_) => None,
3678 },
3679 )
3680 }
3681 }
3682 }
3683
3684 pub fn from_json(
3690 id: Option<PolicyId>,
3691 json: serde_json::Value,
3692 ) -> Result<Self, PolicyFromJsonError> {
3693 let est: est::Policy = serde_json::from_value(json)
3694 .map_err(|e| entities_json_errors::JsonDeserializationError::Serde(e.into()))
3695 .map_err(cedar_policy_core::est::FromJsonError::from)?;
3696 Self::from_est(id, est)
3697 }
3698
3699 fn from_est(id: Option<PolicyId>, est: est::Policy) -> Result<Self, PolicyFromJsonError> {
3700 Ok(Self {
3701 ast: est.clone().try_into_ast_template(id.map(PolicyId::into))?,
3702 lossless: LosslessTemplate::Est(est),
3703 })
3704 }
3705
3706 pub(crate) fn from_ast(ast: ast::Template) -> Self {
3707 Self {
3708 lossless: LosslessTemplate::Est(ast.clone().into()),
3709 ast,
3710 }
3711 }
3712
3713 pub fn to_json(&self) -> Result<serde_json::Value, PolicyToJsonError> {
3715 let est = self.lossless.est(|| self.ast.clone().into())?;
3716 serde_json::to_value(est).map_err(Into::into)
3717 }
3718
3719 pub fn to_cedar(&self) -> String {
3728 match &self.lossless {
3729 LosslessTemplate::Empty | LosslessTemplate::Est(_) | LosslessTemplate::Pst(_) => {
3730 self.ast.to_string()
3731 }
3732 LosslessTemplate::Text(text) => text.clone(),
3733 }
3734 }
3735
3736 pub fn get_valid_request_envs(&self, s: &Schema) -> impl Iterator<Item = RequestEnv> {
3741 get_valid_request_envs(&self.ast, s)
3742 }
3743}
3744
3745impl std::fmt::Display for Template {
3746 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3747 self.lossless.fmt(|| self.ast.clone().into(), f)
3749 }
3750}
3751
3752impl FromStr for Template {
3753 type Err = ParseErrors;
3754
3755 fn from_str(src: &str) -> Result<Self, Self::Err> {
3756 Self::parse(None, src)
3757 }
3758}
3759
3760#[derive(Debug, Clone, PartialEq, Eq)]
3762pub enum PrincipalConstraint {
3763 Any,
3765 In(EntityUid),
3767 Eq(EntityUid),
3769 Is(EntityTypeName),
3771 IsIn(EntityTypeName, EntityUid),
3773}
3774
3775#[derive(Debug, Clone, PartialEq, Eq)]
3777pub enum TemplatePrincipalConstraint {
3778 Any,
3780 In(Option<EntityUid>),
3783 Eq(Option<EntityUid>),
3786 Is(EntityTypeName),
3788 IsIn(EntityTypeName, Option<EntityUid>),
3791}
3792
3793impl TemplatePrincipalConstraint {
3794 pub fn has_slot(&self) -> bool {
3796 match self {
3797 Self::Any | Self::Is(_) => false,
3798 Self::In(o) | Self::Eq(o) | Self::IsIn(_, o) => o.is_none(),
3799 }
3800 }
3801}
3802
3803#[derive(Debug, Clone, PartialEq, Eq)]
3805pub enum ActionConstraint {
3806 Any,
3808 In(Vec<EntityUid>),
3810 Eq(EntityUid),
3812}
3813
3814#[derive(Debug, Clone, PartialEq, Eq)]
3816pub enum ResourceConstraint {
3817 Any,
3819 In(EntityUid),
3821 Eq(EntityUid),
3823 Is(EntityTypeName),
3825 IsIn(EntityTypeName, EntityUid),
3827}
3828
3829#[derive(Debug, Clone, PartialEq, Eq)]
3831pub enum TemplateResourceConstraint {
3832 Any,
3834 In(Option<EntityUid>),
3837 Eq(Option<EntityUid>),
3840 Is(EntityTypeName),
3842 IsIn(EntityTypeName, Option<EntityUid>),
3845}
3846
3847impl TemplateResourceConstraint {
3848 pub fn has_slot(&self) -> bool {
3850 match self {
3851 Self::Any | Self::Is(_) => false,
3852 Self::In(o) | Self::Eq(o) | Self::IsIn(_, o) => o.is_none(),
3853 }
3854 }
3855}
3856
3857#[derive(Debug, Clone)]
3859pub struct Policy {
3860 pub(crate) ast: ast::Policy,
3863 pub(crate) lossless: LosslessPolicy,
3872}
3873
3874impl PartialEq for Policy {
3875 fn eq(&self, other: &Self) -> bool {
3876 self.ast.eq(&other.ast)
3878 }
3879}
3880impl Eq for Policy {}
3881
3882#[doc(hidden)] impl AsRef<ast::Policy> for Policy {
3884 fn as_ref(&self) -> &ast::Policy {
3885 &self.ast
3886 }
3887}
3888
3889#[doc(hidden)]
3890impl From<ast::Policy> for Policy {
3891 fn from(policy: ast::Policy) -> Self {
3892 Self::from_ast(policy)
3893 }
3894}
3895
3896#[doc(hidden)]
3897impl From<ast::StaticPolicy> for Policy {
3898 fn from(policy: ast::StaticPolicy) -> Self {
3899 ast::Policy::from(policy).into()
3900 }
3901}
3902
3903impl Policy {
3904 pub fn from_pst(pst_policy: pst::Policy) -> Result<Self, pst::PstConstructionError> {
3907 let ast = ast::Policy::try_from(pst_policy.clone())?;
3908 Ok(Self {
3909 ast,
3910 lossless: LosslessPolicy::Pst(pst_policy),
3911 })
3912 }
3913
3914 pub fn template_id(&self) -> Option<&PolicyId> {
3917 if self.is_static() {
3918 None
3919 } else {
3920 Some(PolicyId::ref_cast(self.ast.template().id()))
3921 }
3922 }
3923
3924 pub fn template_links(&self) -> Option<HashMap<SlotId, EntityUid>> {
3927 if self.is_static() {
3928 None
3929 } else {
3930 let wrapped_vals: HashMap<SlotId, EntityUid> = self
3931 .ast
3932 .env()
3933 .iter()
3934 .map(|(key, value)| ((*key).into(), value.clone().into()))
3935 .collect();
3936 Some(wrapped_vals)
3937 }
3938 }
3939
3940 pub fn effect(&self) -> Effect {
3942 self.ast.effect()
3943 }
3944
3945 pub fn has_non_scope_constraint(&self) -> bool {
3947 self.ast.non_scope_constraints().is_some()
3948 }
3949
3950 pub fn annotation(&self, key: impl AsRef<str>) -> Option<&str> {
3955 self.ast
3956 .annotation(&key.as_ref().parse().ok()?)
3957 .map(AsRef::as_ref)
3958 }
3959
3960 pub fn annotations(&self) -> impl Iterator<Item = (&str, &str)> {
3964 self.ast
3965 .annotations()
3966 .map(|(k, v)| (k.as_ref(), v.as_ref()))
3967 }
3968
3969 pub fn id(&self) -> &PolicyId {
3971 PolicyId::ref_cast(self.ast.id())
3972 }
3973
3974 #[must_use]
3976 pub fn new_id(&self, id: PolicyId) -> Self {
3977 Self {
3978 ast: self.ast.new_id(id.clone().into()),
3979 lossless: self.lossless.new_id(id),
3980 }
3981 }
3982
3983 pub fn is_static(&self) -> bool {
3985 self.ast.is_static()
3986 }
3987
3988 pub fn principal_constraint(&self) -> PrincipalConstraint {
3990 let slot_id = ast::SlotId::principal();
3991 match self.ast.template().principal_constraint().as_inner() {
3992 ast::PrincipalOrResourceConstraint::Any => PrincipalConstraint::Any,
3993 ast::PrincipalOrResourceConstraint::In(eref) => {
3994 PrincipalConstraint::In(self.convert_entity_reference(eref, slot_id).clone())
3995 }
3996 ast::PrincipalOrResourceConstraint::Eq(eref) => {
3997 PrincipalConstraint::Eq(self.convert_entity_reference(eref, slot_id).clone())
3998 }
3999 ast::PrincipalOrResourceConstraint::Is(entity_type) => {
4000 PrincipalConstraint::Is(entity_type.as_ref().clone().into())
4001 }
4002 ast::PrincipalOrResourceConstraint::IsIn(entity_type, eref) => {
4003 PrincipalConstraint::IsIn(
4004 entity_type.as_ref().clone().into(),
4005 self.convert_entity_reference(eref, slot_id).clone(),
4006 )
4007 }
4008 }
4009 }
4010
4011 pub fn action_constraint(&self) -> ActionConstraint {
4013 match self.ast.template().action_constraint() {
4015 ast::ActionConstraint::Any => ActionConstraint::Any,
4016 ast::ActionConstraint::In(ids) => ActionConstraint::In(
4017 ids.iter()
4018 .map(|euid| EntityUid::ref_cast(euid.as_ref()))
4019 .cloned()
4020 .collect(),
4021 ),
4022 ast::ActionConstraint::Eq(id) => ActionConstraint::Eq(EntityUid::ref_cast(id).clone()),
4023 #[cfg(feature = "tolerant-ast")]
4024 #[expect(clippy::unimplemented, reason = "experimental feature")]
4025 ast::ActionConstraint::ErrorConstraint => {
4026 unimplemented!("internal ErrorConstraint cannot be represented in the public API")
4027 }
4028 }
4029 }
4030
4031 pub fn resource_constraint(&self) -> ResourceConstraint {
4033 let slot_id = ast::SlotId::resource();
4034 match self.ast.template().resource_constraint().as_inner() {
4035 ast::PrincipalOrResourceConstraint::Any => ResourceConstraint::Any,
4036 ast::PrincipalOrResourceConstraint::In(eref) => {
4037 ResourceConstraint::In(self.convert_entity_reference(eref, slot_id).clone())
4038 }
4039 ast::PrincipalOrResourceConstraint::Eq(eref) => {
4040 ResourceConstraint::Eq(self.convert_entity_reference(eref, slot_id).clone())
4041 }
4042 ast::PrincipalOrResourceConstraint::Is(entity_type) => {
4043 ResourceConstraint::Is(entity_type.as_ref().clone().into())
4044 }
4045 ast::PrincipalOrResourceConstraint::IsIn(entity_type, eref) => {
4046 ResourceConstraint::IsIn(
4047 entity_type.as_ref().clone().into(),
4048 self.convert_entity_reference(eref, slot_id).clone(),
4049 )
4050 }
4051 }
4052 }
4053
4054 fn convert_entity_reference<'a>(
4061 &'a self,
4062 r: &'a ast::EntityReference,
4063 slot: ast::SlotId,
4064 ) -> &'a EntityUid {
4065 match r {
4066 ast::EntityReference::EUID(euid) => EntityUid::ref_cast(euid),
4067 #[expect(
4068 clippy::unwrap_used,
4069 reason = "This `unwrap` here is safe due the invariant (values total map) on policies"
4070 )]
4071 ast::EntityReference::Slot(_) => {
4072 EntityUid::ref_cast(self.ast.env().get(&slot).unwrap())
4073 }
4074 }
4075 }
4076
4077 pub fn parse(id: Option<PolicyId>, policy_src: impl AsRef<str>) -> Result<Self, ParseErrors> {
4086 let inline_ast = parser::parse_policy(id.map(Into::into), policy_src.as_ref())?;
4087 let (_, ast) = ast::Template::link_static_policy(inline_ast);
4088 Ok(Self {
4089 ast,
4090 lossless: LosslessPolicy::policy_or_template_text(Some(policy_src.as_ref())),
4091 })
4092 }
4093
4094 pub fn from_json(
4160 id: Option<PolicyId>,
4161 json: serde_json::Value,
4162 ) -> Result<Self, PolicyFromJsonError> {
4163 let est: est::Policy = serde_json::from_value(json)
4164 .map_err(|e| entities_json_errors::JsonDeserializationError::Serde(e.into()))
4165 .map_err(cedar_policy_core::est::FromJsonError::from)?;
4166 Self::from_est(id, est)
4167 }
4168
4169 pub fn get_valid_request_envs(&self, s: &Schema) -> impl Iterator<Item = RequestEnv> {
4174 get_valid_request_envs(self.ast.template(), s)
4175 }
4176
4177 pub fn entity_literals(&self) -> Vec<EntityUid> {
4179 self.ast
4180 .condition()
4181 .subexpressions()
4182 .filter_map(|e| match e.expr_kind() {
4183 cedar_policy_core::ast::ExprKind::Lit(
4184 cedar_policy_core::ast::Literal::EntityUID(euid),
4185 ) => Some(EntityUid((*euid).as_ref().clone())),
4186 _ => None,
4187 })
4188 .collect()
4189 }
4190
4191 pub fn sub_entity_literals(
4194 &self,
4195 mapping: BTreeMap<EntityUid, EntityUid>,
4196 ) -> Result<Self, PolicyFromJsonError> {
4197 #[expect(
4198 clippy::expect_used,
4199 reason = "This can't fail for a policy that was already constructed"
4200 )]
4201 let cloned_est = self
4202 .lossless
4203 .est(|| self.ast.clone().into())
4204 .expect("Internal error, failed to construct est.");
4205
4206 let mapping = mapping.into_iter().map(|(k, v)| (k.0, v.0)).collect();
4207
4208 #[expect(
4209 clippy::expect_used,
4210 reason = "This can't fail for a policy that was already constructed"
4211 )]
4212 let est = cloned_est
4213 .sub_entity_literals(&mapping)
4214 .expect("Internal error, failed to sub entity literals.");
4215
4216 let ast = est
4217 .clone()
4218 .try_into_ast_policy(Some(self.ast.id().clone()))?;
4219
4220 Ok(Self {
4221 ast,
4222 lossless: LosslessPolicy::Est(est),
4223 })
4224 }
4225
4226 fn from_est(id: Option<PolicyId>, est: est::Policy) -> Result<Self, PolicyFromJsonError> {
4227 Ok(Self {
4228 ast: est.clone().try_into_ast_policy(id.map(PolicyId::into))?,
4229 lossless: LosslessPolicy::Est(est),
4230 })
4231 }
4232
4233 pub fn to_json(&self) -> Result<serde_json::Value, PolicyToJsonError> {
4252 let est = self.lossless.est(|| self.ast.clone().into())?;
4253 serde_json::to_value(est).map_err(Into::into)
4254 }
4255
4256 pub fn to_cedar(&self) -> Option<String> {
4271 match &self.lossless {
4272 LosslessPolicy::Empty | LosslessPolicy::Est(_) | LosslessPolicy::Pst(_) => {
4273 Some(self.ast.to_string())
4274 }
4275 LosslessPolicy::Text { text, slots } => {
4276 if slots.is_empty() {
4277 Some(text.clone())
4278 } else {
4279 None
4280 }
4281 }
4282 }
4283 }
4284
4285 pub fn to_pst(&self) -> Result<pst::Policy, pst::PstConstructionError> {
4287 Self::pst_with_id(
4288 self.ast.id().clone(),
4289 self.lossless
4290 .pst(|| pst::Policy::try_from(self.ast.clone())),
4291 )
4292 }
4293
4294 pub fn try_into_pst(self) -> Result<pst::Policy, pst::PstConstructionError> {
4297 let id = self.ast.id().clone();
4298 Self::pst_with_id(
4299 id,
4300 self.lossless
4301 .try_into_pst(|| pst::Policy::try_from(self.ast)),
4302 )
4303 }
4304
4305 fn pst_with_id(
4307 id: ast::PolicyID,
4308 policy: Result<pst::Policy, pst::PstConstructionError>,
4309 ) -> Result<pst::Policy, pst::PstConstructionError> {
4310 policy.map(|policy| policy.new_id(id.into()))
4311 }
4312
4313 #[doc = include_str!("../experimental_warning.md")]
4315 #[cfg(feature = "partial-eval")]
4316 pub fn unknown_entities(&self) -> HashSet<EntityUid> {
4317 self.ast
4318 .unknown_entities()
4319 .into_iter()
4320 .map(Into::into)
4321 .collect()
4322 }
4323
4324 pub(crate) fn from_ast(ast: ast::Policy) -> Self {
4330 Self {
4346 ast,
4347 lossless: LosslessPolicy::Empty,
4348 }
4349 }
4350}
4351
4352impl std::fmt::Display for Policy {
4353 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4354 self.lossless.fmt(|| self.ast.clone().into(), f)
4356 }
4357}
4358
4359impl FromStr for Policy {
4360 type Err = ParseErrors;
4361 fn from_str(policy: &str) -> Result<Self, Self::Err> {
4369 Self::parse(None, policy)
4370 }
4371}
4372
4373#[derive(Debug, Clone)]
4376pub(crate) enum LosslessTemplate {
4377 Empty,
4379 Est(est::Policy),
4381 Pst(pst::Template),
4383 Text(String),
4385}
4386
4387impl LosslessTemplate {
4388 fn from_text(text: Option<impl Into<String>>) -> Self {
4390 text.map_or(Self::Empty, |text| Self::Text(text.into()))
4391 }
4392
4393 fn new_id(&self, id: PolicyId) -> Self {
4396 match self {
4397 Self::Pst(pst) => {
4398 let mut pst = pst.clone();
4399 pst.id = id.into();
4400 Self::Pst(pst)
4401 }
4402 other => other.clone(),
4403 }
4404 }
4405
4406 fn est(
4408 &self,
4409 fallback_est: impl FnOnce() -> est::Policy,
4410 ) -> Result<est::Policy, PolicyToJsonError> {
4411 match self {
4412 Self::Empty => Ok(fallback_est()),
4413 Self::Est(est) => Ok(est.clone()),
4414 Self::Pst(pst) => Ok(pst.clone().try_into()?),
4415 Self::Text(text) => {
4416 Ok(parser::parse_policy_or_template_to_est(text).map_err(ParseErrors::from)?)
4417 }
4418 }
4419 }
4420
4421 fn pst(
4423 &self,
4424 fallback_pst: impl FnOnce() -> Result<pst::Template, pst::PstConstructionError>,
4425 ) -> Result<pst::Template, pst::PstConstructionError> {
4426 match self {
4427 Self::Empty => fallback_pst(),
4428 Self::Est(est) => Ok(est.clone().try_into()?),
4429 Self::Pst(pst) => Ok(pst.clone()),
4430 Self::Text(text) => Ok(parser::parse_policy_or_template_to_est(text)?.try_into()?),
4431 }
4432 }
4433
4434 fn try_into_pst(
4436 self,
4437 fallback_pst: impl FnOnce() -> Result<pst::Template, pst::PstConstructionError>,
4438 ) -> Result<pst::Template, pst::PstConstructionError> {
4439 match self {
4440 Self::Empty => fallback_pst(),
4441 Self::Est(est) => Ok(est.try_into()?),
4442 Self::Pst(pst) => Ok(pst),
4443 Self::Text(text) => Ok(parser::parse_policy_or_template_to_est(&text)?.try_into()?),
4444 }
4445 }
4446
4447 fn link<'a>(
4449 self,
4450 link_id: ast::PolicyID,
4451 vals: impl IntoIterator<Item = (ast::SlotId, &'a ast::EntityUID)>,
4452 ) -> Result<LosslessPolicy, est::LinkingError> {
4453 match self {
4454 Self::Empty => Ok(LosslessPolicy::Empty),
4455 Self::Est(est) => {
4456 let unwrapped_vals: HashMap<
4457 ast::SlotId,
4458 cedar_policy_core::entities::EntityUidJson,
4459 > = vals.into_iter().map(|(k, v)| (k, v.into())).collect();
4460 Ok(LosslessPolicy::Est(est.link(&unwrapped_vals)?))
4461 }
4462 Self::Pst(template) => {
4463 let values: HashMap<pst::SlotId, pst::EntityUID> = vals
4464 .into_iter()
4465 .map(|(k, v)| (k.into(), v.clone().into()))
4466 .collect();
4467 let pst_policy = pst::LinkedPolicy::new(Arc::new(template), values, link_id.into())
4468 .map_err(est::LinkingError::from)?;
4469 Ok(LosslessPolicy::Pst(pst::Policy::Linked(pst_policy)))
4470 }
4471 Self::Text(text) => {
4472 let slots = vals.into_iter().map(|(k, v)| (k, v.clone())).collect();
4473 Ok(LosslessPolicy::Text { text, slots })
4474 }
4475 }
4476 }
4477
4478 fn fmt(
4479 &self,
4480 fallback_est: impl FnOnce() -> est::Policy,
4481 f: &mut std::fmt::Formatter<'_>,
4482 ) -> std::fmt::Result {
4483 match self {
4484 Self::Empty => match self.est(fallback_est) {
4485 Ok(est) => write!(f, "{est}"),
4486 Err(e) => write!(f, "<invalid policy: {e}>"),
4487 },
4488 Self::Pst(pst) => write!(f, "{pst}"), Self::Est(est) => write!(f, "{est}"),
4490 Self::Text(text) => write!(f, "{text}"),
4491 }
4492 }
4493}
4494
4495#[derive(Debug, Clone)]
4498pub(crate) enum LosslessPolicy {
4499 Empty,
4501 Est(est::Policy),
4503 Pst(pst::Policy),
4505 Text {
4507 text: String,
4509 slots: HashMap<ast::SlotId, ast::EntityUID>,
4512 },
4513}
4514
4515impl LosslessPolicy {
4516 fn policy_or_template_text(text: Option<impl Into<String>>) -> Self {
4518 text.map_or(Self::Empty, |text| Self::Text {
4519 text: text.into(),
4520 slots: HashMap::new(),
4521 })
4522 }
4523
4524 fn new_id(&self, id: PolicyId) -> Self {
4527 match self {
4528 Self::Pst(pst) => Self::Pst(pst.new_id(id.into())),
4529 other => other.clone(),
4530 }
4531 }
4532
4533 fn est(
4535 &self,
4536 fallback_est: impl FnOnce() -> est::Policy,
4537 ) -> Result<est::Policy, PolicyToJsonError> {
4538 match self {
4539 Self::Empty => Ok(fallback_est()),
4540 Self::Est(est) => Ok(est.clone()),
4541 Self::Pst(pst) => {
4542 match pst {
4545 pst::Policy::Static(sp) => Ok(sp.body().clone().try_into()?),
4546 pst::Policy::Linked(lp) => {
4547 let static_policy = lp.into_static_policy()?;
4548 Ok(static_policy.body().clone().try_into()?)
4549 }
4550 }
4551 }
4552 Self::Text { text, slots } => {
4553 let est =
4554 parser::parse_policy_or_template_to_est(text).map_err(ParseErrors::from)?;
4555 if slots.is_empty() {
4556 Ok(est)
4557 } else {
4558 let unwrapped_vals = slots.iter().map(|(k, v)| (*k, v.into())).collect();
4559 Ok(est.link(&unwrapped_vals)?)
4560 }
4561 }
4562 }
4563 }
4564
4565 fn pst(
4567 &self,
4568 fallback_pst: impl FnOnce() -> Result<pst::Policy, pst::PstConstructionError>,
4569 ) -> Result<pst::Policy, pst::PstConstructionError> {
4570 match self {
4571 Self::Empty => fallback_pst(),
4572 Self::Est(est) => {
4573 let template: pst::Template = est.clone().try_into()?;
4574 Ok(pst::Policy::Static(pst::StaticPolicy::try_from(template)?))
4575 }
4576 Self::Pst(pst) => Ok(pst.clone()),
4577 Self::Text { text, slots } => {
4578 let template: pst::Template =
4579 parser::parse_policy_or_template_to_est(text)?.try_into()?;
4580 if slots.is_empty() {
4581 Ok(pst::Policy::Static(pst::StaticPolicy::try_from(template)?))
4582 } else {
4583 let pst_vals: HashMap<pst::SlotId, pst::EntityUID> = slots
4584 .iter()
4585 .map(|(k, v)| ((*k).into(), v.clone().into()))
4586 .collect();
4587 let static_policy = template.link(&pst_vals)?;
4588 Ok(pst::Policy::Static(static_policy))
4589 }
4590 }
4591 }
4592 }
4593
4594 fn try_into_pst(
4596 self,
4597 fallback_pst: impl FnOnce() -> Result<pst::Policy, pst::PstConstructionError>,
4598 ) -> Result<pst::Policy, pst::PstConstructionError> {
4599 match self {
4600 Self::Empty => fallback_pst(),
4601 Self::Est(est) => {
4602 let template: pst::Template = est.try_into()?;
4603 Ok(pst::Policy::Static(pst::StaticPolicy::try_from(template)?))
4604 }
4605 Self::Pst(pst) => Ok(pst),
4606 Self::Text { text, slots } => {
4607 let template: pst::Template =
4608 parser::parse_policy_or_template_to_est(&text)?.try_into()?;
4609 if slots.is_empty() {
4610 Ok(pst::Policy::Static(pst::StaticPolicy::try_from(template)?))
4611 } else {
4612 let pst_vals: HashMap<pst::SlotId, pst::EntityUID> = slots
4613 .into_iter()
4614 .map(|(k, v)| (pst::SlotId::from(k), v.into()))
4615 .collect();
4616 let static_policy = template.link(&pst_vals)?;
4617 Ok(pst::Policy::Static(static_policy))
4618 }
4619 }
4620 }
4621 }
4622
4623 fn fmt(
4624 &self,
4625 fallback_est: impl FnOnce() -> est::Policy,
4626 f: &mut std::fmt::Formatter<'_>,
4627 ) -> std::fmt::Result {
4628 match self {
4629 Self::Empty => match self.est(fallback_est) {
4630 Ok(est) => write!(f, "{est}"),
4631 Err(e) => write!(f, "<invalid policy: {e}>"),
4632 },
4633 Self::Pst(pst) => write!(f, "{pst}"), Self::Est(est) => write!(f, "{est}"),
4635 Self::Text { text, slots } => {
4636 if slots.is_empty() {
4637 write!(f, "{text}")
4638 } else {
4639 match self.est(fallback_est) {
4640 Ok(est) => write!(f, "{est}"),
4641 Err(e) => write!(f, "<invalid linked policy: {e}>"),
4642 }
4643 }
4644 }
4645 }
4646 }
4647}
4648
4649#[repr(transparent)]
4651#[derive(Debug, Clone, RefCast)]
4652pub struct Expression(pub(crate) ast::Expr);
4653
4654#[doc(hidden)] impl AsRef<ast::Expr> for Expression {
4656 fn as_ref(&self) -> &ast::Expr {
4657 &self.0
4658 }
4659}
4660
4661#[doc(hidden)]
4662impl From<ast::Expr> for Expression {
4663 fn from(expr: ast::Expr) -> Self {
4664 Self(expr)
4665 }
4666}
4667
4668impl Expression {
4669 pub fn new_string(value: String) -> Self {
4671 Self(ast::Expr::val(value))
4672 }
4673
4674 pub fn new_bool(value: bool) -> Self {
4676 Self(ast::Expr::val(value))
4677 }
4678
4679 pub fn new_long(value: ast::Integer) -> Self {
4681 Self(ast::Expr::val(value))
4682 }
4683
4684 pub fn new_record(
4688 fields: impl IntoIterator<Item = (String, Self)>,
4689 ) -> Result<Self, ExpressionConstructionError> {
4690 Ok(Self(ast::Expr::record(
4691 fields.into_iter().map(|(k, v)| (SmolStr::from(k), v.0)),
4692 )?))
4693 }
4694
4695 pub fn new_set(values: impl IntoIterator<Item = Self>) -> Self {
4697 Self(ast::Expr::set(values.into_iter().map(|v| v.0)))
4698 }
4699
4700 pub fn new_ip(src: impl AsRef<str>) -> Self {
4704 let src_expr = ast::Expr::val(src.as_ref());
4705 Self(ast::Expr::call_extension_fn(
4706 ip_extension_name(),
4707 vec![src_expr],
4708 ))
4709 }
4710
4711 pub fn new_decimal(src: impl AsRef<str>) -> Self {
4715 let src_expr = ast::Expr::val(src.as_ref());
4716 Self(ast::Expr::call_extension_fn(
4717 decimal_extension_name(),
4718 vec![src_expr],
4719 ))
4720 }
4721
4722 pub fn new_datetime(src: impl AsRef<str>) -> Self {
4726 let src_expr = ast::Expr::val(src.as_ref());
4727 Self(ast::Expr::call_extension_fn(
4728 datetime_extension_name(),
4729 vec![src_expr],
4730 ))
4731 }
4732
4733 pub fn new_duration(src: impl AsRef<str>) -> Self {
4737 let src_expr = ast::Expr::val(src.as_ref());
4738 Self(ast::Expr::call_extension_fn(
4739 duration_extension_name(),
4740 vec![src_expr],
4741 ))
4742 }
4743}
4744
4745#[cfg(test)]
4746impl Expression {
4747 pub(crate) fn into_inner(self) -> ast::Expr {
4750 self.0
4751 }
4752}
4753
4754impl FromStr for Expression {
4755 type Err = ParseErrors;
4756
4757 fn from_str(expression: &str) -> Result<Self, Self::Err> {
4759 ast::Expr::from_str(expression)
4760 .map(Expression)
4761 .map_err(Into::into)
4762 }
4763}
4764
4765impl std::fmt::Display for Expression {
4766 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4767 write!(f, "{}", self.0)
4768 }
4769}
4770
4771#[repr(transparent)]
4787#[derive(Debug, Clone, RefCast, PartialEq, Eq)]
4788pub struct RestrictedExpression(pub(crate) ast::RestrictedExpr);
4789
4790#[doc(hidden)] impl AsRef<ast::RestrictedExpr> for RestrictedExpression {
4792 fn as_ref(&self) -> &ast::RestrictedExpr {
4793 &self.0
4794 }
4795}
4796
4797#[doc(hidden)]
4798impl From<ast::RestrictedExpr> for RestrictedExpression {
4799 fn from(expr: ast::RestrictedExpr) -> Self {
4800 Self(expr)
4801 }
4802}
4803
4804impl RestrictedExpression {
4805 pub fn new_string(value: String) -> Self {
4807 Self(ast::RestrictedExpr::val(value))
4808 }
4809
4810 pub fn new_bool(value: bool) -> Self {
4812 Self(ast::RestrictedExpr::val(value))
4813 }
4814
4815 pub fn new_long(value: ast::Integer) -> Self {
4817 Self(ast::RestrictedExpr::val(value))
4818 }
4819
4820 pub fn new_entity_uid(value: EntityUid) -> Self {
4822 Self(ast::RestrictedExpr::val(ast::EntityUID::from(value)))
4823 }
4824
4825 pub fn new_record(
4829 fields: impl IntoIterator<Item = (String, Self)>,
4830 ) -> Result<Self, ExpressionConstructionError> {
4831 Ok(Self(ast::RestrictedExpr::record(
4832 fields.into_iter().map(|(k, v)| (SmolStr::from(k), v.0)),
4833 )?))
4834 }
4835
4836 pub fn new_set(values: impl IntoIterator<Item = Self>) -> Self {
4838 Self(ast::RestrictedExpr::set(values.into_iter().map(|v| v.0)))
4839 }
4840
4841 pub fn new_ip(src: impl AsRef<str>) -> Self {
4845 let src_expr = ast::RestrictedExpr::val(src.as_ref());
4846 Self(ast::RestrictedExpr::call_extension_fn(
4847 ip_extension_name(),
4848 [src_expr],
4849 ))
4850 }
4851
4852 pub fn new_decimal(src: impl AsRef<str>) -> Self {
4856 let src_expr = ast::RestrictedExpr::val(src.as_ref());
4857 Self(ast::RestrictedExpr::call_extension_fn(
4858 decimal_extension_name(),
4859 [src_expr],
4860 ))
4861 }
4862
4863 pub fn new_datetime(src: impl AsRef<str>) -> Self {
4867 let src_expr = ast::RestrictedExpr::val(src.as_ref());
4868 Self(ast::RestrictedExpr::call_extension_fn(
4869 datetime_extension_name(),
4870 [src_expr],
4871 ))
4872 }
4873
4874 pub fn new_duration(src: impl AsRef<str>) -> Self {
4878 let src_expr = ast::RestrictedExpr::val(src.as_ref());
4879 Self(ast::RestrictedExpr::call_extension_fn(
4880 duration_extension_name(),
4881 [src_expr],
4882 ))
4883 }
4884
4885 #[cfg(feature = "partial-eval")]
4887 pub fn new_unknown(name: impl AsRef<str>) -> Self {
4888 Self(ast::RestrictedExpr::unknown(ast::Unknown::new_untyped(
4889 name.as_ref(),
4890 )))
4891 }
4892}
4893
4894#[cfg(test)]
4895impl RestrictedExpression {
4896 pub(crate) fn into_inner(self) -> ast::RestrictedExpr {
4899 self.0
4900 }
4901}
4902
4903fn decimal_extension_name() -> ast::Name {
4904 #[expect(
4905 clippy::unwrap_used,
4906 reason = "This is a constant and is known to be safe, verified by a test"
4907 )]
4908 ast::Name::unqualified_name("decimal".parse().unwrap())
4909}
4910
4911fn ip_extension_name() -> ast::Name {
4912 #[expect(
4913 clippy::unwrap_used,
4914 reason = "This is a constant and is known to be safe, verified by a test"
4915 )]
4916 ast::Name::unqualified_name("ip".parse().unwrap())
4917}
4918
4919fn datetime_extension_name() -> ast::Name {
4920 #[expect(
4921 clippy::unwrap_used,
4922 reason = "This is a constant and is known to be safe, verified by a test"
4923 )]
4924 ast::Name::unqualified_name("datetime".parse().unwrap())
4925}
4926
4927fn duration_extension_name() -> ast::Name {
4928 #[expect(
4929 clippy::unwrap_used,
4930 reason = "This is a constant and is known to be safe, verified by a test"
4931 )]
4932 ast::Name::unqualified_name("duration".parse().unwrap())
4933}
4934
4935impl FromStr for RestrictedExpression {
4936 type Err = RestrictedExpressionParseError;
4937
4938 fn from_str(expression: &str) -> Result<Self, Self::Err> {
4940 ast::RestrictedExpr::from_str(expression)
4941 .map(RestrictedExpression)
4942 .map_err(Into::into)
4943 }
4944}
4945
4946#[doc = include_str!("../experimental_warning.md")]
4951#[cfg(feature = "partial-eval")]
4952#[derive(Debug, Clone)]
4953pub struct RequestBuilder<S> {
4954 principal: ast::EntityUIDEntry,
4955 action: ast::EntityUIDEntry,
4956 resource: ast::EntityUIDEntry,
4957 context: Option<ast::Context>,
4959 schema: S,
4960}
4961
4962#[doc = include_str!("../experimental_warning.md")]
4964#[cfg(feature = "partial-eval")]
4965#[derive(Debug, Clone, Copy)]
4966pub struct UnsetSchema;
4967
4968#[cfg(feature = "partial-eval")]
4969impl Default for RequestBuilder<UnsetSchema> {
4970 fn default() -> Self {
4971 Self {
4972 principal: ast::EntityUIDEntry::unknown(),
4973 action: ast::EntityUIDEntry::unknown(),
4974 resource: ast::EntityUIDEntry::unknown(),
4975 context: None,
4976 schema: UnsetSchema,
4977 }
4978 }
4979}
4980
4981#[cfg(feature = "partial-eval")]
4982impl<S> RequestBuilder<S> {
4983 #[must_use]
4988 pub fn principal(self, principal: EntityUid) -> Self {
4989 Self {
4990 principal: ast::EntityUIDEntry::known(principal.into(), None),
4991 ..self
4992 }
4993 }
4994
4995 #[must_use]
4999 pub fn unknown_principal_with_type(self, principal_type: EntityTypeName) -> Self {
5000 Self {
5001 principal: ast::EntityUIDEntry::unknown_with_type(principal_type.0, None),
5002 ..self
5003 }
5004 }
5005
5006 #[must_use]
5011 pub fn action(self, action: EntityUid) -> Self {
5012 Self {
5013 action: ast::EntityUIDEntry::known(action.into(), None),
5014 ..self
5015 }
5016 }
5017
5018 #[must_use]
5023 pub fn resource(self, resource: EntityUid) -> Self {
5024 Self {
5025 resource: ast::EntityUIDEntry::known(resource.into(), None),
5026 ..self
5027 }
5028 }
5029
5030 #[must_use]
5034 pub fn unknown_resource_with_type(self, resource_type: EntityTypeName) -> Self {
5035 Self {
5036 resource: ast::EntityUIDEntry::unknown_with_type(resource_type.0, None),
5037 ..self
5038 }
5039 }
5040
5041 #[must_use]
5043 pub fn context(self, context: Context) -> Self {
5044 Self {
5045 context: Some(context.0),
5046 ..self
5047 }
5048 }
5049}
5050
5051#[cfg(feature = "partial-eval")]
5052impl RequestBuilder<UnsetSchema> {
5053 #[must_use]
5055 pub fn schema(self, schema: &Schema) -> RequestBuilder<&Schema> {
5056 RequestBuilder {
5057 principal: self.principal,
5058 action: self.action,
5059 resource: self.resource,
5060 context: self.context,
5061 schema,
5062 }
5063 }
5064
5065 pub fn build(self) -> Request {
5067 Request(ast::Request::new_unchecked(
5068 self.principal,
5069 self.action,
5070 self.resource,
5071 self.context,
5072 ))
5073 }
5074}
5075
5076#[cfg(feature = "partial-eval")]
5077impl RequestBuilder<&Schema> {
5078 pub fn build(self) -> Result<Request, RequestValidationError> {
5080 Ok(Request(ast::Request::new_with_unknowns(
5081 self.principal,
5082 self.action,
5083 self.resource,
5084 self.context,
5085 Some(&self.schema.0),
5086 Extensions::all_available(),
5087 )?))
5088 }
5089}
5090
5091#[repr(transparent)]
5100#[derive(Debug, Clone, RefCast)]
5101pub struct Request(pub(crate) ast::Request);
5102
5103#[doc(hidden)] impl AsRef<ast::Request> for Request {
5105 fn as_ref(&self) -> &ast::Request {
5106 &self.0
5107 }
5108}
5109
5110#[doc(hidden)]
5111impl From<ast::Request> for Request {
5112 fn from(req: ast::Request) -> Self {
5113 Self(req)
5114 }
5115}
5116
5117impl PartialEq for Request {
5118 fn eq(&self, other: &Self) -> bool {
5119 self.principal() == other.principal()
5120 && self.action() == other.action()
5121 && self.resource() == other.resource()
5122 && self.context() == other.context()
5123 }
5124}
5125
5126impl Request {
5127 #[doc = include_str!("../experimental_warning.md")]
5129 #[cfg(feature = "partial-eval")]
5130 pub fn builder() -> RequestBuilder<UnsetSchema> {
5131 RequestBuilder::default()
5132 }
5133
5134 pub fn new(
5147 principal: EntityUid,
5148 action: EntityUid,
5149 resource: EntityUid,
5150 context: Context,
5151 schema: Option<&Schema>,
5152 ) -> Result<Self, RequestValidationError> {
5153 Ok(Self(ast::Request::new(
5154 (principal.into(), None),
5155 (action.into(), None),
5156 (resource.into(), None),
5157 context.0,
5158 schema.map(|schema| &schema.0),
5159 Extensions::all_available(),
5160 )?))
5161 }
5162
5163 pub fn context(&self) -> Option<&Context> {
5166 self.0.context().map(Context::ref_cast)
5167 }
5168
5169 pub fn principal(&self) -> Option<&EntityUid> {
5172 match self.0.principal() {
5173 ast::EntityUIDEntry::Known { euid, .. } => Some(EntityUid::ref_cast(euid.as_ref())),
5174 ast::EntityUIDEntry::Unknown { .. } => None,
5175 }
5176 }
5177
5178 pub fn action(&self) -> Option<&EntityUid> {
5181 match self.0.action() {
5182 ast::EntityUIDEntry::Known { euid, .. } => Some(EntityUid::ref_cast(euid.as_ref())),
5183 ast::EntityUIDEntry::Unknown { .. } => None,
5184 }
5185 }
5186
5187 pub fn resource(&self) -> Option<&EntityUid> {
5190 match self.0.resource() {
5191 ast::EntityUIDEntry::Known { euid, .. } => Some(EntityUid::ref_cast(euid.as_ref())),
5192 ast::EntityUIDEntry::Unknown { .. } => None,
5193 }
5194 }
5195}
5196
5197#[repr(transparent)]
5199#[derive(Debug, Clone, PartialEq, Eq, RefCast)]
5200pub struct Context(ast::Context);
5201
5202#[doc(hidden)] impl AsRef<ast::Context> for Context {
5204 fn as_ref(&self) -> &ast::Context {
5205 &self.0
5206 }
5207}
5208
5209impl Context {
5210 pub fn empty() -> Self {
5217 Self(ast::Context::empty())
5218 }
5219
5220 pub fn from_pairs(
5237 pairs: impl IntoIterator<Item = (String, RestrictedExpression)>,
5238 ) -> Result<Self, ContextCreationError> {
5239 Ok(Self(ast::Context::from_pairs(
5240 pairs.into_iter().map(|(k, v)| (SmolStr::from(k), v.0)),
5241 Extensions::all_available(),
5242 )?))
5243 }
5244
5245 pub fn get(&self, key: &str) -> Option<EvalResult> {
5269 match &self.0 {
5270 ast::Context::Value(map) => map.get(key).map(|v| EvalResult::from(v.clone())),
5271 ast::Context::RestrictedResidual(_) => None,
5272 }
5273 }
5274
5275 pub fn from_json_str(
5305 json: &str,
5306 schema: Option<(&Schema, &EntityUid)>,
5307 ) -> Result<Self, ContextJsonError> {
5308 let schema = schema
5309 .map(|(s, uid)| Self::get_context_schema(s, uid))
5310 .transpose()?;
5311 let context = cedar_policy_core::entities::ContextJsonParser::new(
5312 schema.as_ref(),
5313 Extensions::all_available(),
5314 )
5315 .from_json_str(json)?;
5316 Ok(Self(context))
5317 }
5318
5319 pub fn from_json_value(
5369 json: serde_json::Value,
5370 schema: Option<(&Schema, &EntityUid)>,
5371 ) -> Result<Self, ContextJsonError> {
5372 let schema = schema
5373 .map(|(s, uid)| Self::get_context_schema(s, uid))
5374 .transpose()?;
5375 let context = cedar_policy_core::entities::ContextJsonParser::new(
5376 schema.as_ref(),
5377 Extensions::all_available(),
5378 )
5379 .from_json_value(json)?;
5380 Ok(Self(context))
5381 }
5382
5383 pub fn from_json_file(
5415 json: impl std::io::Read,
5416 schema: Option<(&Schema, &EntityUid)>,
5417 ) -> Result<Self, ContextJsonError> {
5418 let schema = schema
5419 .map(|(s, uid)| Self::get_context_schema(s, uid))
5420 .transpose()?;
5421 let context = cedar_policy_core::entities::ContextJsonParser::new(
5422 schema.as_ref(),
5423 Extensions::all_available(),
5424 )
5425 .from_json_file(json)?;
5426 Ok(Self(context))
5427 }
5428
5429 pub fn to_json_value(
5431 &self,
5432 ) -> Result<serde_json::Value, entities_json_errors::JsonSerializationError> {
5433 self.0.to_json_value()
5434 }
5435
5436 fn get_context_schema(
5438 schema: &Schema,
5439 action: &EntityUid,
5440 ) -> Result<impl ContextSchema, ContextJsonError> {
5441 cedar_policy_core::validator::context_schema_for_action(&schema.0, action.as_ref())
5442 .ok_or_else(|| ContextJsonError::missing_action(action.clone()))
5443 }
5444
5445 pub fn merge(
5449 self,
5450 other_context: impl IntoIterator<Item = (String, RestrictedExpression)>,
5451 ) -> Result<Self, ContextCreationError> {
5452 Self::from_pairs(self.into_iter().chain(other_context))
5453 }
5454
5455 pub fn validate(
5462 &self,
5463 schema: &crate::Schema,
5464 action: &EntityUid,
5465 ) -> std::result::Result<(), RequestValidationError> {
5466 Ok(RequestSchema::validate_context(
5468 &schema.0,
5469 &self.0,
5470 action.as_ref(),
5471 Extensions::all_available(),
5472 )?)
5473 }
5474}
5475
5476mod context {
5478 use super::{ast, RestrictedExpression};
5479
5480 #[derive(Debug)]
5482 pub struct IntoIter {
5483 pub(super) inner: <ast::Context as IntoIterator>::IntoIter,
5484 }
5485
5486 impl Iterator for IntoIter {
5487 type Item = (String, RestrictedExpression);
5488
5489 fn next(&mut self) -> Option<Self::Item> {
5490 self.inner
5491 .next()
5492 .map(|(k, v)| (k.to_string(), RestrictedExpression(v)))
5493 }
5494 }
5495}
5496
5497impl IntoIterator for Context {
5498 type Item = (String, RestrictedExpression);
5499
5500 type IntoIter = context::IntoIter;
5501
5502 fn into_iter(self) -> Self::IntoIter {
5503 Self::IntoIter {
5504 inner: self.0.into_iter(),
5505 }
5506 }
5507}
5508
5509#[doc(hidden)]
5510impl From<ast::Context> for Context {
5511 fn from(c: ast::Context) -> Self {
5512 Self(c)
5513 }
5514}
5515
5516impl std::fmt::Display for Request {
5517 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5518 write!(f, "{}", self.0)
5519 }
5520}
5521
5522impl std::fmt::Display for Context {
5523 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5524 write!(f, "{}", self.0)
5525 }
5526}
5527
5528#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
5530pub enum EvalResult {
5531 Bool(bool),
5533 Long(ast::Integer),
5535 String(String),
5537 EntityUid(EntityUid),
5539 Set(Set),
5541 Record(Record),
5543 ExtensionValue(String),
5545 }
5547
5548#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)]
5550pub struct Set(BTreeSet<EvalResult>);
5551
5552impl Set {
5553 pub fn iter(&self) -> impl Iterator<Item = &EvalResult> {
5555 self.0.iter()
5556 }
5557
5558 pub fn contains(&self, elem: &EvalResult) -> bool {
5560 self.0.contains(elem)
5561 }
5562
5563 pub fn len(&self) -> usize {
5565 self.0.len()
5566 }
5567
5568 pub fn is_empty(&self) -> bool {
5570 self.0.is_empty()
5571 }
5572}
5573
5574#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)]
5576pub struct Record(BTreeMap<String, EvalResult>);
5577
5578impl Record {
5579 pub fn iter(&self) -> impl Iterator<Item = (&String, &EvalResult)> {
5581 self.0.iter()
5582 }
5583
5584 pub fn contains_attribute(&self, key: impl AsRef<str>) -> bool {
5586 self.0.contains_key(key.as_ref())
5587 }
5588
5589 pub fn get(&self, key: impl AsRef<str>) -> Option<&EvalResult> {
5591 self.0.get(key.as_ref())
5592 }
5593
5594 pub fn len(&self) -> usize {
5596 self.0.len()
5597 }
5598
5599 pub fn is_empty(&self) -> bool {
5601 self.0.is_empty()
5602 }
5603}
5604
5605#[doc(hidden)]
5606impl From<ast::Value> for EvalResult {
5607 fn from(v: ast::Value) -> Self {
5608 match v.value {
5609 ast::ValueKind::Lit(ast::Literal::Bool(b)) => Self::Bool(b),
5610 ast::ValueKind::Lit(ast::Literal::Long(i)) => Self::Long(i),
5611 ast::ValueKind::Lit(ast::Literal::String(s)) => Self::String(s.to_string()),
5612 ast::ValueKind::Lit(ast::Literal::EntityUID(e)) => {
5613 Self::EntityUid(ast::EntityUID::clone(&e).into())
5614 }
5615 ast::ValueKind::Set(set) => Self::Set(Set(set
5616 .authoritative
5617 .iter()
5618 .map(|v| v.clone().into())
5619 .collect())),
5620 ast::ValueKind::Record(record) => Self::Record(Record(
5621 record
5622 .iter()
5623 .map(|(k, v)| (k.to_string(), v.clone().into()))
5624 .collect(),
5625 )),
5626 ast::ValueKind::ExtensionValue(ev) => {
5627 Self::ExtensionValue(RestrictedExpr::from(ev.as_ref().clone()).to_string())
5628 }
5629 }
5630 }
5631}
5632
5633#[doc(hidden)]
5634#[expect(
5635 clippy::fallible_impl_from,
5636 reason = "see the panic safety comments below"
5637)]
5638impl From<EvalResult> for Expression {
5639 fn from(res: EvalResult) -> Self {
5640 match res {
5641 EvalResult::Bool(b) => Self::new_bool(b),
5642 EvalResult::Long(l) => Self::new_long(l),
5643 EvalResult::String(s) => Self::new_string(s),
5644 EvalResult::EntityUid(eid) => {
5645 Self::from(ast::Expr::from(ast::Value::from(ast::EntityUID::from(eid))))
5646 }
5647 EvalResult::Set(set) => Self::new_set(set.iter().cloned().map(Self::from)),
5648 EvalResult::Record(r) =>
5649 {
5650 #[expect(
5651 clippy::unwrap_used,
5652 reason = "record originates from EvalResult so should not panic when reconstructing as an Expression"
5653 )]
5654 Self::new_record(r.iter().map(|(k, v)| (k.clone(), Self::from(v.clone())))).unwrap()
5655 }
5656 EvalResult::ExtensionValue(s) => {
5657 #[expect(
5658 clippy::unwrap_used,
5659 reason = "the string s is constructed using RestrictedExpr::to_string() so should not panic when being parsed back into a RestrictedExpr"
5660 )]
5661 let expr: ast::Expr = ast::RestrictedExpr::from_str(&s).unwrap().into();
5662 Self::from(expr)
5663 }
5664 }
5665 }
5666}
5667
5668impl std::fmt::Display for EvalResult {
5669 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5670 match self {
5671 Self::Bool(b) => write!(f, "{b}"),
5672 Self::Long(l) => write!(f, "{l}"),
5673 Self::String(s) => write!(f, "\"{}\"", s.escape_debug()),
5674 Self::EntityUid(uid) => write!(f, "{uid}"),
5675 Self::Set(s) => {
5676 write!(f, "[")?;
5677 for (i, ev) in s.iter().enumerate() {
5678 write!(f, "{ev}")?;
5679 if (i + 1) < s.len() {
5680 write!(f, ", ")?;
5681 }
5682 }
5683 write!(f, "]")?;
5684 Ok(())
5685 }
5686 Self::Record(r) => {
5687 write!(f, "{{")?;
5688 for (i, (k, v)) in r.iter().enumerate() {
5689 write!(f, "\"{}\": {v}", k.escape_debug())?;
5690 if (i + 1) < r.len() {
5691 write!(f, ", ")?;
5692 }
5693 }
5694 write!(f, "}}")?;
5695 Ok(())
5696 }
5697 Self::ExtensionValue(s) => write!(f, "{s}"),
5698 }
5699 }
5700}
5701
5702pub fn eval_expression(
5707 request: &Request,
5708 entities: &Entities,
5709 expr: &Expression,
5710) -> Result<EvalResult, EvaluationError> {
5711 let all_ext = Extensions::all_available();
5712 let eval = Evaluator::new(request.0.clone(), &entities.0, all_ext);
5713 Ok(EvalResult::from(
5714 eval.interpret(&expr.0, &ast::SlotEnv::new())?,
5716 ))
5717}
5718
5719#[cfg(test)]
5721mod test_access {
5722 use cedar_policy_core::ast;
5723
5724 use super::*;
5725
5726 fn schema() -> Schema {
5727 let src = r#"
5728 type Task = {
5729 "id": Long,
5730 "name": String,
5731 "state": String,
5732};
5733
5734type T = String;
5735
5736type Tasks = Set<Task>;
5737entity List in [Application] = {
5738 "editors": Team,
5739 "name": String,
5740 "owner": User,
5741 "readers": Team,
5742 "tasks": Tasks,
5743};
5744entity Application;
5745entity User in [Team, Application] = {
5746 "joblevel": Long,
5747 "location": String,
5748};
5749
5750entity CoolList;
5751
5752entity Team in [Team, Application];
5753
5754action Read, Write, Create;
5755
5756action DeleteList, EditShare, UpdateList, CreateTask, UpdateTask, DeleteTask in Write appliesTo {
5757 principal: [User],
5758 resource : [List]
5759};
5760
5761action GetList in Read appliesTo {
5762 principal : [User],
5763 resource : [List, CoolList]
5764};
5765
5766action GetLists in Read appliesTo {
5767 principal : [User],
5768 resource : [Application]
5769};
5770
5771action CreateList in Create appliesTo {
5772 principal : [User],
5773 resource : [Application]
5774};
5775
5776 "#;
5777
5778 src.parse().unwrap()
5779 }
5780
5781 #[test]
5782 fn principals() {
5783 let schema = schema();
5784 let principals = schema.principals().collect::<HashSet<_>>();
5785 assert_eq!(principals.len(), 1);
5786 let user: EntityTypeName = "User".parse().unwrap();
5787 assert!(principals.contains(&user));
5788 let principals = schema.principals().collect::<Vec<_>>();
5789 assert!(principals.len() > 1);
5790 assert!(principals.iter().all(|ety| **ety == user));
5791 assert!(principals.iter().all(|ety| ety.0.loc().is_some()));
5792
5793 let et = ast::EntityType::EntityType(ast::Name::from_normalized_str("User").unwrap());
5794 let et = schema.0.get_entity_type(&et).unwrap();
5795 assert!(et.loc.is_some());
5796 }
5797
5798 #[cfg(feature = "extended-schema")]
5799 #[test]
5800 fn common_types_extended() {
5801 use cool_asserts::assert_matches;
5802
5803 use cedar_policy_core::validator::{types::Type, LocatedCommonType};
5804
5805 let schema = schema();
5806 assert_eq!(schema.0.common_types().collect::<HashSet<_>>().len(), 3);
5807 let task_type = LocatedCommonType {
5808 name: "Task".into(),
5809 name_loc: None,
5810 type_loc: None,
5811 };
5812 assert!(schema.0.common_types().contains(&task_type));
5813
5814 let tasks_type = LocatedCommonType {
5815 name: "Tasks".into(),
5816 name_loc: None,
5817 type_loc: None,
5818 };
5819 assert!(schema.0.common_types().contains(&tasks_type));
5820 assert!(schema.0.common_types().all(|ct| ct.name_loc.is_some()));
5821 assert!(schema.0.common_types().all(|ct| ct.type_loc.is_some()));
5822
5823 let tasks_type = LocatedCommonType {
5824 name: "T".into(),
5825 name_loc: None,
5826 type_loc: None,
5827 };
5828 assert!(schema.0.common_types().contains(&tasks_type));
5829
5830 let et = ast::EntityType::EntityType(ast::Name::from_normalized_str("List").unwrap());
5831 let et = schema.0.get_entity_type(&et).unwrap();
5832 let attrs = et.attributes();
5833
5834 let t = attrs.get_attr("tasks").unwrap();
5836 assert!(t.loc.is_some());
5837 assert_matches!(t.attr_type.as_ref(), cedar_policy_core::validator::types::Type::Set { ref element_type } => {
5838 let el = element_type.as_ref().unwrap();
5839 assert_matches!(el.as_ref(), Type::Record{ attrs, .. } => {
5840 assert!(attrs.get_attr("name").unwrap().loc.is_some());
5841 assert!(attrs.get_attr("id").unwrap().loc.is_some());
5842 assert!(attrs.get_attr("state").unwrap().loc.is_some());
5843 });
5844 });
5845 }
5846
5847 #[cfg(feature = "extended-schema")]
5848 #[test]
5849 fn namespace_extended() {
5850 let schema = schema();
5851 assert_eq!(schema.0.namespaces().collect::<HashSet<_>>().len(), 1);
5852 let default_namespace = schema.0.namespaces().last().unwrap();
5853 assert_eq!(default_namespace.name, SmolStr::from("__cedar"));
5854 assert!(default_namespace.name_loc.is_none());
5855 assert!(default_namespace.def_loc.is_none());
5856 }
5857
5858 #[test]
5859 fn empty_schema_principals_and_resources() {
5860 let empty: Schema = "".parse().unwrap();
5861 assert!(empty.principals().next().is_none());
5862 assert!(empty.resources().next().is_none());
5863 }
5864
5865 #[test]
5866 fn resources() {
5867 let schema = schema();
5868 let resources = schema.resources().cloned().collect::<HashSet<_>>();
5869 let expected: HashSet<EntityTypeName> = HashSet::from([
5870 "List".parse().unwrap(),
5871 "Application".parse().unwrap(),
5872 "CoolList".parse().unwrap(),
5873 ]);
5874 assert_eq!(resources, expected);
5875 assert!(resources.iter().all(|ety| ety.0.loc().is_some()));
5876 }
5877
5878 #[test]
5879 fn principals_for_action() {
5880 let schema = schema();
5881 let delete_list: EntityUid = r#"Action::"DeleteList""#.parse().unwrap();
5882 let delete_user: EntityUid = r#"Action::"DeleteUser""#.parse().unwrap();
5883 let got = schema
5884 .principals_for_action(&delete_list)
5885 .unwrap()
5886 .cloned()
5887 .collect::<Vec<_>>();
5888 assert_eq!(got, vec!["User".parse().unwrap()]);
5889 assert!(got.iter().all(|ety| ety.0.loc().is_some()));
5890 assert!(schema.principals_for_action(&delete_user).is_none());
5891 }
5892
5893 #[test]
5894 fn resources_for_action() {
5895 let schema = schema();
5896 let delete_list: EntityUid = r#"Action::"DeleteList""#.parse().unwrap();
5897 let delete_user: EntityUid = r#"Action::"DeleteUser""#.parse().unwrap();
5898 let create_list: EntityUid = r#"Action::"CreateList""#.parse().unwrap();
5899 let get_list: EntityUid = r#"Action::"GetList""#.parse().unwrap();
5900 let got = schema
5901 .resources_for_action(&delete_list)
5902 .unwrap()
5903 .cloned()
5904 .collect::<Vec<_>>();
5905 assert_eq!(got, vec!["List".parse().unwrap()]);
5906 assert!(got.iter().all(|ety| ety.0.loc().is_some()));
5907 let got = schema
5908 .resources_for_action(&create_list)
5909 .unwrap()
5910 .cloned()
5911 .collect::<Vec<_>>();
5912 assert_eq!(got, vec!["Application".parse().unwrap()]);
5913 assert!(got.iter().all(|ety| ety.0.loc().is_some()));
5914 let got = schema
5915 .resources_for_action(&get_list)
5916 .unwrap()
5917 .cloned()
5918 .collect::<HashSet<_>>();
5919 assert_eq!(
5920 got,
5921 HashSet::from(["List".parse().unwrap(), "CoolList".parse().unwrap()])
5922 );
5923 assert!(got.iter().all(|ety| ety.0.loc().is_some()));
5924 assert!(schema.principals_for_action(&delete_user).is_none());
5925 }
5926
5927 #[test]
5928 fn principal_parents() {
5929 let schema = schema();
5930 let user: EntityTypeName = "User".parse().unwrap();
5931 let parents = schema
5932 .ancestors(&user)
5933 .unwrap()
5934 .cloned()
5935 .collect::<HashSet<_>>();
5936 assert!(parents.iter().all(|ety| ety.0.loc().is_some()));
5937 let expected = HashSet::from(["Team".parse().unwrap(), "Application".parse().unwrap()]);
5938 assert_eq!(parents, expected);
5939 let parents = schema
5940 .ancestors(&"List".parse().unwrap())
5941 .unwrap()
5942 .cloned()
5943 .collect::<HashSet<_>>();
5944 assert!(parents.iter().all(|ety| ety.0.loc().is_some()));
5945 let expected = HashSet::from(["Application".parse().unwrap()]);
5946 assert_eq!(parents, expected);
5947 assert!(schema.ancestors(&"Foo".parse().unwrap()).is_none());
5948 let parents = schema
5949 .ancestors(&"CoolList".parse().unwrap())
5950 .unwrap()
5951 .cloned()
5952 .collect::<HashSet<_>>();
5953 assert!(parents.iter().all(|ety| ety.0.loc().is_some()));
5954 let expected = HashSet::from([]);
5955 assert_eq!(parents, expected);
5956 }
5957
5958 #[test]
5959 fn action_groups() {
5960 let schema = schema();
5961 let groups = schema.action_groups().cloned().collect::<HashSet<_>>();
5962 let expected = ["Read", "Write", "Create"]
5963 .into_iter()
5964 .map(|ty| format!("Action::\"{ty}\"").parse().unwrap())
5965 .collect::<HashSet<EntityUid>>();
5966 #[cfg(feature = "extended-schema")]
5967 assert!(groups.iter().all(|ety| ety.0.loc().is_some()));
5968 assert_eq!(groups, expected);
5969 }
5970
5971 #[test]
5972 fn actions() {
5973 let schema = schema();
5974 let actions = schema.actions().cloned().collect::<HashSet<_>>();
5975 let expected = [
5976 "Read",
5977 "Write",
5978 "Create",
5979 "DeleteList",
5980 "EditShare",
5981 "UpdateList",
5982 "CreateTask",
5983 "UpdateTask",
5984 "DeleteTask",
5985 "GetList",
5986 "GetLists",
5987 "CreateList",
5988 ]
5989 .into_iter()
5990 .map(|ty| format!("Action::\"{ty}\"").parse().unwrap())
5991 .collect::<HashSet<EntityUid>>();
5992 assert_eq!(actions, expected);
5993 #[cfg(feature = "extended-schema")]
5994 assert!(actions.iter().all(|ety| ety.0.loc().is_some()));
5995 }
5996
5997 #[test]
5998 fn actions_for_principal_and_resource() {
5999 let schema = schema();
6000 let pty: EntityTypeName = "User".parse().unwrap();
6001 let rty: EntityTypeName = "Application".parse().unwrap();
6002 let actions = schema
6003 .actions_for_principal_and_resource(&pty, &rty)
6004 .cloned()
6005 .collect::<HashSet<EntityUid>>();
6006 let expected = ["GetLists", "CreateList"]
6007 .into_iter()
6008 .map(|ty| format!("Action::\"{ty}\"").parse().unwrap())
6009 .collect::<HashSet<EntityUid>>();
6010 assert_eq!(actions, expected);
6011 }
6012
6013 #[test]
6014 fn entities() {
6015 let schema = schema();
6016 let entities = schema.entity_types().cloned().collect::<HashSet<_>>();
6017 let expected = ["List", "Application", "User", "CoolList", "Team"]
6018 .into_iter()
6019 .map(|ty| ty.parse().unwrap())
6020 .collect::<HashSet<EntityTypeName>>();
6021 assert_eq!(entities, expected);
6022 }
6023}
6024
6025#[cfg(test)]
6026mod test_access_namespace {
6027 use super::*;
6028
6029 fn schema() -> Schema {
6030 let src = r#"
6031 namespace Foo {
6032 type Task = {
6033 "id": Long,
6034 "name": String,
6035 "state": String,
6036};
6037
6038type Tasks = Set<Task>;
6039entity List in [Application] = {
6040 "editors": Team,
6041 "name": String,
6042 "owner": User,
6043 "readers": Team,
6044 "tasks": Tasks,
6045};
6046entity Application;
6047entity User in [Team, Application] = {
6048 "joblevel": Long,
6049 "location": String,
6050};
6051
6052entity CoolList;
6053
6054entity Team in [Team, Application];
6055
6056action Read, Write, Create;
6057
6058action DeleteList, EditShare, UpdateList, CreateTask, UpdateTask, DeleteTask in Write appliesTo {
6059 principal: [User],
6060 resource : [List]
6061};
6062
6063action GetList in Read appliesTo {
6064 principal : [User],
6065 resource : [List, CoolList]
6066};
6067
6068action GetLists in Read appliesTo {
6069 principal : [User],
6070 resource : [Application]
6071};
6072
6073action CreateList in Create appliesTo {
6074 principal : [User],
6075 resource : [Application]
6076};
6077 }
6078
6079 "#;
6080
6081 src.parse().unwrap()
6082 }
6083
6084 #[test]
6085 fn principals() {
6086 let schema = schema();
6087 let principals = schema.principals().collect::<HashSet<_>>();
6088 assert_eq!(principals.len(), 1);
6089 let user: EntityTypeName = "Foo::User".parse().unwrap();
6090 assert!(principals.contains(&user));
6091 let principals = schema.principals().collect::<Vec<_>>();
6092 assert!(principals.len() > 1);
6093 assert!(principals.iter().all(|ety| **ety == user));
6094 assert!(principals.iter().all(|ety| ety.0.loc().is_some()));
6095 }
6096
6097 #[test]
6098 fn empty_schema_principals_and_resources() {
6099 let empty: Schema = "".parse().unwrap();
6100 assert!(empty.principals().next().is_none());
6101 assert!(empty.resources().next().is_none());
6102 }
6103
6104 #[test]
6105 fn resources() {
6106 let schema = schema();
6107 let resources = schema.resources().cloned().collect::<HashSet<_>>();
6108 let expected: HashSet<EntityTypeName> = HashSet::from([
6109 "Foo::List".parse().unwrap(),
6110 "Foo::Application".parse().unwrap(),
6111 "Foo::CoolList".parse().unwrap(),
6112 ]);
6113 assert_eq!(resources, expected);
6114 assert!(resources.iter().all(|ety| ety.0.loc().is_some()));
6115 }
6116
6117 #[test]
6118 fn principals_for_action() {
6119 let schema = schema();
6120 let delete_list: EntityUid = r#"Foo::Action::"DeleteList""#.parse().unwrap();
6121 let delete_user: EntityUid = r#"Foo::Action::"DeleteUser""#.parse().unwrap();
6122 let got = schema
6123 .principals_for_action(&delete_list)
6124 .unwrap()
6125 .cloned()
6126 .collect::<Vec<_>>();
6127 assert_eq!(got, vec!["Foo::User".parse().unwrap()]);
6128 assert!(schema.principals_for_action(&delete_user).is_none());
6129 }
6130
6131 #[test]
6132 fn resources_for_action() {
6133 let schema = schema();
6134 let delete_list: EntityUid = r#"Foo::Action::"DeleteList""#.parse().unwrap();
6135 let delete_user: EntityUid = r#"Foo::Action::"DeleteUser""#.parse().unwrap();
6136 let create_list: EntityUid = r#"Foo::Action::"CreateList""#.parse().unwrap();
6137 let get_list: EntityUid = r#"Foo::Action::"GetList""#.parse().unwrap();
6138 let got = schema
6139 .resources_for_action(&delete_list)
6140 .unwrap()
6141 .cloned()
6142 .collect::<Vec<_>>();
6143 assert!(got.iter().all(|ety| ety.0.loc().is_some()));
6144
6145 assert_eq!(got, vec!["Foo::List".parse().unwrap()]);
6146 let got = schema
6147 .resources_for_action(&create_list)
6148 .unwrap()
6149 .cloned()
6150 .collect::<Vec<_>>();
6151 assert_eq!(got, vec!["Foo::Application".parse().unwrap()]);
6152 assert!(got.iter().all(|ety| ety.0.loc().is_some()));
6153
6154 let got = schema
6155 .resources_for_action(&get_list)
6156 .unwrap()
6157 .cloned()
6158 .collect::<HashSet<_>>();
6159 assert_eq!(
6160 got,
6161 HashSet::from([
6162 "Foo::List".parse().unwrap(),
6163 "Foo::CoolList".parse().unwrap()
6164 ])
6165 );
6166 assert!(schema.principals_for_action(&delete_user).is_none());
6167 }
6168
6169 #[test]
6170 fn principal_parents() {
6171 let schema = schema();
6172 let user: EntityTypeName = "Foo::User".parse().unwrap();
6173 let parents = schema
6174 .ancestors(&user)
6175 .unwrap()
6176 .cloned()
6177 .collect::<HashSet<_>>();
6178 let expected = HashSet::from([
6179 "Foo::Team".parse().unwrap(),
6180 "Foo::Application".parse().unwrap(),
6181 ]);
6182 assert_eq!(parents, expected);
6183 let parents = schema
6184 .ancestors(&"Foo::List".parse().unwrap())
6185 .unwrap()
6186 .cloned()
6187 .collect::<HashSet<_>>();
6188 let expected = HashSet::from(["Foo::Application".parse().unwrap()]);
6189 assert_eq!(parents, expected);
6190 assert!(schema.ancestors(&"Foo::Foo".parse().unwrap()).is_none());
6191 let parents = schema
6192 .ancestors(&"Foo::CoolList".parse().unwrap())
6193 .unwrap()
6194 .cloned()
6195 .collect::<HashSet<_>>();
6196 let expected = HashSet::from([]);
6197 assert_eq!(parents, expected);
6198 }
6199
6200 #[test]
6201 fn action_groups() {
6202 let schema = schema();
6203 let groups = schema.action_groups().cloned().collect::<HashSet<_>>();
6204 let expected = ["Read", "Write", "Create"]
6205 .into_iter()
6206 .map(|ty| format!("Foo::Action::\"{ty}\"").parse().unwrap())
6207 .collect::<HashSet<EntityUid>>();
6208 assert_eq!(groups, expected);
6209 }
6210
6211 #[test]
6212 fn actions() {
6213 let schema = schema();
6214 let actions = schema.actions().cloned().collect::<HashSet<_>>();
6215 let expected = [
6216 "Read",
6217 "Write",
6218 "Create",
6219 "DeleteList",
6220 "EditShare",
6221 "UpdateList",
6222 "CreateTask",
6223 "UpdateTask",
6224 "DeleteTask",
6225 "GetList",
6226 "GetLists",
6227 "CreateList",
6228 ]
6229 .into_iter()
6230 .map(|ty| format!("Foo::Action::\"{ty}\"").parse().unwrap())
6231 .collect::<HashSet<EntityUid>>();
6232 assert_eq!(actions, expected);
6233 }
6234
6235 #[test]
6236 fn entities() {
6237 let schema = schema();
6238 let entities = schema.entity_types().cloned().collect::<HashSet<_>>();
6239 let expected = [
6240 "Foo::List",
6241 "Foo::Application",
6242 "Foo::User",
6243 "Foo::CoolList",
6244 "Foo::Team",
6245 ]
6246 .into_iter()
6247 .map(|ty| ty.parse().unwrap())
6248 .collect::<HashSet<EntityTypeName>>();
6249 assert_eq!(entities, expected);
6250 }
6251
6252 #[test]
6253 fn test_request_context() {
6254 let context =
6256 Context::from_json_str(r#"{"testKey": "testValue", "numKey": 42}"#, None).unwrap();
6257
6258 let principal: EntityUid = "User::\"alice\"".parse().unwrap();
6260 let action: EntityUid = "Action::\"view\"".parse().unwrap();
6261 let resource: EntityUid = "Resource::\"doc123\"".parse().unwrap();
6262
6263 let request = Request::new(
6265 principal, action, resource, context, None, )
6267 .unwrap();
6268
6269 let retrieved_context = request.context().expect("Context should be present");
6271
6272 assert!(retrieved_context.get("testKey").is_some());
6274 assert!(retrieved_context.get("numKey").is_some());
6275 assert!(retrieved_context.get("nonexistent").is_none());
6276 }
6277
6278 #[cfg(feature = "extended-schema")]
6279 #[test]
6280 fn namespace_extended() {
6281 let schema = schema();
6282 assert_eq!(schema.0.namespaces().collect::<HashSet<_>>().len(), 2);
6283 let default_namespace = schema
6284 .0
6285 .namespaces()
6286 .filter(|n| n.name == *"__cedar")
6287 .last()
6288 .unwrap();
6289 assert!(default_namespace.name_loc.is_none());
6290 assert!(default_namespace.def_loc.is_none());
6291
6292 let default_namespace = schema
6293 .0
6294 .namespaces()
6295 .filter(|n| n.name == *"Foo")
6296 .last()
6297 .unwrap();
6298 assert!(default_namespace.name_loc.is_some());
6299 assert!(default_namespace.def_loc.is_some());
6300 }
6301}
6302
6303#[cfg(test)]
6304mod test_lossless_empty {
6305 use super::{LosslessPolicy, LosslessTemplate, Policy, PolicyId, Template};
6306 use cedar_policy_core::pst;
6307 use cool_asserts::assert_matches;
6308
6309 #[test]
6310 fn test_lossless_empty_policy() {
6311 const STATIC_POLICY_TEXT: &str = "permit(principal,action,resource);";
6312 let policy0 = Policy::parse(Some(PolicyId::new("policy0")), STATIC_POLICY_TEXT)
6313 .expect("Failed to parse");
6314 let lossy_policy0 = Policy {
6315 ast: policy0.ast.clone(),
6316 lossless: LosslessPolicy::policy_or_template_text(None::<&str>),
6317 };
6318 assert_eq!(
6320 lossy_policy0.to_cedar(),
6321 Some(String::from(
6322 "permit(\n principal,\n action,\n resource\n);"
6323 ))
6324 );
6325 let lossy_policy0_est = lossy_policy0
6327 .lossless
6328 .est(|| policy0.ast.clone().into())
6329 .unwrap();
6330 assert_eq!(lossy_policy0_est, policy0.ast.into());
6331 }
6332
6333 #[test]
6334 fn test_lossless_empty_template() {
6335 const TEMPLATE_TEXT: &str = "permit(principal == ?principal,action,resource);";
6336 let template0 = Template::parse(Some(PolicyId::new("template0")), TEMPLATE_TEXT)
6337 .expect("Failed to parse");
6338 let lossy_template0 = Template {
6339 ast: template0.ast.clone(),
6340 lossless: LosslessTemplate::from_text(None::<&str>),
6341 };
6342 assert_eq!(
6344 lossy_template0.to_cedar(),
6345 String::from("permit(\n principal == ?principal,\n action,\n resource\n);")
6346 );
6347 let lossy_template0_est = lossy_template0
6349 .lossless
6350 .est(|| template0.ast.clone().into())
6351 .unwrap();
6352 assert_eq!(lossy_template0_est, template0.ast.into());
6353 }
6354
6355 #[test]
6356 fn try_into_pst_empty_policy() {
6357 let p = Policy::parse(
6358 Some(PolicyId::new("p")),
6359 "permit(principal,action,resource);",
6360 )
6361 .expect("parse");
6362 let empty = Policy {
6363 ast: p.ast,
6364 lossless: LosslessPolicy::policy_or_template_text(None::<&str>),
6365 };
6366 assert_matches!(
6367 empty.try_into_pst().unwrap().body(),
6368 pst::Template {
6369 effect: pst::Effect::Permit,
6370 principal: pst::PrincipalConstraint::Any,
6371 resource: pst::ResourceConstraint::Any,
6372 action: pst::ActionConstraint::Any,
6373 ..
6374 },
6375 );
6376 }
6377
6378 #[test]
6379 fn try_into_pst_empty_template() {
6380 let t = Template::parse(
6381 Some(PolicyId::new("t")),
6382 "permit(principal == ?principal,action,resource);",
6383 )
6384 .expect("parse");
6385 let empty = Template {
6386 ast: t.ast,
6387 lossless: LosslessTemplate::from_text(None::<&str>),
6388 };
6389 assert_matches!(
6390 empty.try_into_pst().unwrap(),
6391 pst::Template {
6392 effect: pst::Effect::Permit,
6393 principal: pst::PrincipalConstraint::Eq(pst::EntityOrSlot::Slot(
6394 pst::SlotId::Principal
6395 )),
6396 resource: pst::ResourceConstraint::Any,
6397 action: pst::ActionConstraint::Any,
6398 ..
6399 },
6400 );
6401 }
6402}
6403
6404#[doc = include_str!("../experimental_warning.md")]
6411#[deprecated = "The `entity-manifest` experimental feature and all associated functions are deprecated. Migrate to `PolicySet::is_authorized_batch` for efficient authorization with on-demand entity loading."]
6412#[cfg(feature = "entity-manifest")]
6413pub fn compute_entity_manifest(
6414 validator: &Validator,
6415 pset: &PolicySet,
6416) -> Result<EntityManifest, EntityManifestError> {
6417 entity_manifest::compute_entity_manifest(&validator.0, &pset.ast).map_err(Into::into)
6418}