1use axum::{
7 http::StatusCode,
8 response::{IntoResponse, Response},
9 Json,
10};
11use serde::Serialize;
12use std::sync::Arc;
13use tracing::error;
14
15use crate::{nsupdate::NsupdateExecutor, rndc::RndcExecutor};
16
17#[derive(Clone)]
19pub struct AppState {
20 pub rndc: Arc<RndcExecutor>,
22 pub nsupdate: Arc<NsupdateExecutor>,
24 pub zone_dir: String,
26}
27
28#[derive(Serialize)]
30pub struct ErrorResponse {
31 pub error: String,
32 pub details: Option<String>,
33}
34
35#[derive(Debug, thiserror::Error)]
37pub enum ApiError {
38 #[error("Zone file error: {0}")]
39 ZoneFileError(String),
40
41 #[error("RNDC command failed: {0}")]
42 RndcError(String),
43
44 #[error("Invalid request: {0}")]
45 InvalidRequest(String),
46
47 #[error("Zone not found: {0}")]
48 ZoneNotFound(String),
49
50 #[error("Zone already exists: {0}")]
51 ZoneAlreadyExists(String),
52
53 #[error("Internal server error: {0}")]
54 InternalError(String),
55
56 #[error("Dynamic updates not enabled: {0}")]
57 DynamicUpdatesNotEnabled(String),
58
59 #[error("nsupdate command failed: {0}")]
60 NsupdateError(String),
61
62 #[error("Invalid record: {0}")]
63 InvalidRecord(String),
64}
65
66const GENERIC_SERVER_ERROR: &str = "Internal server error";
74
75impl IntoResponse for ApiError {
76 fn into_response(self) -> Response {
77 let (status, error_message) = match &self {
82 ApiError::InvalidRequest(_) => (StatusCode::BAD_REQUEST, self.to_string()),
83 ApiError::ZoneNotFound(_) => (StatusCode::NOT_FOUND, self.to_string()),
84 ApiError::ZoneAlreadyExists(_) => (StatusCode::CONFLICT, self.to_string()),
85 ApiError::DynamicUpdatesNotEnabled(_) => (StatusCode::BAD_REQUEST, self.to_string()),
86 ApiError::InvalidRecord(_) => (StatusCode::BAD_REQUEST, self.to_string()),
87 ApiError::ZoneFileError(_)
88 | ApiError::RndcError(_)
89 | ApiError::InternalError(_)
90 | ApiError::NsupdateError(_) => {
91 error!("returning 500 to client; internal error: {}", self);
92 (
93 StatusCode::INTERNAL_SERVER_ERROR,
94 GENERIC_SERVER_ERROR.to_string(),
95 )
96 }
97 };
98
99 let body = Json(ErrorResponse {
100 error: error_message,
101 details: None,
102 });
103
104 (status, body).into_response()
105 }
106}