Files
Stalwart/crates/jmap-proto/src/error/request.rs
2024-07-18 20:04:09 +02:00

225 lines
6.5 KiB
Rust

/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::{borrow::Cow, fmt::Display};
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub enum RequestLimitError {
#[serde(rename = "maxSizeRequest")]
SizeRequest,
#[serde(rename = "maxSizeUpload")]
SizeUpload,
#[serde(rename = "maxCallsInRequest")]
CallsIn,
#[serde(rename = "maxConcurrentRequests")]
ConcurrentRequest,
#[serde(rename = "maxConcurrentUpload")]
ConcurrentUpload,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub enum RequestErrorType {
#[serde(rename = "urn:ietf:params:jmap:error:unknownCapability")]
UnknownCapability,
#[serde(rename = "urn:ietf:params:jmap:error:notJSON")]
NotJSON,
#[serde(rename = "urn:ietf:params:jmap:error:notRequest")]
NotRequest,
#[serde(rename = "urn:ietf:params:jmap:error:limit")]
Limit,
#[serde(rename = "about:blank")]
Other,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct RequestError<'x> {
#[serde(rename = "type")]
pub p_type: RequestErrorType,
pub status: u16,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<Cow<'x, str>>,
pub detail: Cow<'x, str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<RequestLimitError>,
}
impl<'x> RequestError<'x> {
pub fn blank(
status: u16,
title: impl Into<Cow<'x, str>>,
detail: impl Into<Cow<'x, str>>,
) -> Self {
RequestError {
p_type: RequestErrorType::Other,
status,
title: Some(title.into()),
detail: detail.into(),
limit: None,
}
}
pub fn internal_server_error() -> Self {
RequestError::blank(
500,
"Internal Server Error",
concat!(
"There was a problem while processing your request. ",
"Please contact the system administrator if this problem persists."
),
)
}
pub fn unavailable() -> Self {
RequestError::blank(
503,
"Temporarily Unavailable",
concat!(
"There was a temporary problem while processing your request. ",
"Please try again in a few moments."
),
)
}
pub fn invalid_parameters() -> Self {
RequestError::blank(
400,
"Invalid Parameters",
"One or multiple parameters could not be parsed.",
)
}
pub fn forbidden() -> Self {
RequestError::blank(
403,
"Forbidden",
"You do not have enough permissions to access this resource.",
)
}
pub fn over_blob_quota(max_files: usize, max_bytes: usize) -> Self {
RequestError::blank(
403,
"Quota exceeded",
format!(
"You have exceeded the blob upload quota of {} files or {} bytes.",
max_files, max_bytes
),
)
}
pub fn over_quota() -> Self {
RequestError::blank(
403,
"Quota exceeded",
"You have exceeded your account quota.",
)
}
pub fn too_many_requests() -> Self {
RequestError::blank(
429,
"Too Many Requests",
"Your request has been rate limited. Please try again in a few seconds.",
)
}
pub fn too_many_auth_attempts() -> Self {
RequestError::blank(
429,
"Too Many Authentication Attempts",
"Your request has been rate limited. Please try again in a few minutes.",
)
}
pub fn limit(limit_type: RequestLimitError) -> Self {
RequestError {
p_type: RequestErrorType::Limit,
status: 400,
title: None,
detail: match limit_type {
RequestLimitError::SizeRequest => concat!(
"The request is larger than the server ",
"is willing to process."
),
RequestLimitError::SizeUpload => concat!(
"The uploaded file is larger than the server ",
"is willing to process."
),
RequestLimitError::CallsIn => concat!(
"The request exceeds the maximum number ",
"of calls in a single request."
),
RequestLimitError::ConcurrentRequest => concat!(
"The request exceeds the maximum number ",
"of concurrent requests."
),
RequestLimitError::ConcurrentUpload => concat!(
"The request exceeds the maximum number ",
"of concurrent uploads."
),
}
.into(),
limit: Some(limit_type),
}
}
pub fn not_found() -> Self {
RequestError::blank(
404,
"Not Found",
"The requested resource does not exist on this server.",
)
}
pub fn unauthorized() -> Self {
RequestError::blank(401, "Unauthorized", "You have to authenticate first.")
}
pub fn unknown_capability(capability: &str) -> RequestError {
RequestError {
p_type: RequestErrorType::UnknownCapability,
limit: None,
title: None,
status: 400,
detail: format!(
concat!(
"The Request object used capability ",
"'{}', which is not supported",
"by this server."
),
capability
)
.into(),
}
}
pub fn not_json(detail: &str) -> RequestError {
RequestError {
p_type: RequestErrorType::NotJSON,
limit: None,
title: None,
status: 400,
detail: format!("Failed to parse JSON: {detail}").into(),
}
}
pub fn not_request(detail: impl Into<Cow<'x, str>>) -> RequestError<'x> {
RequestError {
p_type: RequestErrorType::NotRequest,
limit: None,
title: None,
status: 400,
detail: detail.into(),
}
}
}
impl Display for RequestError<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.detail)
}
}