cedar_policy_validator/
err.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 std::collections::HashSet;
18
19use cedar_policy_core::{
20    ast::{EntityAttrEvaluationError, EntityUID, Name},
21    parser::err::{ParseError, ParseErrors},
22    transitive_closure,
23};
24use itertools::Itertools;
25use thiserror::Error;
26
27#[derive(Debug, Error)]
28pub enum SchemaError {
29    /// Error thrown by the `serde_json` crate during deserialization
30    #[error("failed to parse schema: {0}")]
31    Serde(#[from] serde_json::Error),
32    /// Errors occurring while computing or enforcing transitive closure on
33    /// action hierarchy.
34    #[error("transitive closure computation/enforcement error on action hierarchy: {0}")]
35    ActionTransitiveClosure(Box<transitive_closure::TcError<EntityUID>>),
36    /// Errors occurring while computing or enforcing transitive closure on
37    /// entity type hierarchy.
38    #[error("transitive closure computation/enforcement error on entity type hierarchy: {0}")]
39    EntityTypeTransitiveClosure(#[from] transitive_closure::TcError<Name>),
40    /// Error generated when processing a schema file that uses unsupported features
41    #[error("unsupported feature used in schema: {0}")]
42    UnsupportedFeature(UnsupportedFeature),
43    /// Undeclared entity type(s) used in the `memberOf` field of an entity
44    /// type, the `appliesTo` fields of an action, or an attribute type in a
45    /// context or entity attribute record. Entity types in the error message
46    /// are fully qualified, including any implicit or explicit namespaces.
47    #[error("undeclared entity type(s): {0:?}")]
48    UndeclaredEntityTypes(HashSet<String>),
49    /// Undeclared action(s) used in the `memberOf` field of an action.
50    #[error("undeclared action(s): {0:?}")]
51    UndeclaredActions(HashSet<String>),
52    /// Undeclared common type(s) used in entity or context attributes.
53    #[error("undeclared common type(s): {0:?}")]
54    UndeclaredCommonTypes(HashSet<String>),
55    /// Duplicate specifications for an entity type. Argument is the name of
56    /// the duplicate entity type.
57    #[error("duplicate entity type `{0}`")]
58    DuplicateEntityType(String),
59    /// Duplicate specifications for an action. Argument is the name of the
60    /// duplicate action.
61    #[error("duplicate action `{0}`")]
62    DuplicateAction(String),
63    /// Duplicate specification for a reusable type declaration.
64    #[error("duplicate common type `{0}`")]
65    DuplicateCommonType(String),
66    /// Cycle in the schema's action hierarchy.
67    #[error("cycle in action hierarchy containing `{0}`")]
68    CycleInActionHierarchy(EntityUID),
69    /// Parse errors occurring while parsing an entity type.
70    #[error("parse error in entity type: {}", Self::format_parse_errs(.0))]
71    ParseEntityType(ParseErrors),
72    /// Parse errors occurring while parsing a namespace identifier.
73    #[error("parse error in namespace identifier: {}", Self::format_parse_errs(.0))]
74    ParseNamespace(ParseErrors),
75    /// Parse errors occurring while parsing an extension type.
76    #[error("parse error in extension type: {}", Self::format_parse_errs(.0))]
77    ParseExtensionType(ParseErrors),
78    /// Parse errors occurring while parsing the name of one of reusable
79    /// declared types.
80    #[error("parse error in common type identifier: {}", Self::format_parse_errs(.0))]
81    ParseCommonType(ParseErrors),
82    /// The schema file included an entity type `Action` in the entity type
83    /// list. The `Action` entity type is always implicitly declared, and it
84    /// cannot currently have attributes or be in any groups, so there is no
85    /// purposes in adding an explicit entry.
86    #[error("entity type `Action` declared in `entityTypes` list")]
87    ActionEntityTypeDeclared,
88    /// `context` or `shape` fields are not records
89    #[error("{0} is declared with a type other than `Record`")]
90    ContextOrShapeNotRecord(ContextOrShape),
91    /// An action entity (transitively) has an attribute that is an empty set.
92    /// The validator cannot assign a type to an empty set.
93    /// This error variant should only be used when `PermitAttributes` is enabled.
94    #[error("action `{0}` has an attribute that is an empty set")]
95    ActionAttributesContainEmptySet(EntityUID),
96    /// An action entity (transitively) has an attribute of unsupported type (`ExprEscape`, `EntityEscape` or `ExtnEscape`).
97    /// This error variant should only be used when `PermitAttributes` is enabled.
98    #[error("action `{0}` has an attribute with unsupported JSON representation: {1}")]
99    UnsupportedActionAttribute(EntityUID, String),
100    /// Error when evaluating an action attribute
101    #[error(transparent)]
102    ActionAttrEval(EntityAttrEvaluationError),
103    /// Error thrown when the schema contains the `__expr` escape.
104    /// Support for this escape form has been dropped.
105    #[error("uses the `__expr` escape, which is no longer supported")]
106    ExprEscapeUsed,
107}
108
109impl From<transitive_closure::TcError<EntityUID>> for SchemaError {
110    fn from(e: transitive_closure::TcError<EntityUID>) -> Self {
111        // we use code in transitive_closure to check for cycles in the action
112        // hierarchy, but in case of an error we want to report the more descriptive
113        // CycleInActionHierarchy instead of ActionTransitiveClosureError
114        match e {
115            transitive_closure::TcError::MissingTcEdge { .. } => {
116                SchemaError::ActionTransitiveClosure(Box::new(e))
117            }
118            transitive_closure::TcError::HasCycle { vertex_with_loop } => {
119                SchemaError::CycleInActionHierarchy(vertex_with_loop)
120            }
121        }
122    }
123}
124
125pub type Result<T> = std::result::Result<T, SchemaError>;
126
127impl SchemaError {
128    fn format_parse_errs(errs: &[ParseError]) -> String {
129        errs.iter().map(|e| e.to_string()).join(", ")
130    }
131}
132
133#[derive(Debug)]
134pub enum ContextOrShape {
135    ActionContext(EntityUID),
136    EntityTypeShape(Name),
137}
138
139impl std::fmt::Display for ContextOrShape {
140    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141        match self {
142            ContextOrShape::ActionContext(action) => write!(f, "Context for action {}", action),
143            ContextOrShape::EntityTypeShape(entity_type) => {
144                write!(f, "Shape for entity type {}", entity_type)
145            }
146        }
147    }
148}
149
150#[derive(Debug)]
151pub enum UnsupportedFeature {
152    OpenRecordsAndEntities,
153    // Action attributes are allowed if `ActionBehavior` is `PermitAttributes`
154    ActionAttributes(Vec<String>),
155}
156
157impl std::fmt::Display for UnsupportedFeature {
158    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159        match self {
160            Self::OpenRecordsAndEntities => write!(
161                f,
162                "records and entities with `additionalAttributes` are experimental, but the experimental `partial-validate` feature is not enabled"
163            ),
164            Self::ActionAttributes(attrs) => write!(
165                f,
166                "action declared with attributes: [{}]",
167                attrs.iter().join(", ")
168            ),
169        }
170    }
171}