cedar_policy_validator/
validation_result.rs

1/*
2 * Copyright 2022-2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 cedar_policy_core::{ast::PolicyID, parser::SourceInfo};
18use thiserror::Error;
19
20use crate::{TypeErrorKind, ValidationWarning};
21
22/// Contains the result of policy validation. The result includes the list of
23/// issues found by validation and whether validation succeeds or fails.
24/// Validation succeeds if there are no fatal errors. There may still be
25/// non-fatal warnings present when validation passes.
26#[derive(Debug)]
27pub struct ValidationResult<'a> {
28    validation_errors: Vec<ValidationError<'a>>,
29    validation_warnings: Vec<ValidationWarning<'a>>,
30}
31
32impl<'a> ValidationResult<'a> {
33    pub fn new(
34        errors: impl IntoIterator<Item = ValidationError<'a>>,
35        warnings: impl IntoIterator<Item = ValidationWarning<'a>>,
36    ) -> Self {
37        Self {
38            validation_errors: errors.into_iter().collect(),
39            validation_warnings: warnings.into_iter().collect(),
40        }
41    }
42
43    /// True when validation passes. There are no errors, but there may be
44    /// non-fatal warnings.
45    pub fn validation_passed(&self) -> bool {
46        self.validation_errors.is_empty()
47    }
48
49    /// Get an iterator over the errors found by the validator.
50    pub fn validation_errors(&self) -> impl Iterator<Item = &ValidationError> {
51        self.validation_errors.iter()
52    }
53
54    /// Get an iterator over the warnings found by the validator.
55    pub fn validation_warnings(&self) -> impl Iterator<Item = &ValidationWarning> {
56        self.validation_warnings.iter()
57    }
58
59    /// Get an iterator over the errors and warnings found by the validator.
60    pub fn into_errors_and_warnings(
61        self,
62    ) -> (
63        impl Iterator<Item = ValidationError<'a>>,
64        impl Iterator<Item = ValidationWarning<'a>>,
65    ) {
66        (
67            self.validation_errors.into_iter(),
68            self.validation_warnings.into_iter(),
69        )
70    }
71}
72
73/// An error generated by the validator when it finds a potential problem in a
74/// policy. The error contains a enumeration that specifies the kind of problem,
75/// and provides details specific to that kind of problem. The error also records
76/// where the problem was encountered.
77#[derive(Debug)]
78#[cfg_attr(test, derive(Eq, PartialEq))]
79pub struct ValidationError<'a> {
80    location: SourceLocation<'a>,
81    error_kind: ValidationErrorKind,
82}
83
84impl<'a> ValidationError<'a> {
85    pub(crate) fn with_policy_id(
86        id: &'a PolicyID,
87        source_info: Option<SourceInfo>,
88        error_kind: ValidationErrorKind,
89    ) -> Self {
90        Self {
91            error_kind,
92            location: SourceLocation::new(id, source_info),
93        }
94    }
95
96    /// Deconstruct this into its component source location and error kind.
97    pub fn into_location_and_error_kind(self) -> (SourceLocation<'a>, ValidationErrorKind) {
98        (self.location, self.error_kind)
99    }
100
101    /// Extract details about the exact issue detected by the validator.
102    pub fn error_kind(&self) -> &ValidationErrorKind {
103        &self.error_kind
104    }
105
106    /// Extract the location where the validator found the issue.
107    pub fn location(&self) -> &SourceLocation {
108        &self.location
109    }
110}
111
112/// Represents a location in Cedar policy source.
113#[derive(Debug, Clone, Eq, PartialEq)]
114pub struct SourceLocation<'a> {
115    policy_id: &'a PolicyID,
116    source_info: Option<SourceInfo>,
117}
118
119impl<'a> SourceLocation<'a> {
120    pub(crate) fn new(policy_id: &'a PolicyID, source_info: Option<SourceInfo>) -> Self {
121        Self {
122            policy_id,
123            source_info,
124        }
125    }
126
127    /// Get the `PolicyId` for the policy at this source location.
128    pub fn policy_id(&self) -> &'a PolicyID {
129        self.policy_id
130    }
131
132    pub fn source_info(&self) -> &Option<SourceInfo> {
133        &self.source_info
134    }
135
136    pub fn into_source_info(self) -> Option<SourceInfo> {
137        self.source_info
138    }
139}
140
141/// Enumeration of the possible diagnostic error that could be found by the
142/// verification steps.
143#[derive(Debug, Error)]
144#[cfg_attr(test, derive(Eq, PartialEq))]
145#[non_exhaustive]
146pub enum ValidationErrorKind {
147    /// A policy contains an entity type that is not declared in the schema.
148    #[error(
149        "unrecognized entity type `{}`{}",
150        .0.actual_entity_type,
151        match &.0.suggested_entity_type {
152            Some(s) => format!(", did you mean `{}`?", s),
153            None => "".to_string()
154        }
155    )]
156    UnrecognizedEntityType(UnrecognizedEntityType),
157    /// A policy contains an action that is not declared in the schema.
158    #[error(
159        "unrecognized action `{}`{}",
160        .0.actual_action_id,
161        match &.0.suggested_action_id {
162            Some(s) => format!(", did you mean `{}`?", s),
163            None => "".to_string()
164        }
165    )]
166    UnrecognizedActionId(UnrecognizedActionId),
167    /// There is no action satisfying the action head constraint that can be
168    /// applied to a principal and resources that both satisfy their respective
169    /// head conditions.
170    #[error(
171        "unable to find an applicable action given the policy head constraints{}{}",
172        if .0.would_in_fix_principal { ". Note: Try replacing `==` with `in` in the principal clause" } else { "" },
173        if .0.would_in_fix_resource { ". Note: Try replacing `==` with `in` in the resource clause" } else { "" }
174    )]
175    InvalidActionApplication(InvalidActionApplication),
176    /// The type checker found an error.
177    #[error(transparent)]
178    TypeError(TypeErrorKind),
179    /// An unspecified entity was used in a policy. This should be impossible,
180    /// assuming that the policy was constructed by the parser.
181    #[error(
182        "unspecified entity with eid `{}`. Unspecified entities cannot be used in policies",
183        .0.entity_id,
184    )]
185    UnspecifiedEntity(UnspecifiedEntity),
186}
187
188impl ValidationErrorKind {
189    pub(crate) fn unrecognized_entity_type(
190        actual_entity_type: String,
191        suggested_entity_type: Option<String>,
192    ) -> ValidationErrorKind {
193        Self::UnrecognizedEntityType(UnrecognizedEntityType {
194            actual_entity_type,
195            suggested_entity_type,
196        })
197    }
198
199    pub(crate) fn unrecognized_action_id(
200        actual_action_id: String,
201        suggested_action_id: Option<String>,
202    ) -> ValidationErrorKind {
203        Self::UnrecognizedActionId(UnrecognizedActionId {
204            actual_action_id,
205            suggested_action_id,
206        })
207    }
208
209    pub(crate) fn invalid_action_application(
210        would_in_fix_principal: bool,
211        would_in_fix_resource: bool,
212    ) -> ValidationErrorKind {
213        Self::InvalidActionApplication(InvalidActionApplication {
214            would_in_fix_principal,
215            would_in_fix_resource,
216        })
217    }
218
219    pub(crate) fn type_error(type_error: TypeErrorKind) -> ValidationErrorKind {
220        Self::TypeError(type_error)
221    }
222
223    pub(crate) fn unspecified_entity(entity_id: String) -> ValidationErrorKind {
224        Self::UnspecifiedEntity(UnspecifiedEntity { entity_id })
225    }
226}
227
228/// Structure containing details about an unrecognized entity type error.
229#[derive(Debug)]
230#[cfg_attr(test, derive(Eq, PartialEq))]
231pub struct UnrecognizedEntityType {
232    /// The entity type seen in the policy.
233    pub(crate) actual_entity_type: String,
234    /// An entity type from the schema that the user might reasonably have
235    /// intended to write.
236    pub(crate) suggested_entity_type: Option<String>,
237}
238
239/// Structure containing details about an unrecognized action id error.
240#[derive(Debug)]
241#[cfg_attr(test, derive(Eq, PartialEq))]
242pub struct UnrecognizedActionId {
243    /// Action Id seen in the policy.
244    pub(crate) actual_action_id: String,
245    /// An action id from the schema that the user might reasonably have
246    /// intended to write.
247    pub(crate) suggested_action_id: Option<String>,
248}
249
250/// Structure containing details about an invalid action application error.
251#[derive(Debug)]
252#[cfg_attr(test, derive(Eq, PartialEq))]
253pub struct InvalidActionApplication {
254    pub(crate) would_in_fix_principal: bool,
255    pub(crate) would_in_fix_resource: bool,
256}
257
258/// Structure containing details about an unspecified entity error.
259#[derive(Debug)]
260#[cfg_attr(test, derive(Eq, PartialEq))]
261pub struct UnspecifiedEntity {
262    /// EID of the unspecified entity.
263    pub(crate) entity_id: String,
264}