1use 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}