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 { resource_type: String, id: String },
27
28    /// Schema not found errors
29    #[error("Schema not found: {schema_id}")]
30    SchemaNotFound { schema_id: String },
31
32    /// Internal server errors
33    #[error("Internal server error: {message}")]
34    Internal { message: String },
35
36    /// Invalid request format or parameters
37    #[error("Invalid request: {message}")]
38    InvalidRequest { message: String },
39
40    /// Unsupported resource type
41    #[error("Unsupported resource type: {0}")]
42    UnsupportedResourceType(String),
43
44    /// Unsupported operation for resource type
45    #[error("Unsupported operation '{operation}' for resource type '{resource_type}'")]
46    UnsupportedOperation {
47        resource_type: String,
48        operation: String,
49    },
50
51    /// Method not found on resource
52    #[error("Method '{0}' not found")]
53    MethodNotFound(String),
54
55    /// Schema mapper not found
56    #[error("Schema mapper at index {0} not found")]
57    MapperNotFound(usize),
58
59    /// Resource provider error with string message
60    #[error("Resource provider error: {0}")]
61    ProviderError(String),
62}
63
64/// Validation errors for schema compliance checking.
65///
66/// These errors occur when resource data doesn't conform to the defined schema,
67/// providing detailed information about what validation rule was violated.
68#[derive(Debug, thiserror::Error)]
69pub enum ValidationError {
70    /// Required attribute is missing
71    #[error("Required attribute '{attribute}' is missing")]
72    MissingRequiredAttribute { attribute: String },
73
74    /// Attribute value doesn't match expected type
75    #[error("Attribute '{attribute}' has invalid type, expected {expected}, got {actual}")]
76    InvalidAttributeType {
77        attribute: String,
78        expected: String,
79        actual: String,
80    },
81
82    /// Multi-valued attribute provided as single value
83    #[error("Attribute '{attribute}' must be multi-valued (array)")]
84    ExpectedMultiValue { attribute: String },
85
86    /// Single-valued attribute provided as array
87    #[error("Attribute '{attribute}' must be single-valued (not array)")]
88    ExpectedSingleValue { attribute: String },
89
90    /// Attribute value violates uniqueness constraint
91    #[error("Attribute '{attribute}' violates uniqueness constraint")]
92    UniquenesViolation { attribute: String },
93
94    /// Invalid value for attribute with canonical values
95    #[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    /// Complex attribute missing required sub-attributes
103    #[error("Complex attribute '{attribute}' missing required sub-attribute '{sub_attribute}'")]
104    MissingSubAttribute {
105        attribute: String,
106        sub_attribute: String,
107    },
108
109    /// Unknown attribute in resource
110    #[error("Unknown attribute '{attribute}' in schema '{schema_id}'")]
111    UnknownAttribute {
112        attribute: String,
113        schema_id: String,
114    },
115
116    /// General validation error with custom message
117    #[error("Validation failed: {message}")]
118    Custom { message: String },
119
120    /// Missing schemas attribute
121    #[error("Missing required 'schemas' attribute")]
122    MissingSchemas,
123
124    /// Empty schemas array
125    #[error("'schemas' array cannot be empty")]
126    EmptySchemas,
127
128    /// Invalid schema URI format
129    #[error("Invalid schema URI format: {uri}")]
130    InvalidSchemaUri { uri: String },
131
132    /// Unknown schema URI
133    #[error("Unknown schema URI: {uri}")]
134    UnknownSchemaUri { uri: String },
135
136    /// Duplicate schema URI
137    #[error("Duplicate schema URI: {uri}")]
138    DuplicateSchemaUri { uri: String },
139
140    /// Missing base schema
141    #[error("Missing base schema for resource type")]
142    MissingBaseSchema,
143
144    /// Extension without base schema
145    #[error("Extension schema requires base schema")]
146    ExtensionWithoutBase,
147
148    /// Missing required extension
149    #[error("Missing required extension schema")]
150    MissingRequiredExtension,
151
152    /// Missing id attribute
153    #[error("Missing required 'id' attribute")]
154    MissingId,
155
156    /// Empty id value
157    #[error("'id' attribute cannot be empty")]
158    EmptyId,
159
160    /// Invalid id format
161    #[error("Invalid 'id' format: {id}")]
162    InvalidIdFormat { id: String },
163
164    /// Client provided id in creation
165    #[error("Client cannot provide 'id' during resource creation")]
166    ClientProvidedId,
167
168    /// Invalid external id
169    #[error("Invalid 'externalId' format")]
170    InvalidExternalId,
171
172    /// Invalid meta structure
173    #[error("Invalid 'meta' structure")]
174    InvalidMetaStructure,
175
176    /// Missing meta resource type
177    #[error("Missing 'meta.resourceType'")]
178    MissingResourceType,
179
180    /// Invalid meta resource type
181    #[error("Invalid 'meta.resourceType': {resource_type}")]
182    InvalidResourceType { resource_type: String },
183
184    /// Client provided meta
185    #[error("Client cannot provide read-only meta attributes")]
186    ClientProvidedMeta,
187
188    /// Invalid created datetime
189    #[error("Invalid 'meta.created' datetime format")]
190    InvalidCreatedDateTime,
191
192    /// Invalid modified datetime
193    #[error("Invalid 'meta.lastModified' datetime format")]
194    InvalidModifiedDateTime,
195
196    /// Invalid location URI
197    #[error("Invalid 'meta.location' URI format")]
198    InvalidLocationUri,
199
200    /// Invalid version format
201    #[error("Invalid 'meta.version' format")]
202    InvalidVersionFormat,
203
204    /// Invalid data type for attribute
205    #[error("Attribute '{attribute}' has invalid type, expected {expected}, got {actual}")]
206    InvalidDataType {
207        attribute: String,
208        expected: String,
209        actual: String,
210    },
211
212    /// Invalid string format
213    #[error("Attribute '{attribute}' has invalid string format: {details}")]
214    InvalidStringFormat { attribute: String, details: String },
215
216    /// Invalid boolean value
217    #[error("Attribute '{attribute}' has invalid boolean value: {value}")]
218    InvalidBooleanValue { attribute: String, value: String },
219
220    /// Invalid decimal format
221    #[error("Attribute '{attribute}' has invalid decimal format: {value}")]
222    InvalidDecimalFormat { attribute: String, value: String },
223
224    /// Invalid integer value
225    #[error("Attribute '{attribute}' has invalid integer value: {value}")]
226    InvalidIntegerValue { attribute: String, value: String },
227
228    /// Invalid datetime format
229    #[error("Attribute '{attribute}' has invalid datetime format: {value}")]
230    InvalidDateTimeFormat { attribute: String, value: String },
231
232    /// Invalid binary data
233    #[error("Attribute '{attribute}' has invalid binary data: {details}")]
234    InvalidBinaryData { attribute: String, details: String },
235
236    /// Invalid reference URI
237    #[error("Attribute '{attribute}' has invalid reference URI: {uri}")]
238    InvalidReferenceUri { attribute: String, uri: String },
239
240    /// Invalid reference type
241    #[error("Attribute '{attribute}' has invalid reference type: {ref_type}")]
242    InvalidReferenceType { attribute: String, ref_type: String },
243
244    /// Broken reference
245    #[error("Attribute '{attribute}' contains broken reference: {reference}")]
246    BrokenReference {
247        attribute: String,
248        reference: String,
249    },
250
251    // Multi-valued Attribute Validation Errors (33-38)
252    /// Single value provided for multi-valued attribute
253    #[error("Attribute '{attribute}' must be multi-valued (array)")]
254    SingleValueForMultiValued { attribute: String },
255
256    /// Array provided for single-valued attribute
257    #[error("Attribute '{attribute}' must be single-valued (not array)")]
258    ArrayForSingleValued { attribute: String },
259
260    /// Multiple primary values in multi-valued attribute
261    #[error("Attribute '{attribute}' cannot have multiple primary values")]
262    MultiplePrimaryValues { attribute: String },
263
264    /// Invalid multi-valued structure
265    #[error("Attribute '{attribute}' has invalid multi-valued structure: {details}")]
266    InvalidMultiValuedStructure { attribute: String, details: String },
267
268    /// Missing required sub-attribute in multi-valued
269    #[error("Attribute '{attribute}' missing required sub-attribute '{sub_attribute}'")]
270    MissingRequiredSubAttribute {
271        attribute: String,
272        sub_attribute: String,
273    },
274
275    // Complex Attribute Validation Errors (39-43)
276    /// Missing required sub-attributes in complex attribute
277    #[error("Complex attribute '{attribute}' missing required sub-attributes: {missing:?}")]
278    MissingRequiredSubAttributes {
279        attribute: String,
280        missing: Vec<String>,
281    },
282
283    /// Invalid sub-attribute type in complex attribute
284    #[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    /// Unknown sub-attribute in complex attribute
295    #[error("Complex attribute '{attribute}' contains unknown sub-attribute '{sub_attribute}'")]
296    UnknownSubAttribute {
297        attribute: String,
298        sub_attribute: String,
299    },
300
301    /// Nested complex attributes (not allowed)
302    #[error("Nested complex attributes are not allowed: '{attribute}'")]
303    NestedComplexAttributes { attribute: String },
304
305    /// Malformed complex attribute structure
306    #[error("Complex attribute '{attribute}' has malformed structure: {details}")]
307    MalformedComplexStructure { attribute: String, details: String },
308
309    // Attribute Characteristics Validation Errors (44-52)
310    /// Case sensitivity violation
311    #[error("Attribute '{attribute}' violates case sensitivity rules: {details}")]
312    CaseSensitivityViolation { attribute: String, details: String },
313
314    /// Read-only mutability violation
315    #[error("Attribute '{attribute}' is read-only and cannot be modified")]
316    ReadOnlyMutabilityViolation { attribute: String },
317
318    /// Immutable mutability violation
319    #[error("Attribute '{attribute}' is immutable and cannot be modified after creation")]
320    ImmutableMutabilityViolation { attribute: String },
321
322    /// Write-only attribute returned
323    #[error("Attribute '{attribute}' is write-only and should not be returned")]
324    WriteOnlyAttributeReturned { attribute: String },
325
326    /// Server uniqueness violation
327    #[error("Attribute '{attribute}' violates server uniqueness constraint with value '{value}'")]
328    ServerUniquenessViolation { attribute: String, value: String },
329
330    /// Global uniqueness violation
331    #[error("Attribute '{attribute}' violates global uniqueness constraint with value '{value}'")]
332    GlobalUniquenessViolation { attribute: String, value: String },
333
334    /// Invalid canonical value choice
335    #[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    /// Unknown attribute for schema
345    #[error("Unknown attribute '{attribute}' for schema '{schema}'")]
346    UnknownAttributeForSchema { attribute: String, schema: String },
347
348    /// Required characteristic violation
349    #[error("Attribute '{attribute}' violates required characteristic '{characteristic}'")]
350    RequiredCharacteristicViolation {
351        attribute: String,
352        characteristic: String,
353    },
354
355    // Schema-Driven Value Object Errors
356    /// Unsupported attribute type for value object creation
357    #[error("Unsupported attribute type '{type_name}' for attribute '{attribute}'")]
358    UnsupportedAttributeType {
359        attribute: String,
360        type_name: String,
361    },
362
363    /// Invalid attribute name mismatch
364    #[error("Invalid attribute name '{actual}', expected '{expected}'")]
365    InvalidAttributeName { actual: String, expected: String },
366
367    /// Required attribute missing
368    #[error("Required attribute '{0}' is missing")]
369    RequiredAttributeMissing(String),
370
371    /// Null value for optional attribute (used in factory)
372    #[error("Null value provided for optional attribute '{0}'")]
373    NullValueForOptionalAttribute(String),
374
375    /// Expected array for multi-valued attribute
376    #[error("Expected array for multi-valued attribute '{0}'")]
377    ExpectedArray(String),
378
379    /// Invalid primary index for multi-valued attribute
380    #[error("Invalid primary index {index} for attribute '{attribute}' (length: {length})")]
381    InvalidPrimaryIndex {
382        attribute: String,
383        index: usize,
384        length: usize,
385    },
386
387    /// Attribute is not multi-valued
388    #[error("Attribute '{0}' is not multi-valued")]
389    NotMultiValued(String),
390
391    /// Reserved username error
392    #[error("Username '{0}' is reserved and cannot be used")]
393    ReservedUsername(String),
394
395    /// Username too short
396    #[error("Username '{0}' is too short (minimum 3 characters)")]
397    UsernameTooShort(String),
398
399    /// Username too long
400    #[error("Username '{0}' is too long (maximum 64 characters)")]
401    UsernameTooLong(String),
402
403    /// Invalid username format
404    #[error("Username '{0}' has invalid format")]
405    InvalidUsernameFormat(String),
406
407    /// Invalid email domain
408    #[error("Email '{email}' has invalid domain, allowed domains: {allowed_domains:?}")]
409    InvalidEmailDomain {
410        email: String,
411        allowed_domains: Vec<String>,
412    },
413
414    /// Work email required
415    #[error("A work email address is required")]
416    WorkEmailRequired,
417
418    /// External ID required
419    #[error("External ID is required")]
420    ExternalIdRequired,
421
422    /// Name component required
423    #[error("At least one name component (given, family, or formatted) is required")]
424    NameComponentRequired,
425
426    /// Empty formatted name
427    #[error("Formatted name cannot be empty")]
428    EmptyFormattedName,
429}
430
431/// Errors that can occur during server building/configuration.
432///
433/// These errors are typically programming errors and should be caught
434/// during development rather than runtime.
435#[derive(Debug, thiserror::Error)]
436pub enum BuildError {
437    /// Resource provider was not configured
438    #[error("Resource provider is required but not provided")]
439    MissingResourceProvider,
440
441    /// Invalid configuration provided
442    #[error("Invalid configuration: {message}")]
443    InvalidConfiguration { message: String },
444
445    /// Schema loading failed
446    #[error("Failed to load schema: {schema_id}")]
447    SchemaLoadError { schema_id: String },
448}
449
450// Convenience methods for creating common errors
451impl ScimError {
452    /// Create a resource not found error
453    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    /// Create a schema not found error
461    pub fn schema_not_found(schema_id: impl Into<String>) -> Self {
462        Self::SchemaNotFound {
463            schema_id: schema_id.into(),
464        }
465    }
466
467    /// Create an internal server error
468    pub fn internal(message: impl Into<String>) -> Self {
469        Self::Internal {
470            message: message.into(),
471        }
472    }
473
474    /// Create an invalid request error
475    pub fn invalid_request(message: impl Into<String>) -> Self {
476        Self::InvalidRequest {
477            message: message.into(),
478        }
479    }
480
481    /// Wrap a provider error
482    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    /// Create a missing required attribute error
492    pub fn missing_required(attribute: impl Into<String>) -> Self {
493        Self::MissingRequiredAttribute {
494            attribute: attribute.into(),
495        }
496    }
497
498    /// Create an invalid type error
499    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    /// Create a custom validation error
512    pub fn custom(message: impl Into<String>) -> Self {
513        Self::Custom {
514            message: message.into(),
515        }
516    }
517}
518
519// Result type aliases for convenience
520pub type ScimResult<T> = Result<T, ScimError>;
521pub type ValidationResult<T> = Result<T, ValidationError>;
522pub type BuildResult<T> = Result<T, BuildError>;
523
524// Implement From for common error conversions
525impl 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}