scim_server/
error.rs

1//! Error types for SCIM server operations.
2//!
3//! This module provides comprehensive error handling for all SCIM operations,
4//! following Rust's error handling best practices with detailed error information.
5
6/// Main error type for SCIM server operations.
7///
8/// This enum covers all possible error conditions that can occur during
9/// SCIM server operations, providing detailed context for each error type.
10#[derive(Debug, thiserror::Error)]
11pub enum ScimError {
12    /// Validation errors when resource data doesn't conform to schema
13    #[error("Validation error: {0}")]
14    Validation(#[from] ValidationError),
15
16    /// Errors from the user-provided resource provider
17    #[error("Resource provider error: {0}")]
18    Provider(#[source] Box<dyn std::error::Error + Send + Sync>),
19
20    /// JSON serialization/deserialization errors
21    #[error("JSON error: {0}")]
22    Json(#[from] serde_json::Error),
23
24    /// Resource not found errors
25    #[error("Resource not found: {resource_type} with ID {id}")]
26    ResourceNotFound {
27        /// The type of resource that was not found
28        resource_type: String,
29        /// The ID of the resource that was not found
30        id: String
31    },
32
33    /// Schema not found errors
34    #[error("Schema not found: {schema_id}")]
35    SchemaNotFound {
36        /// The ID of the schema that was not found
37        schema_id: String
38    },
39
40    /// Internal server errors
41    #[error("Internal server error: {message}")]
42    Internal {
43        /// Description of the internal error
44        message: String
45    },
46
47    /// Invalid request format or parameters
48    #[error("Invalid request: {message}")]
49    InvalidRequest {
50        /// Description of what makes the request invalid
51        message: String
52    },
53
54    /// Unsupported resource type
55    #[error("Unsupported resource type: {0}")]
56    UnsupportedResourceType(String),
57
58    /// Unsupported operation for resource type
59    #[error("Unsupported operation {operation} for resource type {resource_type}")]
60    UnsupportedOperation {
61        /// The resource type for which the operation is unsupported
62        resource_type: String,
63        /// The operation that is not supported
64        operation: String,
65    },
66
67    /// Method not found on resource
68    #[error("Method '{0}' not found")]
69    MethodNotFound(String),
70
71    /// Schema mapper not found
72    #[error("Schema mapper at index {0} not found")]
73    MapperNotFound(usize),
74
75    /// Resource provider error with string message
76    #[error("Resource provider error: {0}")]
77    ProviderError(String),
78}
79
80/// Validation errors for schema compliance checking.
81///
82/// These errors occur when resource data doesn't conform to the defined schema,
83/// providing detailed information about what validation rule was violated.
84#[derive(Debug, thiserror::Error)]
85pub enum ValidationError {
86    /// Required attribute is missing
87    #[error("Required attribute '{attribute}' is missing")]
88    MissingRequiredAttribute {
89        /// The name of the missing required attribute
90        attribute: String
91    },
92
93    /// Attribute value doesn't match expected type
94    #[error("Attribute '{attribute}' has invalid type, expected {expected}, got {actual}")]
95    InvalidAttributeType {
96        /// The name of the attribute with invalid type
97        attribute: String,
98        /// The expected type for this attribute
99        expected: String,
100        /// The actual type that was provided
101        actual: String,
102    },
103
104    /// Multi-valued attribute provided as single value
105    #[error("Attribute '{attribute}' must be multi-valued (array)")]
106    ExpectedMultiValue {
107        /// The name of the attribute that should be multi-valued
108        attribute: String
109    },
110
111    /// Single-valued attribute provided as array
112    #[error("Attribute '{attribute}' must be single-valued (not array)")]
113    ExpectedSingleValue {
114        /// The name of the attribute that should be single-valued
115        attribute: String
116    },
117
118    /// Attribute value violates uniqueness constraint
119    #[error("Attribute '{attribute}' violates uniqueness constraint")]
120    UniquenesViolation {
121        /// The name of the attribute that violates uniqueness
122        attribute: String
123    },
124
125    /// Invalid value for attribute with canonical values
126    #[error("Attribute '{attribute}' has invalid value '{value}', allowed values: {allowed:?}")]
127    InvalidCanonicalValue {
128        /// The name of the attribute with invalid canonical value
129        attribute: String,
130        /// The invalid value that was provided
131        value: String,
132        /// The list of allowed canonical values
133        allowed: Vec<String>,
134    },
135
136    /// Complex attribute missing required sub-attributes
137    #[error("Complex attribute '{attribute}' missing required sub-attribute '{sub_attribute}'")]
138    MissingSubAttribute {
139        /// The name of the complex attribute
140        attribute: String,
141        /// The name of the missing required sub-attribute
142        sub_attribute: String,
143    },
144
145    /// Unknown attribute in resource
146    #[error("Unknown attribute '{attribute}' in schema '{schema_id}'")]
147    UnknownAttribute {
148        /// The name of the unknown attribute
149        attribute: String,
150        /// The ID of the schema where the attribute was not found
151        schema_id: String,
152    },
153
154    /// General validation error with custom message
155    #[error("Validation failed: {message}")]
156    Custom {
157        /// Custom validation error message
158        message: String
159    },
160
161    /// Missing schemas attribute
162    #[error("Missing required 'schemas' attribute")]
163    MissingSchemas,
164
165    /// Empty schemas array
166    #[error("'schemas' array cannot be empty")]
167    EmptySchemas,
168
169    /// Invalid schema URI format
170    #[error("Invalid schema URI format: {uri}")]
171    InvalidSchemaUri {
172        /// The invalid schema URI
173        uri: String
174    },
175
176    /// Unknown schema URI
177    #[error("Unknown schema URI: {uri}")]
178    UnknownSchemaUri {
179        /// The unknown schema URI
180        uri: String
181    },
182
183    /// Duplicate schema URI
184    #[error("Duplicate schema URI: {uri}")]
185    DuplicateSchemaUri {
186        /// The duplicated schema URI
187        uri: String
188    },
189
190    /// Missing base schema
191    #[error("Missing base schema for resource type")]
192    MissingBaseSchema,
193
194    /// Extension without base schema
195    #[error("Extension schema requires base schema")]
196    ExtensionWithoutBase,
197
198    /// Missing required extension
199    #[error("Missing required extension schema")]
200    MissingRequiredExtension,
201
202    /// Missing id attribute
203    #[error("Missing required 'id' attribute")]
204    MissingId,
205
206    /// Empty id value
207    #[error("'id' attribute cannot be empty")]
208    EmptyId,
209
210    /// Invalid id format
211    #[error("Invalid 'id' format: {id}")]
212    InvalidIdFormat {
213        /// The invalid ID value that was provided
214        id: String
215    },
216
217    /// Client provided id in creation
218    #[error("Client cannot provide 'id' during resource creation")]
219    ClientProvidedId,
220
221    /// Invalid external id
222    #[error("Invalid 'externalId' format")]
223    InvalidExternalId,
224
225    /// Invalid meta structure
226    #[error("Invalid 'meta' structure")]
227    InvalidMetaStructure,
228
229    /// Missing meta resource type
230    #[error("Missing 'meta.resourceType'")]
231    MissingResourceType,
232
233    /// Invalid meta resource type
234    #[error("Invalid 'meta.resourceType': {resource_type}")]
235    InvalidResourceType {
236        /// The invalid resource type value
237        resource_type: String
238    },
239
240    /// Client provided meta
241    #[error("Client cannot provide read-only meta attributes")]
242    ClientProvidedMeta,
243
244    /// Invalid created datetime
245    #[error("Invalid 'meta.created' datetime format")]
246    InvalidCreatedDateTime,
247
248    /// Invalid modified datetime
249    #[error("Invalid 'meta.lastModified' datetime format")]
250    InvalidModifiedDateTime,
251
252    /// Invalid location URI
253    #[error("Invalid 'meta.location' URI format")]
254    InvalidLocationUri,
255
256    /// Invalid version format
257    #[error("Invalid 'meta.version' format")]
258    InvalidVersionFormat,
259
260    /// Invalid data type for attribute
261    #[error("Attribute '{attribute}' has invalid type, expected {expected}, got {actual}")]
262    InvalidDataType {
263        /// The name of the attribute with invalid data type
264        attribute: String,
265        /// The expected data type
266        expected: String,
267        /// The actual data type that was provided
268        actual: String,
269    },
270
271    /// Invalid string format
272    #[error("Attribute '{attribute}' has invalid string format: {details}")]
273    InvalidStringFormat {
274        /// The name of the attribute with invalid string format
275        attribute: String,
276        /// Details about what makes the format invalid
277        details: String
278    },
279
280    /// Invalid boolean value
281    #[error("Attribute '{attribute}' has invalid boolean value: {value}")]
282    InvalidBooleanValue {
283        /// The name of the attribute with invalid boolean value
284        attribute: String,
285        /// The invalid boolean value that was provided
286        value: String
287    },
288
289    /// Invalid decimal format
290    #[error("Attribute '{attribute}' has invalid decimal format: {value}")]
291    InvalidDecimalFormat {
292        /// The name of the attribute with invalid decimal format
293        attribute: String,
294        /// The invalid decimal value that was provided
295        value: String
296    },
297
298    /// Invalid integer value
299    #[error("Attribute '{attribute}' has invalid integer value: {value}")]
300    InvalidIntegerValue {
301        /// The name of the attribute with invalid integer value
302        attribute: String,
303        /// The invalid integer value that was provided
304        value: String
305    },
306
307    /// Invalid datetime format
308    #[error("Attribute '{attribute}' has invalid datetime format: {value}")]
309    InvalidDateTimeFormat {
310        /// The name of the attribute with invalid datetime format
311        attribute: String,
312        /// The invalid datetime value that was provided
313        value: String
314    },
315
316    /// Invalid binary data
317    #[error("Attribute '{attribute}' has invalid binary data: {details}")]
318    InvalidBinaryData {
319        /// The name of the attribute with invalid binary data
320        attribute: String,
321        /// Details about what makes the binary data invalid
322        details: String
323    },
324
325    /// Invalid reference URI
326    #[error("Attribute '{attribute}' has invalid reference URI: {uri}")]
327    InvalidReferenceUri {
328        /// The name of the attribute with invalid reference URI
329        attribute: String,
330        /// The invalid URI value that was provided
331        uri: String
332    },
333
334    /// Invalid reference type
335    #[error("Attribute '{attribute}' has invalid reference type: {ref_type}")]
336    InvalidReferenceType {
337        /// The name of the attribute with invalid reference type
338        attribute: String,
339        /// The invalid reference type that was provided
340        ref_type: String
341    },
342
343    /// Broken reference
344    #[error("Attribute '{attribute}' contains broken reference: {reference}")]
345    BrokenReference {
346        /// The name of the attribute with broken reference
347        attribute: String,
348        /// The broken reference value
349        reference: String,
350    },
351
352    // Multi-valued Attribute Validation Errors (33-38)
353    /// Single value provided for multi-valued attribute
354    #[error("Attribute '{attribute}' must be multi-valued (array)")]
355    SingleValueForMultiValued {
356        /// The name of the attribute that requires multiple values
357        attribute: String
358    },
359
360    /// Array provided for single-valued attribute
361    #[error("Attribute '{attribute}' must be single-valued (not array)")]
362    ArrayForSingleValued {
363        /// The name of the attribute that requires a single value
364        attribute: String
365    },
366
367    /// Multiple primary values in multi-valued attribute
368    #[error("Attribute '{attribute}' cannot have multiple primary values")]
369    MultiplePrimaryValues {
370        /// The name of the attribute with multiple primary values
371        attribute: String
372    },
373
374    /// Invalid multi-valued structure
375    #[error("Attribute '{attribute}' has invalid multi-valued structure: {details}")]
376    InvalidMultiValuedStructure {
377        /// The name of the attribute with invalid multi-valued structure
378        attribute: String,
379        /// Details about what makes the structure invalid
380        details: String
381    },
382
383    /// Missing required sub-attribute in multi-valued
384    #[error("Attribute '{attribute}' missing required sub-attribute '{sub_attribute}'")]
385    MissingRequiredSubAttribute {
386        /// The name of the multi-valued attribute
387        attribute: String,
388        /// The name of the missing required sub-attribute
389        sub_attribute: String,
390    },
391
392    // Complex Attribute Validation Errors (39-43)
393    /// Missing required sub-attributes in complex attribute
394    #[error("Complex attribute '{attribute}' missing required sub-attributes: {missing:?}")]
395    MissingRequiredSubAttributes {
396        /// The name of the complex attribute
397        attribute: String,
398        /// The list of missing required sub-attributes
399        missing: Vec<String>,
400    },
401
402    /// Invalid sub-attribute type in complex attribute
403    #[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    /// Unknown sub-attribute in complex attribute
414    #[error("Complex attribute '{attribute}' contains unknown sub-attribute '{sub_attribute}'")]
415    UnknownSubAttribute {
416        attribute: String,
417        sub_attribute: String,
418    },
419
420    /// Nested complex attributes (not allowed)
421    #[error("Nested complex attributes are not allowed: '{attribute}'")]
422    NestedComplexAttributes { attribute: String },
423
424    /// Malformed complex attribute structure
425    #[error("Complex attribute '{attribute}' has malformed structure: {details}")]
426    MalformedComplexStructure { attribute: String, details: String },
427
428    // Attribute Characteristics Validation Errors (44-52)
429    /// Case sensitivity violation
430    #[error("Attribute '{attribute}' violates case sensitivity rules: {details}")]
431    CaseSensitivityViolation { attribute: String, details: String },
432
433    /// Read-only mutability violation
434    #[error("Attribute '{attribute}' is read-only and cannot be modified")]
435    ReadOnlyMutabilityViolation { attribute: String },
436
437    /// Immutable mutability violation
438    #[error("Attribute '{attribute}' is immutable and cannot be modified after creation")]
439    ImmutableMutabilityViolation { attribute: String },
440
441    /// Write-only attribute returned
442    #[error("Attribute '{attribute}' is write-only and should not be returned")]
443    WriteOnlyAttributeReturned { attribute: String },
444
445    /// Server uniqueness violation
446    #[error("Attribute '{attribute}' violates server uniqueness constraint with value '{value}'")]
447    ServerUniquenessViolation { attribute: String, value: String },
448
449    /// Global uniqueness violation
450    #[error("Attribute '{attribute}' violates global uniqueness constraint with value '{value}'")]
451    GlobalUniquenessViolation { attribute: String, value: String },
452
453    /// Invalid canonical value choice
454    #[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    /// Unknown attribute for schema
464    #[error("Unknown attribute '{attribute}' for schema '{schema}'")]
465    UnknownAttributeForSchema { attribute: String, schema: String },
466
467    /// Required characteristic violation
468    #[error("Attribute '{attribute}' violates required characteristic '{characteristic}'")]
469    RequiredCharacteristicViolation {
470        attribute: String,
471        characteristic: String,
472    },
473
474    // Schema-Driven Value Object Errors
475    /// Unsupported attribute type for value object creation
476    #[error("Unsupported attribute type '{type_name}' for attribute '{attribute}'")]
477    UnsupportedAttributeType {
478        attribute: String,
479        type_name: String,
480    },
481
482    /// Invalid attribute name mismatch
483    #[error("Invalid attribute name '{actual}', expected '{expected}'")]
484    InvalidAttributeName { actual: String, expected: String },
485
486    /// Required attribute missing
487    #[error("Required attribute '{0}' is missing")]
488    RequiredAttributeMissing(String),
489
490    /// Null value for optional attribute (used in factory)
491    #[error("Null value provided for optional attribute '{0}'")]
492    NullValueForOptionalAttribute(String),
493
494    /// Expected array for multi-valued attribute
495    #[error("Expected array for multi-valued attribute '{0}'")]
496    ExpectedArray(String),
497
498    /// Invalid primary index for multi-valued attribute
499    #[error("Invalid primary index {index} for attribute '{attribute}' (length: {length})")]
500    InvalidPrimaryIndex {
501        attribute: String,
502        index: usize,
503        length: usize,
504    },
505
506    /// Attribute is not multi-valued
507    #[error("Attribute '{0}' is not multi-valued")]
508    NotMultiValued(String),
509
510    /// Reserved username error
511    #[error("Username '{0}' is reserved and cannot be used")]
512    ReservedUsername(String),
513
514    /// Username too short
515    #[error("Username '{0}' is too short (minimum 3 characters)")]
516    UsernameTooShort(String),
517
518    /// Username too long
519    #[error("Username '{0}' is too long (maximum 64 characters)")]
520    UsernameTooLong(String),
521
522    /// Invalid username format
523    #[error("Username '{0}' has invalid format")]
524    InvalidUsernameFormat(String),
525
526    /// Invalid email domain
527    #[error("Email '{email}' has invalid domain, allowed domains: {allowed_domains:?}")]
528    InvalidEmailDomain {
529        email: String,
530        allowed_domains: Vec<String>,
531    },
532
533    /// Work email required
534    #[error("A work email address is required")]
535    WorkEmailRequired,
536
537    /// External ID required
538    #[error("External ID is required")]
539    ExternalIdRequired,
540
541    /// Name component required
542    #[error("At least one name component (given, family, or formatted) is required")]
543    NameComponentRequired,
544
545    /// Empty formatted name
546    #[error("Formatted name cannot be empty")]
547    EmptyFormattedName,
548}
549
550/// Errors that can occur during server building/configuration.
551///
552/// These errors are typically programming errors and should be caught
553/// during development rather than runtime.
554#[derive(Debug, thiserror::Error)]
555pub enum BuildError {
556    /// Resource provider was not configured
557    #[error("Resource provider is required but not provided")]
558    MissingResourceProvider,
559
560    /// Invalid configuration provided
561    #[error("Invalid configuration: {message}")]
562    InvalidConfiguration { message: String },
563
564    /// Schema loading failed
565    #[error("Failed to load schema: {schema_id}")]
566    SchemaLoadError { schema_id: String },
567}
568
569// Convenience methods for creating common errors
570impl ScimError {
571    /// Create a resource not found error
572    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    /// Create a schema not found error
580    pub fn schema_not_found(schema_id: impl Into<String>) -> Self {
581        Self::SchemaNotFound {
582            schema_id: schema_id.into(),
583        }
584    }
585
586    /// Create an internal server error
587    pub fn internal(message: impl Into<String>) -> Self {
588        Self::Internal {
589            message: message.into(),
590        }
591    }
592
593    /// Create an invalid request error
594    pub fn invalid_request(message: impl Into<String>) -> Self {
595        Self::InvalidRequest {
596            message: message.into(),
597        }
598    }
599
600    /// Wrap a provider error
601    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    /// Create a missing required attribute error
611    pub fn missing_required(attribute: impl Into<String>) -> Self {
612        Self::MissingRequiredAttribute {
613            attribute: attribute.into(),
614        }
615    }
616
617    /// Create an invalid type error
618    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    /// Create a custom validation error
631    pub fn custom(message: impl Into<String>) -> Self {
632        Self::Custom {
633            message: message.into(),
634        }
635    }
636}
637
638// Result type aliases for convenience
639pub type ScimResult<T> = Result<T, ScimError>;
640pub type ValidationResult<T> = Result<T, ValidationError>;
641pub type BuildResult<T> = Result<T, BuildError>;
642
643// Implement From for common error conversions
644impl 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}