use std::{fmt::Display, str::FromStr};
use crate::error::HttpError;
use axum::http::StatusCode;
use docbox_core::database::models::{
document_box::DocumentBox,
folder::{FolderWithExtra, ResolvedFolderWithExtra},
};
use garde::Validate;
use serde::{Deserialize, Deserializer, Serialize};
use thiserror::Error;
use utoipa::ToSchema;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, ToSchema, Serialize)]
#[serde(transparent)]
#[schema(examples( "user:1:files"), value_type = String)]
pub struct DocumentBoxScope(pub String);
impl Display for DocumentBoxScope {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
const ALLOWED_CHARS: [char; 4] = [':', '-', '_', '.'];
impl DocumentBoxScope {
pub fn validate_scope(value: &str) -> bool {
if value.trim().is_empty() {
return false;
}
value
.chars()
.all(|char| char.is_ascii_alphanumeric() || ALLOWED_CHARS.contains(&char))
}
}
impl<'de> Deserialize<'de> for DocumentBoxScope {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
if !DocumentBoxScope::validate_scope(&value) {
return Err(serde::de::Error::custom(InvalidDocumentBoxScope));
}
Ok(Self(value))
}
}
impl Validate for DocumentBoxScope {
type Context = ();
fn validate_into(
&self,
_ctx: &Self::Context,
parent: &mut dyn FnMut() -> garde::Path,
report: &mut garde::Report,
) {
if !DocumentBoxScope::validate_scope(&self.0) {
report.append(parent(), garde::Error::new("document box scope is invalid"))
}
}
}
#[derive(Debug, Error)]
#[error("invalid document box scope")]
pub struct InvalidDocumentBoxScope;
impl FromStr for DocumentBoxScope {
type Err = InvalidDocumentBoxScope;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if !DocumentBoxScope::validate_scope(s) {
return Err(InvalidDocumentBoxScope);
}
Ok(Self(s.to_string()))
}
}
#[derive(Debug, Validate, Deserialize, ToSchema)]
pub struct CreateDocumentBoxRequest {
#[garde(length(min = 1))]
#[schema(min_length = 1)]
pub scope: String,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct DocumentBoxOptions {
pub max_file_size: i32,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct DocumentBoxResponse {
pub document_box: DocumentBox,
pub root: FolderWithExtra,
pub children: ResolvedFolderWithExtra,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct DocumentBoxStats {
pub total_files: i64,
pub total_links: i64,
pub total_folders: i64,
pub file_size: i64,
}
#[derive(Debug, Error)]
pub enum HttpDocumentBoxError {
#[error("document box with matching scope already exists")]
ScopeAlreadyExists,
#[error("unknown document box")]
UnknownDocumentBox,
}
impl HttpError for HttpDocumentBoxError {
fn status(&self) -> axum::http::StatusCode {
match self {
HttpDocumentBoxError::ScopeAlreadyExists => StatusCode::CONFLICT,
HttpDocumentBoxError::UnknownDocumentBox => StatusCode::NOT_FOUND,
}
}
}