Implement error catchers for all requests (closes: #5)

* Introduce an error JSON output
* Return error JSON output if the status data is not there (yet)
* Introduce a default catcher to return error JSON output in all other
  unsupported/unhandled cases
* Update the documentation
This commit is contained in:
Paul van Tilburg 2023-01-16 21:08:03 +01:00
parent 8d892d8619
commit ca116351db
Signed by: paul
GPG Key ID: C6DE073EDA9EEC4D
2 changed files with 46 additions and 6 deletions

View File

@ -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/)

View File

@ -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<str>) -> Self {
let error = String::from(message.as_ref());
Self { error }
}
}
/// Returns the current (last known) status.
#[get("/", format = "application/json")]
async fn status() -> Option<Json<Status>> {
async fn status() -> Result<Json<Status>, Json<Error>> {
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<Error> {
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<Build> {
rocket::build()
.mount("/", routes![status])
.register("/", catchers![unsupported])
.attach(AdHoc::config::<Config>())
.attach(AdHoc::on_liftoff("Updater", |rocket| {
Box::pin(async move {