cedar_policy_core/evaluator/
err.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 crate::ast::*;
18use crate::extensions::ExtensionFunctionLookupError;
19use crate::parser::Loc;
20use miette::Diagnostic;
21use nonempty::{nonempty, NonEmpty};
22use smol_str::SmolStr;
23use std::sync::Arc;
24use thiserror::Error;
25
26// How many attrs or tags will we store in an error before cutting off for performance reason
27const TOO_MANY_ATTRS: usize = 5;
28
29/// Enumeration of the possible errors that can occur during evaluation
30//
31// CAUTION: this type is publicly exported in `cedar-policy`.
32// Don't make fields `pub`, don't make breaking changes, and use caution when
33// adding public methods.
34#[derive(Debug, PartialEq, Eq, Clone, Diagnostic, Error)]
35pub enum EvaluationError {
36    /// Tried to lookup an entity UID, but it didn't exist in the provided
37    /// entities
38    #[error(transparent)]
39    #[diagnostic(transparent)]
40    EntityDoesNotExist(#[from] evaluation_errors::EntityDoesNotExistError),
41
42    /// Tried to get an attribute or tag, but the specified entity didn't
43    /// have that attribute or tag
44    #[error(transparent)]
45    #[diagnostic(transparent)]
46    EntityAttrDoesNotExist(#[from] evaluation_errors::EntityAttrDoesNotExistError),
47
48    /// Tried to get an attribute of a (non-entity) record, but that record
49    /// didn't have that attribute
50    #[error(transparent)]
51    #[diagnostic(transparent)]
52    RecordAttrDoesNotExist(#[from] evaluation_errors::RecordAttrDoesNotExistError),
53
54    /// An error occurred when looking up an extension function
55    #[error(transparent)]
56    #[diagnostic(transparent)]
57    FailedExtensionFunctionLookup(#[from] ExtensionFunctionLookupError),
58
59    /// Tried to evaluate an operation on values with incorrect types for that
60    /// operation
61    #[error(transparent)]
62    #[diagnostic(transparent)]
63    TypeError(#[from] evaluation_errors::TypeError),
64
65    /// Wrong number of arguments provided to an extension function
66    #[error(transparent)]
67    #[diagnostic(transparent)]
68    WrongNumArguments(#[from] evaluation_errors::WrongNumArgumentsError),
69
70    /// Overflow during an integer operation
71    #[error(transparent)]
72    #[diagnostic(transparent)]
73    IntegerOverflow(#[from] evaluation_errors::IntegerOverflowError),
74
75    /// Not all template slots were linked
76    #[error(transparent)]
77    #[diagnostic(transparent)]
78    UnlinkedSlot(#[from] evaluation_errors::UnlinkedSlotError),
79
80    /// Evaluation error thrown by an extension function
81    #[error(transparent)]
82    #[diagnostic(transparent)]
83    FailedExtensionFunctionExecution(#[from] evaluation_errors::ExtensionFunctionExecutionError),
84
85    /// This error is raised if an expression contains unknowns and cannot be
86    /// reduced to a [`Value`]. In order to return partial results, use the
87    /// partial evaluation APIs instead.
88    #[error(transparent)]
89    #[diagnostic(transparent)]
90    NonValue(#[from] evaluation_errors::NonValueError),
91
92    /// Maximum recursion limit reached for expression evaluation
93    #[error(transparent)]
94    #[diagnostic(transparent)]
95    RecursionLimit(#[from] evaluation_errors::RecursionLimitError),
96}
97
98impl EvaluationError {
99    /// Extract the source location of the error, if one is attached
100    pub(crate) fn source_loc(&self) -> Option<&Loc> {
101        match self {
102            Self::EntityDoesNotExist(e) => e.source_loc.as_ref(),
103            Self::EntityAttrDoesNotExist(e) => e.source_loc.as_ref(),
104            Self::RecordAttrDoesNotExist(e) => e.source_loc.as_ref(),
105            Self::FailedExtensionFunctionLookup(e) => e.source_loc(),
106            Self::TypeError(e) => e.source_loc.as_ref(),
107            Self::WrongNumArguments(e) => e.source_loc.as_ref(),
108            Self::IntegerOverflow(e) => e.source_loc(),
109            Self::UnlinkedSlot(e) => e.source_loc.as_ref(),
110            Self::FailedExtensionFunctionExecution(e) => e.source_loc.as_ref(),
111            Self::NonValue(e) => e.source_loc.as_ref(),
112            Self::RecursionLimit(e) => e.source_loc.as_ref(),
113        }
114    }
115
116    /// Return the `EvaluationError`, but with the new `source_loc` (or `None`).
117    pub(crate) fn with_maybe_source_loc(self, source_loc: Option<Loc>) -> Self {
118        match self {
119            Self::EntityDoesNotExist(e) => {
120                Self::EntityDoesNotExist(evaluation_errors::EntityDoesNotExistError {
121                    source_loc,
122                    ..e
123                })
124            }
125            Self::EntityAttrDoesNotExist(e) => {
126                Self::EntityAttrDoesNotExist(evaluation_errors::EntityAttrDoesNotExistError {
127                    source_loc,
128                    ..e
129                })
130            }
131            Self::RecordAttrDoesNotExist(e) => {
132                Self::RecordAttrDoesNotExist(evaluation_errors::RecordAttrDoesNotExistError {
133                    source_loc,
134                    ..e
135                })
136            }
137            Self::FailedExtensionFunctionLookup(e) => {
138                Self::FailedExtensionFunctionLookup(e.with_maybe_source_loc(source_loc))
139            }
140            Self::TypeError(e) => Self::TypeError(evaluation_errors::TypeError { source_loc, ..e }),
141            Self::WrongNumArguments(e) => {
142                Self::WrongNumArguments(evaluation_errors::WrongNumArgumentsError {
143                    source_loc,
144                    ..e
145                })
146            }
147            Self::IntegerOverflow(e) => Self::IntegerOverflow(e.with_maybe_source_loc(source_loc)),
148            Self::UnlinkedSlot(e) => {
149                Self::UnlinkedSlot(evaluation_errors::UnlinkedSlotError { source_loc, ..e })
150            }
151            Self::FailedExtensionFunctionExecution(e) => Self::FailedExtensionFunctionExecution(
152                evaluation_errors::ExtensionFunctionExecutionError { source_loc, ..e },
153            ),
154            Self::NonValue(e) => {
155                Self::NonValue(evaluation_errors::NonValueError { source_loc, ..e })
156            }
157            Self::RecursionLimit(_) => {
158                Self::RecursionLimit(evaluation_errors::RecursionLimitError { source_loc })
159            }
160        }
161    }
162
163    /// Construct a [`EntityDoesNotExist`] error
164    pub(crate) fn entity_does_not_exist(uid: Arc<EntityUID>, source_loc: Option<Loc>) -> Self {
165        evaluation_errors::EntityDoesNotExistError { uid, source_loc }.into()
166    }
167
168    /// Construct a [`EntityAttrDoesNotExist`] error
169    ///
170    /// `does_attr_exist_as_a_tag`: does `attr` exist on `entity` as a tag (rather than an attribute)
171    pub(crate) fn entity_attr_does_not_exist<'a>(
172        entity: Arc<EntityUID>,
173        attr: SmolStr,
174        available_attrs: impl IntoIterator<Item = &'a SmolStr>,
175        does_attr_exist_as_a_tag: bool,
176        total_attrs: usize,
177        source_loc: Option<Loc>,
178    ) -> Self {
179        evaluation_errors::EntityAttrDoesNotExistError {
180            entity,
181            attr_or_tag: attr,
182            was_attr: true,
183            exists_the_other_kind: does_attr_exist_as_a_tag,
184            available_attrs_or_tags: available_attrs
185                .into_iter()
186                .take(TOO_MANY_ATTRS)
187                .cloned()
188                .collect::<Vec<_>>(),
189            total_attrs_or_tags: total_attrs,
190            source_loc,
191        }
192        .into()
193    }
194
195    /// Construct an error for the case where an entity tag does not exist
196    ///
197    /// `does_tag_exist_as_an_attr`: does `tag` exist on `entity` as an attribute (rather than a tag)
198    pub(crate) fn entity_tag_does_not_exist<'a>(
199        entity: Arc<EntityUID>,
200        tag: SmolStr,
201        available_tags: impl IntoIterator<Item = &'a SmolStr>,
202        does_tag_exist_as_an_attr: bool,
203        total_tags: usize,
204        source_loc: Option<Loc>,
205    ) -> Self {
206        evaluation_errors::EntityAttrDoesNotExistError {
207            entity,
208            attr_or_tag: tag,
209            was_attr: false,
210            exists_the_other_kind: does_tag_exist_as_an_attr,
211            available_attrs_or_tags: available_tags
212                .into_iter()
213                .take(TOO_MANY_ATTRS)
214                .cloned()
215                .collect::<Vec<_>>(),
216            total_attrs_or_tags: total_tags,
217            source_loc,
218        }
219        .into()
220    }
221
222    /// Construct a [`RecordAttrDoesNotExist`] error
223    pub(crate) fn record_attr_does_not_exist<'a>(
224        attr: SmolStr,
225        available_attrs: impl IntoIterator<Item = &'a SmolStr>,
226        total_attrs: usize,
227        source_loc: Option<Loc>,
228    ) -> Self {
229        evaluation_errors::RecordAttrDoesNotExistError {
230            attr,
231            available_attrs: available_attrs
232                .into_iter()
233                .take(TOO_MANY_ATTRS)
234                .cloned()
235                .collect(),
236            total_attrs,
237            source_loc,
238        }
239        .into()
240    }
241
242    /// Construct a [`TypeError`] error
243    pub(crate) fn type_error(expected: NonEmpty<Type>, actual: &Value) -> Self {
244        evaluation_errors::TypeError {
245            expected,
246            actual: actual.type_of(),
247            advice: None,
248            source_loc: actual.source_loc().cloned(),
249        }
250        .into()
251    }
252
253    pub(crate) fn type_error_single(expected: Type, actual: &Value) -> Self {
254        Self::type_error(nonempty![expected], actual)
255    }
256
257    /// Construct a [`TypeError`] error with the advice field set
258    pub(crate) fn type_error_with_advice(
259        expected: NonEmpty<Type>,
260        actual: &Value,
261        advice: String,
262    ) -> Self {
263        evaluation_errors::TypeError {
264            expected,
265            actual: actual.type_of(),
266            advice: Some(advice),
267            source_loc: actual.source_loc().cloned(),
268        }
269        .into()
270    }
271
272    pub(crate) fn type_error_with_advice_single(
273        expected: Type,
274        actual: &Value,
275        advice: String,
276    ) -> Self {
277        Self::type_error_with_advice(nonempty![expected], actual, advice)
278    }
279
280    /// Construct a [`WrongNumArguments`] error
281    pub(crate) fn wrong_num_arguments(
282        function_name: Name,
283        expected: usize,
284        actual: usize,
285        source_loc: Option<Loc>,
286    ) -> Self {
287        evaluation_errors::WrongNumArgumentsError {
288            function_name,
289            expected,
290            actual,
291            source_loc,
292        }
293        .into()
294    }
295
296    /// Construct a [`UnlinkedSlot`] error
297    pub(crate) fn unlinked_slot(slot: SlotId, source_loc: Option<Loc>) -> Self {
298        evaluation_errors::UnlinkedSlotError { slot, source_loc }.into()
299    }
300
301    /// Construct a [`FailedExtensionFunctionApplication`] error
302    pub(crate) fn failed_extension_function_application(
303        extension_name: Name,
304        msg: String,
305        source_loc: Option<Loc>,
306        advice: Option<String>,
307    ) -> Self {
308        evaluation_errors::ExtensionFunctionExecutionError {
309            extension_name,
310            msg,
311            source_loc,
312            advice,
313        }
314        .into()
315    }
316
317    /// Construct a [`NonValue`] error
318    pub(crate) fn non_value(expr: Expr) -> Self {
319        let source_loc = expr.source_loc().cloned();
320        evaluation_errors::NonValueError { expr, source_loc }.into()
321    }
322
323    /// Construct a [`RecursionLimit`] error
324    pub(crate) fn recursion_limit(source_loc: Option<Loc>) -> Self {
325        evaluation_errors::RecursionLimitError { source_loc }.into()
326    }
327}
328
329/// Error subtypes for [`EvaluationError`]
330pub mod evaluation_errors {
331    use crate::ast::{BinaryOp, EntityUID, Expr, SlotId, Type, UnaryOp, Value};
332    use crate::parser::Loc;
333    use itertools::Itertools;
334    use miette::Diagnostic;
335    use nonempty::NonEmpty;
336    use smol_str::SmolStr;
337    use std::sync::Arc;
338    use thiserror::Error;
339
340    use super::Name;
341
342    /// Tried to lookup an entity UID, but it didn't exist in the provided entities
343    //
344    // CAUTION: this type is publicly exported in `cedar-policy`.
345    // Don't make fields `pub`, don't make breaking changes, and use caution
346    // when adding public methods.
347    #[derive(Debug, PartialEq, Eq, Clone, Error)]
348    #[error("entity `{uid}` does not exist")]
349    pub struct EntityDoesNotExistError {
350        /// Entity UID which didn't exist in the provided entities
351        pub(crate) uid: Arc<EntityUID>,
352        /// Source location
353        pub(crate) source_loc: Option<Loc>,
354    }
355
356    // This and similar `Diagnostic` impls could just be derived with
357    //
358    // #[source_code]
359    // #[label]
360    // source_loc: Option<Loc>,
361    //
362    // if [miette#377](https://github.com/zkat/miette/issues/377) gets fixed.
363    // Or, we could have separate fields for source code and label instead of
364    // combining them into `Loc`, which would work around the issue.
365    impl Diagnostic for EntityDoesNotExistError {
366        impl_diagnostic_from_source_loc_opt_field!(source_loc);
367    }
368
369    /// Tried to get an attribute, but the specified entity didn't have that
370    /// attribute
371    //
372    // CAUTION: this type is publicly exported in `cedar-policy`.
373    // Don't make fields `pub`, don't make breaking changes, and use caution
374    // when adding public methods.
375    #[derive(Debug, PartialEq, Eq, Clone, Error)]
376    #[error("`{entity}` does not have the {} `{attr_or_tag}`", if *.was_attr { "attribute" } else { "tag" })]
377    pub struct EntityAttrDoesNotExistError {
378        /// Entity that didn't have the attribute
379        pub(crate) entity: Arc<EntityUID>,
380        /// Name of the attribute or tag it didn't have
381        pub(crate) attr_or_tag: SmolStr,
382        /// Whether this was an attempted attribute access (`true`) or tag access (`false`)
383        pub(crate) was_attr: bool,
384        /// If `true`, this is a case where we tried accessing an attribute but
385        /// there's a tag of that name, or vice versa
386        pub(crate) exists_the_other_kind: bool,
387        /// (First five) Available attributes/tags on the entity, depending on `was_attr`
388        pub(crate) available_attrs_or_tags: Vec<SmolStr>,
389        /// Total number of attributes/tags on the entity, depending on `was_attr`
390        pub(crate) total_attrs_or_tags: usize,
391        /// Source location
392        pub(crate) source_loc: Option<Loc>,
393    }
394
395    impl Diagnostic for EntityAttrDoesNotExistError {
396        impl_diagnostic_from_source_loc_opt_field!(source_loc);
397
398        fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
399            let mut help_text = if self.available_attrs_or_tags.is_empty() {
400                format!(
401                    "`{}` does not have any {}",
402                    &self.entity,
403                    if self.was_attr { "attributes" } else { "tags" }
404                )
405            } else if self.available_attrs_or_tags.len() == self.total_attrs_or_tags {
406                format!(
407                    "available {}: [{}]",
408                    if self.was_attr { "attributes" } else { "tags" },
409                    self.available_attrs_or_tags.iter().join(",")
410                )
411            } else {
412                format!(
413                    "available {}: [{}, ... ({} more attributes) ]",
414                    if self.was_attr { "attributes" } else { "tags" },
415                    self.available_attrs_or_tags.iter().join(","),
416                    self.total_attrs_or_tags - self.available_attrs_or_tags.len()
417                )
418            };
419            if self.exists_the_other_kind {
420                help_text.push_str(&format!(
421                    "; note that {} (not {}) named `{}` does exist",
422                    if self.was_attr {
423                        "a tag"
424                    } else {
425                        "an attribute"
426                    },
427                    if self.was_attr {
428                        "an attribute"
429                    } else {
430                        "a tag"
431                    },
432                    self.attr_or_tag,
433                ));
434            }
435            Some(Box::new(help_text))
436        }
437    }
438
439    /// Tried to get an attribute of a (non-entity) record, but that record didn't
440    /// have that attribute
441    //
442    // CAUTION: this type is publicly exported in `cedar-policy`.
443    // Don't make fields `pub`, don't make breaking changes, and use caution
444    // when adding public methods.
445    #[derive(Debug, PartialEq, Eq, Clone, Error)]
446    #[error("record does not have the attribute `{attr}`")]
447    pub struct RecordAttrDoesNotExistError {
448        /// Name of the attribute we tried to access
449        pub(crate) attr: SmolStr,
450        /// (First five) Available attributes on the record
451        pub(crate) available_attrs: Vec<SmolStr>,
452        /// The total number of attrs this record has
453        pub(crate) total_attrs: usize,
454        /// Source location
455        pub(crate) source_loc: Option<Loc>,
456    }
457
458    impl Diagnostic for RecordAttrDoesNotExistError {
459        impl_diagnostic_from_source_loc_opt_field!(source_loc);
460
461        fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
462            if self.available_attrs.is_empty() {
463                Some(Box::new("record does not have any attributes"))
464            } else if self.available_attrs.len() == self.total_attrs {
465                Some(Box::new(format!(
466                    "available attributes: {:?}",
467                    self.available_attrs
468                )))
469            } else {
470                Some(Box::new(format!(
471                    "available attributes: [{}, ... ({} more attributes) ]",
472                    self.available_attrs.iter().join(","),
473                    self.total_attrs - self.available_attrs.len()
474                )))
475            }
476        }
477    }
478
479    /// Tried to evaluate an operation on values with incorrect types for that
480    /// operation
481    //
482    // CAUTION: this type is publicly exported in `cedar-policy`.
483    // Don't make fields `pub`, don't make breaking changes, and use caution
484    // when adding public methods.
485    #[derive(Debug, PartialEq, Eq, Clone, Error)]
486    pub struct TypeError {
487        /// Expected one of these types
488        pub(crate) expected: NonEmpty<Type>,
489        /// Encountered this type instead
490        pub(crate) actual: Type,
491        /// Optional advice for how to fix this error
492        pub(crate) advice: Option<String>,
493        /// Source location
494        pub(crate) source_loc: Option<Loc>,
495    }
496
497    impl Diagnostic for TypeError {
498        impl_diagnostic_from_source_loc_opt_field!(source_loc);
499
500        fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
501            self.advice.as_ref().map(|advice| Box::new(advice) as _)
502        }
503    }
504
505    impl std::fmt::Display for TypeError {
506        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
507            if self.expected.len() == 1 {
508                write!(
509                    f,
510                    "type error: expected {}, got {}",
511                    self.expected.first(),
512                    self.actual
513                )
514            } else {
515                write!(
516                    f,
517                    "type error: expected one of [{}], got {}",
518                    self.expected.iter().join(", "),
519                    self.actual
520                )
521            }
522        }
523    }
524
525    /// Wrong number of arguments provided to an extension function
526    //
527    // CAUTION: this type is publicly exported in `cedar-policy`.
528    // Don't make fields `pub`, don't make breaking changes, and use caution
529    // when adding public methods.
530    #[derive(Debug, PartialEq, Eq, Clone, Error)]
531    #[error("wrong number of arguments provided to extension function `{function_name}`: expected {expected}, got {actual}")]
532    pub struct WrongNumArgumentsError {
533        /// arguments to this function
534        pub(crate) function_name: Name,
535        /// expected number of arguments
536        pub(crate) expected: usize,
537        /// actual number of arguments
538        pub(crate) actual: usize,
539        /// Source location
540        pub(crate) source_loc: Option<Loc>,
541    }
542
543    impl Diagnostic for WrongNumArgumentsError {
544        impl_diagnostic_from_source_loc_opt_field!(source_loc);
545    }
546
547    /// Overflow during an integer operation
548    //
549    // CAUTION: this type is publicly exported in `cedar-policy`.
550    // Don't make fields `pub`, don't make breaking changes, and use caution
551    // when adding public methods.
552    #[derive(Debug, PartialEq, Eq, Clone, Diagnostic, Error)]
553    pub enum IntegerOverflowError {
554        /// Overflow during a binary operation
555        #[error(transparent)]
556        #[diagnostic(transparent)]
557        BinaryOp(#[from] BinaryOpOverflowError),
558
559        /// Overflow during a unary operation
560        #[error(transparent)]
561        #[diagnostic(transparent)]
562        UnaryOp(#[from] UnaryOpOverflowError),
563    }
564
565    impl IntegerOverflowError {
566        pub(crate) fn source_loc(&self) -> Option<&Loc> {
567            match self {
568                Self::BinaryOp(e) => e.source_loc.as_ref(),
569                Self::UnaryOp(e) => e.source_loc.as_ref(),
570            }
571        }
572
573        pub(crate) fn with_maybe_source_loc(self, source_loc: Option<Loc>) -> Self {
574            match self {
575                Self::BinaryOp(e) => Self::BinaryOp(BinaryOpOverflowError { source_loc, ..e }),
576                Self::UnaryOp(e) => Self::UnaryOp(UnaryOpOverflowError { source_loc, ..e }),
577            }
578        }
579    }
580
581    /// Overflow during a binary operation
582    //
583    // CAUTION: this type is publicly exported in `cedar-policy`.
584    // Don't make fields `pub`, don't make breaking changes, and use caution
585    // when adding public methods.
586    #[derive(Debug, PartialEq, Eq, Clone, Error)]
587    #[error("integer overflow while attempting to {} the values `{arg1}` and `{arg2}`", match .op { BinaryOp::Add => "add", BinaryOp::Sub => "subtract", BinaryOp::Mul => "multiply", _ => "perform an operation on" })]
588    pub struct BinaryOpOverflowError {
589        /// overflow while evaluating this operator
590        pub(crate) op: BinaryOp,
591        /// first argument to that operator
592        pub(crate) arg1: Value,
593        /// second argument to that operator
594        pub(crate) arg2: Value,
595        /// Source location
596        pub(crate) source_loc: Option<Loc>,
597    }
598
599    impl Diagnostic for BinaryOpOverflowError {
600        impl_diagnostic_from_source_loc_opt_field!(source_loc);
601    }
602
603    /// Overflow during a unary operation
604    //
605    // CAUTION: this type is publicly exported in `cedar-policy`.
606    // Don't make fields `pub`, don't make breaking changes, and use caution
607    // when adding public methods.
608    #[derive(Debug, PartialEq, Eq, Clone, Error)]
609    #[error("integer overflow while attempting to {} the value `{arg}`", match .op { UnaryOp::Neg => "negate", _ => "perform an operation on" })]
610    pub struct UnaryOpOverflowError {
611        /// overflow while evaluating this operator
612        pub(crate) op: UnaryOp,
613        /// argument to that operator
614        pub(crate) arg: Value,
615        /// Source location
616        pub(crate) source_loc: Option<Loc>,
617    }
618
619    impl Diagnostic for UnaryOpOverflowError {
620        impl_diagnostic_from_source_loc_opt_field!(source_loc);
621    }
622
623    /// Not all template slots were linked
624    //
625    // CAUTION: this type is publicly exported in `cedar-policy`.
626    // Don't make fields `pub`, don't make breaking changes, and use caution
627    // when adding public methods.
628    #[derive(Debug, PartialEq, Eq, Clone, Error)]
629    #[error("template slot `{slot}` was not linked")]
630    pub struct UnlinkedSlotError {
631        /// Slot which was not linked
632        pub(crate) slot: SlotId,
633        /// Source location
634        pub(crate) source_loc: Option<Loc>,
635    }
636
637    impl Diagnostic for UnlinkedSlotError {
638        impl_diagnostic_from_source_loc_opt_field!(source_loc);
639    }
640
641    /// Evaluation error thrown by an extension function
642    //
643    // CAUTION: this type is publicly exported in `cedar-policy`.
644    // Don't make fields `pub`, don't make breaking changes, and use caution
645    // when adding public methods.
646    #[derive(Debug, PartialEq, Eq, Clone, Error)]
647    #[error("error while evaluating `{extension_name}` extension function: {msg}")]
648    pub struct ExtensionFunctionExecutionError {
649        /// Name of the extension throwing the error
650        pub(crate) extension_name: Name,
651        /// Error message from the extension
652        pub(crate) msg: String,
653        /// Optional advice for how to fix this error
654        pub(crate) advice: Option<String>,
655        /// Source location
656        pub(crate) source_loc: Option<Loc>,
657    }
658
659    impl Diagnostic for ExtensionFunctionExecutionError {
660        impl_diagnostic_from_source_loc_opt_field!(source_loc);
661
662        fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
663            self.advice.as_ref().map(|v| Box::new(v) as _)
664        }
665    }
666
667    impl ExtensionFunctionExecutionError {
668        /// Get the name of the extension that threw this error
669        pub fn extension_name(&self) -> String {
670            self.extension_name.to_string()
671        }
672    }
673
674    /// This error is raised if an expression contains unknowns and cannot be
675    /// reduced to a [`Value`]. In order to return partial results, use the
676    /// partial evaluation APIs instead.
677    //
678    // CAUTION: this type is publicly exported in `cedar-policy`.
679    // Don't make fields `pub`, don't make breaking changes, and use caution
680    // when adding public methods.
681    #[derive(Debug, PartialEq, Eq, Clone, Error)]
682    #[error("the expression contains unknown(s): `{expr}`")]
683    pub struct NonValueError {
684        /// Expression that contained unknown(s)
685        pub(crate) expr: Expr,
686        /// Source location
687        pub(crate) source_loc: Option<Loc>,
688    }
689
690    impl Diagnostic for NonValueError {
691        impl_diagnostic_from_source_loc_opt_field!(source_loc);
692
693        fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
694            Some(Box::new("consider using the partial evaluation APIs"))
695        }
696    }
697
698    /// Maximum recursion limit reached for expression evaluation
699    //
700    // CAUTION: this type is publicly exported in `cedar-policy`.
701    // Don't make fields `pub`, don't make breaking changes, and use caution
702    // when adding public methods.
703    #[derive(Debug, PartialEq, Eq, Clone, Error)]
704    #[error("recursion limit reached")]
705    pub struct RecursionLimitError {
706        /// Source location
707        pub(crate) source_loc: Option<Loc>,
708    }
709
710    impl Diagnostic for RecursionLimitError {
711        impl_diagnostic_from_source_loc_opt_field!(source_loc);
712    }
713}
714
715/// Type alias for convenience
716pub type Result<T> = std::result::Result<T, EvaluationError>;