changeset 36:ebdb9c50adb1

Better responses, more database encapsulation
author Lewin Bormann <lbo@spheniscida.de>
date Thu, 03 Dec 2020 09:25:29 +0100
parents 097f1c1c5f2b
children 08b4f7127980
files src/db.rs src/http.rs src/main.rs src/types.rs
diffstat 4 files changed, 104 insertions(+), 29 deletions(-) [+]
line wrap: on
line diff
--- a/src/db.rs	Thu Dec 03 08:12:25 2020 +0100
+++ b/src/db.rs	Thu Dec 03 09:25:29 2020 +0100
@@ -1,3 +1,4 @@
+use crate::ids;
 use crate::types;
 
 /// Managed by Rocket.
@@ -33,6 +34,29 @@
         Ok(returnable)
     }
 
+    pub fn log_geopoint(
+        &self,
+        name: &str,
+        secret: &str,
+        point: &types::GeoPoint,
+    ) -> Result<(), postgres::Error> {
+        let stmt = self.0.prepare_cached("INSERT INTO geohub.geodata (client, lat, long, spd, t, ele, secret) VALUES ($1, $2, $3, $4, $5, $6, public.digest($7, 'sha256'))").unwrap();
+        let channel = format!("NOTIFY {}, '{}'", ids::channel_name(name, secret), name);
+        let notify = self.0.prepare_cached(channel.as_str()).unwrap();
+        stmt.execute(&[
+            &name,
+            &point.lat,
+            &point.long,
+            &point.spd,
+            &point.time,
+            &point.ele,
+            &secret,
+        ])
+        .unwrap();
+        notify.execute(&[]).unwrap();
+        Ok(())
+    }
+
     /// Queries for at most `limit` rows since entry ID `last`.
     pub fn check_for_new_rows(
         &self,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/http.rs	Thu Dec 03 09:25:29 2020 +0100
@@ -0,0 +1,29 @@
+
+use rocket::response::Responder;
+
+#[derive(Responder)]
+pub enum GeoHubResponse {
+    #[response(status=200, content_type="json")]
+    Ok(String),
+    #[response(status=400)]
+    BadRequest(String),
+    #[response(status=500)]
+    ServerError(String),
+}
+
+pub fn return_json<T: serde::Serialize>(obj: &T) -> GeoHubResponse {
+    let json = serde_json::to_string(&obj);
+    if let Ok(json) = json {
+        return GeoHubResponse::Ok(json);
+    } else {
+        return GeoHubResponse::ServerError(json.unwrap_err().to_string());
+    }
+}
+
+pub fn bad_request(msg: String) -> GeoHubResponse {
+    GeoHubResponse::BadRequest(msg)
+}
+
+pub fn server_error(msg: String) -> GeoHubResponse {
+    GeoHubResponse::ServerError(msg)
+}
--- a/src/main.rs	Thu Dec 03 08:12:25 2020 +0100
+++ b/src/main.rs	Thu Dec 03 09:25:29 2020 +0100
@@ -1,6 +1,7 @@
 #![feature(proc_macro_hygiene, decl_macro)]
 
 mod db;
+mod http;
 mod ids;
 mod notifier;
 mod types;
@@ -58,14 +59,12 @@
     name: String,
     secret: Option<String>,
     timeout: Option<u64>,
-) -> rocket_contrib::json::Json<LiveUpdate> {
+) -> http::GeoHubResponse {
     if !ids::name_and_secret_acceptable(name.as_str(), secret.as_ref().map(|s| s.as_str())) {
-        return rocket_contrib::json::Json(LiveUpdate {
-            typ: "GeoHubUpdate".into(),
-            last: None,
-            geo: None,
-            error: Some("You have supplied an invalid secret or name. Both must be ASCII alphanumeric strings.".into()),
-        });
+        return http::bad_request(
+            "You have supplied an invalid secret or name. Both must be ASCII alphanumeric strings."
+                .into(),
+        );
     }
 
     // Ask the notify thread to tell us when there is an update for this client name and secret.
@@ -82,18 +81,18 @@
     notify_manager.send(req).unwrap();
 
     if let Ok(response) = recv.recv_timeout(time::Duration::new(timeout.unwrap_or(30), 0)) {
-        return rocket_contrib::json::Json(LiveUpdate {
+        return http::return_json(&LiveUpdate {
             typ: "GeoHubUpdate".into(),
             last: response.last,
             geo: response.geo,
             error: None,
         });
     }
-    return rocket_contrib::json::Json(LiveUpdate {
+    return http::return_json(&LiveUpdate {
         typ: "GeoHubUpdate".into(),
         last: None,
         geo: None,
-        error: Some("No new rows returned".into()),
+        error: None,
     });
 }
 
@@ -106,7 +105,13 @@
     from: Option<String>,
     to: Option<String>,
     limit: Option<i64>,
-) -> rocket_contrib::json::Json<types::GeoJSON> {
+) -> http::GeoHubResponse {
+    if !ids::name_and_secret_acceptable(name.as_str(), secret.as_ref().map(|s| s.as_str())) {
+        return http::bad_request(
+            "You have supplied an invalid secret or name. Both must be ASCII alphanumeric strings."
+                .into(),
+        );
+    }
     let db = db::DBQuery(&db.0);
     let from_ts =
         from.and_then(util::flexible_timestamp_parse)
@@ -120,12 +125,11 @@
     let limit = limit.unwrap_or(16384);
     let secret = secret.as_ref().map(|s| s.as_str()).unwrap_or("");
 
-    if let Ok(json) = db.retrieve_json(name.as_str(), from_ts, to_ts, secret, limit) {
-        return rocket_contrib::json::Json(json);
+    let result = db.retrieve_json(name.as_str(), from_ts, to_ts, secret, limit);
+    match result {
+        Ok(json) => http::return_json(&json),
+        Err(e) => http::server_error(e.to_string()),
     }
-
-    // Todo: Use custom database error return
-    rocket_contrib::json::Json(types::GeoJSON::new())
 }
 
 /// Ingest geo data.
@@ -142,26 +146,34 @@
     time: Option<String>,
     s: Option<f64>,
     ele: Option<f64>,
-) -> rocket::http::Status {
+) -> http::GeoHubResponse {
+    let db = db::DBQuery(&db.0);
     // Check that secret and client name are legal.
     if !ids::name_and_secret_acceptable(name.as_str(), secret.as_ref().map(|s| s.as_str())) {
-        return rocket::http::Status::NotAcceptable;
+        return http::bad_request(
+            "You have supplied an invalid secret or name. Both must be ASCII alphanumeric strings."
+                .into(),
+        );
     }
     let mut ts = chrono::Utc::now();
     if let Some(time) = time {
         ts = util::flexible_timestamp_parse(time).unwrap_or(ts);
     }
-    let stmt = db.0.prepare_cached("INSERT INTO geohub.geodata (client, lat, long, spd, t, ele, secret) VALUES ($1, $2, $3, $4, $5, $6, public.digest($7, 'sha256'))").unwrap();
-    let channel = format!(
-        "NOTIFY {}, '{}'",
-        ids::channel_name(name.as_str(), secret.as_ref().unwrap_or(&"".into())),
-        name
-    );
-    let notify = db.0.prepare_cached(channel.as_str()).unwrap();
-    stmt.execute(&[&name, &lat, &longitude, &s, &ts, &ele, &secret])
-        .unwrap();
-    notify.execute(&[]).unwrap();
-    rocket::http::Status::Ok
+    let point = types::GeoPoint {
+        lat: lat,
+        long: longitude,
+        time: ts,
+        spd: s,
+        ele: ele,
+    };
+    if let Err(e) = db.log_geopoint(
+        name.as_str(),
+        secret.as_ref().map(|s| s.as_str()).unwrap_or(""),
+        &point,
+    ) {
+        return http::server_error(e.to_string());
+    }
+    http::GeoHubResponse::Ok("".into())
 }
 
 /// Serve static files.
--- a/src/types.rs	Thu Dec 03 08:12:25 2020 +0100
+++ b/src/types.rs	Thu Dec 03 09:25:29 2020 +0100
@@ -1,3 +1,13 @@
+
+/// Non-JSON plain point representation.
+#[derive(Debug, Clone)]
+pub struct GeoPoint {
+    pub lat: f64,
+    pub long: f64,
+    pub spd: Option<f64>,
+    pub ele: Option<f64>,
+    pub time: chrono::DateTime<chrono::Utc>,
+}
 
 /// Fetch geodata as JSON.
 ///