1use miette::Diagnostic;
21use thiserror::Error;
22use validation_errors::UnrecognizedActionIdHelp;
23
24use std::collections::BTreeSet;
25
26use cedar_policy_core::ast::{EntityType, Expr, PolicyID};
27use cedar_policy_core::parser::Loc;
28
29use crate::types::{EntityLUB, Type};
30
31pub mod validation_errors;
32pub mod validation_warnings;
33
34#[derive(Debug)]
39pub struct ValidationResult {
40 validation_errors: Vec<ValidationError>,
41 validation_warnings: Vec<ValidationWarning>,
42}
43
44impl ValidationResult {
45 pub fn new(
48 errors: impl IntoIterator<Item = ValidationError>,
49 warnings: impl IntoIterator<Item = ValidationWarning>,
50 ) -> Self {
51 Self {
52 validation_errors: errors.into_iter().collect(),
53 validation_warnings: warnings.into_iter().collect(),
54 }
55 }
56
57 pub fn validation_passed(&self) -> bool {
60 self.validation_errors.is_empty()
61 }
62
63 pub fn validation_errors(&self) -> impl Iterator<Item = &ValidationError> {
65 self.validation_errors.iter()
66 }
67
68 pub fn validation_warnings(&self) -> impl Iterator<Item = &ValidationWarning> {
70 self.validation_warnings.iter()
71 }
72
73 pub fn into_errors_and_warnings(
75 self,
76 ) -> (
77 impl Iterator<Item = ValidationError>,
78 impl Iterator<Item = ValidationWarning>,
79 ) {
80 (
81 self.validation_errors.into_iter(),
82 self.validation_warnings.into_iter(),
83 )
84 }
85}
86
87#[derive(Clone, Debug, Diagnostic, Error, Hash, Eq, PartialEq)]
94pub enum ValidationError {
95 #[error(transparent)]
97 #[diagnostic(transparent)]
98 UnrecognizedEntityType(#[from] validation_errors::UnrecognizedEntityType),
99 #[error(transparent)]
101 #[diagnostic(transparent)]
102 UnrecognizedActionId(#[from] validation_errors::UnrecognizedActionId),
103 #[error(transparent)]
107 #[diagnostic(transparent)]
108 InvalidActionApplication(#[from] validation_errors::InvalidActionApplication),
109 #[error(transparent)]
112 #[diagnostic(transparent)]
113 UnexpectedType(#[from] validation_errors::UnexpectedType),
114 #[error(transparent)]
116 #[diagnostic(transparent)]
117 IncompatibleTypes(#[from] validation_errors::IncompatibleTypes),
118 #[error(transparent)]
121 #[diagnostic(transparent)]
122 UnsafeAttributeAccess(#[from] validation_errors::UnsafeAttributeAccess),
123 #[error(transparent)]
126 #[diagnostic(transparent)]
127 UnsafeOptionalAttributeAccess(#[from] validation_errors::UnsafeOptionalAttributeAccess),
128 #[error(transparent)]
130 #[diagnostic(transparent)]
131 UnsafeTagAccess(#[from] validation_errors::UnsafeTagAccess),
132 #[error(transparent)]
134 #[diagnostic(transparent)]
135 NoTagsAllowed(#[from] validation_errors::NoTagsAllowed),
136 #[error(transparent)]
138 #[diagnostic(transparent)]
139 UndefinedFunction(#[from] validation_errors::UndefinedFunction),
140 #[error(transparent)]
142 #[diagnostic(transparent)]
143 WrongNumberArguments(#[from] validation_errors::WrongNumberArguments),
144 #[diagnostic(transparent)]
147 #[error(transparent)]
148 FunctionArgumentValidation(#[from] validation_errors::FunctionArgumentValidation),
149 #[diagnostic(transparent)]
151 #[error(transparent)]
152 EmptySetForbidden(#[from] validation_errors::EmptySetForbidden),
153 #[diagnostic(transparent)]
156 #[error(transparent)]
157 NonLitExtConstructor(#[from] validation_errors::NonLitExtConstructor),
158 #[error(transparent)]
162 #[diagnostic(transparent)]
163 HierarchyNotRespected(#[from] validation_errors::HierarchyNotRespected),
164 #[error(transparent)]
167 #[diagnostic(transparent)]
168 InternalInvariantViolation(#[from] validation_errors::InternalInvariantViolation),
169 #[cfg(feature = "level-validate")]
170 #[error(transparent)]
173 #[diagnostic(transparent)]
174 EntityDerefLevelViolation(#[from] validation_errors::EntityDerefLevelViolation),
175}
176
177impl ValidationError {
178 pub(crate) fn unrecognized_entity_type(
179 source_loc: Option<Loc>,
180 policy_id: PolicyID,
181 actual_entity_type: String,
182 suggested_entity_type: Option<String>,
183 ) -> Self {
184 validation_errors::UnrecognizedEntityType {
185 source_loc,
186 policy_id,
187 actual_entity_type,
188 suggested_entity_type,
189 }
190 .into()
191 }
192
193 pub(crate) fn unrecognized_action_id(
194 source_loc: Option<Loc>,
195
196 policy_id: PolicyID,
197 actual_action_id: String,
198 hint: Option<UnrecognizedActionIdHelp>,
199 ) -> Self {
200 validation_errors::UnrecognizedActionId {
201 source_loc,
202 policy_id,
203 actual_action_id,
204 hint,
205 }
206 .into()
207 }
208
209 pub(crate) fn invalid_action_application(
210 source_loc: Option<Loc>,
211 policy_id: PolicyID,
212 would_in_fix_principal: bool,
213 would_in_fix_resource: bool,
214 ) -> Self {
215 validation_errors::InvalidActionApplication {
216 source_loc,
217 policy_id,
218 would_in_fix_principal,
219 would_in_fix_resource,
220 }
221 .into()
222 }
223
224 pub(crate) fn expected_one_of_types(
226 source_loc: Option<Loc>,
227 policy_id: PolicyID,
228 expected: Vec<Type>,
229 actual: Type,
230 help: Option<validation_errors::UnexpectedTypeHelp>,
231 ) -> Self {
232 validation_errors::UnexpectedType {
233 source_loc,
234 policy_id,
235 expected,
236 actual,
237 help,
238 }
239 .into()
240 }
241
242 pub(crate) fn incompatible_types(
245 source_loc: Option<Loc>,
246 policy_id: PolicyID,
247 types: impl IntoIterator<Item = Type>,
248 hint: validation_errors::LubHelp,
249 context: validation_errors::LubContext,
250 ) -> Self {
251 validation_errors::IncompatibleTypes {
252 source_loc,
253 policy_id,
254 types: types.into_iter().collect::<BTreeSet<_>>(),
255 hint,
256 context,
257 }
258 .into()
259 }
260
261 pub(crate) fn unsafe_attribute_access(
262 source_loc: Option<Loc>,
263 policy_id: PolicyID,
264 attribute_access: validation_errors::AttributeAccess,
265 suggestion: Option<String>,
266 may_exist: bool,
267 ) -> Self {
268 validation_errors::UnsafeAttributeAccess {
269 source_loc,
270 policy_id,
271 attribute_access,
272 suggestion,
273 may_exist,
274 }
275 .into()
276 }
277
278 pub(crate) fn unsafe_optional_attribute_access(
279 source_loc: Option<Loc>,
280 policy_id: PolicyID,
281 attribute_access: validation_errors::AttributeAccess,
282 ) -> Self {
283 validation_errors::UnsafeOptionalAttributeAccess {
284 source_loc,
285 policy_id,
286 attribute_access,
287 }
288 .into()
289 }
290
291 pub(crate) fn unsafe_tag_access(
292 source_loc: Option<Loc>,
293 policy_id: PolicyID,
294 entity_ty: Option<EntityLUB>,
295 tag: Expr<Option<Type>>,
296 ) -> Self {
297 validation_errors::UnsafeTagAccess {
298 source_loc,
299 policy_id,
300 entity_ty,
301 tag,
302 }
303 .into()
304 }
305
306 pub(crate) fn no_tags_allowed(
307 source_loc: Option<Loc>,
308 policy_id: PolicyID,
309 entity_ty: Option<EntityType>,
310 ) -> Self {
311 validation_errors::NoTagsAllowed {
312 source_loc,
313 policy_id,
314 entity_ty,
315 }
316 .into()
317 }
318
319 pub(crate) fn undefined_extension(
320 source_loc: Option<Loc>,
321 policy_id: PolicyID,
322 name: String,
323 ) -> Self {
324 validation_errors::UndefinedFunction {
325 source_loc,
326 policy_id,
327 name,
328 }
329 .into()
330 }
331
332 pub(crate) fn wrong_number_args(
333 source_loc: Option<Loc>,
334 policy_id: PolicyID,
335 expected: usize,
336 actual: usize,
337 ) -> Self {
338 validation_errors::WrongNumberArguments {
339 source_loc,
340 policy_id,
341 expected,
342 actual,
343 }
344 .into()
345 }
346
347 pub(crate) fn function_argument_validation(
348 source_loc: Option<Loc>,
349 policy_id: PolicyID,
350 msg: String,
351 ) -> Self {
352 validation_errors::FunctionArgumentValidation {
353 source_loc,
354 policy_id,
355 msg,
356 }
357 .into()
358 }
359
360 pub(crate) fn empty_set_forbidden(source_loc: Option<Loc>, policy_id: PolicyID) -> Self {
361 validation_errors::EmptySetForbidden {
362 source_loc,
363 policy_id,
364 }
365 .into()
366 }
367
368 pub(crate) fn non_lit_ext_constructor(source_loc: Option<Loc>, policy_id: PolicyID) -> Self {
369 validation_errors::NonLitExtConstructor {
370 source_loc,
371 policy_id,
372 }
373 .into()
374 }
375
376 pub(crate) fn hierarchy_not_respected(
377 source_loc: Option<Loc>,
378 policy_id: PolicyID,
379 in_lhs: Option<EntityType>,
380 in_rhs: Option<EntityType>,
381 ) -> Self {
382 validation_errors::HierarchyNotRespected {
383 source_loc,
384 policy_id,
385 in_lhs,
386 in_rhs,
387 }
388 .into()
389 }
390
391 pub(crate) fn internal_invariant_violation(
392 source_loc: Option<Loc>,
393 policy_id: PolicyID,
394 ) -> Self {
395 validation_errors::InternalInvariantViolation {
396 source_loc,
397 policy_id,
398 }
399 .into()
400 }
401}
402
403#[derive(Debug, Clone, PartialEq, Diagnostic, Error, Eq, Hash)]
406pub enum ValidationWarning {
407 #[diagnostic(transparent)]
409 #[error(transparent)]
410 MixedScriptString(#[from] validation_warnings::MixedScriptString),
411 #[diagnostic(transparent)]
413 #[error(transparent)]
414 BidiCharsInString(#[from] validation_warnings::BidiCharsInString),
415 #[diagnostic(transparent)]
417 #[error(transparent)]
418 BidiCharsInIdentifier(#[from] validation_warnings::BidiCharsInIdentifier),
419 #[diagnostic(transparent)]
421 #[error(transparent)]
422 MixedScriptIdentifier(#[from] validation_warnings::MixedScriptIdentifier),
423 #[diagnostic(transparent)]
425 #[error(transparent)]
426 ConfusableIdentifier(#[from] validation_warnings::ConfusableIdentifier),
427 #[diagnostic(transparent)]
429 #[error(transparent)]
430 ImpossiblePolicy(#[from] validation_warnings::ImpossiblePolicy),
431}
432
433impl ValidationWarning {
434 pub(crate) fn mixed_script_string(
435 source_loc: Option<Loc>,
436 policy_id: PolicyID,
437 string: impl Into<String>,
438 ) -> Self {
439 validation_warnings::MixedScriptString {
440 source_loc,
441 policy_id,
442 string: string.into(),
443 }
444 .into()
445 }
446
447 pub(crate) fn bidi_chars_strings(
448 source_loc: Option<Loc>,
449 policy_id: PolicyID,
450 string: impl Into<String>,
451 ) -> Self {
452 validation_warnings::BidiCharsInString {
453 source_loc,
454 policy_id,
455 string: string.into(),
456 }
457 .into()
458 }
459
460 pub(crate) fn mixed_script_identifier(
461 source_loc: Option<Loc>,
462 policy_id: PolicyID,
463 id: impl Into<String>,
464 ) -> Self {
465 validation_warnings::MixedScriptIdentifier {
466 source_loc,
467 policy_id,
468 id: id.into(),
469 }
470 .into()
471 }
472
473 pub(crate) fn bidi_chars_identifier(
474 source_loc: Option<Loc>,
475 policy_id: PolicyID,
476 id: impl Into<String>,
477 ) -> Self {
478 validation_warnings::BidiCharsInIdentifier {
479 source_loc,
480 policy_id,
481 id: id.into(),
482 }
483 .into()
484 }
485
486 pub(crate) fn confusable_identifier(
487 source_loc: Option<Loc>,
488 policy_id: PolicyID,
489 id: impl Into<String>,
490 confusable_character: char,
491 ) -> Self {
492 validation_warnings::ConfusableIdentifier {
493 source_loc,
494 policy_id,
495 id: id.into(),
496 confusable_character,
497 }
498 .into()
499 }
500
501 pub(crate) fn impossible_policy(source_loc: Option<Loc>, policy_id: PolicyID) -> Self {
502 validation_warnings::ImpossiblePolicy {
503 source_loc,
504 policy_id,
505 }
506 .into()
507 }
508}