cedar_policy_core/est/
scope_constraints.rs

1/*
2 * Copyright Cedar Contributors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17use super::{FromJsonError, LinkingError};
18use crate::ast;
19use crate::entities::json::{err::JsonDeserializationErrorContext, EntityUidJson};
20use crate::parser::err::parse_errors;
21use serde::{Deserialize, Serialize};
22use smol_str::SmolStr;
23use std::collections::HashMap;
24use std::sync::Arc;
25
26#[cfg(feature = "wasm")]
27extern crate tsify;
28
29/// Serde JSON structure for a principal scope constraint in the EST format
30#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
31#[serde(deny_unknown_fields)]
32#[serde(tag = "op")]
33#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
34#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
35pub enum PrincipalConstraint {
36    /// No constraint (e.g., `principal,`)
37    #[serde(alias = "all")]
38    All,
39    /// `==` constraint
40    #[serde(rename = "==")]
41    Eq(EqConstraint),
42    /// `in` constraint
43    #[serde(rename = "in")]
44    In(PrincipalOrResourceInConstraint),
45    /// `is` (and possibly `in`) constraint
46    #[serde(rename = "is")]
47    Is(PrincipalOrResourceIsConstraint),
48}
49
50/// Serde JSON structure for an action scope constraint in the EST format
51#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
52#[serde(deny_unknown_fields)]
53#[serde(tag = "op")]
54#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
55#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
56pub enum ActionConstraint {
57    /// No constraint (i.e., `action,`)
58    #[serde(alias = "all")]
59    All,
60    /// `==` constraint
61    #[serde(rename = "==")]
62    Eq(EqConstraint),
63    /// `in` constraint
64    #[serde(rename = "in")]
65    In(ActionInConstraint),
66}
67
68/// Serde JSON structure for a resource scope constraint in the EST format
69#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
70#[serde(deny_unknown_fields)]
71#[serde(tag = "op")]
72#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
73#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
74pub enum ResourceConstraint {
75    /// No constraint (e.g., `resource,`)
76    #[serde(alias = "all")]
77    All,
78    /// `==` constraint
79    #[serde(rename = "==")]
80    Eq(EqConstraint),
81    /// `in` constraint
82    #[serde(rename = "in")]
83    In(PrincipalOrResourceInConstraint),
84    #[serde(rename = "is")]
85    /// `is` (and possibly `in`) constraint
86    Is(PrincipalOrResourceIsConstraint),
87}
88
89/// Serde JSON structure for a `==` scope constraint in the EST format
90#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
91#[serde(untagged)]
92#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
93#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
94pub enum EqConstraint {
95    /// `==` a literal entity
96    Entity {
97        /// Entity it must be `==` to
98        entity: EntityUidJson,
99    },
100    /// Template slot
101    Slot {
102        /// slot
103        #[cfg_attr(feature = "wasm", tsify(type = "string"))]
104        slot: ast::SlotId,
105    },
106}
107
108/// Serde JSON structure for an `in` scope constraint for principal/resource in
109/// the EST format
110#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
111#[serde(untagged)]
112#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
113#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
114pub enum PrincipalOrResourceInConstraint {
115    /// `in` a literal entity
116    Entity {
117        /// Entity it must be `in`
118        entity: EntityUidJson,
119    },
120    /// Template slot
121    Slot {
122        /// slot
123        #[cfg_attr(feature = "wasm", tsify(type = "string"))]
124        slot: ast::SlotId,
125    },
126}
127
128/// Serde JSON structure for an `is` scope constraint for principal/resource in
129/// the EST format
130#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
131#[serde(deny_unknown_fields)]
132#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
133#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
134pub struct PrincipalOrResourceIsConstraint {
135    #[cfg_attr(feature = "wasm", tsify(type = "string"))]
136    entity_type: SmolStr,
137    #[serde(skip_serializing_if = "Option::is_none")]
138    #[serde(rename = "in")]
139    in_entity: Option<PrincipalOrResourceInConstraint>,
140}
141
142/// Serde JSON structure for an `in` scope constraint for action in the EST
143/// format
144#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
145#[serde(untagged)]
146#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
147#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
148pub enum ActionInConstraint {
149    /// Single entity
150    Single {
151        /// the single entity
152        entity: EntityUidJson,
153    },
154    /// Set of entities
155    Set {
156        /// the set of entities
157        entities: Vec<EntityUidJson>,
158    },
159}
160
161impl PrincipalConstraint {
162    /// Fill in any slots in the principal constraint using the values in
163    /// `vals`. Throws an error if `vals` doesn't contain a necessary mapping,
164    /// but does not throw an error if `vals` contains unused mappings.
165    pub fn link(self, vals: &HashMap<ast::SlotId, EntityUidJson>) -> Result<Self, LinkingError> {
166        match self {
167            PrincipalConstraint::All => Ok(PrincipalConstraint::All),
168            PrincipalConstraint::Eq(EqConstraint::Entity { entity }) => {
169                Ok(PrincipalConstraint::Eq(EqConstraint::Entity { entity }))
170            }
171            PrincipalConstraint::In(PrincipalOrResourceInConstraint::Entity { entity }) => Ok(
172                PrincipalConstraint::In(PrincipalOrResourceInConstraint::Entity { entity }),
173            ),
174            PrincipalConstraint::Eq(EqConstraint::Slot { slot }) => match vals.get(&slot) {
175                Some(val) => Ok(PrincipalConstraint::Eq(EqConstraint::Entity {
176                    entity: val.clone(),
177                })),
178                None => Err(LinkingError::MissedSlot { slot }),
179            },
180            PrincipalConstraint::In(PrincipalOrResourceInConstraint::Slot { slot }) => {
181                match vals.get(&slot) {
182                    Some(val) => Ok(PrincipalConstraint::In(
183                        PrincipalOrResourceInConstraint::Entity {
184                            entity: val.clone(),
185                        },
186                    )),
187                    None => Err(LinkingError::MissedSlot { slot }),
188                }
189            }
190            e @ PrincipalConstraint::Is(PrincipalOrResourceIsConstraint {
191                entity_type: _,
192                in_entity: None | Some(PrincipalOrResourceInConstraint::Entity { .. }),
193            }) => Ok(e),
194            PrincipalConstraint::Is(PrincipalOrResourceIsConstraint {
195                entity_type,
196                in_entity: Some(PrincipalOrResourceInConstraint::Slot { slot }),
197            }) => Ok(PrincipalConstraint::Is(PrincipalOrResourceIsConstraint {
198                entity_type,
199                in_entity: Some(PrincipalOrResourceInConstraint::Entity {
200                    entity: vals
201                        .get(&slot)
202                        .ok_or(LinkingError::MissedSlot { slot })?
203                        .clone(),
204                }),
205            })),
206        }
207    }
208}
209
210impl ResourceConstraint {
211    /// Fill in any slots in the resource constraint using the values in
212    /// `vals`. Throws an error if `vals` doesn't contain a necessary mapping,
213    /// but does not throw an error if `vals` contains unused mappings.
214    pub fn link(self, vals: &HashMap<ast::SlotId, EntityUidJson>) -> Result<Self, LinkingError> {
215        match self {
216            ResourceConstraint::All => Ok(ResourceConstraint::All),
217            ResourceConstraint::Eq(EqConstraint::Entity { entity }) => {
218                Ok(ResourceConstraint::Eq(EqConstraint::Entity { entity }))
219            }
220            ResourceConstraint::In(PrincipalOrResourceInConstraint::Entity { entity }) => Ok(
221                ResourceConstraint::In(PrincipalOrResourceInConstraint::Entity { entity }),
222            ),
223            ResourceConstraint::Eq(EqConstraint::Slot { slot }) => match vals.get(&slot) {
224                Some(val) => Ok(ResourceConstraint::Eq(EqConstraint::Entity {
225                    entity: val.clone(),
226                })),
227                None => Err(LinkingError::MissedSlot { slot }),
228            },
229            ResourceConstraint::In(PrincipalOrResourceInConstraint::Slot { slot }) => {
230                match vals.get(&slot) {
231                    Some(val) => Ok(ResourceConstraint::In(
232                        PrincipalOrResourceInConstraint::Entity {
233                            entity: val.clone(),
234                        },
235                    )),
236                    None => Err(LinkingError::MissedSlot { slot }),
237                }
238            }
239            e @ ResourceConstraint::Is(PrincipalOrResourceIsConstraint {
240                entity_type: _,
241                in_entity: None | Some(PrincipalOrResourceInConstraint::Entity { .. }),
242            }) => Ok(e),
243            ResourceConstraint::Is(PrincipalOrResourceIsConstraint {
244                entity_type,
245                in_entity: Some(PrincipalOrResourceInConstraint::Slot { slot }),
246            }) => Ok(ResourceConstraint::Is(PrincipalOrResourceIsConstraint {
247                entity_type,
248                in_entity: Some(PrincipalOrResourceInConstraint::Entity {
249                    entity: vals
250                        .get(&slot)
251                        .ok_or(LinkingError::MissedSlot { slot })?
252                        .clone(),
253                }),
254            })),
255        }
256    }
257}
258
259impl ActionConstraint {
260    /// Fill in any slots in the action constraint using the values in `vals`.
261    /// Throws an error if `vals` doesn't contain a necessary mapping, but does
262    /// not throw an error if `vals` contains unused mappings.
263    pub fn link(self, _vals: &HashMap<ast::SlotId, EntityUidJson>) -> Result<Self, LinkingError> {
264        // currently, slots are not allowed in action constraints
265        Ok(self)
266    }
267}
268
269impl std::fmt::Display for PrincipalConstraint {
270    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
271        match self {
272            Self::All => write!(f, "principal"),
273            Self::Eq(ec) => {
274                write!(f, "principal ")?;
275                std::fmt::Display::fmt(ec, f)?;
276                Ok(())
277            }
278            Self::In(ic) => {
279                write!(f, "principal ")?;
280                std::fmt::Display::fmt(ic, f)?;
281                Ok(())
282            }
283            Self::Is(isc) => {
284                write!(f, "principal ")?;
285                std::fmt::Display::fmt(isc, f)?;
286                Ok(())
287            }
288        }
289    }
290}
291
292impl std::fmt::Display for ActionConstraint {
293    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
294        match self {
295            Self::All => write!(f, "action"),
296            Self::Eq(ec) => {
297                write!(f, "action ")?;
298                std::fmt::Display::fmt(ec, f)?;
299                Ok(())
300            }
301            Self::In(aic) => {
302                write!(f, "action ")?;
303                std::fmt::Display::fmt(aic, f)?;
304                Ok(())
305            }
306        }
307    }
308}
309
310impl std::fmt::Display for ResourceConstraint {
311    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
312        match self {
313            Self::All => write!(f, "resource"),
314            Self::Eq(ec) => {
315                write!(f, "resource ")?;
316                std::fmt::Display::fmt(ec, f)?;
317                Ok(())
318            }
319            Self::In(ic) => {
320                write!(f, "resource ")?;
321                std::fmt::Display::fmt(ic, f)?;
322                Ok(())
323            }
324            Self::Is(isc) => {
325                write!(f, "resource ")?;
326                std::fmt::Display::fmt(isc, f)?;
327                Ok(())
328            }
329        }
330    }
331}
332
333impl std::fmt::Display for EqConstraint {
334    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
335        match self {
336            Self::Entity { entity } => {
337                match entity
338                    .clone()
339                    .into_euid(|| JsonDeserializationErrorContext::EntityUid)
340                {
341                    Ok(euid) => write!(f, "== {euid}"),
342                    Err(e) => write!(f, "== (invalid entity uid: {e})"),
343                }
344            }
345            Self::Slot { slot } => write!(f, "== {slot}"),
346        }
347    }
348}
349
350impl std::fmt::Display for PrincipalOrResourceInConstraint {
351    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
352        match self {
353            Self::Entity { entity } => {
354                match entity
355                    .clone()
356                    .into_euid(|| JsonDeserializationErrorContext::EntityUid)
357                {
358                    Ok(euid) => write!(f, "in {euid}"),
359                    Err(e) => write!(f, "in (invalid entity uid: {e})"),
360                }
361            }
362            Self::Slot { slot } => write!(f, "in {slot}"),
363        }
364    }
365}
366
367impl std::fmt::Display for PrincipalOrResourceIsConstraint {
368    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
369        write!(f, "is {}", self.entity_type)?;
370        if let Some(in_entity) = &self.in_entity {
371            write!(f, " {}", in_entity)?;
372        }
373        Ok(())
374    }
375}
376
377impl std::fmt::Display for ActionInConstraint {
378    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
379        match self {
380            Self::Single { entity } => {
381                match entity
382                    .clone()
383                    .into_euid(|| JsonDeserializationErrorContext::EntityUid)
384                {
385                    Ok(euid) => write!(f, "in {euid}"),
386                    Err(e) => write!(f, "in (invalid entity uid: {e})"),
387                }
388            }
389            Self::Set { entities } => {
390                write!(f, "in [")?;
391                for (i, entity) in entities.iter().enumerate() {
392                    match entity
393                        .clone()
394                        .into_euid(|| JsonDeserializationErrorContext::EntityUid)
395                    {
396                        Ok(euid) => write!(f, "{euid}"),
397                        Err(e) => write!(f, "(invalid entity uid: {e})"),
398                    }?;
399                    if i < (entities.len() - 1) {
400                        write!(f, ", ")?;
401                    }
402                }
403                write!(f, "]")?;
404                Ok(())
405            }
406        }
407    }
408}
409
410impl From<ast::PrincipalConstraint> for PrincipalConstraint {
411    fn from(constraint: ast::PrincipalConstraint) -> PrincipalConstraint {
412        constraint.constraint.into()
413    }
414}
415
416impl TryFrom<PrincipalConstraint> for ast::PrincipalConstraint {
417    type Error = FromJsonError;
418    fn try_from(constraint: PrincipalConstraint) -> Result<ast::PrincipalConstraint, Self::Error> {
419        constraint.try_into().map(ast::PrincipalConstraint::new)
420    }
421}
422
423impl From<ast::ResourceConstraint> for ResourceConstraint {
424    fn from(constraint: ast::ResourceConstraint) -> ResourceConstraint {
425        constraint.constraint.into()
426    }
427}
428
429impl TryFrom<ResourceConstraint> for ast::ResourceConstraint {
430    type Error = FromJsonError;
431    fn try_from(constraint: ResourceConstraint) -> Result<ast::ResourceConstraint, Self::Error> {
432        constraint.try_into().map(ast::ResourceConstraint::new)
433    }
434}
435
436impl From<ast::PrincipalOrResourceConstraint> for PrincipalConstraint {
437    fn from(constraint: ast::PrincipalOrResourceConstraint) -> PrincipalConstraint {
438        match constraint {
439            ast::PrincipalOrResourceConstraint::Any => PrincipalConstraint::All,
440            ast::PrincipalOrResourceConstraint::Eq(ast::EntityReference::EUID(e)) => {
441                PrincipalConstraint::Eq(EqConstraint::Entity {
442                    entity: EntityUidJson::ImplicitEntityEscape((&*e).into()),
443                })
444            }
445            ast::PrincipalOrResourceConstraint::Eq(ast::EntityReference::Slot) => {
446                PrincipalConstraint::Eq(EqConstraint::Slot {
447                    slot: ast::SlotId::principal(),
448                })
449            }
450            ast::PrincipalOrResourceConstraint::In(ast::EntityReference::EUID(e)) => {
451                PrincipalConstraint::In(PrincipalOrResourceInConstraint::Entity {
452                    entity: EntityUidJson::ImplicitEntityEscape((&*e).into()),
453                })
454            }
455            ast::PrincipalOrResourceConstraint::In(ast::EntityReference::Slot) => {
456                PrincipalConstraint::In(PrincipalOrResourceInConstraint::Slot {
457                    slot: ast::SlotId::principal(),
458                })
459            }
460            ast::PrincipalOrResourceConstraint::IsIn(entity_type, euid) => {
461                PrincipalConstraint::Is(PrincipalOrResourceIsConstraint {
462                    entity_type: entity_type.to_string().into(),
463                    in_entity: Some(match euid {
464                        ast::EntityReference::EUID(e) => PrincipalOrResourceInConstraint::Entity {
465                            entity: EntityUidJson::ImplicitEntityEscape((&*e).into()),
466                        },
467                        ast::EntityReference::Slot => PrincipalOrResourceInConstraint::Slot {
468                            slot: ast::SlotId::principal(),
469                        },
470                    }),
471                })
472            }
473            ast::PrincipalOrResourceConstraint::Is(entity_type) => {
474                PrincipalConstraint::Is(PrincipalOrResourceIsConstraint {
475                    entity_type: entity_type.to_string().into(),
476                    in_entity: None,
477                })
478            }
479        }
480    }
481}
482
483impl From<ast::PrincipalOrResourceConstraint> for ResourceConstraint {
484    fn from(constraint: ast::PrincipalOrResourceConstraint) -> ResourceConstraint {
485        match constraint {
486            ast::PrincipalOrResourceConstraint::Any => ResourceConstraint::All,
487            ast::PrincipalOrResourceConstraint::Eq(ast::EntityReference::EUID(e)) => {
488                ResourceConstraint::Eq(EqConstraint::Entity {
489                    entity: EntityUidJson::ImplicitEntityEscape((&*e).into()),
490                })
491            }
492            ast::PrincipalOrResourceConstraint::Eq(ast::EntityReference::Slot) => {
493                ResourceConstraint::Eq(EqConstraint::Slot {
494                    slot: ast::SlotId::resource(),
495                })
496            }
497            ast::PrincipalOrResourceConstraint::In(ast::EntityReference::EUID(e)) => {
498                ResourceConstraint::In(PrincipalOrResourceInConstraint::Entity {
499                    entity: EntityUidJson::ImplicitEntityEscape((&*e).into()),
500                })
501            }
502            ast::PrincipalOrResourceConstraint::In(ast::EntityReference::Slot) => {
503                ResourceConstraint::In(PrincipalOrResourceInConstraint::Slot {
504                    slot: ast::SlotId::resource(),
505                })
506            }
507            ast::PrincipalOrResourceConstraint::IsIn(entity_type, euid) => {
508                ResourceConstraint::Is(PrincipalOrResourceIsConstraint {
509                    entity_type: entity_type.to_string().into(),
510                    in_entity: Some(match euid {
511                        ast::EntityReference::EUID(e) => PrincipalOrResourceInConstraint::Entity {
512                            entity: EntityUidJson::ImplicitEntityEscape((&*e).into()),
513                        },
514                        ast::EntityReference::Slot => PrincipalOrResourceInConstraint::Slot {
515                            slot: ast::SlotId::resource(),
516                        },
517                    }),
518                })
519            }
520            ast::PrincipalOrResourceConstraint::Is(entity_type) => {
521                ResourceConstraint::Is(PrincipalOrResourceIsConstraint {
522                    entity_type: entity_type.to_string().into(),
523                    in_entity: None,
524                })
525            }
526        }
527    }
528}
529
530impl TryFrom<PrincipalConstraint> for ast::PrincipalOrResourceConstraint {
531    type Error = FromJsonError;
532    fn try_from(
533        constraint: PrincipalConstraint,
534    ) -> Result<ast::PrincipalOrResourceConstraint, Self::Error> {
535        match constraint {
536            PrincipalConstraint::All => Ok(ast::PrincipalOrResourceConstraint::Any),
537            PrincipalConstraint::Eq(EqConstraint::Entity { entity }) => Ok(
538                ast::PrincipalOrResourceConstraint::Eq(ast::EntityReference::EUID(Arc::new(
539                    entity.into_euid(|| JsonDeserializationErrorContext::EntityUid)?,
540                ))),
541            ),
542            PrincipalConstraint::Eq(EqConstraint::Slot { slot }) => {
543                if slot == ast::SlotId::principal() {
544                    Ok(ast::PrincipalOrResourceConstraint::Eq(
545                        ast::EntityReference::Slot,
546                    ))
547                } else {
548                    Err(Self::Error::InvalidSlotName)
549                }
550            }
551            PrincipalConstraint::In(PrincipalOrResourceInConstraint::Entity { entity }) => Ok(
552                ast::PrincipalOrResourceConstraint::In(ast::EntityReference::EUID(Arc::new(
553                    entity.into_euid(|| JsonDeserializationErrorContext::EntityUid)?,
554                ))),
555            ),
556            PrincipalConstraint::In(PrincipalOrResourceInConstraint::Slot { slot }) => {
557                if slot == ast::SlotId::principal() {
558                    Ok(ast::PrincipalOrResourceConstraint::In(
559                        ast::EntityReference::Slot,
560                    ))
561                } else {
562                    Err(Self::Error::InvalidSlotName)
563                }
564            }
565            PrincipalConstraint::Is(PrincipalOrResourceIsConstraint {
566                entity_type,
567                in_entity,
568            }) => ast::EntityType::from_normalized_str(entity_type.as_str())
569                .map_err(Self::Error::InvalidEntityType)
570                .and_then(|entity_type| {
571                    Ok(match in_entity {
572                        None => ast::PrincipalOrResourceConstraint::is_entity_type(Arc::new(
573                            entity_type,
574                        )),
575                        Some(PrincipalOrResourceInConstraint::Entity { entity }) => {
576                            ast::PrincipalOrResourceConstraint::is_entity_type_in(
577                                Arc::new(entity_type),
578                                Arc::new(
579                                    entity
580                                        .into_euid(|| JsonDeserializationErrorContext::EntityUid)?,
581                                ),
582                            )
583                        }
584                        Some(PrincipalOrResourceInConstraint::Slot { .. }) => {
585                            ast::PrincipalOrResourceConstraint::is_entity_type_in_slot(Arc::new(
586                                entity_type,
587                            ))
588                        }
589                    })
590                }),
591        }
592    }
593}
594
595impl TryFrom<ResourceConstraint> for ast::PrincipalOrResourceConstraint {
596    type Error = FromJsonError;
597    fn try_from(
598        constraint: ResourceConstraint,
599    ) -> Result<ast::PrincipalOrResourceConstraint, Self::Error> {
600        match constraint {
601            ResourceConstraint::All => Ok(ast::PrincipalOrResourceConstraint::Any),
602            ResourceConstraint::Eq(EqConstraint::Entity { entity }) => Ok(
603                ast::PrincipalOrResourceConstraint::Eq(ast::EntityReference::EUID(Arc::new(
604                    entity.into_euid(|| JsonDeserializationErrorContext::EntityUid)?,
605                ))),
606            ),
607            ResourceConstraint::Eq(EqConstraint::Slot { slot }) => {
608                if slot == ast::SlotId::resource() {
609                    Ok(ast::PrincipalOrResourceConstraint::Eq(
610                        ast::EntityReference::Slot,
611                    ))
612                } else {
613                    Err(Self::Error::InvalidSlotName)
614                }
615            }
616            ResourceConstraint::In(PrincipalOrResourceInConstraint::Entity { entity }) => Ok(
617                ast::PrincipalOrResourceConstraint::In(ast::EntityReference::EUID(Arc::new(
618                    entity.into_euid(|| JsonDeserializationErrorContext::EntityUid)?,
619                ))),
620            ),
621            ResourceConstraint::In(PrincipalOrResourceInConstraint::Slot { slot }) => {
622                if slot == ast::SlotId::resource() {
623                    Ok(ast::PrincipalOrResourceConstraint::In(
624                        ast::EntityReference::Slot,
625                    ))
626                } else {
627                    Err(Self::Error::InvalidSlotName)
628                }
629            }
630            ResourceConstraint::Is(PrincipalOrResourceIsConstraint {
631                entity_type,
632                in_entity,
633            }) => ast::EntityType::from_normalized_str(entity_type.as_str())
634                .map_err(Self::Error::InvalidEntityType)
635                .and_then(|entity_type| {
636                    Ok(match in_entity {
637                        None => ast::PrincipalOrResourceConstraint::is_entity_type(Arc::new(
638                            entity_type,
639                        )),
640                        Some(PrincipalOrResourceInConstraint::Entity { entity }) => {
641                            ast::PrincipalOrResourceConstraint::is_entity_type_in(
642                                Arc::new(entity_type),
643                                Arc::new(
644                                    entity
645                                        .into_euid(|| JsonDeserializationErrorContext::EntityUid)?,
646                                ),
647                            )
648                        }
649                        Some(PrincipalOrResourceInConstraint::Slot { .. }) => {
650                            ast::PrincipalOrResourceConstraint::is_entity_type_in_slot(Arc::new(
651                                entity_type,
652                            ))
653                        }
654                    })
655                }),
656        }
657    }
658}
659
660impl From<ast::ActionConstraint> for ActionConstraint {
661    fn from(constraint: ast::ActionConstraint) -> ActionConstraint {
662        match constraint {
663            ast::ActionConstraint::Any => ActionConstraint::All,
664            ast::ActionConstraint::Eq(e) => ActionConstraint::Eq(EqConstraint::Entity {
665                entity: EntityUidJson::ImplicitEntityEscape((&*e).into()),
666            }),
667            ast::ActionConstraint::In(es) => match &es[..] {
668                [e] => ActionConstraint::In(ActionInConstraint::Single {
669                    entity: EntityUidJson::ImplicitEntityEscape((&**e).into()),
670                }),
671                es => ActionConstraint::In(ActionInConstraint::Set {
672                    entities: es
673                        .iter()
674                        .map(|e| EntityUidJson::ImplicitEntityEscape((&**e).into()))
675                        .collect(),
676                }),
677            },
678        }
679    }
680}
681
682impl TryFrom<ActionConstraint> for ast::ActionConstraint {
683    type Error = FromJsonError;
684    fn try_from(constraint: ActionConstraint) -> Result<ast::ActionConstraint, Self::Error> {
685        let ast_action_constraint = match constraint {
686            ActionConstraint::All => Ok(ast::ActionConstraint::Any),
687            ActionConstraint::Eq(EqConstraint::Entity { entity }) => Ok(ast::ActionConstraint::Eq(
688                Arc::new(entity.into_euid(|| JsonDeserializationErrorContext::EntityUid)?),
689            )),
690            ActionConstraint::Eq(EqConstraint::Slot { .. }) => Err(Self::Error::ActionSlot),
691            ActionConstraint::In(ActionInConstraint::Single { entity }) => {
692                Ok(ast::ActionConstraint::In(vec![Arc::new(
693                    entity.into_euid(|| JsonDeserializationErrorContext::EntityUid)?,
694                )]))
695            }
696            ActionConstraint::In(ActionInConstraint::Set { entities }) => {
697                Ok(ast::ActionConstraint::In(
698                    entities
699                        .into_iter()
700                        .map(|e| {
701                            e.into_euid(|| JsonDeserializationErrorContext::EntityUid)
702                                .map(Arc::new)
703                        })
704                        .collect::<Result<Vec<_>, _>>()?,
705                ))
706            }
707        }?;
708
709        ast_action_constraint
710            .contains_only_action_types()
711            .map_err(|non_action_euids| {
712                parse_errors::InvalidActionType {
713                    euids: non_action_euids,
714                }
715                .into()
716            })
717    }
718}