1use cedar_policy_core::ast::PolicyID;
18use cedar_policy_core::parser::Loc;
19use miette::Diagnostic;
20use thiserror::Error;
21
22use crate::TypeErrorKind;
23
24#[derive(Debug)]
29pub struct ValidationResult<'a> {
30 validation_errors: Vec<ValidationError<'a>>,
31 validation_warnings: Vec<ValidationWarning<'a>>,
32}
33
34impl<'a> ValidationResult<'a> {
35 pub fn new(
36 errors: impl IntoIterator<Item = ValidationError<'a>>,
37 warnings: impl IntoIterator<Item = ValidationWarning<'a>>,
38 ) -> Self {
39 Self {
40 validation_errors: errors.into_iter().collect(),
41 validation_warnings: warnings.into_iter().collect(),
42 }
43 }
44
45 pub fn validation_passed(&self) -> bool {
48 self.validation_errors.is_empty()
49 }
50
51 pub fn validation_errors(&self) -> impl Iterator<Item = &ValidationError> {
53 self.validation_errors.iter()
54 }
55
56 pub fn validation_warnings(&self) -> impl Iterator<Item = &ValidationWarning> {
58 self.validation_warnings.iter()
59 }
60
61 pub fn into_errors_and_warnings(
63 self,
64 ) -> (
65 impl Iterator<Item = ValidationError<'a>>,
66 impl Iterator<Item = ValidationWarning<'a>>,
67 ) {
68 (
69 self.validation_errors.into_iter(),
70 self.validation_warnings.into_iter(),
71 )
72 }
73}
74
75#[derive(Debug)]
80#[cfg_attr(test, derive(Eq, PartialEq))]
81pub struct ValidationError<'a> {
82 location: SourceLocation<'a>,
83 error_kind: ValidationErrorKind,
84}
85
86impl<'a> ValidationError<'a> {
87 pub(crate) fn with_policy_id(
88 id: &'a PolicyID,
89 source_loc: Option<Loc>,
90 error_kind: ValidationErrorKind,
91 ) -> Self {
92 Self {
93 error_kind,
94 location: SourceLocation::new(id, source_loc),
95 }
96 }
97
98 pub fn into_location_and_error_kind(self) -> (SourceLocation<'a>, ValidationErrorKind) {
100 (self.location, self.error_kind)
101 }
102
103 pub fn error_kind(&self) -> &ValidationErrorKind {
105 &self.error_kind
106 }
107
108 pub fn location(&self) -> &SourceLocation {
110 &self.location
111 }
112}
113
114#[derive(Debug, Clone, Hash, Eq, PartialEq)]
116pub struct SourceLocation<'a> {
117 policy_id: &'a PolicyID,
118 source_loc: Option<Loc>,
119}
120
121impl<'a> SourceLocation<'a> {
122 pub(crate) fn new(policy_id: &'a PolicyID, source_loc: Option<Loc>) -> Self {
123 Self {
124 policy_id,
125 source_loc,
126 }
127 }
128
129 pub fn policy_id(&self) -> &'a PolicyID {
131 self.policy_id
132 }
133
134 pub fn source_loc(&self) -> Option<&Loc> {
135 self.source_loc.as_ref()
136 }
137}
138
139#[derive(Debug, Clone, Diagnostic, Error)]
142#[cfg_attr(test, derive(Eq, PartialEq))]
143#[non_exhaustive]
144pub enum ValidationErrorKind {
145 #[error(transparent)]
147 #[diagnostic(transparent)]
148 UnrecognizedEntityType(#[from] UnrecognizedEntityType),
149 #[error(transparent)]
151 #[diagnostic(transparent)]
152 UnrecognizedActionId(#[from] UnrecognizedActionId),
153 #[error(transparent)]
157 #[diagnostic(transparent)]
158 InvalidActionApplication(#[from] InvalidActionApplication),
159 #[error(transparent)]
161 #[diagnostic(transparent)]
162 TypeError(#[from] TypeErrorKind),
163 #[error(transparent)]
166 #[diagnostic(transparent)]
167 UnspecifiedEntity(#[from] UnspecifiedEntityError),
168}
169
170impl ValidationErrorKind {
171 pub(crate) fn unrecognized_entity_type(
172 actual_entity_type: String,
173 suggested_entity_type: Option<String>,
174 ) -> ValidationErrorKind {
175 UnrecognizedEntityType {
176 actual_entity_type,
177 suggested_entity_type,
178 }
179 .into()
180 }
181
182 pub(crate) fn unrecognized_action_id(
183 actual_action_id: String,
184 suggested_action_id: Option<String>,
185 ) -> ValidationErrorKind {
186 UnrecognizedActionId {
187 actual_action_id,
188 suggested_action_id,
189 }
190 .into()
191 }
192
193 pub(crate) fn invalid_action_application(
194 would_in_fix_principal: bool,
195 would_in_fix_resource: bool,
196 ) -> ValidationErrorKind {
197 InvalidActionApplication {
198 would_in_fix_principal,
199 would_in_fix_resource,
200 }
201 .into()
202 }
203
204 pub(crate) fn type_error(type_error: TypeErrorKind) -> ValidationErrorKind {
205 type_error.into()
206 }
207
208 pub(crate) fn unspecified_entity(entity_id: String) -> ValidationErrorKind {
209 UnspecifiedEntityError { entity_id }.into()
210 }
211}
212
213#[derive(Debug, Clone, Error)]
215#[cfg_attr(test, derive(Eq, PartialEq))]
216#[error("unrecognized entity type `{actual_entity_type}`")]
217pub struct UnrecognizedEntityType {
218 pub(crate) actual_entity_type: String,
220 pub(crate) suggested_entity_type: Option<String>,
223}
224
225impl Diagnostic for UnrecognizedEntityType {
226 fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
227 match &self.suggested_entity_type {
228 Some(s) => Some(Box::new(format!("did you mean `{s}`?"))),
229 None => None,
230 }
231 }
232}
233
234#[derive(Debug, Clone, Error)]
236#[cfg_attr(test, derive(Eq, PartialEq))]
237#[error("unrecognized action `{actual_action_id}`")]
238pub struct UnrecognizedActionId {
239 pub(crate) actual_action_id: String,
241 pub(crate) suggested_action_id: Option<String>,
244}
245
246impl Diagnostic for UnrecognizedActionId {
247 fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
248 match &self.suggested_action_id {
249 Some(s) => Some(Box::new(format!("did you mean `{s}`?"))),
250 None => None,
251 }
252 }
253}
254
255#[derive(Debug, Clone, Error)]
257#[cfg_attr(test, derive(Eq, PartialEq))]
258#[error("unable to find an applicable action given the policy scope constraints")]
259pub struct InvalidActionApplication {
260 pub(crate) would_in_fix_principal: bool,
261 pub(crate) would_in_fix_resource: bool,
262}
263
264impl Diagnostic for InvalidActionApplication {
265 fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
266 match (self.would_in_fix_principal, self.would_in_fix_resource) {
267 (true, false) => Some(Box::new(
268 "try replacing `==` with `in` in the principal clause",
269 )),
270 (false, true) => Some(Box::new(
271 "try replacing `==` with `in` in the resource clause",
272 )),
273 (true, true) => Some(Box::new(
274 "try replacing `==` with `in` in the principal clause and the resource clause",
275 )),
276 (false, false) => None,
277 }
278 }
279}
280
281#[derive(Debug, Clone, Diagnostic, Error)]
283#[cfg_attr(test, derive(Eq, PartialEq))]
284#[error("unspecified entity with id `{entity_id}`")]
285#[diagnostic(help("unspecified entities cannot be used in policies"))]
286pub struct UnspecifiedEntityError {
287 pub(crate) entity_id: String,
289}
290
291#[derive(Debug, Clone, Hash, Eq, PartialEq)]
293pub struct ValidationWarning<'a> {
294 pub(crate) location: SourceLocation<'a>,
295 pub(crate) kind: ValidationWarningKind,
296}
297
298impl<'a> ValidationWarning<'a> {
299 pub(crate) fn with_policy_id(
300 id: &'a PolicyID,
301 source_loc: Option<Loc>,
302 warning_kind: ValidationWarningKind,
303 ) -> Self {
304 Self {
305 kind: warning_kind,
306 location: SourceLocation::new(id, source_loc),
307 }
308 }
309
310 pub fn location(&self) -> &SourceLocation<'a> {
311 &self.location
312 }
313
314 pub fn kind(&self) -> &ValidationWarningKind {
315 &self.kind
316 }
317
318 pub fn to_kind_and_location(self) -> (SourceLocation<'a>, ValidationWarningKind) {
319 (self.location, self.kind)
320 }
321}
322
323impl std::fmt::Display for ValidationWarning<'_> {
324 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
325 write!(
326 f,
327 "validation warning on policy `{}`: {}",
328 self.location.policy_id(),
329 self.kind
330 )
331 }
332}
333
334#[derive(Debug, Clone, PartialEq, Diagnostic, Error, Eq, Hash)]
338#[non_exhaustive]
339#[diagnostic(severity(Warning))]
340pub enum ValidationWarningKind {
341 #[error("string `\"{0}\"` contains mixed scripts")]
343 MixedScriptString(String),
344 #[error("string `\"{0}\"` contains BIDI control characters")]
346 BidiCharsInString(String),
347 #[error("identifier `{0}` contains BIDI control characters")]
349 BidiCharsInIdentifier(String),
350 #[error("identifier `{0}` contains mixed scripts")]
352 MixedScriptIdentifier(String),
353 #[error("identifier `{0}` contains characters that fall outside of the General Security Profile for Identifiers")]
355 ConfusableIdentifier(String),
356 #[error(
358 "policy is impossible: the policy expression evaluates to false for all valid requests"
359 )]
360 ImpossiblePolicy,
361}