1use super::function::{
23 self,
24 UnaryFunction::{self, Udf, Uuf},
25};
26use super::op;
27use super::result::CompileError;
28use super::tags::SymTags;
29use super::term::{Term, TermPrim, TermVar};
30use super::term_type::TermType;
31use super::type_abbrevs::*;
32use cedar_policy::{RequestEnv, Schema};
33use cedar_policy_core::validator::ValidatorSchema;
34use cedar_policy_core::validator::{
35 types::{Attributes, OpenTag, Type},
36 ValidatorActionId,
37};
38use cedar_policy_core::validator::{ValidatorEntityType, ValidatorEntityTypeKind};
39use smol_str::{format_smolstr, SmolStr};
40use std::collections::{BTreeMap, BTreeSet};
41use std::ops::Deref;
42use std::sync::Arc;
43
44#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
47pub struct SymRequest {
48 pub principal: Term,
49 pub action: Term,
50 pub resource: Term,
51 pub context: Term,
52}
53
54impl SymRequest {
55 #[cfg(test)]
56 pub fn empty_sym_req() -> Self {
57 SymRequest {
58 principal: Term::Var(TermVar {
59 id: "principal".into(),
60 ty: TermType::Bool,
61 }),
62 action: Term::Var(TermVar {
63 id: "action".into(),
64 ty: TermType::Bool,
65 }),
66 resource: Term::Var(TermVar {
67 id: "resource".into(),
68 ty: TermType::Bool,
69 }),
70 context: Term::Record(Arc::new(BTreeMap::new())),
71 }
72 }
73
74 pub fn is_literal(&self) -> bool {
75 self.principal.is_literal()
76 && self.action.is_literal()
77 && self.resource.is_literal()
78 && self.context.is_literal()
79 }
80}
81
82#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
93pub struct SymEntityData {
94 pub attrs: UnaryFunction,
95 pub ancestors: BTreeMap<EntityType, UnaryFunction>,
103 pub members: Option<BTreeSet<SmolStr>>,
105 pub tags: Option<SymTags>,
107}
108
109impl SymEntityData {
110 pub fn is_literal(&self) -> bool {
111 self.attrs.is_literal()
112 && self.ancestors.values().all(|uf| uf.is_literal())
113 && self.tags.as_ref().is_none_or(|t| t.is_literal())
114 }
115}
116
117#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
118pub struct SymEntities(pub BTreeMap<EntityType, SymEntityData>);
119
120impl Deref for SymEntities {
121 type Target = BTreeMap<EntityType, SymEntityData>;
122
123 fn deref(&self) -> &Self::Target {
124 &self.0
125 }
126}
127
128impl SymEntities {
129 pub fn attrs(&self, ety: &EntityType) -> Option<&UnaryFunction> {
130 Some(&self.get(ety)?.attrs)
131 }
132
133 pub fn ancestors(&self, ety: &EntityType) -> Option<&BTreeMap<EntityType, UnaryFunction>> {
134 Some(&self.get(ety)?.ancestors)
135 }
136
137 pub fn ancestors_of_type(
138 &self,
139 ety: &EntityType,
140 anc_ty: &EntityType,
141 ) -> Option<&UnaryFunction> {
142 self.ancestors(ety)?.get(anc_ty)
143 }
144
145 pub fn is_valid_entity_type(&self, ety: &EntityType) -> bool {
146 self.contains_key(ety)
147 }
148
149 pub fn is_valid_entity_uid(&self, uid: &EntityUID) -> bool {
150 match self.get(uid.type_name()) {
151 Some(d) => {
152 if let Some(eids) = &d.members {
153 eids.contains(AsRef::<str>::as_ref(uid.id()))
154 } else {
155 true
156 }
157 }
158 None => false,
159 }
160 }
161
162 pub fn tags(&self, ety: &EntityType) -> Option<&Option<SymTags>> {
163 self.get(ety).map(|sed| &sed.tags)
164 }
165
166 pub fn is_literal(&self) -> bool {
167 self.values().all(|d| d.is_literal())
168 }
169}
170
171#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
173pub struct SymEnv {
174 pub request: SymRequest,
176 pub entities: Arc<SymEntities>,
178}
179
180impl SymEnv {
181 pub fn is_literal(&self) -> bool {
183 self.request.is_literal() && self.entities.is_literal()
184 }
185}
186
187impl SymEntityData {
189 pub fn of_entity_type(
190 ety: &EntityType,
191 validator_ety: &ValidatorEntityType,
192 schema: &ValidatorSchema,
193 ) -> Result<Self, CompileError> {
194 match EntitySchemaEntry::of_schema(ety, validator_ety, schema) {
195 EntitySchemaEntry::Standard(sch) => {
197 let attrs_uuf = Uuf(Arc::new(op::Uuf {
198 id: format_smolstr!("attrs[{ety}]"),
199 arg: entity(ety.clone()), out: TermType::of_type(&record(sch.attrs))?,
201 }));
202 let ancs_uuf = |anc_ty: &EntityType| {
203 Uuf(Arc::new(op::Uuf {
204 id: format_smolstr!("ancs[{ety}, {anc_ty}]"),
205 arg: entity(ety.clone()), out: TermType::set_of(entity(anc_ty.clone())), }))
208 };
209 let sym_tags = |tag_ty: Type| -> Result<SymTags, CompileError> {
210 Ok(SymTags {
211 keys: Uuf(Arc::new(op::Uuf {
212 id: format_smolstr!("tagKeys[{ety}]"),
213 arg: entity(ety.clone()), out: TermType::set_of(TermType::String),
215 })),
216 vals: Uuf(Arc::new(op::Uuf {
217 id: format_smolstr!("tagVals[{ety}]"),
218 arg: TermType::tag_for(ety.clone()), out: TermType::of_type(&tag_ty)?,
220 })),
221 })
222 };
223
224 Ok(SymEntityData {
225 attrs: attrs_uuf,
226 ancestors: sch
227 .ancestors
228 .into_iter()
229 .map(|anc_ty| {
230 let uuf = ancs_uuf(&anc_ty);
231 (anc_ty, uuf)
232 })
233 .collect(),
234 members: None,
235 tags: sch.tags.map(sym_tags).transpose()?,
236 })
237 }
238
239 EntitySchemaEntry::Enum(eids) => {
241 let attrs_udf = Udf(Arc::new(function::Udf {
242 arg: entity(ety.clone()),
243 out: TermType::Record {
244 rty: Arc::new(BTreeMap::new()),
245 },
246 table: Arc::new(BTreeMap::new()),
247 default: Term::Record(Arc::new(BTreeMap::new())),
248 }));
249 Ok(SymEntityData {
250 attrs: attrs_udf,
251 ancestors: BTreeMap::new(),
252 members: Some(eids),
253 tags: None,
254 })
255 }
256 }
257 }
258
259 pub(super) fn of_action_type<'a>(
260 act_ty: &EntityType,
261 act_tys: impl IntoIterator<Item = &'a EntityType>,
262 actions: &ActionSchemaEntries,
263 ) -> Self {
264 let attrs_udf = Udf(Arc::new(function::Udf {
265 arg: entity(act_ty.clone()),
266 out: TermType::Record {
267 rty: Arc::new(BTreeMap::new()),
268 },
269 table: Arc::new(BTreeMap::new()),
270 default: Term::Record(Arc::new(BTreeMap::new())),
271 }));
272 let term_of_type = |ety: EntityType, uid: EntityUID| -> Option<Term> {
273 if uid.type_name() == &ety {
274 Some(Term::Prim(TermPrim::Entity(uid)))
275 } else {
276 None
277 }
278 };
279 let ancs_term = |anc_ty: &EntityType, ancs: &BTreeSet<EntityUID>| -> Term {
280 Term::Set {
281 elts: Arc::new(
282 ancs.iter()
283 .filter_map(|anc| term_of_type(anc_ty.clone(), anc.clone()))
284 .collect(),
285 ),
286 elts_ty: entity(anc_ty.clone()),
287 }
288 };
289 let ancs_udf = |anc_ty: &EntityType| -> UnaryFunction {
290 Udf(Arc::new(function::Udf {
291 arg: entity(act_ty.clone()),
292 out: TermType::set_of(entity(anc_ty.clone())),
293 table: Arc::new(
294 actions
295 .iter()
296 .filter_map(|(uid, entry)| {
297 Some((
298 term_of_type(act_ty.clone(), uid.clone())?,
299 ancs_term(anc_ty, &entry.ancestors),
300 ))
301 })
302 .collect(),
303 ),
304 default: Term::Set {
305 elts: Arc::new(BTreeSet::new()),
306 elts_ty: entity(anc_ty.clone()),
307 },
308 }))
309 };
310 let acts = actions
311 .keys()
312 .filter(|uid| uid.type_name() == act_ty)
313 .map(|uid| SmolStr::new(uid.id()))
314 .collect();
315 SymEntityData {
316 attrs: attrs_udf,
317 ancestors: act_tys
318 .into_iter()
319 .map(|anc_ty| {
320 let udf = ancs_udf(anc_ty);
321 (anc_ty.clone(), udf)
322 })
323 .collect(),
324 members: Some(acts),
325 tags: None,
326 }
327 }
328}
329
330impl SymEntities {
331 fn of_schema(schema: &ValidatorSchema) -> Result<Self, CompileError> {
346 let e_data = schema.entity_types().map(|vdtr_ety| {
347 let ety = core_entity_type_into_entity_type(vdtr_ety.name());
348 match SymEntityData::of_entity_type(ety, vdtr_ety, schema) {
349 Ok(sym_edata) => Ok((ety.clone(), sym_edata)),
350 Err(e) => Err(e),
351 }
352 });
353 #[expect(
354 clippy::expect_used,
355 reason = "ValidatorSchema::action_entities should not error"
356 )]
357 let acts = schema
358 .action_entities()
359 .expect("Schema should have action entities");
360 let act_tys: BTreeSet<&EntityType> = acts
361 .iter()
362 .map(|act| core_entity_type_into_entity_type(act.uid().entity_type()))
363 .collect();
364 let action_schema = ActionSchemaEntries::of_schema(schema);
365 let a_data = act_tys.iter().map(|&act_ty| {
366 Ok((
367 act_ty.clone(),
368 SymEntityData::of_action_type(act_ty, act_tys.iter().copied(), &action_schema),
369 ))
370 });
371 Ok(SymEntities(
372 e_data.into_iter().chain(a_data).collect::<Result<_, _>>()?,
373 ))
374 }
375}
376
377impl SymRequest {
378 fn of_request_type(req_ty: &RequestType<'_>) -> Result<Self, CompileError> {
380 Ok(Self {
381 principal: Term::Var(TermVar {
382 id: "principal".into(),
383 ty: TermType::Entity {
384 ety: req_ty.principal.clone(),
385 },
386 }),
387 action: Term::Prim(TermPrim::Entity(req_ty.action.clone())),
388 resource: Term::Var(TermVar {
389 id: "resource".into(),
390 ty: TermType::Entity {
391 ety: req_ty.resource.clone(),
392 },
393 }),
394 context: Term::Var(TermVar {
395 id: "context".into(),
396 ty: TermType::of_type(&record(req_ty.context.clone()))?,
397 }),
398 })
399 }
400}
401
402impl SymEnv {
403 pub fn of_env(ty_env: &Environment<'_>) -> Result<Self, CompileError> {
406 Ok(SymEnv {
407 request: SymRequest::of_request_type(&ty_env.req_ty)?,
408 entities: Arc::new(SymEntities::of_schema(ty_env.schema)?),
409 })
410 }
411}
412
413#[derive(Debug, Clone)]
432pub struct CompiledSchema {
433 schema: Schema,
434 entities: Arc<SymEntities>,
435}
436
437impl CompiledSchema {
438 pub fn new(schema: &Schema) -> crate::err::Result<Self> {
440 Ok(Self {
441 schema: schema.clone(),
442 entities: Arc::new(SymEntities::of_schema(schema.as_ref())?),
443 })
444 }
445
446 pub fn schema(&self) -> &Schema {
448 &self.schema
449 }
450
451 pub fn sym_env(&self, req_env: &RequestEnv) -> crate::err::Result<SymEnv> {
460 let env = Environment::from_request_env(req_env, self.schema.as_ref())
461 .ok_or_else(|| crate::err::Error::ActionNotInSchema(req_env.action().to_string()))?;
462 Ok(SymEnv {
463 request: SymRequest::of_request_type(&env.req_ty)?,
464 entities: self.entities.clone(),
465 })
466 }
467}
468
469fn entity(ety: EntityType) -> TermType {
474 TermType::Entity { ety }
475}
476
477fn record(attrs: Attributes) -> Type {
478 Type::Record {
479 attrs,
480 open_attributes: OpenTag::ClosedAttributes,
481 }
482}
483
484pub(super) struct StandardEntitySchemaEntry {
486 pub(super) ancestors: BTreeSet<EntityType>,
487 pub(super) attrs: Attributes,
488 pub(super) tags: Option<Type>,
489}
490
491pub(super) enum EntitySchemaEntry {
492 Standard(StandardEntitySchemaEntry),
493 Enum(BTreeSet<SmolStr>),
494}
495
496impl EntitySchemaEntry {
497 pub fn of_schema(
498 ety: &EntityType,
499 validator_ety: &ValidatorEntityType,
500 schema: &ValidatorSchema,
501 ) -> Self {
502 if let Some(entry) = schema.get_entity_type(ety.as_ref()) {
503 if let ValidatorEntityTypeKind::Enum(eids) = &entry.kind {
504 return EntitySchemaEntry::Enum(
505 eids.into_iter().map(|e| e.as_ref().into()).collect(),
506 );
507 }
508 }
509
510 EntitySchemaEntry::Standard(StandardEntitySchemaEntry {
511 ancestors: schema
513 .entity_types()
514 .filter(|other_ety| other_ety.descendants.contains(ety.as_ref()))
515 .map(|other_ety| core_entity_type_into_entity_type(other_ety.name()))
516 .cloned()
517 .collect(),
518 attrs: validator_ety.attributes().clone(),
519 tags: validator_ety.tag_type().cloned(),
520 })
521 }
522}
523
524pub(crate) struct ActionSchemaEntry {
526 ancestors: BTreeSet<EntityUID>,
527 }
534
535pub(crate) struct ActionSchemaEntries(BTreeMap<EntityUID, ActionSchemaEntry>);
536
537impl Deref for ActionSchemaEntries {
538 type Target = BTreeMap<EntityUID, ActionSchemaEntry>;
539
540 fn deref(&self) -> &Self::Target {
541 &self.0
542 }
543}
544
545impl ActionSchemaEntries {
546 pub(crate) fn of_schema(schema: &ValidatorSchema) -> Self {
547 Self(
548 #[expect(
549 clippy::expect_used,
550 reason = "ValidatorSchema::action_entities should not error"
551 )]
552 schema
553 .action_entities()
554 .expect("Failed to get action entities from schema")
555 .into_iter()
556 .map(|action| {
557 let action_schema_entry = ActionSchemaEntry {
558 ancestors: action.ancestors().map(core_uid_into_uid).cloned().collect(),
559 };
560 (core_uid_into_uid(action.uid()).clone(), action_schema_entry)
561 })
562 .collect(),
563 )
564 }
565}
566
567fn context_attributes(action: &ValidatorActionId) -> Result<&Attributes, CompileError> {
568 match action.context_type() {
569 Type::Record {
570 attrs,
571 open_attributes: OpenTag::ClosedAttributes,
572 } => Ok(attrs),
573 _ => Err(CompileError::NonRecordContext),
574 }
575}
576
577#[derive(Debug)]
579struct RequestType<'a> {
580 principal: &'a EntityType,
581 action: &'a EntityUID,
582 resource: &'a EntityType,
583 context: &'a Attributes,
584}
585
586#[derive(Debug)]
588pub struct Environment<'a> {
589 schema: &'a ValidatorSchema,
590 req_ty: RequestType<'a>,
591}
592
593impl<'a> Environment<'a> {
594 pub fn from_request_env(
599 renv: &'a cedar_policy::RequestEnv,
600 schema: &'a ValidatorSchema,
601 ) -> Option<Self> {
602 Some(Self {
603 schema,
604 req_ty: RequestType {
605 principal: renv.principal(),
606 action: renv.action(),
607 resource: renv.resource(),
608 context: context_attributes(schema.get_action_id(renv.action().as_ref())?).ok()?,
609 },
610 })
611 }
612
613 pub fn schema(&self) -> &'a ValidatorSchema {
614 self.schema
615 }
616
617 pub fn context_type(&self) -> Type {
619 Type::record_with_attributes(self.req_ty.context.clone(), OpenTag::ClosedAttributes)
620 }
621}
622
623pub fn to_validator_request_env<'a>(
624 env: &'a cedar_policy::RequestEnv,
625 schema: &'a ValidatorSchema,
626) -> Option<cedar_policy_core::validator::types::RequestEnv<'a>> {
627 let principal = env.principal().as_ref();
628 let resource = env.resource().as_ref();
629 let action = env.action().as_ref();
630 let context_type = schema.context_type(action);
631 context_type.map(
632 |context| cedar_policy_core::validator::types::RequestEnv::DeclaredAction {
633 principal,
634 action,
635 resource,
636 context,
637 principal_slot: None,
638 resource_slot: None,
639 },
640 )
641}