#![allow(clippy::option_if_let_else)]
use axum::{Json, http::StatusCode, response::IntoResponse};
use candid::Principal;
use derive_new::new;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use utoipa::ToSchema;
use crate::custom_domains::base::{
traits::{repository::RepositoryError, validation::ValidationError},
types::domain::RegistrationStatus,
};
#[derive(Serialize, ToSchema)]
pub struct ApiResponse<T> {
status: String,
#[serde(skip_serializing_if = "Option::is_none")]
message: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
data: Option<T>,
#[serde(skip_serializing_if = "Option::is_none")]
errors: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Error)]
pub enum ApiError {
#[error("bad_request: {0}")]
BadRequest(String),
#[error("not_found: {0}")]
NotFound(String),
#[error("conflict: {0}")]
Conflict(String),
#[error("unprocessable_entity: {0}")]
UnprocessableEntity(String),
#[error(
"internal_server_error: An unexpected error occurred. Please try again later or contact support."
)]
InternalServerError(String),
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
#[derive(ToSchema)]
pub struct CreateOrUpdateResponse {
pub domain: String,
#[schema(value_type = String, example = "rrkah-fqaaa-aaaaa-aaaaq-cai")]
pub canister_id: Principal,
}
#[derive(Serialize, Deserialize, Debug, Clone, new)]
#[serde(rename_all = "snake_case")]
#[derive(ToSchema)]
pub struct ErrorResponse {
pub domain: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, new)]
#[serde(rename_all = "snake_case")]
#[derive(ToSchema)]
pub struct DeleteResponse {
pub domain: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
#[derive(ToSchema)]
pub struct GetStatusResponse {
pub domain: String,
#[schema(value_type = Option<String>, example = "rrkah-fqaaa-aaaaa-aaaaq-cai", nullable = true)]
pub canister_id: Option<Principal>,
pub registration_status: RegistrationStatus,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
#[derive(ToSchema)]
pub struct ValidateResponse {
pub domain: String,
#[schema(value_type = String, example = "rrkah-fqaaa-aaaaa-aaaaq-cai")]
pub canister_id: Principal,
pub validation_status: ValidationStatus,
}
impl From<RepositoryError> for ApiError {
fn from(err: RepositoryError) -> Self {
match err {
RepositoryError::CertificateAlreadyIssued(domain) => Self::Conflict(format!(
"Certificate for {domain} already exists; reissuance is not permitted."
)),
RepositoryError::AnotherTaskInProgress(domain) => Self::Conflict(format!(
"Another task for {domain} is already in progress. Please retry after it completes."
)),
RepositoryError::DomainNotFound(domain) => {
Self::NotFound(format!("Domain {domain} not found."))
}
RepositoryError::MissingCertificateForUpdate(domain) => Self::BadRequest(format!(
"Cannot update domain-to-canister mapping: no valid certificate found for domain {domain}."
)),
_ => Self::InternalServerError("".to_string()),
}
}
}
impl From<ValidationError> for ApiError {
fn from(value: ValidationError) -> Self {
Self::BadRequest(value.to_string())
}
}
pub fn success_response<T: Serialize>(
code: StatusCode,
data: T,
message: Option<String>,
) -> axum::response::Response {
let json: Json<ApiResponse<T>> = Json(ApiResponse {
status: "success".to_string(),
message,
data: Some(data),
errors: None,
});
(code, json).into_response()
}
pub fn error_response<T: Serialize>(
error: ApiError,
data: T,
message: Option<String>,
) -> axum::response::Response {
let code = match error {
ApiError::BadRequest { .. } => StatusCode::BAD_REQUEST,
ApiError::NotFound { .. } => StatusCode::NOT_FOUND,
ApiError::Conflict { .. } => StatusCode::CONFLICT,
ApiError::UnprocessableEntity { .. } => StatusCode::UNPROCESSABLE_ENTITY,
ApiError::InternalServerError { .. } => StatusCode::INTERNAL_SERVER_ERROR,
};
let json: Json<ApiResponse<T>> = Json(ApiResponse {
status: "error".to_string(),
message,
data: Some(data),
errors: Some(error.to_string()),
});
(code, json).into_response()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[derive(ToSchema)]
pub enum ValidationStatus {
Valid,
Invalid(String),
}