bindcar/
types.rs

1// Copyright (c) 2025 Erick Bourgeois, firestoned
2// SPDX-License-Identifier: MIT
3
4//! Common types and errors used throughout the bindcar library
5
6use axum::{
7    http::StatusCode,
8    response::{IntoResponse, Response},
9    Json,
10};
11use serde::Serialize;
12use std::sync::Arc;
13
14use crate::{nsupdate::NsupdateExecutor, rndc::RndcExecutor};
15
16/// Application state shared across handlers
17#[derive(Clone)]
18pub struct AppState {
19    /// RNDC command executor
20    pub rndc: Arc<RndcExecutor>,
21    /// nsupdate command executor
22    pub nsupdate: Arc<NsupdateExecutor>,
23    /// Zone file directory
24    pub zone_dir: String,
25}
26
27/// Error response
28#[derive(Serialize)]
29pub struct ErrorResponse {
30    pub error: String,
31    pub details: Option<String>,
32}
33
34/// API error type
35#[derive(Debug, thiserror::Error)]
36pub enum ApiError {
37    #[error("Zone file error: {0}")]
38    ZoneFileError(String),
39
40    #[error("RNDC command failed: {0}")]
41    RndcError(String),
42
43    #[error("Invalid request: {0}")]
44    InvalidRequest(String),
45
46    #[error("Zone not found: {0}")]
47    ZoneNotFound(String),
48
49    #[error("Zone already exists: {0}")]
50    ZoneAlreadyExists(String),
51
52    #[error("Internal server error: {0}")]
53    InternalError(String),
54
55    #[error("Dynamic updates not enabled: {0}")]
56    DynamicUpdatesNotEnabled(String),
57
58    #[error("nsupdate command failed: {0}")]
59    NsupdateError(String),
60
61    #[error("Invalid record: {0}")]
62    InvalidRecord(String),
63}
64
65impl IntoResponse for ApiError {
66    fn into_response(self) -> Response {
67        let (status, error_message) = match &self {
68            ApiError::ZoneFileError(_) => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()),
69            ApiError::RndcError(_) => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()),
70            ApiError::InvalidRequest(_) => (StatusCode::BAD_REQUEST, self.to_string()),
71            ApiError::ZoneNotFound(_) => (StatusCode::NOT_FOUND, self.to_string()),
72            ApiError::ZoneAlreadyExists(_) => (StatusCode::CONFLICT, self.to_string()),
73            ApiError::InternalError(_) => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()),
74            ApiError::DynamicUpdatesNotEnabled(_) => (StatusCode::BAD_REQUEST, self.to_string()),
75            ApiError::NsupdateError(_) => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()),
76            ApiError::InvalidRecord(_) => (StatusCode::BAD_REQUEST, self.to_string()),
77        };
78
79        let body = Json(ErrorResponse {
80            error: error_message,
81            details: None,
82        });
83
84        (status, body).into_response()
85    }
86}