diff --git a/README.md b/README.md index d7753b3..0d57e35 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,16 @@ This contains the current production power (`current_w`) in Watt, the total of produced energy since installation (`total_kwh`) in kilowatt-hour and the (UNIX) timestamp that indicates when the information was last updated. +### Error response + +If the API endpoint is accessed before any statistical data has been retrieved, +or if any other request than `GET /` is made, an error response is returned +that looks like this: + +```json +{"error":"No status found (yet)"} +``` + ## Integration in Home Assistant To integrate the Solar Grabber service in your [Home Assistant](https://www.home-assistant.io/) diff --git a/src/lib.rs b/src/lib.rs index 38cf6ed..2252b5c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,12 +20,12 @@ mod update; use std::sync::Mutex; use once_cell::sync::Lazy; -use rocket::fairing::AdHoc; -use rocket::serde::json::Json; use rocket::{ + catch, catchers, + fairing::AdHoc, get, routes, - serde::{Deserialize, Serialize}, - Build, Rocket, + serde::{json::Json, Deserialize, Serialize}, + Build, Request, Rocket, }; use self::update::update_loop; @@ -53,17 +53,47 @@ struct Status { last_updated: u64, } +/// An error used as JSON response. +#[derive(Debug, Serialize)] +#[serde(crate = "rocket::serde")] +struct Error { + /// The error message. + error: String, +} + +impl Error { + /// Creates a new error result from a message. + fn from(message: impl AsRef) -> Self { + let error = String::from(message.as_ref()); + + Self { error } + } +} + /// Returns the current (last known) status. #[get("/", format = "application/json")] -async fn status() -> Option> { +async fn status() -> Result, Json> { let status_guard = STATUS.lock().expect("Status mutex was poisoined"); - status_guard.map(Json) + status_guard + .map(Json) + .ok_or_else(|| Json(Error::from("No status found (yet)"))) +} + +/// Default catcher for any unsuppored request +#[catch(default)] +fn unsupported(status: rocket::http::Status, _request: &Request<'_>) -> Json { + let code = status.code; + + Json(Error::from(format!( + "Unhandled/unsupported API call or path (HTTP {code})" + ))) } /// Creates a Rocket and attaches the config parsing and update loop as fairings. pub fn setup() -> Rocket { rocket::build() .mount("/", routes![status]) + .register("/", catchers![unsupported]) .attach(AdHoc::config::()) .attach(AdHoc::on_liftoff("Updater", |rocket| { Box::pin(async move {