Skip to main content

rustauth_scim/
errors.rs

1//! SCIM error responses.
2
3use http::{header, Response, StatusCode};
4use rustauth_core::api::ApiResponse;
5use rustauth_core::error::RustAuthError;
6use serde::{Deserialize, Serialize};
7
8pub const SCIM_ERROR_SCHEMA: &str = "urn:ietf:params:scim:api:messages:2.0:Error";
9
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct ScimError {
12    pub status: StatusCode,
13    pub detail: Option<String>,
14    pub scim_type: Option<String>,
15}
16
17impl ScimError {
18    pub fn new(status: StatusCode, detail: impl Into<String>) -> Self {
19        Self {
20            status,
21            detail: Some(detail.into()),
22            scim_type: None,
23        }
24    }
25
26    pub fn unauthorized(detail: impl Into<String>) -> Self {
27        Self::new(StatusCode::UNAUTHORIZED, detail)
28    }
29
30    pub fn bad_request(detail: impl Into<String>) -> Self {
31        Self::new(StatusCode::BAD_REQUEST, detail)
32    }
33
34    pub fn conflict(detail: impl Into<String>) -> Self {
35        Self::new(StatusCode::CONFLICT, detail)
36    }
37
38    pub fn not_found(detail: impl Into<String>) -> Self {
39        Self::new(StatusCode::NOT_FOUND, detail)
40    }
41
42    pub fn precondition_failed(detail: impl Into<String>) -> Self {
43        Self::new(StatusCode::PRECONDITION_FAILED, detail)
44    }
45
46    pub fn not_implemented(detail: impl Into<String>) -> Self {
47        Self::new(StatusCode::NOT_IMPLEMENTED, detail)
48    }
49
50    #[must_use]
51    pub fn with_scim_type(mut self, scim_type: impl Into<String>) -> Self {
52        self.scim_type = Some(scim_type.into());
53        self
54    }
55
56    pub fn body(&self) -> ScimErrorBody {
57        ScimErrorBody {
58            schemas: vec![SCIM_ERROR_SCHEMA.to_owned()],
59            status: self.status.as_u16().to_string(),
60            detail: self.detail.clone(),
61            scim_type: self.scim_type.clone(),
62        }
63    }
64
65    pub fn into_response(self) -> Result<ApiResponse, RustAuthError> {
66        let body = serde_json::to_vec(&self.body())
67            .map_err(|error| RustAuthError::Api(error.to_string()))?;
68        Response::builder()
69            .status(self.status)
70            .header(header::CONTENT_TYPE, "application/scim+json")
71            .body(body)
72            .map_err(|error| RustAuthError::Api(error.to_string()))
73    }
74}
75
76#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
77pub struct ScimErrorBody {
78    pub schemas: Vec<String>,
79    pub status: String,
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub detail: Option<String>,
82    #[serde(skip_serializing_if = "Option::is_none", rename = "scimType")]
83    pub scim_type: Option<String>,
84}