cedar_policy_validator/schema/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 cedar_policy_core::{
18 ast::{EntityUID, ReservedNameError},
19 transitive_closure,
20};
21use itertools::{Either, Itertools};
22use miette::Diagnostic;
23use nonempty::NonEmpty;
24use thiserror::Error;
25
26use crate::cedar_schema;
27
28/// Error creating a schema from the Cedar syntax
29#[derive(Debug, Error, Diagnostic)]
30pub enum CedarSchemaError {
31 /// Errors with the schema content
32 #[error(transparent)]
33 #[diagnostic(transparent)]
34 Schema(#[from] SchemaError),
35 /// IO error
36 #[error(transparent)]
37 IO(#[from] std::io::Error),
38 /// Parse error
39 #[error(transparent)]
40 #[diagnostic(transparent)]
41 Parsing(#[from] CedarSchemaParseError),
42}
43
44/// Error parsing a Cedar-syntax schema
45// WARNING: this type is publicly exported from `cedar-policy`
46#[derive(Debug, Error)]
47#[error("error parsing schema: {errs}")]
48pub struct CedarSchemaParseError {
49 /// Underlying parse error(s)
50 errs: cedar_schema::parser::CedarSchemaParseErrors,
51 /// Did the schema look like it was intended to be JSON format instead of
52 /// Cedar?
53 suspect_json_format: bool,
54}
55
56impl Diagnostic for CedarSchemaParseError {
57 fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
58 let suspect_json_help = if self.suspect_json_format {
59 Some(Box::new("this API was expecting a schema in the Cedar schema format; did you mean to use a different function, which expects a JSON-format Cedar schema"))
60 } else {
61 None
62 };
63 match (suspect_json_help, self.errs.help()) {
64 (Some(json), Some(inner)) => Some(Box::new(format!("{inner}\n{json}"))),
65 (Some(h), None) => Some(h),
66 (None, Some(h)) => Some(h),
67 (None, None) => None,
68 }
69 }
70
71 // Everything else is forwarded to `errs`
72
73 fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
74 self.errs.code()
75 }
76 fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
77 self.errs.labels()
78 }
79 fn severity(&self) -> Option<miette::Severity> {
80 self.errs.severity()
81 }
82 fn url<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
83 self.errs.url()
84 }
85 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
86 self.errs.source_code()
87 }
88 fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
89 self.errs.diagnostic_source()
90 }
91 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
92 self.errs.related()
93 }
94}
95
96impl CedarSchemaParseError {
97 /// `errs`: the `cedar_schema::parser::CedarSyntaxParseErrors` that were thrown
98 ///
99 /// `src`: the Cedar-syntax text that we were trying to parse
100 pub(crate) fn new(errs: cedar_schema::parser::CedarSchemaParseErrors, src: &str) -> Self {
101 // let's see what the first non-whitespace character is
102 let suspect_json_format = match src.trim_start().chars().next() {
103 None => false, // schema is empty or only whitespace; the problem is unlikely to be JSON vs Cedar format
104 Some('{') => true, // yes, this looks like it was intended to be a JSON schema
105 Some(_) => false, // any character other than '{', not likely it was intended to be a JSON schema
106 };
107 Self {
108 errs,
109 suspect_json_format,
110 }
111 }
112
113 /// Did the schema look like it was JSON data?
114 /// If so, it was probably intended to be parsed as the JSON schema format.
115 /// In that case, the reported errors are probably not super helpful.
116 /// (This check is provided on a best-effort basis)
117 pub fn suspect_json_format(&self) -> bool {
118 self.suspect_json_format
119 }
120
121 /// Get the errors that were encountered while parsing
122 pub fn errors(&self) -> &cedar_schema::parser::CedarSchemaParseErrors {
123 &self.errs
124 }
125}
126
127/// Error when constructing a schema
128//
129// CAUTION: this type is publicly exported in `cedar-policy`.
130// Don't make fields `pub`, don't make breaking changes, and use caution
131// when adding public methods.
132#[derive(Debug, Diagnostic, Error)]
133#[non_exhaustive]
134pub enum SchemaError {
135 /// Error thrown by the `serde_json` crate during serialization
136 #[error(transparent)]
137 #[diagnostic(transparent)]
138 JsonSerialization(#[from] schema_errors::JsonSerializationError),
139 /// This error is thrown when `serde_json` fails to deserialize the JSON
140 #[error(transparent)]
141 #[diagnostic(transparent)]
142 JsonDeserialization(#[from] schema_errors::JsonDeserializationError),
143 /// Errors occurring while computing or enforcing transitive closure on
144 /// action hierarchy.
145 #[error(transparent)]
146 #[diagnostic(transparent)]
147 ActionTransitiveClosure(#[from] schema_errors::ActionTransitiveClosureError),
148 /// Errors occurring while computing or enforcing transitive closure on
149 /// entity type hierarchy.
150 #[error(transparent)]
151 #[diagnostic(transparent)]
152 EntityTypeTransitiveClosure(#[from] schema_errors::EntityTypeTransitiveClosureError),
153 /// Error generated when processing a schema file that uses unsupported features
154 #[error(transparent)]
155 #[diagnostic(transparent)]
156 UnsupportedFeature(#[from] schema_errors::UnsupportedFeatureError),
157 /// Undeclared entity type(s) used in the `memberOf` field of an entity
158 /// type, the `appliesTo` fields of an action, or an attribute type in a
159 /// context or entity attribute record. Entity types in the error message
160 /// are fully qualified, including any implicit or explicit namespaces.
161 #[error(transparent)]
162 #[diagnostic(transparent)]
163 UndeclaredEntityTypes(#[from] schema_errors::UndeclaredEntityTypesError),
164 /// This error occurs when we cannot resolve a typename (because it refers
165 /// to an entity type or common type that was not defined).
166 #[error(transparent)]
167 #[diagnostic(transparent)]
168 TypeNotDefined(#[from] schema_errors::TypeNotDefinedError),
169 /// This error occurs when we cannot resolve an action name used in the
170 /// `memberOf` field of an action (because it refers to an action that was
171 /// not defined).
172 #[error(transparent)]
173 #[diagnostic(transparent)]
174 ActionNotDefined(#[from] schema_errors::ActionNotDefinedError),
175 /// Entity/common type shadowing error. Some shadowing relationships are not
176 /// allowed for clarity reasons; see
177 /// [RFC 70](https://github.com/cedar-policy/rfcs/blob/main/text/0070-disallow-empty-namespace-shadowing.md).
178 #[error(transparent)]
179 #[diagnostic(transparent)]
180 TypeShadowing(#[from] schema_errors::TypeShadowingError),
181 /// Action shadowing error. Some shadowing relationships are not
182 /// allowed for clarity reasons; see
183 /// [RFC 70](https://github.com/cedar-policy/rfcs/blob/main/text/0070-disallow-empty-namespace-shadowing.md).
184 #[error(transparent)]
185 #[diagnostic(transparent)]
186 ActionShadowing(#[from] schema_errors::ActionShadowingError),
187 /// Duplicate specifications for an entity type
188 #[error(transparent)]
189 #[diagnostic(transparent)]
190 DuplicateEntityType(#[from] schema_errors::DuplicateEntityTypeError),
191 /// Duplicate specifications for an action
192 #[error(transparent)]
193 #[diagnostic(transparent)]
194 DuplicateAction(#[from] schema_errors::DuplicateActionError),
195 /// Duplicate specification for a common type declaration
196 #[error(transparent)]
197 #[diagnostic(transparent)]
198 DuplicateCommonType(#[from] schema_errors::DuplicateCommonTypeError),
199 /// Cycle in the schema's action hierarchy.
200 #[error(transparent)]
201 #[diagnostic(transparent)]
202 CycleInActionHierarchy(#[from] schema_errors::CycleInActionHierarchyError),
203 /// Cycle in the schema's common type declarations.
204 #[error(transparent)]
205 #[diagnostic(transparent)]
206 CycleInCommonTypeReferences(#[from] schema_errors::CycleInCommonTypeReferencesError),
207 /// The schema file included an entity type `Action` in the entity type
208 /// list. The `Action` entity type is always implicitly declared, and it
209 /// cannot currently have attributes or be in any groups, so there is no
210 /// purposes in adding an explicit entry.
211 #[error(transparent)]
212 #[diagnostic(transparent)]
213 ActionEntityTypeDeclared(#[from] schema_errors::ActionEntityTypeDeclaredError),
214 /// `context` or `shape` fields are not records
215 #[error(transparent)]
216 #[diagnostic(transparent)]
217 ContextOrShapeNotRecord(#[from] schema_errors::ContextOrShapeNotRecordError),
218 /// An action entity (transitively) has an attribute that is an empty set.
219 /// The validator cannot assign a type to an empty set.
220 /// This error variant should only be used when `PermitAttributes` is enabled.
221 #[error(transparent)]
222 #[diagnostic(transparent)]
223 ActionAttributesContainEmptySet(#[from] schema_errors::ActionAttributesContainEmptySetError),
224 /// An action entity (transitively) has an attribute of unsupported type (`ExprEscape`, `EntityEscape` or `ExtnEscape`).
225 /// This error variant should only be used when `PermitAttributes` is enabled.
226 #[error(transparent)]
227 #[diagnostic(transparent)]
228 UnsupportedActionAttribute(#[from] schema_errors::UnsupportedActionAttributeError),
229 /// Error when evaluating an action attribute
230 #[error(transparent)]
231 #[diagnostic(transparent)]
232 ActionAttrEval(#[from] schema_errors::ActionAttrEvalError),
233 /// Error thrown when the schema contains the `__expr` escape.
234 /// Support for this escape form has been dropped.
235 #[error(transparent)]
236 #[diagnostic(transparent)]
237 ExprEscapeUsed(#[from] schema_errors::ExprEscapeUsedError),
238 /// The schema used an extension type that the validator doesn't know about.
239 #[error(transparent)]
240 #[diagnostic(transparent)]
241 UnknownExtensionType(schema_errors::UnknownExtensionTypeError),
242 /// The schema used a reserved namespace or typename (as of this writing, just `__cedar`).
243 #[error(transparent)]
244 #[diagnostic(transparent)]
245 ReservedName(#[from] ReservedNameError),
246 /// Could not find a definition for a common type, at a point in the code
247 /// where internal invariants should guarantee that we would find one.
248 #[error(transparent)]
249 #[diagnostic(transparent)]
250 CommonTypeInvariantViolation(#[from] schema_errors::CommonTypeInvariantViolationError),
251 /// Could not find a definition for an action, at a point in the code where
252 /// internal invariants should guarantee that we would find one.
253 #[error(transparent)]
254 #[diagnostic(transparent)]
255 ActionInvariantViolation(#[from] schema_errors::ActionInvariantViolationError),
256}
257
258impl From<transitive_closure::TcError<EntityUID>> for SchemaError {
259 fn from(e: transitive_closure::TcError<EntityUID>) -> Self {
260 // we use code in transitive_closure to check for cycles in the action
261 // hierarchy, but in case of an error we want to report the more descriptive
262 // CycleInActionHierarchy instead of ActionTransitiveClosureError
263 match e {
264 transitive_closure::TcError::MissingTcEdge { .. } => {
265 SchemaError::ActionTransitiveClosure(Box::new(e).into())
266 }
267 transitive_closure::TcError::HasCycle(err) => {
268 schema_errors::CycleInActionHierarchyError(err.vertex_with_loop().clone()).into()
269 }
270 }
271 }
272}
273
274impl SchemaError {
275 /// Given one or more `SchemaError`, collect them into a single `SchemaError`.
276 /// Due to current structures, some errors may have to be dropped in some cases.
277 pub fn join_nonempty(errs: NonEmpty<SchemaError>) -> SchemaError {
278 // if we have any `TypeNotDefinedError`s, we can report all of those at once (but have to drop the others).
279 // Same for `ActionNotDefinedError`s.
280 // Any other error, we can just report the first one and have to drop the others.
281 let (type_ndef_errors, non_type_ndef_errors): (Vec<_>, Vec<_>) =
282 errs.into_iter().partition_map(|e| match e {
283 SchemaError::TypeNotDefined(e) => Either::Left(e),
284 _ => Either::Right(e),
285 });
286 if let Some(errs) = NonEmpty::from_vec(type_ndef_errors) {
287 schema_errors::TypeNotDefinedError::join_nonempty(errs).into()
288 } else {
289 let (action_ndef_errors, other_errors): (Vec<_>, Vec<_>) =
290 non_type_ndef_errors.into_iter().partition_map(|e| match e {
291 SchemaError::ActionNotDefined(e) => Either::Left(e),
292 _ => Either::Right(e),
293 });
294 if let Some(errs) = NonEmpty::from_vec(action_ndef_errors) {
295 schema_errors::ActionNotDefinedError::join_nonempty(errs).into()
296 } else {
297 // We partitioned a `NonEmpty` (`errs`) into what we now know is an empty vector
298 // (`type_ndef_errors`) and `non_type_ndef_errors`, so `non_type_ndef_errors` cannot
299 // be empty. Then we partitioned `non_type_ndef_errors` into what we now know is an
300 // empty vector (`action_ndef_errors`) and `other_errors`, so `other_errors` cannot
301 // be empty.
302 // PANIC SAFETY: see comments immediately above
303 #[allow(clippy::expect_used)]
304 other_errors.into_iter().next().expect("cannot be empty")
305 }
306 }
307 }
308}
309
310/// Convenience alias
311pub type Result<T> = std::result::Result<T, SchemaError>;
312
313/// Error subtypes for [`SchemaError`]
314pub mod schema_errors {
315 use std::{collections::BTreeSet, fmt::Display};
316
317 use cedar_policy_core::{
318 ast::{EntityAttrEvaluationError, EntityType, EntityUID, InternalName, Name},
319 parser::join_with_conjunction,
320 transitive_closure,
321 };
322 use itertools::Itertools;
323 use miette::Diagnostic;
324 use nonempty::NonEmpty;
325 use smol_str::SmolStr;
326 use thiserror::Error;
327
328 /// JSON deserialization error
329 //
330 // CAUTION: this type is publicly exported in `cedar-policy`.
331 // Don't make fields `pub`, don't make breaking changes, and use caution
332 // when adding public methods.
333 #[derive(Debug, Diagnostic, Error)]
334 #[error(transparent)]
335 pub struct JsonSerializationError(#[from] pub(crate) serde_json::Error);
336
337 /// Transitive closure of action hierarchy computation or enforcement error
338 //
339 // CAUTION: this type is publicly exported in `cedar-policy`.
340 // Don't make fields `pub`, don't make breaking changes, and use caution
341 // when adding public methods.
342 #[derive(Debug, Diagnostic, Error)]
343 #[error("transitive closure computation/enforcement error on action hierarchy")]
344 #[diagnostic(transparent)]
345 pub struct ActionTransitiveClosureError(
346 #[from] pub(crate) Box<transitive_closure::TcError<EntityUID>>,
347 );
348
349 /// Transitive closure of entity type hierarchy computation or enforcement error
350 //
351 // CAUTION: this type is publicly exported in `cedar-policy`.
352 // Don't make fields `pub`, don't make breaking changes, and use caution
353 // when adding public methods.
354 #[derive(Debug, Diagnostic, Error)]
355 #[error("transitive closure computation/enforcement error on entity type hierarchy")]
356 #[diagnostic(transparent)]
357 pub struct EntityTypeTransitiveClosureError(
358 #[from] pub(crate) Box<transitive_closure::TcError<EntityType>>,
359 );
360
361 /// Undeclared entity types error
362 //
363 // CAUTION: this type is publicly exported in `cedar-policy`.
364 // Don't make fields `pub`, don't make breaking changes, and use caution
365 // when adding public methods.
366 #[derive(Debug, Diagnostic, Error)]
367 #[diagnostic(help(
368 "any entity types appearing anywhere in a schema need to be declared in `entityTypes`"
369 ))]
370 pub struct UndeclaredEntityTypesError(pub(crate) BTreeSet<EntityType>);
371
372 impl Display for UndeclaredEntityTypesError {
373 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
374 if self.0.len() == 1 {
375 write!(f, "undeclared entity type: ")?;
376 } else {
377 write!(f, "undeclared entity types: ")?;
378 }
379 join_with_conjunction(f, "and", self.0.iter(), |f, s| s.fmt(f))
380 }
381 }
382
383 /// Type resolution error
384 //
385 // CAUTION: this type is publicly exported in `cedar-policy`.
386 // Don't make fields `pub`, don't make breaking changes, and use caution
387 // when adding public methods.
388 #[derive(Debug, Diagnostic, Error)]
389 #[error("failed to resolve type{}: {}", if .0.len() > 1 { "s" } else { "" }, .0.iter().map(crate::ConditionalName::raw).join(", "))]
390 #[diagnostic(help("{}", .0.first().resolution_failure_help()))] // we choose to give only the help for the first failed-to-resolve name, because otherwise the help message would be too cluttered and complicated
391 pub struct TypeNotDefinedError(pub(crate) NonEmpty<crate::ConditionalName>);
392
393 impl TypeNotDefinedError {
394 /// Combine all the errors into a single [`TypeNotDefinedError`].
395 ///
396 /// This cannot fail, because `NonEmpty` guarantees there is at least
397 /// one error to join.
398 pub(crate) fn join_nonempty(errs: NonEmpty<TypeNotDefinedError>) -> Self {
399 Self(errs.flat_map(|err| err.0))
400 }
401 }
402
403 /// Action resolution error
404 //
405 // CAUTION: this type is publicly exported in `cedar-policy`.
406 // Don't make fields `pub`, don't make breaking changes, and use caution
407 // when adding public methods.
408 #[derive(Debug, Diagnostic, Error)]
409 #[diagnostic(help("any actions appearing as parents need to be declared as actions"))]
410 pub struct ActionNotDefinedError(
411 pub(crate) NonEmpty<crate::json_schema::ActionEntityUID<crate::ConditionalName>>,
412 );
413
414 impl ActionNotDefinedError {
415 /// Combine all the errors into a single [`ActionNotDefinedError`].
416 ///
417 /// This cannot fail, because `NonEmpty` guarantees there is at least
418 /// one error to join.
419 pub(crate) fn join_nonempty(errs: NonEmpty<ActionNotDefinedError>) -> Self {
420 Self(errs.flat_map(|err| err.0))
421 }
422 }
423
424 impl Display for ActionNotDefinedError {
425 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
426 if self.0.len() == 1 {
427 write!(f, "undeclared action: ")?;
428 } else {
429 write!(f, "undeclared actions: ")?;
430 }
431 join_with_conjunction(
432 f,
433 "and",
434 self.0.iter().map(|aeuid| aeuid.as_raw()),
435 |f, s| s.fmt(f),
436 )
437 }
438 }
439
440 /// Entity/common type shadowing error. Some shadowing relationships are not
441 /// allowed for clarity reasons; see
442 /// [RFC 70](https://github.com/cedar-policy/rfcs/blob/main/text/0070-disallow-empty-namespace-shadowing.md).
443 //
444 // CAUTION: this type is publicly exported in `cedar-policy`.
445 // Don't make fields `pub`, don't make breaking changes, and use caution
446 // when adding public methods.
447 #[derive(Debug, Diagnostic, Error)]
448 #[error(
449 "definition of `{shadowing_def}` illegally shadows the existing definition of `{shadowed_def}`"
450 )]
451 #[diagnostic(help(
452 "try renaming one of the definitions, or moving `{shadowed_def}` to a different namespace"
453 ))]
454 pub struct TypeShadowingError {
455 /// Definition that is being shadowed illegally
456 pub(crate) shadowed_def: InternalName,
457 /// Definition that is responsible for shadowing it illegally
458 pub(crate) shadowing_def: InternalName,
459 }
460
461 /// Action shadowing error. Some shadowing relationships are not allowed for
462 /// clarity reasons; see
463 /// [RFC 70](https://github.com/cedar-policy/rfcs/blob/main/text/0070-disallow-empty-namespace-shadowing.md).
464 //
465 // CAUTION: this type is publicly exported in `cedar-policy`.
466 // Don't make fields `pub`, don't make breaking changes, and use caution
467 // when adding public methods.
468 #[derive(Debug, Diagnostic, Error)]
469 #[error(
470 "definition of `{shadowing_def}` illegally shadows the existing definition of `{shadowed_def}`"
471 )]
472 #[diagnostic(help(
473 "try renaming one of the actions, or moving `{shadowed_def}` to a different namespace"
474 ))]
475 pub struct ActionShadowingError {
476 /// Definition that is being shadowed illegally
477 pub(crate) shadowed_def: EntityUID,
478 /// Definition that is responsible for shadowing it illegally
479 pub(crate) shadowing_def: EntityUID,
480 }
481
482 /// Duplicate entity type error
483 //
484 // CAUTION: this type is publicly exported in `cedar-policy`.
485 // Don't make fields `pub`, don't make breaking changes, and use caution
486 // when adding public methods.
487 #[derive(Debug, Diagnostic, Error)]
488 #[error("duplicate entity type `{0}`")]
489 pub struct DuplicateEntityTypeError(pub(crate) EntityType);
490
491 /// Duplicate action error
492 //
493 // CAUTION: this type is publicly exported in `cedar-policy`.
494 // Don't make fields `pub`, don't make breaking changes, and use caution
495 // when adding public methods.
496 #[derive(Debug, Diagnostic, Error)]
497 #[error("duplicate action `{0}`")]
498 pub struct DuplicateActionError(pub(crate) SmolStr);
499
500 /// Duplicate common type error
501 //
502 // CAUTION: this type is publicly exported in `cedar-policy`.
503 // Don't make fields `pub`, don't make breaking changes, and use caution
504 // when adding public methods.
505 #[derive(Debug, Diagnostic, Error)]
506 #[error("duplicate common type type `{0}`")]
507 pub struct DuplicateCommonTypeError(pub(crate) InternalName);
508
509 /// Cycle in action hierarchy error
510 //
511 // CAUTION: this type is publicly exported in `cedar-policy`.
512 // Don't make fields `pub`, don't make breaking changes, and use caution
513 // when adding public methods.
514 #[derive(Debug, Diagnostic, Error)]
515 #[error("cycle in action hierarchy containing `{0}`")]
516 pub struct CycleInActionHierarchyError(pub(crate) EntityUID);
517
518 /// Cycle in common type hierarchy error
519 //
520 // CAUTION: this type is publicly exported in `cedar-policy`.
521 // Don't make fields `pub`, don't make breaking changes, and use caution
522 // when adding public methods.
523 #[derive(Debug, Diagnostic, Error)]
524 #[error("cycle in common type references containing `{0}`")]
525 pub struct CycleInCommonTypeReferencesError(pub(crate) InternalName);
526
527 /// Action declared in `entityType` list error
528 //
529 // CAUTION: this type is publicly exported in `cedar-policy`.
530 // Don't make fields `pub`, don't make breaking changes, and use caution
531 // when adding public methods.
532 #[derive(Debug, Clone, Diagnostic, Error)]
533 #[error("entity type `Action` declared in `entityTypes` list")]
534 pub struct ActionEntityTypeDeclaredError {}
535
536 /// Context or entity type shape not declared as record error
537 //
538 // CAUTION: this type is publicly exported in `cedar-policy`.
539 // Don't make fields `pub`, don't make breaking changes, and use caution
540 // when adding public methods.
541 #[derive(Debug, Diagnostic, Error)]
542 #[error("{0} is declared with a type other than `Record`")]
543 #[diagnostic(help("{}", match .0 {
544 ContextOrShape::ActionContext(_) => "action contexts must have type `Record`",
545 ContextOrShape::EntityTypeShape(_) => "entity type shapes must have type `Record`",
546}))]
547 pub struct ContextOrShapeNotRecordError(pub(crate) ContextOrShape);
548
549 /// Action attributes contain empty set error
550 //
551 // CAUTION: this type is publicly exported in `cedar-policy`.
552 // Don't make fields `pub`, don't make breaking changes, and use caution
553 // when adding public methods.
554 #[derive(Debug, Diagnostic, Error)]
555 #[error("action `{0}` has an attribute that is an empty set")]
556 #[diagnostic(help(
557 "actions are not currently allowed to have attributes whose value is an empty set"
558 ))]
559 pub struct ActionAttributesContainEmptySetError(pub(crate) EntityUID);
560
561 /// Unsupported action attribute error
562 //
563 // CAUTION: this type is publicly exported in `cedar-policy`.
564 // Don't make fields `pub`, don't make breaking changes, and use caution
565 // when adding public methods.
566 #[derive(Debug, Diagnostic, Error)]
567 #[error("action `{0}` has an attribute with unsupported JSON representation: {1}")]
568 pub struct UnsupportedActionAttributeError(pub(crate) EntityUID, pub(crate) SmolStr);
569
570 /// Unsupported `__expr` escape error
571 //
572 // CAUTION: this type is publicly exported in `cedar-policy`.
573 // Don't make fields `pub`, don't make breaking changes, and use caution
574 // when adding public methods.
575 #[derive(Debug, Clone, Diagnostic, Error)]
576 #[error("the `__expr` escape is no longer supported")]
577 #[diagnostic(help("to create an entity reference, use `__entity`; to create an extension value, use `__extn`; and for all other values, use JSON directly"))]
578 pub struct ExprEscapeUsedError {}
579
580 /// Action attribute evaluation error
581 //
582 // CAUTION: this type is publicly exported in `cedar-policy`.
583 // Don't make fields `pub`, don't make breaking changes, and use caution
584 // when adding public methods.
585 #[derive(Debug, Diagnostic, Error)]
586 #[error(transparent)]
587 #[diagnostic(transparent)]
588 pub struct ActionAttrEvalError(#[from] pub(crate) EntityAttrEvaluationError);
589
590 /// Unsupported feature error
591 //
592 // CAUTION: this type is publicly exported in `cedar-policy`.
593 // Don't make fields `pub`, don't make breaking changes, and use caution
594 // when adding public methods.
595 #[derive(Debug, Diagnostic, Error)]
596 #[error("unsupported feature used in schema")]
597 #[diagnostic(transparent)]
598 pub struct UnsupportedFeatureError(#[from] pub(crate) UnsupportedFeature);
599
600 #[derive(Debug)]
601 pub(crate) enum ContextOrShape {
602 ActionContext(EntityUID),
603 EntityTypeShape(EntityType),
604 }
605
606 impl std::fmt::Display for ContextOrShape {
607 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
608 match self {
609 ContextOrShape::ActionContext(action) => write!(f, "Context for action {}", action),
610 ContextOrShape::EntityTypeShape(entity_type) => {
611 write!(f, "Shape for entity type {}", entity_type)
612 }
613 }
614 }
615 }
616
617 #[derive(Debug, Diagnostic, Error)]
618 pub(crate) enum UnsupportedFeature {
619 #[error("records and entities with `additionalAttributes` are experimental, but the experimental `partial-validate` feature is not enabled")]
620 OpenRecordsAndEntities,
621 // Action attributes are allowed if `ActionBehavior` is `PermitAttributes`
622 #[error("action declared with attributes: [{}]", .0.iter().join(", "))]
623 ActionAttributes(Vec<String>),
624 }
625
626 /// This error is thrown when `serde_json` fails to deserialize the JSON
627 //
628 // CAUTION: this type is publicly exported in `cedar-policy`.
629 // Don't make fields `pub`, don't make breaking changes, and use caution
630 // when adding public methods.
631 #[derive(Debug, Error)]
632 #[error("{err}")]
633 pub struct JsonDeserializationError {
634 /// Error thrown by the `serde_json` crate
635 err: serde_json::Error,
636 /// Possible fix for the error
637 advice: Option<JsonDeserializationAdvice>,
638 }
639
640 impl Diagnostic for JsonDeserializationError {
641 fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
642 self.advice
643 .as_ref()
644 .map(|h| Box::new(h) as Box<dyn Display>)
645 }
646 }
647
648 #[derive(Debug, Error)]
649 enum JsonDeserializationAdvice {
650 #[error("this API was expecting a schema in the JSON format; did you mean to use a different function, which expects the Cedar schema format?")]
651 CedarFormat,
652 #[error("JSON formatted schema must specify a namespace. If you want to use the empty namespace, explicitly specify it with `{{ \"\": {{..}} }}`")]
653 MissingNamespace,
654 }
655
656 impl JsonDeserializationError {
657 /// `err`: the `serde_json::Error` that was thrown
658 ///
659 /// `src`: the JSON that we were trying to deserialize (if available in string form)
660 pub(crate) fn new(err: serde_json::Error, src: Option<&str>) -> Self {
661 match src {
662 None => Self { err, advice: None },
663 Some(src) => {
664 // let's see what the first non-whitespace character is
665 let advice = match src.trim_start().chars().next() {
666 None => None, // schema is empty or only whitespace; the problem is unlikely to be JSON vs Cedar format
667 Some('{') => {
668 // This looks like it was intended to be a JSON schema. Check fields of top level JSON object to see
669 // if it looks like it's missing a namespace.
670 if let Ok(serde_json::Value::Object(obj)) =
671 serde_json::from_str::<serde_json::Value>(src)
672 {
673 if obj.contains_key("entityTypes")
674 || obj.contains_key("actions")
675 || obj.contains_key("commonTypes")
676 {
677 // These keys are expected inside a namespace, so it's likely the user forgot to specify a
678 // namespace if they're at the top level of the schema json object.
679 Some(JsonDeserializationAdvice::MissingNamespace)
680 } else {
681 // Probably something wrong inside a namespace definition.
682 None
683 }
684 } else {
685 // Invalid JSON
686 None
687 }
688 }
689 Some(_) => Some(JsonDeserializationAdvice::CedarFormat), // any character other than '{', we suspect it might be a Cedar-format schema
690 };
691 Self { err, advice }
692 }
693 }
694 }
695 }
696
697 /// Unknown extension type error
698 //
699 // CAUTION: this type is publicly exported in `cedar-policy`.
700 // Don't make fields `pub`, don't make breaking changes, and use caution
701 // when adding public methods.
702 #[derive(Error, Debug)]
703 #[error("unknown extension type `{actual}`")]
704 pub struct UnknownExtensionTypeError {
705 pub(crate) actual: Name,
706 pub(crate) suggested_replacement: Option<String>,
707 }
708
709 impl Diagnostic for UnknownExtensionTypeError {
710 fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
711 self.suggested_replacement.as_ref().map(|suggestion| {
712 Box::new(format!("did you mean `{suggestion}`?")) as Box<dyn Display>
713 })
714 }
715 }
716
717 /// Could not find a definition for a common type, at a point in the code
718 /// where internal invariants should guarantee that we would find one.
719 //
720 // CAUTION: this type is publicly exported in `cedar-policy`.
721 // Don't make fields `pub`, don't make breaking changes, and use caution
722 // when adding public methods.
723 #[derive(Error, Debug, Diagnostic)]
724 #[error("internal invariant violated: failed to find a common-type definition for {name}")]
725 #[help("please file an issue at <https://github.com/cedar-policy/cedar/issues> including the schema that caused this error")]
726 pub struct CommonTypeInvariantViolationError {
727 /// Fully-qualified [`InternalName`] of the common type we failed to find a definition for
728 pub(crate) name: InternalName,
729 }
730
731 /// Could not find a definition for an action, at a point in the code where
732 /// internal invariants should guarantee that we would find one.
733 //
734 // CAUTION: this type is publicly exported in `cedar-policy`.
735 // Don't make fields `pub`, don't make breaking changes, and use caution
736 // when adding public methods.
737 #[derive(Error, Debug, Diagnostic)]
738 #[error("internal invariant violated: failed to find {} for {}", if .euids.len() > 1 { "action definitions" } else { "an action definition" }, .euids.iter().join(", "))]
739 #[help("please file an issue at <https://github.com/cedar-policy/cedar/issues> including the schema that caused this error")]
740 pub struct ActionInvariantViolationError {
741 /// Fully-qualified [`EntityUID`]s of the action(s) we failed to find a definition for
742 pub(crate) euids: NonEmpty<EntityUID>,
743 }
744}