1use super::{FromJsonError, LinkingError};
18use crate::ast;
19use crate::ast::EntityUID;
20use crate::entities::json::{
21 err::JsonDeserializationError, err::JsonDeserializationErrorContext, EntityUidJson,
22};
23use crate::parser::err::parse_errors;
24use serde::{Deserialize, Serialize};
25use smol_str::{SmolStr, ToSmolStr};
26use std::collections::{BTreeMap, HashMap};
27use std::sync::Arc;
28
29#[cfg(feature = "tolerant-ast")]
30static ERROR_CONSTRAINT_STR: &str = "ActionConstraint::ErrorConstraint";
31
32#[cfg(feature = "wasm")]
33extern crate tsify;
34
35#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
37#[serde(deny_unknown_fields)]
38#[serde(tag = "op")]
39#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
40#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
41pub enum PrincipalConstraint {
42 #[serde(alias = "all")]
44 All,
45 #[serde(rename = "==")]
47 Eq(EqConstraint),
48 #[serde(rename = "in")]
50 In(PrincipalOrResourceInConstraint),
51 #[serde(rename = "is")]
53 Is(PrincipalOrResourceIsConstraint),
54}
55
56#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
58#[serde(deny_unknown_fields)]
59#[serde(tag = "op")]
60#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
61#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
62pub enum ActionConstraint {
63 #[serde(alias = "all")]
65 All,
66 #[serde(rename = "==")]
68 Eq(EqConstraint),
69 #[serde(rename = "in")]
71 In(ActionInConstraint),
72 #[cfg(feature = "tolerant-ast")]
73 #[serde(alias = "error")]
74 ErrorConstraint,
76}
77
78#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
80#[serde(deny_unknown_fields)]
81#[serde(tag = "op")]
82#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
83#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
84pub enum ResourceConstraint {
85 #[serde(alias = "all")]
87 All,
88 #[serde(rename = "==")]
90 Eq(EqConstraint),
91 #[serde(rename = "in")]
93 In(PrincipalOrResourceInConstraint),
94 #[serde(rename = "is")]
95 Is(PrincipalOrResourceIsConstraint),
97}
98
99#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
101#[serde(untagged)]
102#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
103#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
104pub enum EqConstraint {
105 Entity {
107 entity: EntityUidJson,
109 },
110 Slot {
112 #[cfg_attr(feature = "wasm", tsify(type = "string"))]
114 slot: ast::SlotId,
115 },
116}
117
118#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
121#[serde(untagged)]
122#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
123#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
124pub enum PrincipalOrResourceInConstraint {
125 Entity {
127 entity: EntityUidJson,
129 },
130 Slot {
132 #[cfg_attr(feature = "wasm", tsify(type = "string"))]
134 slot: ast::SlotId,
135 },
136}
137
138#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
141#[serde(deny_unknown_fields)]
142#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
143#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
144pub struct PrincipalOrResourceIsConstraint {
145 #[cfg_attr(feature = "wasm", tsify(type = "string"))]
146 entity_type: SmolStr,
147 #[serde(skip_serializing_if = "Option::is_none")]
148 #[serde(rename = "in")]
149 in_entity: Option<PrincipalOrResourceInConstraint>,
150}
151
152#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
155#[serde(untagged)]
156#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
157#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
158pub enum ActionInConstraint {
159 Single {
161 entity: EntityUidJson,
163 },
164 Set {
166 entities: Vec<EntityUidJson>,
168 },
169}
170
171impl PrincipalConstraint {
172 pub fn link(self, vals: &HashMap<ast::SlotId, EntityUidJson>) -> Result<Self, LinkingError> {
176 match self {
177 PrincipalConstraint::All => Ok(PrincipalConstraint::All),
178 PrincipalConstraint::Eq(EqConstraint::Entity { entity }) => {
179 Ok(PrincipalConstraint::Eq(EqConstraint::Entity { entity }))
180 }
181 PrincipalConstraint::In(PrincipalOrResourceInConstraint::Entity { entity }) => Ok(
182 PrincipalConstraint::In(PrincipalOrResourceInConstraint::Entity { entity }),
183 ),
184 PrincipalConstraint::Eq(EqConstraint::Slot { slot }) => match vals.get(&slot) {
185 Some(val) => Ok(PrincipalConstraint::Eq(EqConstraint::Entity {
186 entity: val.clone(),
187 })),
188 None => Err(LinkingError::MissedSlot { slot }),
189 },
190 PrincipalConstraint::In(PrincipalOrResourceInConstraint::Slot { slot }) => {
191 match vals.get(&slot) {
192 Some(val) => Ok(PrincipalConstraint::In(
193 PrincipalOrResourceInConstraint::Entity {
194 entity: val.clone(),
195 },
196 )),
197 None => Err(LinkingError::MissedSlot { slot }),
198 }
199 }
200 e @ PrincipalConstraint::Is(PrincipalOrResourceIsConstraint {
201 entity_type: _,
202 in_entity: None | Some(PrincipalOrResourceInConstraint::Entity { .. }),
203 }) => Ok(e),
204 PrincipalConstraint::Is(PrincipalOrResourceIsConstraint {
205 entity_type,
206 in_entity: Some(PrincipalOrResourceInConstraint::Slot { slot }),
207 }) => Ok(PrincipalConstraint::Is(PrincipalOrResourceIsConstraint {
208 entity_type,
209 in_entity: Some(PrincipalOrResourceInConstraint::Entity {
210 entity: vals
211 .get(&slot)
212 .ok_or(LinkingError::MissedSlot { slot })?
213 .clone(),
214 }),
215 })),
216 }
217 }
218
219 pub fn sub_entity_literals(
221 self,
222 mapping: &BTreeMap<EntityUID, EntityUID>,
223 ) -> Result<Self, JsonDeserializationError> {
224 match self.clone() {
225 PrincipalConstraint::All
226 | PrincipalConstraint::Eq(EqConstraint::Slot { .. })
227 | PrincipalConstraint::In(PrincipalOrResourceInConstraint::Slot { .. })
228 | PrincipalConstraint::Is(PrincipalOrResourceIsConstraint {
229 in_entity: Some(PrincipalOrResourceInConstraint::Slot { .. }),
230 ..
231 })
232 | PrincipalConstraint::Is(PrincipalOrResourceIsConstraint {
233 entity_type: _,
234 in_entity: None,
235 }) => Ok(self),
236 PrincipalConstraint::Eq(EqConstraint::Entity { entity }) => {
237 let euid = entity.into_euid(&|| JsonDeserializationErrorContext::EntityUid)?;
238 match mapping.get(&euid) {
239 Some(new_euid) => Ok(PrincipalConstraint::Eq(EqConstraint::Entity {
240 entity: new_euid.into(),
241 })),
242 None => Ok(self),
243 }
244 }
245 PrincipalConstraint::In(PrincipalOrResourceInConstraint::Entity { entity }) => {
246 let euid = entity.into_euid(&|| JsonDeserializationErrorContext::EntityUid)?;
247 match mapping.get(&euid) {
248 Some(new_euid) => Ok(PrincipalConstraint::In(
249 PrincipalOrResourceInConstraint::Entity {
250 entity: new_euid.into(),
251 },
252 )),
253 None => Ok(self),
254 }
255 }
256 PrincipalConstraint::Is(PrincipalOrResourceIsConstraint {
257 entity_type: ety,
258 in_entity: Some(PrincipalOrResourceInConstraint::Entity { entity }),
259 }) => {
260 let euid = entity.into_euid(&|| JsonDeserializationErrorContext::EntityUid)?;
261 match mapping.get(&euid) {
262 Some(new_euid) => {
263 Ok(PrincipalConstraint::Is(PrincipalOrResourceIsConstraint {
264 entity_type: ety,
265 in_entity: Some(PrincipalOrResourceInConstraint::Entity {
266 entity: new_euid.into(),
267 }),
268 }))
269 }
270 None => Ok(self),
271 }
272 }
273 }
274 }
275
276 pub fn has_slot(&self) -> bool {
278 match self {
279 PrincipalConstraint::All => false,
280 PrincipalConstraint::Eq(EqConstraint::Entity { .. }) => false,
281 PrincipalConstraint::Eq(EqConstraint::Slot { .. }) => true,
282 PrincipalConstraint::In(PrincipalOrResourceInConstraint::Entity { .. }) => false,
283 PrincipalConstraint::In(PrincipalOrResourceInConstraint::Slot { .. }) => true,
284 PrincipalConstraint::Is(PrincipalOrResourceIsConstraint {
285 in_entity: None | Some(PrincipalOrResourceInConstraint::Entity { .. }),
286 ..
287 }) => false,
288 PrincipalConstraint::Is(PrincipalOrResourceIsConstraint {
289 in_entity: Some(PrincipalOrResourceInConstraint::Slot { .. }),
290 ..
291 }) => true,
292 }
293 }
294}
295
296impl ResourceConstraint {
297 pub fn link(self, vals: &HashMap<ast::SlotId, EntityUidJson>) -> Result<Self, LinkingError> {
301 match self {
302 ResourceConstraint::All => Ok(ResourceConstraint::All),
303 ResourceConstraint::Eq(EqConstraint::Entity { entity }) => {
304 Ok(ResourceConstraint::Eq(EqConstraint::Entity { entity }))
305 }
306 ResourceConstraint::In(PrincipalOrResourceInConstraint::Entity { entity }) => Ok(
307 ResourceConstraint::In(PrincipalOrResourceInConstraint::Entity { entity }),
308 ),
309 ResourceConstraint::Eq(EqConstraint::Slot { slot }) => match vals.get(&slot) {
310 Some(val) => Ok(ResourceConstraint::Eq(EqConstraint::Entity {
311 entity: val.clone(),
312 })),
313 None => Err(LinkingError::MissedSlot { slot }),
314 },
315 ResourceConstraint::In(PrincipalOrResourceInConstraint::Slot { slot }) => {
316 match vals.get(&slot) {
317 Some(val) => Ok(ResourceConstraint::In(
318 PrincipalOrResourceInConstraint::Entity {
319 entity: val.clone(),
320 },
321 )),
322 None => Err(LinkingError::MissedSlot { slot }),
323 }
324 }
325 e @ ResourceConstraint::Is(PrincipalOrResourceIsConstraint {
326 entity_type: _,
327 in_entity: None | Some(PrincipalOrResourceInConstraint::Entity { .. }),
328 }) => Ok(e),
329 ResourceConstraint::Is(PrincipalOrResourceIsConstraint {
330 entity_type,
331 in_entity: Some(PrincipalOrResourceInConstraint::Slot { slot }),
332 }) => Ok(ResourceConstraint::Is(PrincipalOrResourceIsConstraint {
333 entity_type,
334 in_entity: Some(PrincipalOrResourceInConstraint::Entity {
335 entity: vals
336 .get(&slot)
337 .ok_or(LinkingError::MissedSlot { slot })?
338 .clone(),
339 }),
340 })),
341 }
342 }
343
344 pub fn sub_entity_literals(
346 self,
347 mapping: &BTreeMap<EntityUID, EntityUID>,
348 ) -> Result<Self, JsonDeserializationError> {
349 match self.clone() {
350 ResourceConstraint::All
351 | ResourceConstraint::Eq(EqConstraint::Slot { .. })
352 | ResourceConstraint::In(PrincipalOrResourceInConstraint::Slot { .. })
353 | ResourceConstraint::Is(PrincipalOrResourceIsConstraint {
354 in_entity: Some(PrincipalOrResourceInConstraint::Slot { .. }),
355 ..
356 })
357 | ResourceConstraint::Is(PrincipalOrResourceIsConstraint {
358 entity_type: _,
359 in_entity: None,
360 }) => Ok(self),
361 ResourceConstraint::Eq(EqConstraint::Entity { entity }) => {
362 let euid = entity.into_euid(&|| JsonDeserializationErrorContext::EntityUid)?;
363 match mapping.get(&euid) {
364 Some(new_euid) => Ok(ResourceConstraint::Eq(EqConstraint::Entity {
365 entity: new_euid.into(),
366 })),
367 None => Ok(self),
368 }
369 }
370 ResourceConstraint::In(PrincipalOrResourceInConstraint::Entity { entity }) => {
371 let euid = entity.into_euid(&|| JsonDeserializationErrorContext::EntityUid)?;
372 match mapping.get(&euid) {
373 Some(new_euid) => Ok(ResourceConstraint::In(
374 PrincipalOrResourceInConstraint::Entity {
375 entity: new_euid.into(),
376 },
377 )),
378 None => Ok(self),
379 }
380 }
381 ResourceConstraint::Is(PrincipalOrResourceIsConstraint {
382 entity_type: ety,
383 in_entity: Some(PrincipalOrResourceInConstraint::Entity { entity }),
384 }) => {
385 let euid = entity.into_euid(&|| JsonDeserializationErrorContext::EntityUid)?;
386 match mapping.get(&euid) {
387 Some(new_euid) => Ok(ResourceConstraint::Is(PrincipalOrResourceIsConstraint {
388 entity_type: ety,
389 in_entity: Some(PrincipalOrResourceInConstraint::Entity {
390 entity: new_euid.into(),
391 }),
392 })),
393 None => Ok(self),
394 }
395 }
396 }
397 }
398
399 pub fn has_slot(&self) -> bool {
401 match self {
402 ResourceConstraint::All => false,
403 ResourceConstraint::Eq(EqConstraint::Entity { .. }) => false,
404 ResourceConstraint::In(PrincipalOrResourceInConstraint::Entity { .. }) => false,
405 ResourceConstraint::Eq(EqConstraint::Slot { .. }) => true,
406 ResourceConstraint::In(PrincipalOrResourceInConstraint::Slot { .. }) => true,
407 ResourceConstraint::Is(PrincipalOrResourceIsConstraint {
408 in_entity: None | Some(PrincipalOrResourceInConstraint::Entity { .. }),
409 ..
410 }) => false,
411 ResourceConstraint::Is(PrincipalOrResourceIsConstraint {
412 in_entity: Some(PrincipalOrResourceInConstraint::Slot { .. }),
413 ..
414 }) => true,
415 }
416 }
417}
418
419impl ActionConstraint {
420 pub fn link(self, _vals: &HashMap<ast::SlotId, EntityUidJson>) -> Result<Self, LinkingError> {
424 Ok(self)
426 }
427
428 pub fn sub_entity_literals(
430 self,
431 mapping: &BTreeMap<EntityUID, EntityUID>,
432 ) -> Result<Self, JsonDeserializationError> {
433 match self.clone() {
434 ActionConstraint::Eq(EqConstraint::Entity { entity }) => {
435 let euid = entity.into_euid(&|| JsonDeserializationErrorContext::EntityUid)?;
436 match mapping.get(&euid) {
437 Some(new_euid) => Ok(ActionConstraint::Eq(EqConstraint::Entity {
438 entity: new_euid.into(),
439 })),
440 None => Ok(self),
441 }
442 }
443 ActionConstraint::In(ActionInConstraint::Single { entity }) => {
444 let euid = entity.into_euid(&|| JsonDeserializationErrorContext::EntityUid)?;
445 match mapping.get(&euid) {
446 Some(new_euid) => Ok(ActionConstraint::In(ActionInConstraint::Single {
447 entity: new_euid.into(),
448 })),
449 None => Ok(self),
450 }
451 }
452 ActionConstraint::In(ActionInConstraint::Set { entities }) => {
453 let mut new_entities: Vec<EntityUidJson> = vec![];
454 for entity in entities {
455 let euid = entity
456 .clone()
457 .into_euid(&|| JsonDeserializationErrorContext::EntityUid)?;
458 match mapping.get(&euid) {
459 Some(new_euid) => new_entities.push(new_euid.clone().into()),
460 None => new_entities.push(entity),
461 };
462 }
463 Ok(ActionConstraint::In(ActionInConstraint::Set {
464 entities: new_entities,
465 }))
466 }
467 ActionConstraint::All | ActionConstraint::Eq(EqConstraint::Slot { .. }) => Ok(self),
468 #[cfg(feature = "tolerant-ast")]
469 ActionConstraint::ErrorConstraint => Ok(self),
470 }
471 }
472
473 pub fn has_slot(&self) -> bool {
475 false
477 }
478}
479
480impl std::fmt::Display for PrincipalConstraint {
481 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
482 match self {
483 Self::All => write!(f, "principal"),
484 Self::Eq(ec) => {
485 write!(f, "principal ")?;
486 std::fmt::Display::fmt(ec, f)?;
487 Ok(())
488 }
489 Self::In(ic) => {
490 write!(f, "principal ")?;
491 std::fmt::Display::fmt(ic, f)?;
492 Ok(())
493 }
494 Self::Is(isc) => {
495 write!(f, "principal ")?;
496 std::fmt::Display::fmt(isc, f)?;
497 Ok(())
498 }
499 }
500 }
501}
502
503impl std::fmt::Display for ActionConstraint {
504 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
505 match self {
506 Self::All => write!(f, "action"),
507 Self::Eq(ec) => {
508 write!(f, "action ")?;
509 std::fmt::Display::fmt(ec, f)?;
510 Ok(())
511 }
512 Self::In(aic) => {
513 write!(f, "action ")?;
514 std::fmt::Display::fmt(aic, f)?;
515 Ok(())
516 }
517 #[cfg(feature = "tolerant-ast")]
518 Self::ErrorConstraint => write!(f, "{ERROR_CONSTRAINT_STR}"),
519 }
520 }
521}
522
523impl std::fmt::Display for ResourceConstraint {
524 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
525 match self {
526 Self::All => write!(f, "resource"),
527 Self::Eq(ec) => {
528 write!(f, "resource ")?;
529 std::fmt::Display::fmt(ec, f)?;
530 Ok(())
531 }
532 Self::In(ic) => {
533 write!(f, "resource ")?;
534 std::fmt::Display::fmt(ic, f)?;
535 Ok(())
536 }
537 Self::Is(isc) => {
538 write!(f, "resource ")?;
539 std::fmt::Display::fmt(isc, f)?;
540 Ok(())
541 }
542 }
543 }
544}
545
546impl std::fmt::Display for EqConstraint {
547 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
548 match self {
549 Self::Entity { entity } => {
550 match entity
551 .clone()
552 .into_euid(&|| JsonDeserializationErrorContext::EntityUid)
553 {
554 Ok(euid) => write!(f, "== {euid}"),
555 Err(e) => write!(f, "== (invalid entity uid: {e})"),
556 }
557 }
558 Self::Slot { slot } => write!(f, "== {slot}"),
559 }
560 }
561}
562
563impl std::fmt::Display for PrincipalOrResourceInConstraint {
564 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
565 match self {
566 Self::Entity { entity } => {
567 match entity
568 .clone()
569 .into_euid(&|| JsonDeserializationErrorContext::EntityUid)
570 {
571 Ok(euid) => write!(f, "in {euid}"),
572 Err(e) => write!(f, "in (invalid entity uid: {e})"),
573 }
574 }
575 Self::Slot { slot } => write!(f, "in {slot}"),
576 }
577 }
578}
579
580impl std::fmt::Display for PrincipalOrResourceIsConstraint {
581 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
582 write!(f, "is {}", self.entity_type)?;
583 if let Some(in_entity) = &self.in_entity {
584 write!(f, " {in_entity}")?;
585 }
586 Ok(())
587 }
588}
589
590impl std::fmt::Display for ActionInConstraint {
591 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
592 match self {
593 Self::Single { entity } => {
594 match entity
595 .clone()
596 .into_euid(&|| JsonDeserializationErrorContext::EntityUid)
597 {
598 Ok(euid) => write!(f, "in {euid}"),
599 Err(e) => write!(f, "in (invalid entity uid: {e})"),
600 }
601 }
602 Self::Set { entities } => {
603 write!(f, "in [")?;
604 for (i, entity) in entities.iter().enumerate() {
605 match entity
606 .clone()
607 .into_euid(&|| JsonDeserializationErrorContext::EntityUid)
608 {
609 Ok(euid) => write!(f, "{euid}"),
610 Err(e) => write!(f, "(invalid entity uid: {e})"),
611 }?;
612 if i < (entities.len() - 1) {
613 write!(f, ", ")?;
614 }
615 }
616 write!(f, "]")?;
617 Ok(())
618 }
619 }
620 }
621}
622
623impl From<ast::PrincipalConstraint> for PrincipalConstraint {
624 fn from(constraint: ast::PrincipalConstraint) -> PrincipalConstraint {
625 constraint.constraint.into()
626 }
627}
628
629impl TryFrom<PrincipalConstraint> for ast::PrincipalConstraint {
630 type Error = FromJsonError;
631 fn try_from(constraint: PrincipalConstraint) -> Result<ast::PrincipalConstraint, Self::Error> {
632 constraint.try_into().map(ast::PrincipalConstraint::new)
633 }
634}
635
636impl From<ast::ResourceConstraint> for ResourceConstraint {
637 fn from(constraint: ast::ResourceConstraint) -> ResourceConstraint {
638 constraint.constraint.into()
639 }
640}
641
642impl TryFrom<ResourceConstraint> for ast::ResourceConstraint {
643 type Error = FromJsonError;
644 fn try_from(constraint: ResourceConstraint) -> Result<ast::ResourceConstraint, Self::Error> {
645 constraint.try_into().map(ast::ResourceConstraint::new)
646 }
647}
648
649impl From<ast::PrincipalOrResourceConstraint> for PrincipalConstraint {
650 fn from(constraint: ast::PrincipalOrResourceConstraint) -> PrincipalConstraint {
651 match constraint {
652 ast::PrincipalOrResourceConstraint::Any => PrincipalConstraint::All,
653 ast::PrincipalOrResourceConstraint::Eq(ast::EntityReference::EUID(e)) => {
654 PrincipalConstraint::Eq(EqConstraint::Entity {
655 entity: EntityUidJson::ImplicitEntityEscape((&*e).into()),
656 })
657 }
658 ast::PrincipalOrResourceConstraint::Eq(ast::EntityReference::Slot(_)) => {
659 PrincipalConstraint::Eq(EqConstraint::Slot {
660 slot: ast::SlotId::principal(),
661 })
662 }
663 ast::PrincipalOrResourceConstraint::In(ast::EntityReference::EUID(e)) => {
664 PrincipalConstraint::In(PrincipalOrResourceInConstraint::Entity {
665 entity: EntityUidJson::ImplicitEntityEscape((&*e).into()),
666 })
667 }
668 ast::PrincipalOrResourceConstraint::In(ast::EntityReference::Slot(_)) => {
669 PrincipalConstraint::In(PrincipalOrResourceInConstraint::Slot {
670 slot: ast::SlotId::principal(),
671 })
672 }
673 ast::PrincipalOrResourceConstraint::IsIn(entity_type, euid) => {
674 PrincipalConstraint::Is(PrincipalOrResourceIsConstraint {
675 entity_type: entity_type.to_smolstr(),
676 in_entity: Some(match euid {
677 ast::EntityReference::EUID(e) => PrincipalOrResourceInConstraint::Entity {
678 entity: EntityUidJson::ImplicitEntityEscape((&*e).into()),
679 },
680 ast::EntityReference::Slot(_) => PrincipalOrResourceInConstraint::Slot {
681 slot: ast::SlotId::principal(),
682 },
683 }),
684 })
685 }
686 ast::PrincipalOrResourceConstraint::Is(entity_type) => {
687 PrincipalConstraint::Is(PrincipalOrResourceIsConstraint {
688 entity_type: entity_type.to_smolstr(),
689 in_entity: None,
690 })
691 }
692 }
693 }
694}
695
696impl From<ast::PrincipalOrResourceConstraint> for ResourceConstraint {
697 fn from(constraint: ast::PrincipalOrResourceConstraint) -> ResourceConstraint {
698 match constraint {
699 ast::PrincipalOrResourceConstraint::Any => ResourceConstraint::All,
700 ast::PrincipalOrResourceConstraint::Eq(ast::EntityReference::EUID(e)) => {
701 ResourceConstraint::Eq(EqConstraint::Entity {
702 entity: EntityUidJson::ImplicitEntityEscape((&*e).into()),
703 })
704 }
705 ast::PrincipalOrResourceConstraint::Eq(ast::EntityReference::Slot(_)) => {
706 ResourceConstraint::Eq(EqConstraint::Slot {
707 slot: ast::SlotId::resource(),
708 })
709 }
710 ast::PrincipalOrResourceConstraint::In(ast::EntityReference::EUID(e)) => {
711 ResourceConstraint::In(PrincipalOrResourceInConstraint::Entity {
712 entity: EntityUidJson::ImplicitEntityEscape((&*e).into()),
713 })
714 }
715 ast::PrincipalOrResourceConstraint::In(ast::EntityReference::Slot(_)) => {
716 ResourceConstraint::In(PrincipalOrResourceInConstraint::Slot {
717 slot: ast::SlotId::resource(),
718 })
719 }
720 ast::PrincipalOrResourceConstraint::IsIn(entity_type, euid) => {
721 ResourceConstraint::Is(PrincipalOrResourceIsConstraint {
722 entity_type: entity_type.to_smolstr(),
723 in_entity: Some(match euid {
724 ast::EntityReference::EUID(e) => PrincipalOrResourceInConstraint::Entity {
725 entity: EntityUidJson::ImplicitEntityEscape((&*e).into()),
726 },
727 ast::EntityReference::Slot(_) => PrincipalOrResourceInConstraint::Slot {
728 slot: ast::SlotId::resource(),
729 },
730 }),
731 })
732 }
733 ast::PrincipalOrResourceConstraint::Is(entity_type) => {
734 ResourceConstraint::Is(PrincipalOrResourceIsConstraint {
735 entity_type: entity_type.to_smolstr(),
736 in_entity: None,
737 })
738 }
739 }
740 }
741}
742
743impl TryFrom<PrincipalConstraint> for ast::PrincipalOrResourceConstraint {
744 type Error = FromJsonError;
745 fn try_from(
746 constraint: PrincipalConstraint,
747 ) -> Result<ast::PrincipalOrResourceConstraint, Self::Error> {
748 match constraint {
749 PrincipalConstraint::All => Ok(ast::PrincipalOrResourceConstraint::Any),
750 PrincipalConstraint::Eq(EqConstraint::Entity { entity }) => Ok(
751 ast::PrincipalOrResourceConstraint::Eq(ast::EntityReference::EUID(Arc::new(
752 entity.into_euid(&|| JsonDeserializationErrorContext::EntityUid)?,
753 ))),
754 ),
755 PrincipalConstraint::Eq(EqConstraint::Slot { slot }) => {
756 if slot == ast::SlotId::principal() {
757 Ok(ast::PrincipalOrResourceConstraint::Eq(
758 ast::EntityReference::Slot(None),
759 ))
760 } else {
761 Err(Self::Error::InvalidSlotName)
762 }
763 }
764 PrincipalConstraint::In(PrincipalOrResourceInConstraint::Entity { entity }) => Ok(
765 ast::PrincipalOrResourceConstraint::In(ast::EntityReference::EUID(Arc::new(
766 entity.into_euid(&|| JsonDeserializationErrorContext::EntityUid)?,
767 ))),
768 ),
769 PrincipalConstraint::In(PrincipalOrResourceInConstraint::Slot { slot }) => {
770 if slot == ast::SlotId::principal() {
771 Ok(ast::PrincipalOrResourceConstraint::In(
772 ast::EntityReference::Slot(None),
773 ))
774 } else {
775 Err(Self::Error::InvalidSlotName)
776 }
777 }
778 PrincipalConstraint::Is(PrincipalOrResourceIsConstraint {
779 entity_type,
780 in_entity,
781 }) => {
782 ast::EntityType::from_normalized_str(entity_type.as_str())
783 .map_err(Self::Error::InvalidEntityType)
784 .and_then(|entity_type| {
785 Ok(match in_entity {
786 None => ast::PrincipalOrResourceConstraint::is_entity_type(Arc::new(
787 entity_type,
788 )),
789 Some(PrincipalOrResourceInConstraint::Entity { entity }) => {
790 ast::PrincipalOrResourceConstraint::is_entity_type_in(
791 Arc::new(entity_type),
792 Arc::new(entity.into_euid(&|| {
793 JsonDeserializationErrorContext::EntityUid
794 })?),
795 )
796 }
797 Some(PrincipalOrResourceInConstraint::Slot { .. }) => {
798 ast::PrincipalOrResourceConstraint::is_entity_type_in_slot(
799 Arc::new(entity_type),
800 )
801 }
802 })
803 })
804 }
805 }
806 }
807}
808
809impl TryFrom<ResourceConstraint> for ast::PrincipalOrResourceConstraint {
810 type Error = FromJsonError;
811 fn try_from(
812 constraint: ResourceConstraint,
813 ) -> Result<ast::PrincipalOrResourceConstraint, Self::Error> {
814 match constraint {
815 ResourceConstraint::All => Ok(ast::PrincipalOrResourceConstraint::Any),
816 ResourceConstraint::Eq(EqConstraint::Entity { entity }) => Ok(
817 ast::PrincipalOrResourceConstraint::Eq(ast::EntityReference::EUID(Arc::new(
818 entity.into_euid(&|| JsonDeserializationErrorContext::EntityUid)?,
819 ))),
820 ),
821 ResourceConstraint::Eq(EqConstraint::Slot { slot }) => {
822 if slot == ast::SlotId::resource() {
823 Ok(ast::PrincipalOrResourceConstraint::Eq(
824 ast::EntityReference::Slot(None),
825 ))
826 } else {
827 Err(Self::Error::InvalidSlotName)
828 }
829 }
830 ResourceConstraint::In(PrincipalOrResourceInConstraint::Entity { entity }) => Ok(
831 ast::PrincipalOrResourceConstraint::In(ast::EntityReference::EUID(Arc::new(
832 entity.into_euid(&|| JsonDeserializationErrorContext::EntityUid)?,
833 ))),
834 ),
835 ResourceConstraint::In(PrincipalOrResourceInConstraint::Slot { slot }) => {
836 if slot == ast::SlotId::resource() {
837 Ok(ast::PrincipalOrResourceConstraint::In(
838 ast::EntityReference::Slot(None),
839 ))
840 } else {
841 Err(Self::Error::InvalidSlotName)
842 }
843 }
844 ResourceConstraint::Is(PrincipalOrResourceIsConstraint {
845 entity_type,
846 in_entity,
847 }) => {
848 ast::EntityType::from_normalized_str(entity_type.as_str())
849 .map_err(Self::Error::InvalidEntityType)
850 .and_then(|entity_type| {
851 Ok(match in_entity {
852 None => ast::PrincipalOrResourceConstraint::is_entity_type(Arc::new(
853 entity_type,
854 )),
855 Some(PrincipalOrResourceInConstraint::Entity { entity }) => {
856 ast::PrincipalOrResourceConstraint::is_entity_type_in(
857 Arc::new(entity_type),
858 Arc::new(entity.into_euid(&|| {
859 JsonDeserializationErrorContext::EntityUid
860 })?),
861 )
862 }
863 Some(PrincipalOrResourceInConstraint::Slot { .. }) => {
864 ast::PrincipalOrResourceConstraint::is_entity_type_in_slot(
865 Arc::new(entity_type),
866 )
867 }
868 })
869 })
870 }
871 }
872 }
873}
874
875impl From<ast::ActionConstraint> for ActionConstraint {
876 fn from(constraint: ast::ActionConstraint) -> ActionConstraint {
877 match constraint {
878 ast::ActionConstraint::Any => ActionConstraint::All,
879 ast::ActionConstraint::Eq(e) => ActionConstraint::Eq(EqConstraint::Entity {
880 entity: EntityUidJson::ImplicitEntityEscape((&*e).into()),
881 }),
882 ast::ActionConstraint::In(es) => match &es[..] {
883 [e] => ActionConstraint::In(ActionInConstraint::Single {
884 entity: EntityUidJson::ImplicitEntityEscape((&**e).into()),
885 }),
886 es => ActionConstraint::In(ActionInConstraint::Set {
887 entities: es
888 .iter()
889 .map(|e| EntityUidJson::ImplicitEntityEscape((&**e).into()))
890 .collect(),
891 }),
892 },
893 #[cfg(feature = "tolerant-ast")]
894 ast::ActionConstraint::ErrorConstraint => ActionConstraint::ErrorConstraint,
895 }
896 }
897}
898
899impl TryFrom<ActionConstraint> for ast::ActionConstraint {
900 type Error = FromJsonError;
901 fn try_from(constraint: ActionConstraint) -> Result<ast::ActionConstraint, Self::Error> {
902 let ast_action_constraint = match constraint {
903 ActionConstraint::All => Ok(ast::ActionConstraint::Any),
904 ActionConstraint::Eq(EqConstraint::Entity { entity }) => Ok(ast::ActionConstraint::Eq(
905 Arc::new(entity.into_euid(&|| JsonDeserializationErrorContext::EntityUid)?),
906 )),
907 ActionConstraint::Eq(EqConstraint::Slot { .. }) => Err(Self::Error::ActionSlot),
908 ActionConstraint::In(ActionInConstraint::Single { entity }) => {
909 Ok(ast::ActionConstraint::In(vec![Arc::new(
910 entity.into_euid(&|| JsonDeserializationErrorContext::EntityUid)?,
911 )]))
912 }
913 ActionConstraint::In(ActionInConstraint::Set { entities }) => {
914 Ok(ast::ActionConstraint::In(
915 entities
916 .into_iter()
917 .map(|e| {
918 e.into_euid(&|| JsonDeserializationErrorContext::EntityUid)
919 .map(Arc::new)
920 })
921 .collect::<Result<Vec<_>, _>>()?,
922 ))
923 }
924 #[cfg(feature = "tolerant-ast")]
925 ActionConstraint::ErrorConstraint => Ok(ast::ActionConstraint::ErrorConstraint),
926 }?;
927
928 ast_action_constraint
929 .contains_only_action_types()
930 .map_err(|non_action_euids| {
931 parse_errors::InvalidActionType {
932 euids: non_action_euids,
933 }
934 .into()
935 })
936 }
937}
938
939#[cfg(test)]
940mod test {
941 fn parse_policy(template: &str) -> crate::est::Policy {
942 let cst = crate::parser::text_to_cst::parse_policy(template)
943 .unwrap()
944 .node
945 .unwrap();
946 cst.try_into().unwrap()
947 }
948
949 fn principal_has_slot(principal_text: &str) -> bool {
950 let text = format!("permit({principal_text}, action, resource);");
951 parse_policy(&text).principal.has_slot()
952 }
953
954 fn resource_has_slot(resource_text: &str) -> bool {
955 let text = format!("permit(principal, action, {resource_text});");
956 parse_policy(&text).resource.has_slot()
957 }
958
959 #[test]
960 fn has_slot_principal_all() {
961 assert!(!principal_has_slot(r#"principal"#));
962 }
963
964 #[test]
965 fn has_slot_principal_eq_entity() {
966 assert!(!principal_has_slot(r#"principal == User::"alice""#));
967 }
968
969 #[test]
970 fn has_slot_principal_eq_slot() {
971 assert!(principal_has_slot(r#"principal == ?principal"#));
972 }
973
974 #[test]
975 fn has_slot_principal_in_entity() {
976 assert!(!principal_has_slot(r#"principal in Group::"friends""#));
977 }
978
979 #[test]
980 fn has_slot_principal_in_slot() {
981 assert!(principal_has_slot(r#"principal in ?principal"#));
982 }
983
984 #[test]
985 fn has_slot_principal_is_entity() {
986 assert!(!principal_has_slot(r#"principal is User"#));
987 }
988
989 #[test]
990 fn has_slot_principal_is_slot() {
991 assert!(principal_has_slot(r#"principal is User in ?principal"#));
992 }
993
994 #[test]
995 fn has_slot_resource_all() {
996 assert!(!resource_has_slot(r#"resource"#));
997 }
998
999 #[test]
1000 fn has_slot_resource_eq_entity() {
1001 assert!(!resource_has_slot(
1002 r#"resource == Photo::"VacationPhoto94.jpg""#
1003 ));
1004 }
1005
1006 #[test]
1007 fn has_slot_resource_eq_slot() {
1008 assert!(resource_has_slot(r#"resource == ?resource"#));
1009 }
1010
1011 #[test]
1012 fn has_slot_resource_in_entity() {
1013 assert!(!resource_has_slot(r#"resource in Group::"vacation""#));
1014 }
1015
1016 #[test]
1017 fn has_slot_resource_in_slot() {
1018 assert!(resource_has_slot(r#"resource in ?resource"#));
1019 }
1020
1021 #[test]
1022 fn has_slot_resource_is_entity() {
1023 assert!(!resource_has_slot(r#"resource is Photo"#));
1024 }
1025
1026 #[test]
1027 fn has_slot_resource_is_slot() {
1028 assert!(resource_has_slot(r#"resource is Photo in ?resource"#));
1029 }
1030}