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 pub key_dir: Option<String>,
30}
31
32#[derive(Serialize)]
34pub struct ErrorResponse {
35 pub error: String,
36 pub details: Option<String>,
37}
38
39#[derive(Debug, thiserror::Error)]
41pub enum ApiError {
42 #[error("Zone file error: {0}")]
43 ZoneFileError(String),
44
45 #[error("RNDC command failed: {0}")]
46 RndcError(String),
47
48 #[error("Invalid request: {0}")]
49 InvalidRequest(String),
50
51 #[error("Zone not found: {0}")]
52 ZoneNotFound(String),
53
54 #[error("Zone already exists: {0}")]
55 ZoneAlreadyExists(String),
56
57 #[error("Internal server error: {0}")]
58 InternalError(String),
59
60 #[error("Dynamic updates not enabled: {0}")]
61 DynamicUpdatesNotEnabled(String),
62
63 #[error("nsupdate command failed: {0}")]
64 NsupdateError(String),
65
66 #[error("Invalid record: {0}")]
67 InvalidRecord(String),
68
69 #[error("Unsafe DNSSEC transition: {0}")]
74 UnsafeDnssecTransition(String),
75
76 #[error("DNSSEC material not available: {0}")]
79 DsNotAvailable(String),
80
81 #[error("Not configured on this server: {0}")]
85 NotConfigured(String),
86}
87
88const GENERIC_SERVER_ERROR: &str = "Internal server error";
96
97impl IntoResponse for ApiError {
98 fn into_response(self) -> Response {
99 let (status, error_message) = match &self {
104 ApiError::InvalidRequest(_) => (StatusCode::BAD_REQUEST, self.to_string()),
105 ApiError::ZoneNotFound(_) => (StatusCode::NOT_FOUND, self.to_string()),
106 ApiError::ZoneAlreadyExists(_) => (StatusCode::CONFLICT, self.to_string()),
107 ApiError::DynamicUpdatesNotEnabled(_) => (StatusCode::BAD_REQUEST, self.to_string()),
108 ApiError::InvalidRecord(_) => (StatusCode::BAD_REQUEST, self.to_string()),
109 ApiError::UnsafeDnssecTransition(_) => (StatusCode::CONFLICT, self.to_string()),
110 ApiError::DsNotAvailable(_) => (StatusCode::NOT_FOUND, self.to_string()),
111 ApiError::NotConfigured(_) => (StatusCode::NOT_IMPLEMENTED, self.to_string()),
112 ApiError::ZoneFileError(_)
113 | ApiError::RndcError(_)
114 | ApiError::InternalError(_)
115 | ApiError::NsupdateError(_) => {
116 error!("returning 500 to client; internal error: {}", self);
117 (
118 StatusCode::INTERNAL_SERVER_ERROR,
119 GENERIC_SERVER_ERROR.to_string(),
120 )
121 }
122 };
123
124 let body = Json(ErrorResponse {
125 error: error_message,
126 details: None,
127 });
128
129 (status, body).into_response()
130 }
131}