1#[derive(Debug, thiserror::Error)]
11pub enum ScimError {
12 #[error("Validation error: {0}")]
14 Validation(#[from] ValidationError),
15
16 #[error("Resource provider error: {0}")]
18 Provider(#[source] Box<dyn std::error::Error + Send + Sync>),
19
20 #[error("JSON error: {0}")]
22 Json(#[from] serde_json::Error),
23
24 #[error("Resource not found: {resource_type} with ID {id}")]
26 ResourceNotFound { resource_type: String, id: String },
27
28 #[error("Schema not found: {schema_id}")]
30 SchemaNotFound { schema_id: String },
31
32 #[error("Internal server error: {message}")]
34 Internal { message: String },
35
36 #[error("Invalid request: {message}")]
38 InvalidRequest { message: String },
39
40 #[error("Unsupported resource type: {0}")]
42 UnsupportedResourceType(String),
43
44 #[error("Unsupported operation '{operation}' for resource type '{resource_type}'")]
46 UnsupportedOperation {
47 resource_type: String,
48 operation: String,
49 },
50
51 #[error("Method '{0}' not found")]
53 MethodNotFound(String),
54
55 #[error("Schema mapper at index {0} not found")]
57 MapperNotFound(usize),
58
59 #[error("Resource provider error: {0}")]
61 ProviderError(String),
62}
63
64#[derive(Debug, thiserror::Error)]
69pub enum ValidationError {
70 #[error("Required attribute '{attribute}' is missing")]
72 MissingRequiredAttribute { attribute: String },
73
74 #[error("Attribute '{attribute}' has invalid type, expected {expected}, got {actual}")]
76 InvalidAttributeType {
77 attribute: String,
78 expected: String,
79 actual: String,
80 },
81
82 #[error("Attribute '{attribute}' must be multi-valued (array)")]
84 ExpectedMultiValue { attribute: String },
85
86 #[error("Attribute '{attribute}' must be single-valued (not array)")]
88 ExpectedSingleValue { attribute: String },
89
90 #[error("Attribute '{attribute}' violates uniqueness constraint")]
92 UniquenesViolation { attribute: String },
93
94 #[error("Attribute '{attribute}' has invalid value '{value}', allowed values: {allowed:?}")]
96 InvalidCanonicalValue {
97 attribute: String,
98 value: String,
99 allowed: Vec<String>,
100 },
101
102 #[error("Complex attribute '{attribute}' missing required sub-attribute '{sub_attribute}'")]
104 MissingSubAttribute {
105 attribute: String,
106 sub_attribute: String,
107 },
108
109 #[error("Unknown attribute '{attribute}' in schema '{schema_id}'")]
111 UnknownAttribute {
112 attribute: String,
113 schema_id: String,
114 },
115
116 #[error("Validation failed: {message}")]
118 Custom { message: String },
119
120 #[error("Missing required 'schemas' attribute")]
122 MissingSchemas,
123
124 #[error("'schemas' array cannot be empty")]
126 EmptySchemas,
127
128 #[error("Invalid schema URI format: {uri}")]
130 InvalidSchemaUri { uri: String },
131
132 #[error("Unknown schema URI: {uri}")]
134 UnknownSchemaUri { uri: String },
135
136 #[error("Duplicate schema URI: {uri}")]
138 DuplicateSchemaUri { uri: String },
139
140 #[error("Missing base schema for resource type")]
142 MissingBaseSchema,
143
144 #[error("Extension schema requires base schema")]
146 ExtensionWithoutBase,
147
148 #[error("Missing required extension schema")]
150 MissingRequiredExtension,
151
152 #[error("Missing required 'id' attribute")]
154 MissingId,
155
156 #[error("'id' attribute cannot be empty")]
158 EmptyId,
159
160 #[error("Invalid 'id' format: {id}")]
162 InvalidIdFormat { id: String },
163
164 #[error("Client cannot provide 'id' during resource creation")]
166 ClientProvidedId,
167
168 #[error("Invalid 'externalId' format")]
170 InvalidExternalId,
171
172 #[error("Invalid 'meta' structure")]
174 InvalidMetaStructure,
175
176 #[error("Missing 'meta.resourceType'")]
178 MissingResourceType,
179
180 #[error("Invalid 'meta.resourceType': {resource_type}")]
182 InvalidResourceType { resource_type: String },
183
184 #[error("Client cannot provide read-only meta attributes")]
186 ClientProvidedMeta,
187
188 #[error("Invalid 'meta.created' datetime format")]
190 InvalidCreatedDateTime,
191
192 #[error("Invalid 'meta.lastModified' datetime format")]
194 InvalidModifiedDateTime,
195
196 #[error("Invalid 'meta.location' URI format")]
198 InvalidLocationUri,
199
200 #[error("Invalid 'meta.version' format")]
202 InvalidVersionFormat,
203
204 #[error("Attribute '{attribute}' has invalid type, expected {expected}, got {actual}")]
206 InvalidDataType {
207 attribute: String,
208 expected: String,
209 actual: String,
210 },
211
212 #[error("Attribute '{attribute}' has invalid string format: {details}")]
214 InvalidStringFormat { attribute: String, details: String },
215
216 #[error("Attribute '{attribute}' has invalid boolean value: {value}")]
218 InvalidBooleanValue { attribute: String, value: String },
219
220 #[error("Attribute '{attribute}' has invalid decimal format: {value}")]
222 InvalidDecimalFormat { attribute: String, value: String },
223
224 #[error("Attribute '{attribute}' has invalid integer value: {value}")]
226 InvalidIntegerValue { attribute: String, value: String },
227
228 #[error("Attribute '{attribute}' has invalid datetime format: {value}")]
230 InvalidDateTimeFormat { attribute: String, value: String },
231
232 #[error("Attribute '{attribute}' has invalid binary data: {details}")]
234 InvalidBinaryData { attribute: String, details: String },
235
236 #[error("Attribute '{attribute}' has invalid reference URI: {uri}")]
238 InvalidReferenceUri { attribute: String, uri: String },
239
240 #[error("Attribute '{attribute}' has invalid reference type: {ref_type}")]
242 InvalidReferenceType { attribute: String, ref_type: String },
243
244 #[error("Attribute '{attribute}' contains broken reference: {reference}")]
246 BrokenReference {
247 attribute: String,
248 reference: String,
249 },
250
251 #[error("Attribute '{attribute}' must be multi-valued (array)")]
254 SingleValueForMultiValued { attribute: String },
255
256 #[error("Attribute '{attribute}' must be single-valued (not array)")]
258 ArrayForSingleValued { attribute: String },
259
260 #[error("Attribute '{attribute}' cannot have multiple primary values")]
262 MultiplePrimaryValues { attribute: String },
263
264 #[error("Attribute '{attribute}' has invalid multi-valued structure: {details}")]
266 InvalidMultiValuedStructure { attribute: String, details: String },
267
268 #[error("Attribute '{attribute}' missing required sub-attribute '{sub_attribute}'")]
270 MissingRequiredSubAttribute {
271 attribute: String,
272 sub_attribute: String,
273 },
274
275 #[error("Complex attribute '{attribute}' missing required sub-attributes: {missing:?}")]
278 MissingRequiredSubAttributes {
279 attribute: String,
280 missing: Vec<String>,
281 },
282
283 #[error(
285 "Complex attribute '{attribute}' has invalid sub-attribute '{sub_attribute}' type, expected {expected}, got {actual}"
286 )]
287 InvalidSubAttributeType {
288 attribute: String,
289 sub_attribute: String,
290 expected: String,
291 actual: String,
292 },
293
294 #[error("Complex attribute '{attribute}' contains unknown sub-attribute '{sub_attribute}'")]
296 UnknownSubAttribute {
297 attribute: String,
298 sub_attribute: String,
299 },
300
301 #[error("Nested complex attributes are not allowed: '{attribute}'")]
303 NestedComplexAttributes { attribute: String },
304
305 #[error("Complex attribute '{attribute}' has malformed structure: {details}")]
307 MalformedComplexStructure { attribute: String, details: String },
308
309 #[error("Attribute '{attribute}' violates case sensitivity rules: {details}")]
312 CaseSensitivityViolation { attribute: String, details: String },
313
314 #[error("Attribute '{attribute}' is read-only and cannot be modified")]
316 ReadOnlyMutabilityViolation { attribute: String },
317
318 #[error("Attribute '{attribute}' is immutable and cannot be modified after creation")]
320 ImmutableMutabilityViolation { attribute: String },
321
322 #[error("Attribute '{attribute}' is write-only and should not be returned")]
324 WriteOnlyAttributeReturned { attribute: String },
325
326 #[error("Attribute '{attribute}' violates server uniqueness constraint with value '{value}'")]
328 ServerUniquenessViolation { attribute: String, value: String },
329
330 #[error("Attribute '{attribute}' violates global uniqueness constraint with value '{value}'")]
332 GlobalUniquenessViolation { attribute: String, value: String },
333
334 #[error(
336 "Attribute '{attribute}' has invalid canonical value '{value}', allowed values: {allowed:?}"
337 )]
338 InvalidCanonicalValueChoice {
339 attribute: String,
340 value: String,
341 allowed: Vec<String>,
342 },
343
344 #[error("Unknown attribute '{attribute}' for schema '{schema}'")]
346 UnknownAttributeForSchema { attribute: String, schema: String },
347
348 #[error("Attribute '{attribute}' violates required characteristic '{characteristic}'")]
350 RequiredCharacteristicViolation {
351 attribute: String,
352 characteristic: String,
353 },
354
355 #[error("Unsupported attribute type '{type_name}' for attribute '{attribute}'")]
358 UnsupportedAttributeType {
359 attribute: String,
360 type_name: String,
361 },
362
363 #[error("Invalid attribute name '{actual}', expected '{expected}'")]
365 InvalidAttributeName { actual: String, expected: String },
366
367 #[error("Required attribute '{0}' is missing")]
369 RequiredAttributeMissing(String),
370
371 #[error("Null value provided for optional attribute '{0}'")]
373 NullValueForOptionalAttribute(String),
374
375 #[error("Expected array for multi-valued attribute '{0}'")]
377 ExpectedArray(String),
378
379 #[error("Invalid primary index {index} for attribute '{attribute}' (length: {length})")]
381 InvalidPrimaryIndex {
382 attribute: String,
383 index: usize,
384 length: usize,
385 },
386
387 #[error("Attribute '{0}' is not multi-valued")]
389 NotMultiValued(String),
390
391 #[error("Username '{0}' is reserved and cannot be used")]
393 ReservedUsername(String),
394
395 #[error("Username '{0}' is too short (minimum 3 characters)")]
397 UsernameTooShort(String),
398
399 #[error("Username '{0}' is too long (maximum 64 characters)")]
401 UsernameTooLong(String),
402
403 #[error("Username '{0}' has invalid format")]
405 InvalidUsernameFormat(String),
406
407 #[error("Email '{email}' has invalid domain, allowed domains: {allowed_domains:?}")]
409 InvalidEmailDomain {
410 email: String,
411 allowed_domains: Vec<String>,
412 },
413
414 #[error("A work email address is required")]
416 WorkEmailRequired,
417
418 #[error("External ID is required")]
420 ExternalIdRequired,
421
422 #[error("At least one name component (given, family, or formatted) is required")]
424 NameComponentRequired,
425
426 #[error("Formatted name cannot be empty")]
428 EmptyFormattedName,
429}
430
431#[derive(Debug, thiserror::Error)]
436pub enum BuildError {
437 #[error("Resource provider is required but not provided")]
439 MissingResourceProvider,
440
441 #[error("Invalid configuration: {message}")]
443 InvalidConfiguration { message: String },
444
445 #[error("Failed to load schema: {schema_id}")]
447 SchemaLoadError { schema_id: String },
448}
449
450impl ScimError {
452 pub fn resource_not_found(resource_type: impl Into<String>, id: impl Into<String>) -> Self {
454 Self::ResourceNotFound {
455 resource_type: resource_type.into(),
456 id: id.into(),
457 }
458 }
459
460 pub fn schema_not_found(schema_id: impl Into<String>) -> Self {
462 Self::SchemaNotFound {
463 schema_id: schema_id.into(),
464 }
465 }
466
467 pub fn internal(message: impl Into<String>) -> Self {
469 Self::Internal {
470 message: message.into(),
471 }
472 }
473
474 pub fn invalid_request(message: impl Into<String>) -> Self {
476 Self::InvalidRequest {
477 message: message.into(),
478 }
479 }
480
481 pub fn provider_error<E>(error: E) -> Self
483 where
484 E: std::error::Error + Send + Sync + 'static,
485 {
486 Self::Provider(Box::new(error))
487 }
488}
489
490impl ValidationError {
491 pub fn missing_required(attribute: impl Into<String>) -> Self {
493 Self::MissingRequiredAttribute {
494 attribute: attribute.into(),
495 }
496 }
497
498 pub fn invalid_type(
500 attribute: impl Into<String>,
501 expected: impl Into<String>,
502 actual: impl Into<String>,
503 ) -> Self {
504 Self::InvalidAttributeType {
505 attribute: attribute.into(),
506 expected: expected.into(),
507 actual: actual.into(),
508 }
509 }
510
511 pub fn custom(message: impl Into<String>) -> Self {
513 Self::Custom {
514 message: message.into(),
515 }
516 }
517}
518
519pub type ScimResult<T> = Result<T, ScimError>;
521pub type ValidationResult<T> = Result<T, ValidationError>;
522pub type BuildResult<T> = Result<T, BuildError>;
523
524impl From<serde_json::Error> for ValidationError {
526 fn from(error: serde_json::Error) -> Self {
527 ValidationError::Custom {
528 message: format!("JSON serialization error: {}", error),
529 }
530 }
531}
532
533#[cfg(test)]
534mod tests {
535 use super::*;
536
537 #[test]
538 fn test_error_creation() {
539 let error = ScimError::resource_not_found("User", "123");
540 assert!(error.to_string().contains("User"));
541 assert!(error.to_string().contains("123"));
542 }
543
544 #[test]
545 fn test_validation_error_creation() {
546 let error = ValidationError::missing_required("userName");
547 assert!(error.to_string().contains("userName"));
548 }
549
550 #[test]
551 fn test_error_chain() {
552 let validation_error = ValidationError::missing_required("userName");
553 let scim_error = ScimError::from(validation_error);
554 assert!(scim_error.to_string().contains("Validation error"));
555 }
556}