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