api documentation (#876)

* api documentation

* utoipa openapi docs generator

---------

Co-authored-by: Andrej Walilko <awalilko@liquidweb.com>
This commit is contained in:
Andrej Walilko
2026-02-16 19:41:15 -05:00
committed by GitHub
parent 9d50db40b9
commit 9b6c4cee0b
22 changed files with 542 additions and 15 deletions

View File

@@ -21,9 +21,9 @@ use tokio_util::compat::FuturesAsyncWriteCompatExt;
use tokio_util::io::ReaderStream;
use tokio_util::sync::CancellationToken;
use crate::DiagDeviceCtrlMessage;
use crate::analysis::{AnalysisCtrlMessage, AnalysisStatus};
use crate::config::Config;
use crate::diag::DiagDeviceCtrlMessage;
use crate::display::DisplayState;
use crate::pcap::generate_pcap_data;
use crate::qmdl_store::RecordingStore;
@@ -39,6 +39,21 @@ pub struct ServerState {
pub ui_update_sender: Option<Sender<DisplayState>>,
}
#[cfg_attr(feature = "apidocs", utoipa::path(
get,
path = "/api/qmdl/{name}",
tag = "Recordings",
responses(
(status = StatusCode::OK, description = "QMDL download successful", content_type = "application/octet-stream"),
(status = StatusCode::NOT_FOUND, description = "Could not find file {name}"),
(status = StatusCode::SERVICE_UNAVAILABLE, description = "QMDL file is empty, or error opening file")
),
params(
("name" = String, Path, description = "QMDL filename to convert and download")
),
summary = "Download a QMDL file",
description = "Stream the QMDL file {name} to the client."
))]
pub async fn get_qmdl(
State(state): State<Arc<ServerState>>,
Path(qmdl_name): Path<String>,
@@ -106,12 +121,38 @@ pub async fn serve_static(
}
}
#[cfg_attr(feature = "apidocs", utoipa::path(
get,
path = "/api/config",
tag = "Configuration",
responses(
(status = StatusCode::OK, description = "Success", body = Config)
),
summary = "Get config",
description = "Show the running configuration for Rayhunter."
))]
pub async fn get_config(
State(state): State<Arc<ServerState>>,
) -> Result<Json<Config>, (StatusCode, String)> {
Ok(Json(state.config.clone()))
}
#[cfg_attr(feature = "apidocs", utoipa::path(
post,
path = "/api/config",
tag = "Configuration",
request_body(
content = Option<[Config]>,
description = "Any or all configuration elements from the valid config schema to be altered may be passed. Invalid keys will be discarded. Invalid values or value types will return an error."
),
responses(
(status = StatusCode::ACCEPTED, description = "Success"),
(status = StatusCode::INTERNAL_SERVER_ERROR, description = "Failed to parse or write config file"),
(status = 422, description = "Failed to deserialize JSON body")
),
summary = "Set config",
description = "Write a new configuration for Rayhunter and trigger a restart."
))]
pub async fn set_config(
State(state): State<Arc<ServerState>>,
Json(config): Json<Config>,
@@ -138,6 +179,18 @@ pub async fn set_config(
))
}
#[cfg_attr(feature = "apidocs", utoipa::path(
post,
path = "/api/test-notification",
tag = "Configuration",
responses(
(status = StatusCode::OK, description = "Success"),
(status = StatusCode::BAD_REQUEST, description = "No notification URL set"),
(status = StatusCode::INTERNAL_SERVER_ERROR, description = "Failed to send HTTP request. Ensure your device can reach the internet.")
),
summary = "Test ntfy notification",
description = "Send a test notification to the ntfy_url in the running configuration for Rayhunter."
))]
pub async fn test_notification(
State(state): State<Arc<ServerState>>,
) -> Result<(StatusCode, String), (StatusCode, String)> {
@@ -174,10 +227,13 @@ pub async fn test_notification(
/// Response for GET /api/time
#[derive(Serialize)]
#[cfg_attr(feature = "apidocs", derive(utoipa::ToSchema))]
pub struct TimeResponse {
/// The raw system time (without clock offset)
#[cfg_attr(feature = "apidocs", schema(value_type = String))]
pub system_time: DateTime<Local>,
/// The adjusted time (system time + offset)
#[cfg_attr(feature = "apidocs", schema(value_type = String))]
pub adjusted_time: DateTime<Local>,
/// The current offset in seconds
pub offset_seconds: i64,
@@ -185,11 +241,22 @@ pub struct TimeResponse {
/// Request for POST /api/time-offset
#[derive(Deserialize)]
#[cfg_attr(feature = "apidocs", derive(utoipa::ToSchema))]
pub struct SetTimeOffsetRequest {
/// The offset to set, in seconds
pub offset_seconds: i64,
}
#[cfg_attr(feature = "apidocs", utoipa::path(
get,
path = "/api/time",
tag = "Configuration",
responses(
(status = StatusCode::OK, description = "Success", body = TimeResponse)
),
summary = "Get time",
description = "Get the current time and offset (in seconds) of the device."
))]
pub async fn get_time() -> Json<TimeResponse> {
let system_time = Local::now();
let adjusted_time = rayhunter::clock::get_adjusted_now();
@@ -203,11 +270,39 @@ pub async fn get_time() -> Json<TimeResponse> {
})
}
#[cfg_attr(feature = "apidocs", utoipa::path(
get,
path = "/api/time-offset",
tag = "Configuration",
request_body(
content = SetTimeOffsetRequest
),
responses(
(status = StatusCode::OK, description = "Success", body = TimeResponse)
),
summary = "Set time offset",
description = "Set the difference (in seconds) between the system time and the adjusted time for Rayhunter."
))]
pub async fn set_time_offset(Json(req): Json<SetTimeOffsetRequest>) -> StatusCode {
rayhunter::clock::set_offset(chrono::TimeDelta::seconds(req.offset_seconds));
StatusCode::OK
}
#[cfg_attr(feature = "apidocs", utoipa::path(
get,
path = "/api/zip/{name}",
tag = "Recordings",
responses(
(status = StatusCode::OK, description = "ZIP download successful. It is possible that if the PCAP fails to convert, the same status will be returned, but the file will contain only the QMDL file.", content_type = "application/zip"),
(status = StatusCode::NOT_FOUND, description = "Could not find file {name}"),
(status = StatusCode::SERVICE_UNAVAILABLE, description = "QMDL file is empty, or error opening file")
),
params(
("name" = String, Path, description = "QMDL filename to convert and download")
),
summary = "Download a ZIP file",
description = "Stream a ZIP file to the client which contains the QMDL file {name} and a PCAP generated from the same file."
))]
pub async fn get_zip(
State(state): State<Arc<ServerState>>,
Path(entry_name): Path<String>,
@@ -299,6 +394,21 @@ pub async fn get_zip(
Ok((headers, body).into_response())
}
#[cfg_attr(feature = "apidocs", utoipa::path(
post,
path = "/api/debug/display-state",
tag = "Configuration",
request_body(
content = DisplayState
),
responses(
(status = StatusCode::OK, description = "Display state updated successfully"),
(status = StatusCode::INTERNAL_SERVER_ERROR, description = "Error sending update to the display"),
(status = StatusCode::SERVICE_UNAVAILABLE, description = "Display system not available")
),
summary = "Set display state",
description = "Change the display state (color bar or otherwise) of the device for debugging purposes."
))]
pub async fn debug_set_display_state(
State(state): State<Arc<ServerState>>,
Json(display_state): Json<DisplayState>,