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 }) => ast::EntityType::from_normalized_str(entity_type.as_str())
782 .map_err(Self::Error::InvalidEntityType)
783 .and_then(|entity_type| {
784 Ok(match in_entity {
785 None => ast::PrincipalOrResourceConstraint::is_entity_type(Arc::new(
786 entity_type,
787 )),
788 Some(PrincipalOrResourceInConstraint::Entity { entity }) => {
789 ast::PrincipalOrResourceConstraint::is_entity_type_in(
790 Arc::new(entity_type),
791 Arc::new(
792 entity
793 .into_euid(|| JsonDeserializationErrorContext::EntityUid)?,
794 ),
795 )
796 }
797 Some(PrincipalOrResourceInConstraint::Slot { .. }) => {
798 ast::PrincipalOrResourceConstraint::is_entity_type_in_slot(Arc::new(
799 entity_type,
800 ))
801 }
802 })
803 }),
804 }
805 }
806}
807
808impl TryFrom<ResourceConstraint> for ast::PrincipalOrResourceConstraint {
809 type Error = FromJsonError;
810 fn try_from(
811 constraint: ResourceConstraint,
812 ) -> Result<ast::PrincipalOrResourceConstraint, Self::Error> {
813 match constraint {
814 ResourceConstraint::All => Ok(ast::PrincipalOrResourceConstraint::Any),
815 ResourceConstraint::Eq(EqConstraint::Entity { entity }) => Ok(
816 ast::PrincipalOrResourceConstraint::Eq(ast::EntityReference::EUID(Arc::new(
817 entity.into_euid(|| JsonDeserializationErrorContext::EntityUid)?,
818 ))),
819 ),
820 ResourceConstraint::Eq(EqConstraint::Slot { slot }) => {
821 if slot == ast::SlotId::resource() {
822 Ok(ast::PrincipalOrResourceConstraint::Eq(
823 ast::EntityReference::Slot(None),
824 ))
825 } else {
826 Err(Self::Error::InvalidSlotName)
827 }
828 }
829 ResourceConstraint::In(PrincipalOrResourceInConstraint::Entity { entity }) => Ok(
830 ast::PrincipalOrResourceConstraint::In(ast::EntityReference::EUID(Arc::new(
831 entity.into_euid(|| JsonDeserializationErrorContext::EntityUid)?,
832 ))),
833 ),
834 ResourceConstraint::In(PrincipalOrResourceInConstraint::Slot { slot }) => {
835 if slot == ast::SlotId::resource() {
836 Ok(ast::PrincipalOrResourceConstraint::In(
837 ast::EntityReference::Slot(None),
838 ))
839 } else {
840 Err(Self::Error::InvalidSlotName)
841 }
842 }
843 ResourceConstraint::Is(PrincipalOrResourceIsConstraint {
844 entity_type,
845 in_entity,
846 }) => ast::EntityType::from_normalized_str(entity_type.as_str())
847 .map_err(Self::Error::InvalidEntityType)
848 .and_then(|entity_type| {
849 Ok(match in_entity {
850 None => ast::PrincipalOrResourceConstraint::is_entity_type(Arc::new(
851 entity_type,
852 )),
853 Some(PrincipalOrResourceInConstraint::Entity { entity }) => {
854 ast::PrincipalOrResourceConstraint::is_entity_type_in(
855 Arc::new(entity_type),
856 Arc::new(
857 entity
858 .into_euid(|| JsonDeserializationErrorContext::EntityUid)?,
859 ),
860 )
861 }
862 Some(PrincipalOrResourceInConstraint::Slot { .. }) => {
863 ast::PrincipalOrResourceConstraint::is_entity_type_in_slot(Arc::new(
864 entity_type,
865 ))
866 }
867 })
868 }),
869 }
870 }
871}
872
873impl From<ast::ActionConstraint> for ActionConstraint {
874 fn from(constraint: ast::ActionConstraint) -> ActionConstraint {
875 match constraint {
876 ast::ActionConstraint::Any => ActionConstraint::All,
877 ast::ActionConstraint::Eq(e) => ActionConstraint::Eq(EqConstraint::Entity {
878 entity: EntityUidJson::ImplicitEntityEscape((&*e).into()),
879 }),
880 ast::ActionConstraint::In(es) => match &es[..] {
881 [e] => ActionConstraint::In(ActionInConstraint::Single {
882 entity: EntityUidJson::ImplicitEntityEscape((&**e).into()),
883 }),
884 es => ActionConstraint::In(ActionInConstraint::Set {
885 entities: es
886 .iter()
887 .map(|e| EntityUidJson::ImplicitEntityEscape((&**e).into()))
888 .collect(),
889 }),
890 },
891 #[cfg(feature = "tolerant-ast")]
892 ast::ActionConstraint::ErrorConstraint => ActionConstraint::ErrorConstraint,
893 }
894 }
895}
896
897impl TryFrom<ActionConstraint> for ast::ActionConstraint {
898 type Error = FromJsonError;
899 fn try_from(constraint: ActionConstraint) -> Result<ast::ActionConstraint, Self::Error> {
900 let ast_action_constraint = match constraint {
901 ActionConstraint::All => Ok(ast::ActionConstraint::Any),
902 ActionConstraint::Eq(EqConstraint::Entity { entity }) => Ok(ast::ActionConstraint::Eq(
903 Arc::new(entity.into_euid(|| JsonDeserializationErrorContext::EntityUid)?),
904 )),
905 ActionConstraint::Eq(EqConstraint::Slot { .. }) => Err(Self::Error::ActionSlot),
906 ActionConstraint::In(ActionInConstraint::Single { entity }) => {
907 Ok(ast::ActionConstraint::In(vec![Arc::new(
908 entity.into_euid(|| JsonDeserializationErrorContext::EntityUid)?,
909 )]))
910 }
911 ActionConstraint::In(ActionInConstraint::Set { entities }) => {
912 Ok(ast::ActionConstraint::In(
913 entities
914 .into_iter()
915 .map(|e| {
916 e.into_euid(|| JsonDeserializationErrorContext::EntityUid)
917 .map(Arc::new)
918 })
919 .collect::<Result<Vec<_>, _>>()?,
920 ))
921 }
922 #[cfg(feature = "tolerant-ast")]
923 ActionConstraint::ErrorConstraint => Ok(ast::ActionConstraint::ErrorConstraint),
924 }?;
925
926 ast_action_constraint
927 .contains_only_action_types()
928 .map_err(|non_action_euids| {
929 parse_errors::InvalidActionType {
930 euids: non_action_euids,
931 }
932 .into()
933 })
934 }
935}
936
937#[cfg(test)]
938mod test {
939 fn parse_policy(template: &str) -> crate::est::Policy {
940 let cst = crate::parser::text_to_cst::parse_policy(template)
941 .unwrap()
942 .node
943 .unwrap();
944 cst.try_into().unwrap()
945 }
946
947 fn principal_has_slot(principal_text: &str) -> bool {
948 let text = format!("permit({principal_text}, action, resource);");
949 parse_policy(&text).principal.has_slot()
950 }
951
952 fn resource_has_slot(resource_text: &str) -> bool {
953 let text = format!("permit(principal, action, {resource_text});");
954 parse_policy(&text).resource.has_slot()
955 }
956
957 #[test]
958 fn has_slot_principal_all() {
959 assert!(!principal_has_slot(r#"principal"#));
960 }
961
962 #[test]
963 fn has_slot_principal_eq_entity() {
964 assert!(!principal_has_slot(r#"principal == User::"alice""#));
965 }
966
967 #[test]
968 fn has_slot_principal_eq_slot() {
969 assert!(principal_has_slot(r#"principal == ?principal"#));
970 }
971
972 #[test]
973 fn has_slot_principal_in_entity() {
974 assert!(!principal_has_slot(r#"principal in Group::"friends""#));
975 }
976
977 #[test]
978 fn has_slot_principal_in_slot() {
979 assert!(principal_has_slot(r#"principal in ?principal"#));
980 }
981
982 #[test]
983 fn has_slot_principal_is_entity() {
984 assert!(!principal_has_slot(r#"principal is User"#));
985 }
986
987 #[test]
988 fn has_slot_principal_is_slot() {
989 assert!(principal_has_slot(r#"principal is User in ?principal"#));
990 }
991
992 #[test]
993 fn has_slot_resource_all() {
994 assert!(!resource_has_slot(r#"resource"#));
995 }
996
997 #[test]
998 fn has_slot_resource_eq_entity() {
999 assert!(!resource_has_slot(
1000 r#"resource == Photo::"VacationPhoto94.jpg""#
1001 ));
1002 }
1003
1004 #[test]
1005 fn has_slot_resource_eq_slot() {
1006 assert!(resource_has_slot(r#"resource == ?resource"#));
1007 }
1008
1009 #[test]
1010 fn has_slot_resource_in_entity() {
1011 assert!(!resource_has_slot(r#"resource in Group::"vacation""#));
1012 }
1013
1014 #[test]
1015 fn has_slot_resource_in_slot() {
1016 assert!(resource_has_slot(r#"resource in ?resource"#));
1017 }
1018
1019 #[test]
1020 fn has_slot_resource_is_entity() {
1021 assert!(!resource_has_slot(r#"resource is Photo"#));
1022 }
1023
1024 #[test]
1025 fn has_slot_resource_is_slot() {
1026 assert!(resource_has_slot(r#"resource is Photo in ?resource"#));
1027 }
1028}