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 {
27 resource_type: String,
29 id: String
31 },
32
33 #[error("Schema not found: {schema_id}")]
35 SchemaNotFound {
36 schema_id: String
38 },
39
40 #[error("Internal server error: {message}")]
42 Internal {
43 message: String
45 },
46
47 #[error("Invalid request: {message}")]
49 InvalidRequest {
50 message: String
52 },
53
54 #[error("Unsupported resource type: {0}")]
56 UnsupportedResourceType(String),
57
58 #[error("Unsupported operation {operation} for resource type {resource_type}")]
60 UnsupportedOperation {
61 resource_type: String,
63 operation: String,
65 },
66
67 #[error("Method '{0}' not found")]
69 MethodNotFound(String),
70
71 #[error("Schema mapper at index {0} not found")]
73 MapperNotFound(usize),
74
75 #[error("Resource provider error: {0}")]
77 ProviderError(String),
78}
79
80#[derive(Debug, thiserror::Error)]
85pub enum ValidationError {
86 #[error("Required attribute '{attribute}' is missing")]
88 MissingRequiredAttribute {
89 attribute: String
91 },
92
93 #[error("Attribute '{attribute}' has invalid type, expected {expected}, got {actual}")]
95 InvalidAttributeType {
96 attribute: String,
98 expected: String,
100 actual: String,
102 },
103
104 #[error("Attribute '{attribute}' must be multi-valued (array)")]
106 ExpectedMultiValue {
107 attribute: String
109 },
110
111 #[error("Attribute '{attribute}' must be single-valued (not array)")]
113 ExpectedSingleValue {
114 attribute: String
116 },
117
118 #[error("Attribute '{attribute}' violates uniqueness constraint")]
120 UniquenesViolation {
121 attribute: String
123 },
124
125 #[error("Attribute '{attribute}' has invalid value '{value}', allowed values: {allowed:?}")]
127 InvalidCanonicalValue {
128 attribute: String,
130 value: String,
132 allowed: Vec<String>,
134 },
135
136 #[error("Complex attribute '{attribute}' missing required sub-attribute '{sub_attribute}'")]
138 MissingSubAttribute {
139 attribute: String,
141 sub_attribute: String,
143 },
144
145 #[error("Unknown attribute '{attribute}' in schema '{schema_id}'")]
147 UnknownAttribute {
148 attribute: String,
150 schema_id: String,
152 },
153
154 #[error("Validation failed: {message}")]
156 Custom {
157 message: String
159 },
160
161 #[error("Missing required 'schemas' attribute")]
163 MissingSchemas,
164
165 #[error("'schemas' array cannot be empty")]
167 EmptySchemas,
168
169 #[error("Invalid schema URI format: {uri}")]
171 InvalidSchemaUri {
172 uri: String
174 },
175
176 #[error("Unknown schema URI: {uri}")]
178 UnknownSchemaUri {
179 uri: String
181 },
182
183 #[error("Duplicate schema URI: {uri}")]
185 DuplicateSchemaUri {
186 uri: String
188 },
189
190 #[error("Missing base schema for resource type")]
192 MissingBaseSchema,
193
194 #[error("Extension schema requires base schema")]
196 ExtensionWithoutBase,
197
198 #[error("Missing required extension schema")]
200 MissingRequiredExtension,
201
202 #[error("Missing required 'id' attribute")]
204 MissingId,
205
206 #[error("'id' attribute cannot be empty")]
208 EmptyId,
209
210 #[error("Invalid 'id' format: {id}")]
212 InvalidIdFormat {
213 id: String
215 },
216
217 #[error("Client cannot provide 'id' during resource creation")]
219 ClientProvidedId,
220
221 #[error("Invalid 'externalId' format")]
223 InvalidExternalId,
224
225 #[error("Invalid 'meta' structure")]
227 InvalidMetaStructure,
228
229 #[error("Missing 'meta.resourceType'")]
231 MissingResourceType,
232
233 #[error("Invalid 'meta.resourceType': {resource_type}")]
235 InvalidResourceType {
236 resource_type: String
238 },
239
240 #[error("Client cannot provide read-only meta attributes")]
242 ClientProvidedMeta,
243
244 #[error("Invalid 'meta.created' datetime format")]
246 InvalidCreatedDateTime,
247
248 #[error("Invalid 'meta.lastModified' datetime format")]
250 InvalidModifiedDateTime,
251
252 #[error("Invalid 'meta.location' URI format")]
254 InvalidLocationUri,
255
256 #[error("Invalid 'meta.version' format")]
258 InvalidVersionFormat,
259
260 #[error("Attribute '{attribute}' has invalid type, expected {expected}, got {actual}")]
262 InvalidDataType {
263 attribute: String,
265 expected: String,
267 actual: String,
269 },
270
271 #[error("Attribute '{attribute}' has invalid string format: {details}")]
273 InvalidStringFormat {
274 attribute: String,
276 details: String
278 },
279
280 #[error("Attribute '{attribute}' has invalid boolean value: {value}")]
282 InvalidBooleanValue {
283 attribute: String,
285 value: String
287 },
288
289 #[error("Attribute '{attribute}' has invalid decimal format: {value}")]
291 InvalidDecimalFormat {
292 attribute: String,
294 value: String
296 },
297
298 #[error("Attribute '{attribute}' has invalid integer value: {value}")]
300 InvalidIntegerValue {
301 attribute: String,
303 value: String
305 },
306
307 #[error("Attribute '{attribute}' has invalid datetime format: {value}")]
309 InvalidDateTimeFormat {
310 attribute: String,
312 value: String
314 },
315
316 #[error("Attribute '{attribute}' has invalid binary data: {details}")]
318 InvalidBinaryData {
319 attribute: String,
321 details: String
323 },
324
325 #[error("Attribute '{attribute}' has invalid reference URI: {uri}")]
327 InvalidReferenceUri {
328 attribute: String,
330 uri: String
332 },
333
334 #[error("Attribute '{attribute}' has invalid reference type: {ref_type}")]
336 InvalidReferenceType {
337 attribute: String,
339 ref_type: String
341 },
342
343 #[error("Attribute '{attribute}' contains broken reference: {reference}")]
345 BrokenReference {
346 attribute: String,
348 reference: String,
350 },
351
352 #[error("Attribute '{attribute}' must be multi-valued (array)")]
355 SingleValueForMultiValued {
356 attribute: String
358 },
359
360 #[error("Attribute '{attribute}' must be single-valued (not array)")]
362 ArrayForSingleValued {
363 attribute: String
365 },
366
367 #[error("Attribute '{attribute}' cannot have multiple primary values")]
369 MultiplePrimaryValues {
370 attribute: String
372 },
373
374 #[error("Attribute '{attribute}' has invalid multi-valued structure: {details}")]
376 InvalidMultiValuedStructure {
377 attribute: String,
379 details: String
381 },
382
383 #[error("Attribute '{attribute}' missing required sub-attribute '{sub_attribute}'")]
385 MissingRequiredSubAttribute {
386 attribute: String,
388 sub_attribute: String,
390 },
391
392 #[error("Complex attribute '{attribute}' missing required sub-attributes: {missing:?}")]
395 MissingRequiredSubAttributes {
396 attribute: String,
398 missing: Vec<String>,
400 },
401
402 #[error(
404 "Complex attribute '{attribute}' has invalid sub-attribute '{sub_attribute}' type, expected {expected}, got {actual}"
405 )]
406 InvalidSubAttributeType {
407 attribute: String,
408 sub_attribute: String,
409 expected: String,
410 actual: String,
411 },
412
413 #[error("Complex attribute '{attribute}' contains unknown sub-attribute '{sub_attribute}'")]
415 UnknownSubAttribute {
416 attribute: String,
417 sub_attribute: String,
418 },
419
420 #[error("Nested complex attributes are not allowed: '{attribute}'")]
422 NestedComplexAttributes { attribute: String },
423
424 #[error("Complex attribute '{attribute}' has malformed structure: {details}")]
426 MalformedComplexStructure { attribute: String, details: String },
427
428 #[error("Attribute '{attribute}' violates case sensitivity rules: {details}")]
431 CaseSensitivityViolation { attribute: String, details: String },
432
433 #[error("Attribute '{attribute}' is read-only and cannot be modified")]
435 ReadOnlyMutabilityViolation { attribute: String },
436
437 #[error("Attribute '{attribute}' is immutable and cannot be modified after creation")]
439 ImmutableMutabilityViolation { attribute: String },
440
441 #[error("Attribute '{attribute}' is write-only and should not be returned")]
443 WriteOnlyAttributeReturned { attribute: String },
444
445 #[error("Attribute '{attribute}' violates server uniqueness constraint with value '{value}'")]
447 ServerUniquenessViolation { attribute: String, value: String },
448
449 #[error("Attribute '{attribute}' violates global uniqueness constraint with value '{value}'")]
451 GlobalUniquenessViolation { attribute: String, value: String },
452
453 #[error(
455 "Attribute '{attribute}' has invalid canonical value '{value}', allowed values: {allowed:?}"
456 )]
457 InvalidCanonicalValueChoice {
458 attribute: String,
459 value: String,
460 allowed: Vec<String>,
461 },
462
463 #[error("Unknown attribute '{attribute}' for schema '{schema}'")]
465 UnknownAttributeForSchema { attribute: String, schema: String },
466
467 #[error("Attribute '{attribute}' violates required characteristic '{characteristic}'")]
469 RequiredCharacteristicViolation {
470 attribute: String,
471 characteristic: String,
472 },
473
474 #[error("Unsupported attribute type '{type_name}' for attribute '{attribute}'")]
477 UnsupportedAttributeType {
478 attribute: String,
479 type_name: String,
480 },
481
482 #[error("Invalid attribute name '{actual}', expected '{expected}'")]
484 InvalidAttributeName { actual: String, expected: String },
485
486 #[error("Required attribute '{0}' is missing")]
488 RequiredAttributeMissing(String),
489
490 #[error("Null value provided for optional attribute '{0}'")]
492 NullValueForOptionalAttribute(String),
493
494 #[error("Expected array for multi-valued attribute '{0}'")]
496 ExpectedArray(String),
497
498 #[error("Invalid primary index {index} for attribute '{attribute}' (length: {length})")]
500 InvalidPrimaryIndex {
501 attribute: String,
502 index: usize,
503 length: usize,
504 },
505
506 #[error("Attribute '{0}' is not multi-valued")]
508 NotMultiValued(String),
509
510 #[error("Username '{0}' is reserved and cannot be used")]
512 ReservedUsername(String),
513
514 #[error("Username '{0}' is too short (minimum 3 characters)")]
516 UsernameTooShort(String),
517
518 #[error("Username '{0}' is too long (maximum 64 characters)")]
520 UsernameTooLong(String),
521
522 #[error("Username '{0}' has invalid format")]
524 InvalidUsernameFormat(String),
525
526 #[error("Email '{email}' has invalid domain, allowed domains: {allowed_domains:?}")]
528 InvalidEmailDomain {
529 email: String,
530 allowed_domains: Vec<String>,
531 },
532
533 #[error("A work email address is required")]
535 WorkEmailRequired,
536
537 #[error("External ID is required")]
539 ExternalIdRequired,
540
541 #[error("At least one name component (given, family, or formatted) is required")]
543 NameComponentRequired,
544
545 #[error("Formatted name cannot be empty")]
547 EmptyFormattedName,
548}
549
550#[derive(Debug, thiserror::Error)]
555pub enum BuildError {
556 #[error("Resource provider is required but not provided")]
558 MissingResourceProvider,
559
560 #[error("Invalid configuration: {message}")]
562 InvalidConfiguration { message: String },
563
564 #[error("Failed to load schema: {schema_id}")]
566 SchemaLoadError { schema_id: String },
567}
568
569impl ScimError {
571 pub fn resource_not_found(resource_type: impl Into<String>, id: impl Into<String>) -> Self {
573 Self::ResourceNotFound {
574 resource_type: resource_type.into(),
575 id: id.into(),
576 }
577 }
578
579 pub fn schema_not_found(schema_id: impl Into<String>) -> Self {
581 Self::SchemaNotFound {
582 schema_id: schema_id.into(),
583 }
584 }
585
586 pub fn internal(message: impl Into<String>) -> Self {
588 Self::Internal {
589 message: message.into(),
590 }
591 }
592
593 pub fn invalid_request(message: impl Into<String>) -> Self {
595 Self::InvalidRequest {
596 message: message.into(),
597 }
598 }
599
600 pub fn provider_error<E>(error: E) -> Self
602 where
603 E: std::error::Error + Send + Sync + 'static,
604 {
605 Self::Provider(Box::new(error))
606 }
607}
608
609impl ValidationError {
610 pub fn missing_required(attribute: impl Into<String>) -> Self {
612 Self::MissingRequiredAttribute {
613 attribute: attribute.into(),
614 }
615 }
616
617 pub fn invalid_type(
619 attribute: impl Into<String>,
620 expected: impl Into<String>,
621 actual: impl Into<String>,
622 ) -> Self {
623 Self::InvalidAttributeType {
624 attribute: attribute.into(),
625 expected: expected.into(),
626 actual: actual.into(),
627 }
628 }
629
630 pub fn custom(message: impl Into<String>) -> Self {
632 Self::Custom {
633 message: message.into(),
634 }
635 }
636}
637
638pub type ScimResult<T> = Result<T, ScimError>;
640pub type ValidationResult<T> = Result<T, ValidationError>;
641pub type BuildResult<T> = Result<T, BuildError>;
642
643impl From<serde_json::Error> for ValidationError {
645 fn from(error: serde_json::Error) -> Self {
646 ValidationError::Custom {
647 message: format!("JSON serialization error: {}", error),
648 }
649 }
650}
651
652#[cfg(test)]
653mod tests {
654 use super::*;
655
656 #[test]
657 fn test_error_creation() {
658 let error = ScimError::resource_not_found("User", "123");
659 assert!(error.to_string().contains("User"));
660 assert!(error.to_string().contains("123"));
661 }
662
663 #[test]
664 fn test_validation_error_creation() {
665 let error = ValidationError::missing_required("userName");
666 assert!(error.to_string().contains("userName"));
667 }
668
669 #[test]
670 fn test_error_chain() {
671 let validation_error = ValidationError::missing_required("userName");
672 let scim_error = ScimError::from(validation_error);
673 assert!(scim_error.to_string().contains("Validation error"));
674 }
675}