cedar_policy_core/ast/restricted_expr.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::{
18 EntityUID, Expr, ExprKind, ExpressionConstructionError, Literal, Name, PartialValue, Type,
19 Unknown, Value, ValueKind,
20};
21use crate::entities::json::err::JsonSerializationError;
22use crate::extensions::Extensions;
23use crate::parser::err::ParseErrors;
24use crate::parser::{self, Loc};
25use miette::Diagnostic;
26use serde::{Deserialize, Serialize};
27use smol_str::{SmolStr, ToSmolStr};
28use std::hash::{Hash, Hasher};
29use std::ops::Deref;
30use std::sync::Arc;
31use thiserror::Error;
32
33/// A few places in Core use these "restricted expressions" (for lack of a
34/// better term) which are in some sense the minimal subset of `Expr` required
35/// to express all possible `Value`s.
36///
37/// Specifically, "restricted" expressions are
38/// defined as expressions containing only the following:
39/// - bool, int, and string literals
40/// - literal EntityUIDs such as User::"alice"
41/// - extension function calls, where the arguments must be other things
42/// on this list
43/// - set and record literals, where the values must be other things on
44/// this list
45///
46/// That means the following are not allowed in "restricted" expressions:
47/// - `principal`, `action`, `resource`, `context`
48/// - builtin operators and functions, including `.`, `in`, `has`, `like`,
49/// `.contains()`
50/// - if-then-else expressions
51///
52/// These restrictions represent the expressions that are allowed to appear as
53/// attribute values in `Slice` and `Context`.
54#[derive(Deserialize, Serialize, Hash, Debug, Clone, PartialEq, Eq)]
55#[serde(transparent)]
56pub struct RestrictedExpr(Expr);
57
58impl RestrictedExpr {
59 /// Create a new `RestrictedExpr` from an `Expr`.
60 ///
61 /// This function is "safe" in the sense that it will verify that the
62 /// provided `expr` does indeed qualify as a "restricted" expression,
63 /// returning an error if not.
64 ///
65 /// Note this check requires recursively walking the AST. For a version of
66 /// this function that doesn't perform this check, see `new_unchecked()`
67 /// below.
68 pub fn new(expr: Expr) -> Result<Self, RestrictedExpressionError> {
69 is_restricted(&expr)?;
70 Ok(Self(expr))
71 }
72
73 /// Create a new `RestrictedExpr` from an `Expr`, where the caller is
74 /// responsible for ensuring that the `Expr` is a valid "restricted
75 /// expression". If it is not, internal invariants will be violated, which
76 /// may lead to other errors later, panics, or even incorrect results.
77 ///
78 /// For a "safer" version of this function that returns an error for invalid
79 /// inputs, see `new()` above.
80 pub fn new_unchecked(expr: Expr) -> Self {
81 // in debug builds, this does the check anyway, panicking if it fails
82 if cfg!(debug_assertions) {
83 // PANIC SAFETY: We're in debug mode and panicking intentionally
84 #[allow(clippy::unwrap_used)]
85 Self::new(expr).unwrap()
86 } else {
87 Self(expr)
88 }
89 }
90
91 /// Return the `RestrictedExpr`, but with the new `source_loc` (or `None`).
92 pub fn with_maybe_source_loc(self, source_loc: Option<Loc>) -> Self {
93 Self(self.0.with_maybe_source_loc(source_loc))
94 }
95
96 /// Create a `RestrictedExpr` that's just a single `Literal`.
97 ///
98 /// Note that you can pass this a `Literal`, an `Integer`, a `String`, etc.
99 pub fn val(v: impl Into<Literal>) -> Self {
100 // All literals are valid restricted-exprs
101 Self::new_unchecked(Expr::val(v))
102 }
103
104 /// Create a `RestrictedExpr` that's just a single `Unknown`.
105 pub fn unknown(u: Unknown) -> Self {
106 // All unknowns are valid restricted-exprs
107 Self::new_unchecked(Expr::unknown(u))
108 }
109
110 /// Create a `RestrictedExpr` which evaluates to a Set of the given `RestrictedExpr`s
111 pub fn set(exprs: impl IntoIterator<Item = RestrictedExpr>) -> Self {
112 // Set expressions are valid restricted-exprs if their elements are; and
113 // we know the elements are because we require `RestrictedExpr`s in the
114 // parameter
115 Self::new_unchecked(Expr::set(exprs.into_iter().map(Into::into)))
116 }
117
118 /// Create a `RestrictedExpr` which evaluates to a Record with the given
119 /// (key, value) pairs.
120 ///
121 /// Throws an error if any key occurs two or more times.
122 pub fn record(
123 pairs: impl IntoIterator<Item = (SmolStr, RestrictedExpr)>,
124 ) -> Result<Self, ExpressionConstructionError> {
125 // Record expressions are valid restricted-exprs if their elements are;
126 // and we know the elements are because we require `RestrictedExpr`s in
127 // the parameter
128 Ok(Self::new_unchecked(Expr::record(
129 pairs.into_iter().map(|(k, v)| (k, v.into())),
130 )?))
131 }
132
133 /// Create a `RestrictedExpr` which calls the given extension function
134 pub fn call_extension_fn(
135 function_name: Name,
136 args: impl IntoIterator<Item = RestrictedExpr>,
137 ) -> Self {
138 // Extension-function calls are valid restricted-exprs if their
139 // arguments are; and we know the arguments are because we require
140 // `RestrictedExpr`s in the parameter
141 Self::new_unchecked(Expr::call_extension_fn(
142 function_name,
143 args.into_iter().map(Into::into).collect(),
144 ))
145 }
146
147 /// Write a RestrictedExpr in "natural JSON" format.
148 ///
149 /// Used to output the context as a map from Strings to JSON Values
150 pub fn to_natural_json(&self) -> Result<serde_json::Value, JsonSerializationError> {
151 self.as_borrowed().to_natural_json()
152 }
153
154 /// Get the `bool` value of this `RestrictedExpr` if it's a boolean, or
155 /// `None` if it is not a boolean
156 pub fn as_bool(&self) -> Option<bool> {
157 // the only way a `RestrictedExpr` can be a boolean is if it's a literal
158 match self.expr_kind() {
159 ExprKind::Lit(Literal::Bool(b)) => Some(*b),
160 _ => None,
161 }
162 }
163
164 /// Get the `i64` value of this `RestrictedExpr` if it's a long, or `None`
165 /// if it is not a long
166 pub fn as_long(&self) -> Option<i64> {
167 // the only way a `RestrictedExpr` can be a long is if it's a literal
168 match self.expr_kind() {
169 ExprKind::Lit(Literal::Long(i)) => Some(*i),
170 _ => None,
171 }
172 }
173
174 /// Get the `SmolStr` value of this `RestrictedExpr` if it's a string, or
175 /// `None` if it is not a string
176 pub fn as_string(&self) -> Option<&SmolStr> {
177 // the only way a `RestrictedExpr` can be a string is if it's a literal
178 match self.expr_kind() {
179 ExprKind::Lit(Literal::String(s)) => Some(s),
180 _ => None,
181 }
182 }
183
184 /// Get the `EntityUID` value of this `RestrictedExpr` if it's an entity
185 /// reference, or `None` if it is not an entity reference
186 pub fn as_euid(&self) -> Option<&EntityUID> {
187 // the only way a `RestrictedExpr` can be an entity reference is if it's
188 // a literal
189 match self.expr_kind() {
190 ExprKind::Lit(Literal::EntityUID(e)) => Some(e),
191 _ => None,
192 }
193 }
194
195 /// Get `Unknown` value of this `RestrictedExpr` if it's an `Unknown`, or
196 /// `None` if it is not an `Unknown`
197 pub fn as_unknown(&self) -> Option<&Unknown> {
198 match self.expr_kind() {
199 ExprKind::Unknown(u) => Some(u),
200 _ => None,
201 }
202 }
203
204 /// Iterate over the elements of the set if this `RestrictedExpr` is a set,
205 /// or `None` if it is not a set
206 pub fn as_set_elements(&self) -> Option<impl Iterator<Item = BorrowedRestrictedExpr<'_>>> {
207 match self.expr_kind() {
208 ExprKind::Set(set) => Some(set.iter().map(BorrowedRestrictedExpr::new_unchecked)), // since the RestrictedExpr invariant holds for the input set, it will hold for each element as well
209 _ => None,
210 }
211 }
212
213 /// Iterate over the (key, value) pairs of the record if this
214 /// `RestrictedExpr` is a record, or `None` if it is not a record
215 pub fn as_record_pairs(
216 &self,
217 ) -> Option<impl Iterator<Item = (&SmolStr, BorrowedRestrictedExpr<'_>)>> {
218 match self.expr_kind() {
219 ExprKind::Record(map) => Some(
220 map.iter()
221 .map(|(k, v)| (k, BorrowedRestrictedExpr::new_unchecked(v))),
222 ), // since the RestrictedExpr invariant holds for the input record, it will hold for each attr value as well
223 _ => None,
224 }
225 }
226
227 /// Get the name and args of the called extension function if this
228 /// `RestrictedExpr` is an extension function call, or `None` if it is not
229 /// an extension function call
230 pub fn as_extn_fn_call(
231 &self,
232 ) -> Option<(&Name, impl Iterator<Item = BorrowedRestrictedExpr<'_>>)> {
233 match self.expr_kind() {
234 ExprKind::ExtensionFunctionApp { fn_name, args } => Some((
235 fn_name,
236 args.iter().map(BorrowedRestrictedExpr::new_unchecked),
237 )), // since the RestrictedExpr invariant holds for the input call, it will hold for each argument as well
238 _ => None,
239 }
240 }
241}
242
243impl From<Value> for RestrictedExpr {
244 fn from(value: Value) -> RestrictedExpr {
245 RestrictedExpr::from(value.value).with_maybe_source_loc(value.loc)
246 }
247}
248
249impl From<ValueKind> for RestrictedExpr {
250 fn from(value: ValueKind) -> RestrictedExpr {
251 match value {
252 ValueKind::Lit(lit) => RestrictedExpr::val(lit),
253 ValueKind::Set(set) => {
254 RestrictedExpr::set(set.iter().map(|val| RestrictedExpr::from(val.clone())))
255 }
256 // PANIC SAFETY: cannot have duplicate key because the input was already a BTreeMap
257 #[allow(clippy::expect_used)]
258 ValueKind::Record(record) => RestrictedExpr::record(
259 Arc::unwrap_or_clone(record)
260 .into_iter()
261 .map(|(k, v)| (k, RestrictedExpr::from(v))),
262 )
263 .expect("can't have duplicate keys, because the input `map` was already a BTreeMap"),
264 ValueKind::ExtensionValue(ev) => {
265 let ev = Arc::unwrap_or_clone(ev);
266 ev.into()
267 }
268 }
269 }
270}
271
272impl TryFrom<PartialValue> for RestrictedExpr {
273 type Error = PartialValueToRestrictedExprError;
274 fn try_from(pvalue: PartialValue) -> Result<RestrictedExpr, PartialValueToRestrictedExprError> {
275 match pvalue {
276 PartialValue::Value(v) => Ok(RestrictedExpr::from(v)),
277 PartialValue::Residual(expr) => match RestrictedExpr::new(expr) {
278 Ok(e) => Ok(e),
279 Err(RestrictedExpressionError::InvalidRestrictedExpression(
280 restricted_expr_errors::InvalidRestrictedExpressionError { expr, .. },
281 )) => Err(PartialValueToRestrictedExprError::NontrivialResidual {
282 residual: Box::new(expr),
283 }),
284 },
285 }
286 }
287}
288
289/// Errors when converting `PartialValue` to `RestrictedExpr`
290#[derive(Debug, PartialEq, Eq, Diagnostic, Error)]
291pub enum PartialValueToRestrictedExprError {
292 /// The `PartialValue` contains a nontrivial residual that isn't a valid `RestrictedExpr`
293 #[error("residual is not a valid restricted expression: `{residual}`")]
294 NontrivialResidual {
295 /// Residual that isn't a valid `RestrictedExpr`
296 residual: Box<Expr>,
297 },
298}
299
300impl std::str::FromStr for RestrictedExpr {
301 type Err = RestrictedExpressionParseError;
302
303 fn from_str(s: &str) -> Result<RestrictedExpr, Self::Err> {
304 parser::parse_restrictedexpr(s)
305 }
306}
307
308/// While `RestrictedExpr` wraps an _owned_ `Expr`, `BorrowedRestrictedExpr`
309/// wraps a _borrowed_ `Expr`, with the same invariants.
310///
311/// We derive `Copy` for this type because it's just a single reference, and
312/// `&T` is `Copy` for all `T`.
313#[derive(Serialize, Hash, Debug, Clone, PartialEq, Eq, Copy)]
314pub struct BorrowedRestrictedExpr<'a>(&'a Expr);
315
316impl<'a> BorrowedRestrictedExpr<'a> {
317 /// Create a new `BorrowedRestrictedExpr` from an `&Expr`.
318 ///
319 /// This function is "safe" in the sense that it will verify that the
320 /// provided `expr` does indeed qualify as a "restricted" expression,
321 /// returning an error if not.
322 ///
323 /// Note this check requires recursively walking the AST. For a version of
324 /// this function that doesn't perform this check, see `new_unchecked()`
325 /// below.
326 pub fn new(expr: &'a Expr) -> Result<Self, RestrictedExpressionError> {
327 is_restricted(expr)?;
328 Ok(Self(expr))
329 }
330
331 /// Create a new `BorrowedRestrictedExpr` from an `&Expr`, where the caller
332 /// is responsible for ensuring that the `Expr` is a valid "restricted
333 /// expression". If it is not, internal invariants will be violated, which
334 /// may lead to other errors later, panics, or even incorrect results.
335 ///
336 /// For a "safer" version of this function that returns an error for invalid
337 /// inputs, see `new()` above.
338 pub fn new_unchecked(expr: &'a Expr) -> Self {
339 // in debug builds, this does the check anyway, panicking if it fails
340 if cfg!(debug_assertions) {
341 // PANIC SAFETY: We're in debug mode and panicking intentionally
342 #[allow(clippy::unwrap_used)]
343 Self::new(expr).unwrap()
344 } else {
345 Self(expr)
346 }
347 }
348
349 /// Write a BorrowedRestrictedExpr in "natural JSON" format.
350 ///
351 /// Used to output the context as a map from Strings to JSON Values
352 pub fn to_natural_json(self) -> Result<serde_json::Value, JsonSerializationError> {
353 Ok(serde_json::to_value(
354 crate::entities::json::CedarValueJson::from_expr(self)?,
355 )?)
356 }
357
358 /// Convert `BorrowedRestrictedExpr` to `RestrictedExpr`.
359 /// This has approximately the cost of cloning the `Expr`.
360 pub fn to_owned(self) -> RestrictedExpr {
361 RestrictedExpr::new_unchecked(self.0.clone())
362 }
363
364 /// Get the `bool` value of this `RestrictedExpr` if it's a boolean, or
365 /// `None` if it is not a boolean
366 pub fn as_bool(&self) -> Option<bool> {
367 // the only way a `RestrictedExpr` can be a boolean is if it's a literal
368 match self.expr_kind() {
369 ExprKind::Lit(Literal::Bool(b)) => Some(*b),
370 _ => None,
371 }
372 }
373
374 /// Get the `i64` value of this `RestrictedExpr` if it's a long, or `None`
375 /// if it is not a long
376 pub fn as_long(&self) -> Option<i64> {
377 // the only way a `RestrictedExpr` can be a long is if it's a literal
378 match self.expr_kind() {
379 ExprKind::Lit(Literal::Long(i)) => Some(*i),
380 _ => None,
381 }
382 }
383
384 /// Get the `SmolStr` value of this `RestrictedExpr` if it's a string, or
385 /// `None` if it is not a string
386 pub fn as_string(&self) -> Option<&SmolStr> {
387 // the only way a `RestrictedExpr` can be a string is if it's a literal
388 match self.expr_kind() {
389 ExprKind::Lit(Literal::String(s)) => Some(s),
390 _ => None,
391 }
392 }
393
394 /// Get the `EntityUID` value of this `RestrictedExpr` if it's an entity
395 /// reference, or `None` if it is not an entity reference
396 pub fn as_euid(&self) -> Option<&EntityUID> {
397 // the only way a `RestrictedExpr` can be an entity reference is if it's
398 // a literal
399 match self.expr_kind() {
400 ExprKind::Lit(Literal::EntityUID(e)) => Some(e),
401 _ => None,
402 }
403 }
404
405 /// Get `Unknown` value of this `RestrictedExpr` if it's an `Unknown`, or
406 /// `None` if it is not an `Unknown`
407 pub fn as_unknown(&self) -> Option<&Unknown> {
408 match self.expr_kind() {
409 ExprKind::Unknown(u) => Some(u),
410 _ => None,
411 }
412 }
413
414 /// Iterate over the elements of the set if this `RestrictedExpr` is a set,
415 /// or `None` if it is not a set
416 pub fn as_set_elements(&self) -> Option<impl Iterator<Item = BorrowedRestrictedExpr<'_>>> {
417 match self.expr_kind() {
418 ExprKind::Set(set) => Some(set.iter().map(BorrowedRestrictedExpr::new_unchecked)), // since the RestrictedExpr invariant holds for the input set, it will hold for each element as well
419 _ => None,
420 }
421 }
422
423 /// Iterate over the (key, value) pairs of the record if this
424 /// `RestrictedExpr` is a record, or `None` if it is not a record
425 pub fn as_record_pairs(
426 &self,
427 ) -> Option<impl Iterator<Item = (&'_ SmolStr, BorrowedRestrictedExpr<'_>)>> {
428 match self.expr_kind() {
429 ExprKind::Record(map) => Some(
430 map.iter()
431 .map(|(k, v)| (k, BorrowedRestrictedExpr::new_unchecked(v))),
432 ), // since the RestrictedExpr invariant holds for the input record, it will hold for each attr value as well
433 _ => None,
434 }
435 }
436
437 /// Get the name and args of the called extension function if this
438 /// `RestrictedExpr` is an extension function call, or `None` if it is not
439 /// an extension function call
440 pub fn as_extn_fn_call(
441 &self,
442 ) -> Option<(&Name, impl Iterator<Item = BorrowedRestrictedExpr<'_>>)> {
443 match self.expr_kind() {
444 ExprKind::ExtensionFunctionApp { fn_name, args } => Some((
445 fn_name,
446 args.iter().map(BorrowedRestrictedExpr::new_unchecked),
447 )), // since the RestrictedExpr invariant holds for the input call, it will hold for each argument as well
448 _ => None,
449 }
450 }
451
452 /// Try to compute the runtime type of this expression. See
453 /// [`Expr::try_type_of`] for exactly what this computes.
454 ///
455 /// On a restricted expression, there are fewer cases where we might fail to
456 /// compute the type, but there are still `unknown`s and extension function
457 /// calls which may cause this function to return `None` .
458 pub fn try_type_of(&self, extensions: &Extensions<'_>) -> Option<Type> {
459 self.0.try_type_of(extensions)
460 }
461}
462
463/// Helper function: does the given `Expr` qualify as a "restricted" expression.
464///
465/// Returns `Ok(())` if yes, or a `RestrictedExpressionError` if no.
466fn is_restricted(expr: &Expr) -> Result<(), RestrictedExpressionError> {
467 match expr.expr_kind() {
468 ExprKind::Lit(_) => Ok(()),
469 ExprKind::Unknown(_) => Ok(()),
470 ExprKind::Var(_) => Err(restricted_expr_errors::InvalidRestrictedExpressionError {
471 feature: "variables".into(),
472 expr: expr.clone(),
473 }
474 .into()),
475 ExprKind::Slot(_) => Err(restricted_expr_errors::InvalidRestrictedExpressionError {
476 feature: "template slots".into(),
477 expr: expr.clone(),
478 }
479 .into()),
480 ExprKind::If { .. } => Err(restricted_expr_errors::InvalidRestrictedExpressionError {
481 feature: "if-then-else".into(),
482 expr: expr.clone(),
483 }
484 .into()),
485 ExprKind::And { .. } => Err(restricted_expr_errors::InvalidRestrictedExpressionError {
486 feature: "&&".into(),
487 expr: expr.clone(),
488 }
489 .into()),
490 ExprKind::Or { .. } => Err(restricted_expr_errors::InvalidRestrictedExpressionError {
491 feature: "||".into(),
492 expr: expr.clone(),
493 }
494 .into()),
495 ExprKind::UnaryApp { op, .. } => {
496 Err(restricted_expr_errors::InvalidRestrictedExpressionError {
497 feature: op.to_smolstr(),
498 expr: expr.clone(),
499 }
500 .into())
501 }
502 ExprKind::BinaryApp { op, .. } => {
503 Err(restricted_expr_errors::InvalidRestrictedExpressionError {
504 feature: op.to_smolstr(),
505 expr: expr.clone(),
506 }
507 .into())
508 }
509 ExprKind::GetAttr { .. } => Err(restricted_expr_errors::InvalidRestrictedExpressionError {
510 feature: "attribute accesses".into(),
511 expr: expr.clone(),
512 }
513 .into()),
514 ExprKind::HasAttr { .. } => Err(restricted_expr_errors::InvalidRestrictedExpressionError {
515 feature: "'has'".into(),
516 expr: expr.clone(),
517 }
518 .into()),
519 ExprKind::Like { .. } => Err(restricted_expr_errors::InvalidRestrictedExpressionError {
520 feature: "'like'".into(),
521 expr: expr.clone(),
522 }
523 .into()),
524 ExprKind::Is { .. } => Err(restricted_expr_errors::InvalidRestrictedExpressionError {
525 feature: "'is'".into(),
526 expr: expr.clone(),
527 }
528 .into()),
529 ExprKind::ExtensionFunctionApp { args, .. } => args.iter().try_for_each(is_restricted),
530 ExprKind::Set(exprs) => exprs.iter().try_for_each(is_restricted),
531 ExprKind::Record(map) => map.values().try_for_each(is_restricted),
532 }
533}
534
535// converting into Expr is always safe; restricted exprs are always valid Exprs
536impl From<RestrictedExpr> for Expr {
537 fn from(r: RestrictedExpr) -> Expr {
538 r.0
539 }
540}
541
542impl AsRef<Expr> for RestrictedExpr {
543 fn as_ref(&self) -> &Expr {
544 &self.0
545 }
546}
547
548impl Deref for RestrictedExpr {
549 type Target = Expr;
550 fn deref(&self) -> &Expr {
551 self.as_ref()
552 }
553}
554
555impl std::fmt::Display for RestrictedExpr {
556 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
557 write!(f, "{}", &self.0)
558 }
559}
560
561// converting into Expr is always safe; restricted exprs are always valid Exprs
562impl<'a> From<BorrowedRestrictedExpr<'a>> for &'a Expr {
563 fn from(r: BorrowedRestrictedExpr<'a>) -> &'a Expr {
564 r.0
565 }
566}
567
568impl<'a> AsRef<Expr> for BorrowedRestrictedExpr<'a> {
569 fn as_ref(&self) -> &'a Expr {
570 self.0
571 }
572}
573
574impl RestrictedExpr {
575 /// Turn an `&RestrictedExpr` into a `BorrowedRestrictedExpr`
576 pub fn as_borrowed(&self) -> BorrowedRestrictedExpr<'_> {
577 BorrowedRestrictedExpr::new_unchecked(self.as_ref())
578 }
579}
580
581impl<'a> Deref for BorrowedRestrictedExpr<'a> {
582 type Target = Expr;
583 fn deref(&self) -> &'a Expr {
584 self.0
585 }
586}
587
588impl std::fmt::Display for BorrowedRestrictedExpr<'_> {
589 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
590 write!(f, "{}", &self.0)
591 }
592}
593
594/// Like `ExprShapeOnly`, but for restricted expressions.
595///
596/// A newtype wrapper around (borrowed) restricted expressions that provides
597/// `Eq` and `Hash` implementations that ignore any source information or other
598/// generic data used to annotate the expression.
599#[derive(Eq, Debug, Clone)]
600pub struct RestrictedExprShapeOnly<'a>(BorrowedRestrictedExpr<'a>);
601
602impl<'a> RestrictedExprShapeOnly<'a> {
603 /// Construct a `RestrictedExprShapeOnly` from a `BorrowedRestrictedExpr`.
604 /// The `BorrowedRestrictedExpr` is not modified, but any comparisons on the
605 /// resulting `RestrictedExprShapeOnly` will ignore source information and
606 /// generic data.
607 pub fn new(e: BorrowedRestrictedExpr<'a>) -> RestrictedExprShapeOnly<'a> {
608 RestrictedExprShapeOnly(e)
609 }
610}
611
612impl PartialEq for RestrictedExprShapeOnly<'_> {
613 fn eq(&self, other: &Self) -> bool {
614 self.0.eq_shape(&other.0)
615 }
616}
617
618impl Hash for RestrictedExprShapeOnly<'_> {
619 fn hash<H: Hasher>(&self, state: &mut H) {
620 self.0.hash_shape(state);
621 }
622}
623
624/// Error when constructing a restricted expression from unrestricted
625/// expression
626//
627// CAUTION: this type is publicly exported in `cedar-policy`.
628// Don't make fields `pub`, don't make breaking changes, and use caution
629// when adding public methods.
630#[derive(Debug, Clone, PartialEq, Eq, Error, Diagnostic)]
631pub enum RestrictedExpressionError {
632 /// An expression was expected to be a "restricted" expression, but contained
633 /// a feature that is not allowed in restricted expressions.
634 #[error(transparent)]
635 #[diagnostic(transparent)]
636 InvalidRestrictedExpression(#[from] restricted_expr_errors::InvalidRestrictedExpressionError),
637}
638
639/// Error subtypes for [`RestrictedExpressionError`]
640pub mod restricted_expr_errors {
641 use super::Expr;
642 use crate::impl_diagnostic_from_method_on_field;
643 use miette::Diagnostic;
644 use smol_str::SmolStr;
645 use thiserror::Error;
646
647 /// An expression was expected to be a "restricted" expression, but contained
648 /// a feature that is not allowed in restricted expressions.
649 //
650 // CAUTION: this type is publicly exported in `cedar-policy`.
651 // Don't make fields `pub`, don't make breaking changes, and use caution
652 // when adding public methods.
653 #[derive(Debug, Clone, PartialEq, Eq, Error)]
654 #[error("not allowed to use {feature} in a restricted expression: `{expr}`")]
655 pub struct InvalidRestrictedExpressionError {
656 /// String description of what disallowed feature appeared in the expression
657 pub(crate) feature: SmolStr,
658 /// the (sub-)expression that uses the disallowed feature. This may be a
659 /// sub-expression of a larger expression.
660 pub(crate) expr: Expr,
661 }
662
663 // custom impl of `Diagnostic`: take source location from the `expr` field's `.source_loc()` method
664 impl Diagnostic for InvalidRestrictedExpressionError {
665 impl_diagnostic_from_method_on_field!(expr, source_loc);
666 }
667}
668
669/// Errors possible from `RestrictedExpr::from_str()`
670//
671// This is NOT a publicly exported error type.
672#[derive(Debug, Clone, PartialEq, Eq, Diagnostic, Error)]
673pub enum RestrictedExpressionParseError {
674 /// Failed to parse the expression
675 #[error(transparent)]
676 #[diagnostic(transparent)]
677 Parse(#[from] ParseErrors),
678 /// Parsed successfully as an expression, but failed to construct a
679 /// restricted expression, for the reason indicated in the underlying error
680 #[error(transparent)]
681 #[diagnostic(transparent)]
682 InvalidRestrictedExpression(#[from] RestrictedExpressionError),
683}
684
685#[cfg(test)]
686mod test {
687 use super::*;
688 use crate::ast::expression_construction_errors;
689 use crate::parser::err::{ParseError, ToASTError, ToASTErrorKind};
690 use crate::parser::Loc;
691 use std::str::FromStr;
692 use std::sync::Arc;
693
694 #[test]
695 fn duplicate_key() {
696 // duplicate key is an error when mapped to values of different types
697 assert_eq!(
698 RestrictedExpr::record([
699 ("foo".into(), RestrictedExpr::val(37),),
700 ("foo".into(), RestrictedExpr::val("hello"),),
701 ]),
702 Err(expression_construction_errors::DuplicateKeyError {
703 key: "foo".into(),
704 context: "in record literal",
705 }
706 .into())
707 );
708
709 // duplicate key is an error when mapped to different values of same type
710 assert_eq!(
711 RestrictedExpr::record([
712 ("foo".into(), RestrictedExpr::val(37),),
713 ("foo".into(), RestrictedExpr::val(101),),
714 ]),
715 Err(expression_construction_errors::DuplicateKeyError {
716 key: "foo".into(),
717 context: "in record literal",
718 }
719 .into())
720 );
721
722 // duplicate key is an error when mapped to the same value multiple times
723 assert_eq!(
724 RestrictedExpr::record([
725 ("foo".into(), RestrictedExpr::val(37),),
726 ("foo".into(), RestrictedExpr::val(37),),
727 ]),
728 Err(expression_construction_errors::DuplicateKeyError {
729 key: "foo".into(),
730 context: "in record literal",
731 }
732 .into())
733 );
734
735 // duplicate key is an error even when other keys appear in between
736 assert_eq!(
737 RestrictedExpr::record([
738 ("bar".into(), RestrictedExpr::val(-3),),
739 ("foo".into(), RestrictedExpr::val(37),),
740 ("spam".into(), RestrictedExpr::val("eggs"),),
741 ("foo".into(), RestrictedExpr::val(37),),
742 ("eggs".into(), RestrictedExpr::val("spam"),),
743 ]),
744 Err(expression_construction_errors::DuplicateKeyError {
745 key: "foo".into(),
746 context: "in record literal",
747 }
748 .into())
749 );
750
751 // duplicate key is also an error when parsing from string
752 let str = r#"{ foo: 37, bar: "hi", foo: 101 }"#;
753 assert_eq!(
754 RestrictedExpr::from_str(str),
755 Err(RestrictedExpressionParseError::Parse(
756 ParseErrors::singleton(ParseError::ToAST(ToASTError::new(
757 ToASTErrorKind::ExpressionConstructionError(
758 expression_construction_errors::DuplicateKeyError {
759 key: "foo".into(),
760 context: "in record literal",
761 }
762 .into()
763 ),
764 Loc::new(0..32, Arc::from(str))
765 )))
766 )),
767 )
768 }
769}