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("Resource provider error: {0}")]
69 ProviderError(String),
70}
71
72#[derive(Debug, thiserror::Error)]
77pub enum ValidationError {
78 #[error("Required attribute '{attribute}' is missing")]
80 MissingRequiredAttribute {
81 attribute: String,
83 },
84
85 #[error("Attribute '{attribute}' has invalid type, expected {expected}, got {actual}")]
87 InvalidAttributeType {
88 attribute: String,
90 expected: String,
92 actual: String,
94 },
95
96 #[error("Attribute '{attribute}' must be multi-valued (array)")]
98 ExpectedMultiValue {
99 attribute: String,
101 },
102
103 #[error("Attribute '{attribute}' must be single-valued (not array)")]
105 ExpectedSingleValue {
106 attribute: String,
108 },
109
110 #[error("Attribute '{attribute}' violates uniqueness constraint")]
112 UniquenesViolation {
113 attribute: String,
115 },
116
117 #[error("Attribute '{attribute}' has invalid value '{value}', allowed values: {allowed:?}")]
119 InvalidCanonicalValue {
120 attribute: String,
122 value: String,
124 allowed: Vec<String>,
126 },
127
128 #[error("Complex attribute '{attribute}' missing required sub-attribute '{sub_attribute}'")]
130 MissingSubAttribute {
131 attribute: String,
133 sub_attribute: String,
135 },
136
137 #[error("Unknown attribute '{attribute}' in schema '{schema_id}'")]
139 UnknownAttribute {
140 attribute: String,
142 schema_id: String,
144 },
145
146 #[error("Validation failed: {message}")]
148 Custom {
149 message: String,
151 },
152
153 #[error("Missing required 'schemas' attribute")]
155 MissingSchemas,
156
157 #[error("'schemas' array cannot be empty")]
159 EmptySchemas,
160
161 #[error("Invalid schema URI format: {uri}")]
163 InvalidSchemaUri {
164 uri: String,
166 },
167
168 #[error("Unknown schema URI: {uri}")]
170 UnknownSchemaUri {
171 uri: String,
173 },
174
175 #[error("Duplicate schema URI: {uri}")]
177 DuplicateSchemaUri {
178 uri: String,
180 },
181
182 #[error("Missing base schema for resource type")]
184 MissingBaseSchema,
185
186 #[error("Extension schema requires base schema")]
188 ExtensionWithoutBase,
189
190 #[error("Missing required extension schema")]
192 MissingRequiredExtension,
193
194 #[error("Missing required 'id' attribute")]
196 MissingId,
197
198 #[error("'id' attribute cannot be empty")]
200 EmptyId,
201
202 #[error("Invalid 'id' format: {id}")]
204 InvalidIdFormat {
205 id: String,
207 },
208
209 #[error("Client cannot provide 'id' during resource creation")]
211 ClientProvidedId,
212
213 #[error("Invalid 'externalId' format")]
215 InvalidExternalId,
216
217 #[error("Invalid 'meta' structure")]
219 InvalidMetaStructure,
220
221 #[error("Missing 'meta.resourceType'")]
223 MissingResourceType,
224
225 #[error("Invalid 'meta.resourceType': {resource_type}")]
227 InvalidResourceType {
228 resource_type: String,
230 },
231
232 #[error("Client cannot provide read-only meta attributes")]
234 ClientProvidedMeta,
235
236 #[error("Invalid 'meta.created' datetime format")]
238 InvalidCreatedDateTime,
239
240 #[error("Invalid 'meta.lastModified' datetime format")]
242 InvalidModifiedDateTime,
243
244 #[error("Invalid 'meta.location' URI format")]
246 InvalidLocationUri,
247
248 #[error("Invalid 'meta.version' format")]
250 InvalidVersionFormat,
251
252 #[error("Attribute '{attribute}' has invalid type, expected {expected}, got {actual}")]
254 InvalidDataType {
255 attribute: String,
257 expected: String,
259 actual: String,
261 },
262
263 #[error("Attribute '{attribute}' has invalid string format: {details}")]
265 InvalidStringFormat {
266 attribute: String,
268 details: String,
270 },
271
272 #[error("Attribute '{attribute}' has invalid boolean value: {value}")]
274 InvalidBooleanValue {
275 attribute: String,
277 value: String,
279 },
280
281 #[error("Attribute '{attribute}' has invalid decimal format: {value}")]
283 InvalidDecimalFormat {
284 attribute: String,
286 value: String,
288 },
289
290 #[error("Attribute '{attribute}' has invalid integer value: {value}")]
292 InvalidIntegerValue {
293 attribute: String,
295 value: String,
297 },
298
299 #[error("Attribute '{attribute}' has invalid datetime format: {value}")]
301 InvalidDateTimeFormat {
302 attribute: String,
304 value: String,
306 },
307
308 #[error("Attribute '{attribute}' has invalid binary data: {details}")]
310 InvalidBinaryData {
311 attribute: String,
313 details: String,
315 },
316
317 #[error("Attribute '{attribute}' has invalid reference URI: {uri}")]
319 InvalidReferenceUri {
320 attribute: String,
322 uri: String,
324 },
325
326 #[error("Attribute '{attribute}' has invalid reference type: {ref_type}")]
328 InvalidReferenceType {
329 attribute: String,
331 ref_type: String,
333 },
334
335 #[error("Attribute '{attribute}' contains broken reference: {reference}")]
337 BrokenReference {
338 attribute: String,
340 reference: String,
342 },
343
344 #[error("Attribute '{attribute}' must be multi-valued (array)")]
347 SingleValueForMultiValued {
348 attribute: String,
350 },
351
352 #[error("Attribute '{attribute}' must be single-valued (not array)")]
354 ArrayForSingleValued {
355 attribute: String,
357 },
358
359 #[error("Attribute '{attribute}' cannot have multiple primary values")]
361 MultiplePrimaryValues {
362 attribute: String,
364 },
365
366 #[error("Attribute '{attribute}' has invalid multi-valued structure: {details}")]
368 InvalidMultiValuedStructure {
369 attribute: String,
371 details: String,
373 },
374
375 #[error("Attribute '{attribute}' missing required sub-attribute '{sub_attribute}'")]
377 MissingRequiredSubAttribute {
378 attribute: String,
380 sub_attribute: String,
382 },
383
384 #[error("Complex attribute '{attribute}' missing required sub-attributes: {missing:?}")]
387 MissingRequiredSubAttributes {
388 attribute: String,
390 missing: Vec<String>,
392 },
393
394 #[error(
396 "Complex attribute '{attribute}' has invalid sub-attribute '{sub_attribute}' type, expected {expected}, got {actual}"
397 )]
398 InvalidSubAttributeType {
399 attribute: String,
400 sub_attribute: String,
401 expected: String,
402 actual: String,
403 },
404
405 #[error("Complex attribute '{attribute}' contains unknown sub-attribute '{sub_attribute}'")]
407 UnknownSubAttribute {
408 attribute: String,
409 sub_attribute: String,
410 },
411
412 #[error("Nested complex attributes are not allowed: '{attribute}'")]
414 NestedComplexAttributes { attribute: String },
415
416 #[error("Complex attribute '{attribute}' has malformed structure: {details}")]
418 MalformedComplexStructure { attribute: String, details: String },
419
420 #[error("Attribute '{attribute}' violates case sensitivity rules: {details}")]
423 CaseSensitivityViolation { attribute: String, details: String },
424
425 #[error("Attribute '{attribute}' is read-only and cannot be modified")]
427 ReadOnlyMutabilityViolation { attribute: String },
428
429 #[error("Attribute '{attribute}' is immutable and cannot be modified after creation")]
431 ImmutableMutabilityViolation { attribute: String },
432
433 #[error("Attribute '{attribute}' is write-only and should not be returned")]
435 WriteOnlyAttributeReturned { attribute: String },
436
437 #[error("Attribute '{attribute}' violates server uniqueness constraint with value '{value}'")]
439 ServerUniquenessViolation { attribute: String, value: String },
440
441 #[error("Attribute '{attribute}' violates global uniqueness constraint with value '{value}'")]
443 GlobalUniquenessViolation { attribute: String, value: String },
444
445 #[error(
447 "Attribute '{attribute}' has invalid canonical value '{value}', allowed values: {allowed:?}"
448 )]
449 InvalidCanonicalValueChoice {
450 attribute: String,
451 value: String,
452 allowed: Vec<String>,
453 },
454
455 #[error("Unknown attribute '{attribute}' for schema '{schema}'")]
457 UnknownAttributeForSchema { attribute: String, schema: String },
458
459 #[error("Attribute '{attribute}' violates required characteristic '{characteristic}'")]
461 RequiredCharacteristicViolation {
462 attribute: String,
463 characteristic: String,
464 },
465
466 #[error("Unsupported attribute type '{type_name}' for attribute '{attribute}'")]
469 UnsupportedAttributeType {
470 attribute: String,
471 type_name: String,
472 },
473
474 #[error("Invalid attribute name '{actual}', expected '{expected}'")]
476 InvalidAttributeName { actual: String, expected: String },
477
478 #[error("Required attribute '{0}' is missing")]
480 RequiredAttributeMissing(String),
481
482 #[error("Null value provided for optional attribute '{0}'")]
484 NullValueForOptionalAttribute(String),
485
486 #[error("Expected array for multi-valued attribute '{0}'")]
488 ExpectedArray(String),
489
490 #[error("Invalid primary index {index} for attribute '{attribute}' (length: {length})")]
492 InvalidPrimaryIndex {
493 attribute: String,
494 index: usize,
495 length: usize,
496 },
497
498 #[error("Attribute '{0}' is not multi-valued")]
500 NotMultiValued(String),
501
502 #[error("Username '{0}' is reserved and cannot be used")]
504 ReservedUsername(String),
505
506 #[error("Username '{0}' is too short (minimum 3 characters)")]
508 UsernameTooShort(String),
509
510 #[error("Username '{0}' is too long (maximum 64 characters)")]
512 UsernameTooLong(String),
513
514 #[error("Username '{0}' has invalid format")]
516 InvalidUsernameFormat(String),
517
518 #[error("Email '{email}' has invalid domain, allowed domains: {allowed_domains:?}")]
520 InvalidEmailDomain {
521 email: String,
522 allowed_domains: Vec<String>,
523 },
524
525 #[error("A work email address is required")]
527 WorkEmailRequired,
528
529 #[error("External ID is required")]
531 ExternalIdRequired,
532
533 #[error("At least one name component (given, family, or formatted) is required")]
535 NameComponentRequired,
536
537 #[error("Formatted name cannot be empty")]
539 EmptyFormattedName,
540}
541
542#[derive(Debug, thiserror::Error)]
547pub enum BuildError {
548 #[error("Resource provider is required but not provided")]
550 MissingResourceProvider,
551
552 #[error("Invalid configuration: {message}")]
554 InvalidConfiguration { message: String },
555
556 #[error("Failed to load schema: {schema_id}")]
558 SchemaLoadError { schema_id: String },
559}
560
561impl ScimError {
563 pub fn resource_not_found(resource_type: impl Into<String>, id: impl Into<String>) -> Self {
565 Self::ResourceNotFound {
566 resource_type: resource_type.into(),
567 id: id.into(),
568 }
569 }
570
571 pub fn schema_not_found(schema_id: impl Into<String>) -> Self {
573 Self::SchemaNotFound {
574 schema_id: schema_id.into(),
575 }
576 }
577
578 pub fn internal(message: impl Into<String>) -> Self {
580 Self::Internal {
581 message: message.into(),
582 }
583 }
584
585 pub fn invalid_request(message: impl Into<String>) -> Self {
587 Self::InvalidRequest {
588 message: message.into(),
589 }
590 }
591
592 pub fn provider_error<E>(error: E) -> Self
594 where
595 E: std::error::Error + Send + Sync + 'static,
596 {
597 Self::Provider(Box::new(error))
598 }
599}
600
601impl ValidationError {
602 pub fn missing_required(attribute: impl Into<String>) -> Self {
604 Self::MissingRequiredAttribute {
605 attribute: attribute.into(),
606 }
607 }
608
609 pub fn invalid_type(
611 attribute: impl Into<String>,
612 expected: impl Into<String>,
613 actual: impl Into<String>,
614 ) -> Self {
615 Self::InvalidAttributeType {
616 attribute: attribute.into(),
617 expected: expected.into(),
618 actual: actual.into(),
619 }
620 }
621
622 pub fn custom(message: impl Into<String>) -> Self {
624 Self::Custom {
625 message: message.into(),
626 }
627 }
628}
629
630pub type ScimResult<T> = Result<T, ScimError>;
632pub type ValidationResult<T> = Result<T, ValidationError>;
633pub type BuildResult<T> = Result<T, BuildError>;
634
635impl From<serde_json::Error> for ValidationError {
637 fn from(error: serde_json::Error) -> Self {
638 ValidationError::Custom {
639 message: format!("JSON serialization error: {}", error),
640 }
641 }
642}
643
644#[cfg(test)]
645mod tests {
646 use super::*;
647
648 #[test]
649 fn test_error_creation() {
650 let error = ScimError::resource_not_found("User", "123");
651 assert!(error.to_string().contains("User"));
652 assert!(error.to_string().contains("123"));
653 }
654
655 #[test]
656 fn test_validation_error_creation() {
657 let error = ValidationError::missing_required("userName");
658 assert!(error.to_string().contains("userName"));
659 }
660
661 #[test]
662 fn test_error_chain() {
663 let validation_error = ValidationError::missing_required("userName");
664 let scim_error = ScimError::from(validation_error);
665 assert!(scim_error.to_string().contains("Validation error"));
666 }
667}