1use miette::Diagnostic;
21use thiserror::Error;
22
23use std::collections::BTreeSet;
24
25use cedar_policy_core::ast::{EntityType, PolicyID};
26use cedar_policy_core::parser::Loc;
27
28use crate::types::Type;
29
30pub mod validation_errors;
31pub mod validation_warnings;
32
33#[derive(Debug)]
38pub struct ValidationResult {
39 validation_errors: Vec<ValidationError>,
40 validation_warnings: Vec<ValidationWarning>,
41}
42
43impl ValidationResult {
44 pub fn new(
47 errors: impl IntoIterator<Item = ValidationError>,
48 warnings: impl IntoIterator<Item = ValidationWarning>,
49 ) -> Self {
50 Self {
51 validation_errors: errors.into_iter().collect(),
52 validation_warnings: warnings.into_iter().collect(),
53 }
54 }
55
56 pub fn validation_passed(&self) -> bool {
59 self.validation_errors.is_empty()
60 }
61
62 pub fn validation_errors(&self) -> impl Iterator<Item = &ValidationError> {
64 self.validation_errors.iter()
65 }
66
67 pub fn validation_warnings(&self) -> impl Iterator<Item = &ValidationWarning> {
69 self.validation_warnings.iter()
70 }
71
72 pub fn into_errors_and_warnings(
74 self,
75 ) -> (
76 impl Iterator<Item = ValidationError>,
77 impl Iterator<Item = ValidationWarning>,
78 ) {
79 (
80 self.validation_errors.into_iter(),
81 self.validation_warnings.into_iter(),
82 )
83 }
84}
85
86#[derive(Clone, Debug, Diagnostic, Error, Hash, Eq, PartialEq)]
93pub enum ValidationError {
94 #[error(transparent)]
96 #[diagnostic(transparent)]
97 UnrecognizedEntityType(#[from] validation_errors::UnrecognizedEntityType),
98 #[error(transparent)]
100 #[diagnostic(transparent)]
101 UnrecognizedActionId(#[from] validation_errors::UnrecognizedActionId),
102 #[error(transparent)]
106 #[diagnostic(transparent)]
107 InvalidActionApplication(#[from] validation_errors::InvalidActionApplication),
108 #[error(transparent)]
111 #[diagnostic(transparent)]
112 UnexpectedType(#[from] validation_errors::UnexpectedType),
113 #[error(transparent)]
115 #[diagnostic(transparent)]
116 IncompatibleTypes(#[from] validation_errors::IncompatibleTypes),
117 #[error(transparent)]
120 #[diagnostic(transparent)]
121 UnsafeAttributeAccess(#[from] validation_errors::UnsafeAttributeAccess),
122 #[error(transparent)]
125 #[diagnostic(transparent)]
126 UnsafeOptionalAttributeAccess(#[from] validation_errors::UnsafeOptionalAttributeAccess),
127 #[error(transparent)]
129 #[diagnostic(transparent)]
130 UndefinedFunction(#[from] validation_errors::UndefinedFunction),
131 #[error(transparent)]
133 #[diagnostic(transparent)]
134 WrongNumberArguments(#[from] validation_errors::WrongNumberArguments),
135 #[diagnostic(transparent)]
138 #[error(transparent)]
139 FunctionArgumentValidation(#[from] validation_errors::FunctionArgumentValidation),
140 #[diagnostic(transparent)]
142 #[error(transparent)]
143 EmptySetForbidden(#[from] validation_errors::EmptySetForbidden),
144 #[diagnostic(transparent)]
147 #[error(transparent)]
148 NonLitExtConstructor(#[from] validation_errors::NonLitExtConstructor),
149 #[error(transparent)]
153 #[diagnostic(transparent)]
154 HierarchyNotRespected(#[from] validation_errors::HierarchyNotRespected),
155}
156
157impl ValidationError {
158 pub(crate) fn unrecognized_entity_type(
159 source_loc: Option<Loc>,
160 policy_id: PolicyID,
161 actual_entity_type: String,
162 suggested_entity_type: Option<String>,
163 ) -> Self {
164 validation_errors::UnrecognizedEntityType {
165 source_loc,
166 policy_id,
167 actual_entity_type,
168 suggested_entity_type,
169 }
170 .into()
171 }
172
173 pub(crate) fn unrecognized_action_id(
174 source_loc: Option<Loc>,
175
176 policy_id: PolicyID,
177 actual_action_id: String,
178 suggested_action_id: Option<String>,
179 ) -> Self {
180 validation_errors::UnrecognizedActionId {
181 source_loc,
182 policy_id,
183 actual_action_id,
184 suggested_action_id,
185 }
186 .into()
187 }
188
189 pub(crate) fn invalid_action_application(
190 source_loc: Option<Loc>,
191 policy_id: PolicyID,
192 would_in_fix_principal: bool,
193 would_in_fix_resource: bool,
194 ) -> Self {
195 validation_errors::InvalidActionApplication {
196 source_loc,
197 policy_id,
198 would_in_fix_principal,
199 would_in_fix_resource,
200 }
201 .into()
202 }
203
204 pub(crate) fn expected_one_of_types(
206 source_loc: Option<Loc>,
207 policy_id: PolicyID,
208 expected: impl IntoIterator<Item = Type>,
209 actual: Type,
210 help: Option<validation_errors::UnexpectedTypeHelp>,
211 ) -> Self {
212 validation_errors::UnexpectedType {
213 source_loc,
214 policy_id,
215 expected: expected.into_iter().collect::<BTreeSet<_>>(),
216 actual,
217 help,
218 }
219 .into()
220 }
221
222 pub(crate) fn incompatible_types(
225 source_loc: Option<Loc>,
226 policy_id: PolicyID,
227 types: impl IntoIterator<Item = Type>,
228 hint: validation_errors::LubHelp,
229 context: validation_errors::LubContext,
230 ) -> Self {
231 validation_errors::IncompatibleTypes {
232 source_loc,
233 policy_id,
234 types: types.into_iter().collect::<BTreeSet<_>>(),
235 hint,
236 context,
237 }
238 .into()
239 }
240
241 pub(crate) fn unsafe_attribute_access(
242 source_loc: Option<Loc>,
243 policy_id: PolicyID,
244 attribute_access: validation_errors::AttributeAccess,
245 suggestion: Option<String>,
246 may_exist: bool,
247 ) -> Self {
248 validation_errors::UnsafeAttributeAccess {
249 source_loc,
250 policy_id,
251 attribute_access,
252 suggestion,
253 may_exist,
254 }
255 .into()
256 }
257
258 pub(crate) fn unsafe_optional_attribute_access(
259 source_loc: Option<Loc>,
260 policy_id: PolicyID,
261 attribute_access: validation_errors::AttributeAccess,
262 ) -> Self {
263 validation_errors::UnsafeOptionalAttributeAccess {
264 source_loc,
265 policy_id,
266 attribute_access,
267 }
268 .into()
269 }
270
271 pub(crate) fn undefined_extension(
272 source_loc: Option<Loc>,
273 policy_id: PolicyID,
274 name: String,
275 ) -> Self {
276 validation_errors::UndefinedFunction {
277 source_loc,
278 policy_id,
279 name,
280 }
281 .into()
282 }
283
284 pub(crate) fn wrong_number_args(
285 source_loc: Option<Loc>,
286
287 policy_id: PolicyID,
288 expected: usize,
289 actual: usize,
290 ) -> Self {
291 validation_errors::WrongNumberArguments {
292 source_loc,
293 policy_id,
294 expected,
295 actual,
296 }
297 .into()
298 }
299
300 pub(crate) fn function_argument_validation(
301 source_loc: Option<Loc>,
302 policy_id: PolicyID,
303 msg: String,
304 ) -> Self {
305 validation_errors::FunctionArgumentValidation {
306 source_loc,
307 policy_id,
308 msg,
309 }
310 .into()
311 }
312
313 pub(crate) fn empty_set_forbidden(source_loc: Option<Loc>, policy_id: PolicyID) -> Self {
314 validation_errors::EmptySetForbidden {
315 source_loc,
316 policy_id,
317 }
318 .into()
319 }
320
321 pub(crate) fn non_lit_ext_constructor(source_loc: Option<Loc>, policy_id: PolicyID) -> Self {
322 validation_errors::NonLitExtConstructor {
323 source_loc,
324 policy_id,
325 }
326 .into()
327 }
328
329 pub(crate) fn hierarchy_not_respected(
330 source_loc: Option<Loc>,
331
332 policy_id: PolicyID,
333 in_lhs: Option<EntityType>,
334 in_rhs: Option<EntityType>,
335 ) -> Self {
336 validation_errors::HierarchyNotRespected {
337 source_loc,
338 policy_id,
339 in_lhs,
340 in_rhs,
341 }
342 .into()
343 }
344}
345
346#[derive(Debug, Clone, PartialEq, Diagnostic, Error, Eq, Hash)]
349pub enum ValidationWarning {
350 #[diagnostic(transparent)]
352 #[error(transparent)]
353 MixedScriptString(#[from] validation_warnings::MixedScriptString),
354 #[diagnostic(transparent)]
356 #[error(transparent)]
357 BidiCharsInString(#[from] validation_warnings::BidiCharsInString),
358 #[diagnostic(transparent)]
360 #[error(transparent)]
361 BidiCharsInIdentifier(#[from] validation_warnings::BidiCharsInIdentifier),
362 #[diagnostic(transparent)]
364 #[error(transparent)]
365 MixedScriptIdentifier(#[from] validation_warnings::MixedScriptIdentifier),
366 #[diagnostic(transparent)]
368 #[error(transparent)]
369 ConfusableIdentifier(#[from] validation_warnings::ConfusableIdentifier),
370 #[diagnostic(transparent)]
372 #[error(transparent)]
373 ImpossiblePolicy(#[from] validation_warnings::ImpossiblePolicy),
374}
375
376impl ValidationWarning {
377 pub(crate) fn mixed_script_string(
378 source_loc: Option<Loc>,
379 policy_id: PolicyID,
380 string: impl Into<String>,
381 ) -> Self {
382 validation_warnings::MixedScriptString {
383 source_loc,
384 policy_id,
385 string: string.into(),
386 }
387 .into()
388 }
389
390 pub(crate) fn bidi_chars_strings(
391 source_loc: Option<Loc>,
392 policy_id: PolicyID,
393 string: impl Into<String>,
394 ) -> Self {
395 validation_warnings::BidiCharsInString {
396 source_loc,
397 policy_id,
398 string: string.into(),
399 }
400 .into()
401 }
402
403 pub(crate) fn mixed_script_identifier(
404 source_loc: Option<Loc>,
405 policy_id: PolicyID,
406 id: impl Into<String>,
407 ) -> Self {
408 validation_warnings::MixedScriptIdentifier {
409 source_loc,
410 policy_id,
411 id: id.into(),
412 }
413 .into()
414 }
415
416 pub(crate) fn bidi_chars_identifier(
417 source_loc: Option<Loc>,
418 policy_id: PolicyID,
419 id: impl Into<String>,
420 ) -> Self {
421 validation_warnings::BidiCharsInIdentifier {
422 source_loc,
423 policy_id,
424 id: id.into(),
425 }
426 .into()
427 }
428
429 pub(crate) fn confusable_identifier(
430 source_loc: Option<Loc>,
431 policy_id: PolicyID,
432 id: impl Into<String>,
433 ) -> Self {
434 validation_warnings::ConfusableIdentifier {
435 source_loc,
436 policy_id,
437 id: id.into(),
438 }
439 .into()
440 }
441
442 pub(crate) fn impossible_policy(source_loc: Option<Loc>, policy_id: PolicyID) -> Self {
443 validation_warnings::ImpossiblePolicy {
444 source_loc,
445 policy_id,
446 }
447 .into()
448 }
449}