feat(errors): remake most of the errors from thiserror to snafu
This commit is contained in:
parent
05aa961431
commit
24eb77d78f
12 changed files with 868 additions and 850 deletions
1455
Cargo.lock
generated
1455
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
13
Cargo.toml
13
Cargo.toml
|
@ -6,29 +6,28 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.71"
|
|
||||||
bstr = { version = "1.9.0", features = ["serde"] }
|
bstr = { version = "1.9.0", features = ["serde"] }
|
||||||
bytes = { version = "1.4.0", features = ["serde"] }
|
bytes = { version = "1.4.0", features = ["serde"] }
|
||||||
chrono = { version = "0.4.26", features = ["serde"] }
|
chrono = { version = "0.4.26", features = ["serde"] }
|
||||||
clap = { version = "4.3.8", features = ["derive", "env"] }
|
clap = { version = "4.3.8", features = ["derive", "env"] }
|
||||||
derive_more = "0.99.17"
|
|
||||||
dotenvy = "0.15.7"
|
dotenvy = "0.15.7"
|
||||||
fred = { version = "9.0.3", features = ["nom"] }
|
fred = { version = "10.0.3", features = ["nom"] }
|
||||||
heapless = { version = "0.7.16", features = ["ufmt-impl"] }
|
heapless = { version = "0.8.0", features = ["ufmt"] }
|
||||||
hex = { version = "0.4.3", default-features = false, features = ["std", "alloc"] }
|
hex = { version = "0.4.3", default-features = false, features = ["std", "alloc"] }
|
||||||
hifitime = "3.8.2"
|
hifitime = "4.0.2"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
nom = { version = "7.1.3", default-features = false, features = ["std", "alloc"] }
|
nom = { version = "7.1.3", default-features = false, features = ["std", "alloc"] }
|
||||||
ntex = { version = "1.1.0", features = ["tokio", "cookie", "url"] }
|
ntex = { version = "2.10.0", features = ["tokio", "cookie", "url"] }
|
||||||
phf = { version = "0.11.2", features = ["serde", "macros"] }
|
phf = { version = "0.11.2", features = ["serde", "macros"] }
|
||||||
regex = "1.8.4"
|
regex = "1.8.4"
|
||||||
rust_decimal = { version = "1.30.0", features = ["rkyv", "rkyv-safe"] }
|
rust_decimal = { version = "1.30.0", features = ["rkyv", "rkyv-safe"] }
|
||||||
serde = { version = "1.0.164", features = ["derive", "alloc"] }
|
serde = { version = "1.0.164", features = ["derive", "alloc"] }
|
||||||
serde_json = "1.0.99"
|
serde_json = "1.0.99"
|
||||||
serde_qs = "0.12.0"
|
serde_qs = "0.13.0"
|
||||||
serde_with = { version = "3.6.1", features = ["hex"] }
|
serde_with = { version = "3.6.1", features = ["hex"] }
|
||||||
smallstr = { version = "0.3.0", features = ["std", "union"] }
|
smallstr = { version = "0.3.0", features = ["std", "union"] }
|
||||||
thiserror = "1.0.40"
|
thiserror = "1.0.40"
|
||||||
tokio = { version = "1.28.2", features = ["full"] }
|
tokio = { version = "1.28.2", features = ["full"] }
|
||||||
ufmt = { version = "0.2.0", features = ["std"] }
|
ufmt = { version = "0.2.0", features = ["std"] }
|
||||||
futures-util = { version = "0.3.30", features = ["tokio-io"] }
|
futures-util = { version = "0.3.30", features = ["tokio-io"] }
|
||||||
|
snafu = "0.8.5"
|
||||||
|
|
|
@ -3,6 +3,15 @@ use std::fmt::Debug;
|
||||||
use std::num::ParseFloatError;
|
use std::num::ParseFloatError;
|
||||||
use thiserror::Error as ThisError;
|
use thiserror::Error as ThisError;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Тип ошибки Ingest протокола
|
||||||
|
///
|
||||||
|
/// К сожалению, не может быть переделан на Snafu, так как
|
||||||
|
/// Snafu не поддерживает generic типы как source ошибки,
|
||||||
|
/// не приделывая 'static лайфтайм.
|
||||||
|
///
|
||||||
|
/// См. https://github.com/shepmaster/snafu/issues/99
|
||||||
|
///
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Debug, ThisError)]
|
#[derive(Debug, ThisError)]
|
||||||
pub enum Error<I: Debug> {
|
pub enum Error<I: Debug> {
|
||||||
|
|
|
@ -9,14 +9,15 @@
|
||||||
use crate::ingest_protocol::error::Error;
|
use crate::ingest_protocol::error::Error;
|
||||||
use crate::ingest_protocol::parser::parse_mac_address;
|
use crate::ingest_protocol::parser::parse_mac_address;
|
||||||
use crate::utils::{EpochUTC, SupportedUnit};
|
use crate::utils::{EpochUTC, SupportedUnit};
|
||||||
use crate::web_server::app_error::AppError;
|
use crate::web_server::app_error::{AppError, ServerRedisSnafu};
|
||||||
|
|
||||||
use fred::clients::RedisClient;
|
use fred::clients::Client as RedisClient;
|
||||||
use fred::interfaces::{HashesInterface, KeysInterface};
|
use fred::interfaces::{HashesInterface, KeysInterface};
|
||||||
use hifitime::Epoch;
|
use hifitime::Epoch;
|
||||||
use rust_decimal::Decimal;
|
use rust_decimal::Decimal;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::serde_as;
|
use serde_with::serde_as;
|
||||||
|
use snafu::ResultExt;
|
||||||
use ufmt::uwrite;
|
use ufmt::uwrite;
|
||||||
|
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
@ -116,10 +117,10 @@ impl NMDeviceDataPacket {
|
||||||
|
|
||||||
uwrite!(&mut key, "devices_{}", device_mac_enc).unwrap();
|
uwrite!(&mut key, "devices_{}", device_mac_enc).unwrap();
|
||||||
|
|
||||||
let device_exists: Option<bool> = redis.hget(key.as_str(), "exists").await?;
|
let device_exists: Option<bool> = redis.hget(key.as_str(), "exists").await.context(ServerRedisSnafu)?;
|
||||||
|
|
||||||
if !device_exists.is_some_and(|v| v) {
|
if !device_exists.is_some_and(|v| v) {
|
||||||
return Err(AppError::DeviceNotFound(hex::encode(self.mac)));
|
return Err(AppError::DeviceNotFound { mac: hex::encode(self.mac) });
|
||||||
}
|
}
|
||||||
|
|
||||||
// devices_{device_id}_{tai_timestamp}_{sensor_id}
|
// devices_{device_id}_{tai_timestamp}_{sensor_id}
|
||||||
|
@ -136,7 +137,7 @@ impl NMDeviceDataPacket {
|
||||||
|
|
||||||
redis
|
redis
|
||||||
.set(key.as_str(), sensor.value.to_string(), None, None, false)
|
.set(key.as_str(), sensor.value.to_string(), None, None, false)
|
||||||
.await?;
|
.await.context(ServerRedisSnafu)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(commands) = &self.commands {
|
if let Some(commands) = &self.commands {
|
||||||
|
@ -146,7 +147,7 @@ impl NMDeviceDataPacket {
|
||||||
|
|
||||||
redis
|
redis
|
||||||
.set(key.as_str(), cmd_value, None, None, false)
|
.set(key.as_str(), cmd_value, None, None, false)
|
||||||
.await?;
|
.await.context(ServerRedisSnafu)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ mod hifitime_serde;
|
||||||
|
|
||||||
use phf::phf_map;
|
use phf::phf_map;
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use std::borrow::Cow;
|
use std::{borrow::Cow, sync::Arc};
|
||||||
|
|
||||||
pub use hifitime_serde::EpochUTC;
|
pub use hifitime_serde::EpochUTC;
|
||||||
|
|
||||||
|
@ -83,3 +83,7 @@ static STR_TO_UNITS: phf::Map<&'static str, SupportedUnit> = phf_map! {
|
||||||
"s" => SupportedUnit::Seconds,
|
"s" => SupportedUnit::Seconds,
|
||||||
"KWh" => SupportedUnit::KWh,
|
"KWh" => SupportedUnit::KWh,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub fn convert_to_arc<T: std::error::Error>(error: T) -> Arc<T> {
|
||||||
|
Arc::new(error)
|
||||||
|
}
|
||||||
|
|
|
@ -1,92 +1,114 @@
|
||||||
use derive_more::Display;
|
use std::sync::Arc;
|
||||||
use fred::prelude::*;
|
|
||||||
|
|
||||||
use ntex::http::StatusCode;
|
use ntex::http::StatusCode;
|
||||||
use ntex::web;
|
use ntex::web;
|
||||||
|
|
||||||
use ntex::web::{HttpRequest, HttpResponse};
|
use ntex::web::{HttpRequest, HttpResponse};
|
||||||
|
|
||||||
use thiserror::Error;
|
use snafu::Snafu;
|
||||||
|
|
||||||
use crate::insert_header;
|
use crate::insert_header;
|
||||||
|
use crate::utils::convert_to_arc;
|
||||||
use crate::web_server::old_device_sensor_api::qs_parser::QSParserError;
|
use crate::web_server::old_device_sensor_api::qs_parser::QSParserError;
|
||||||
use rust_decimal::Decimal;
|
use rust_decimal::Decimal;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
use super::old_device_sensor_api::qs_parser;
|
use super::old_device_sensor_api::qs_parser;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// Главный объект ошибки [std::error::Error] для всего Web API.
|
/// Главный объект ошибки [std::error::Error] для всего Web API.
|
||||||
///
|
///
|
||||||
/// В целом, все Result у Web сервера должны использовать этот Error.
|
/// В целом, все Result у Web сервера должны использовать этот Error.
|
||||||
#[derive(Debug, Error, Display)]
|
#[derive(Debug, Snafu, Clone)]
|
||||||
|
#[snafu(visibility(pub))]
|
||||||
pub enum AppError {
|
pub enum AppError {
|
||||||
#[display(fmt = "IDK")]
|
#[snafu(display("Could not read file"))]
|
||||||
JsonError(#[from] serde_json::Error),
|
JsonError {
|
||||||
|
#[snafu(source(from(serde_json::Error, convert_to_arc::<serde_json::Error>)))]
|
||||||
|
source: Arc<serde_json::Error>
|
||||||
|
},
|
||||||
|
|
||||||
#[display(fmt = "IDK")]
|
#[snafu(display("Could not read file"))]
|
||||||
QSError(QSParserError),
|
QSError {
|
||||||
|
source: QSParserError
|
||||||
|
},
|
||||||
|
|
||||||
#[display(fmt = "IDK")]
|
#[snafu(display("Could not read file"))]
|
||||||
ServerRedisError(#[from] RedisError),
|
ServerRedisError {
|
||||||
|
source: fred::error::Error
|
||||||
|
},
|
||||||
|
|
||||||
#[display(fmt = "IDK")]
|
#[snafu(display("Could not read file"))]
|
||||||
UnknownMethod(String),
|
UnknownMethod {
|
||||||
|
method: String
|
||||||
|
},
|
||||||
|
|
||||||
#[display(fmt = "IDK")]
|
#[snafu(display("Could not read file"))]
|
||||||
RequestTooLarge,
|
RequestTooLarge,
|
||||||
|
|
||||||
#[display(fmt = "IDK")]
|
#[snafu(display("API key invalid: {reason}"))]
|
||||||
ApiKeyInvalid { reason: &'static str },
|
ApiKeyInvalid { reason: &'static str },
|
||||||
|
|
||||||
#[display(fmt = "IDK")]
|
#[snafu(display("Could not read file"))]
|
||||||
UnitValidationFailed {
|
UnitValidationFailed {
|
||||||
max: Option<Decimal>,
|
max: Option<Decimal>,
|
||||||
min: Option<Decimal>,
|
min: Option<Decimal>,
|
||||||
},
|
},
|
||||||
|
|
||||||
#[display(fmt = "UTF8")]
|
#[snafu(display("UTF-8 Error"))]
|
||||||
Utf8Error(#[from] std::str::Utf8Error),
|
Utf8Error {
|
||||||
|
source: std::str::Utf8Error
|
||||||
#[display(fmt = "IDK")]
|
|
||||||
UnknownBody {
|
|
||||||
json_err: Option<serde_json::Error>,
|
|
||||||
query_error: Option<qs_parser::QSParserError>,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
#[display(fmt = "IDK")]
|
#[snafu(display("Could not read file"))]
|
||||||
DeviceNotFound(String),
|
UnknownBody {
|
||||||
|
json_err: Arc<Option<serde_json::Error>>,
|
||||||
|
query_error: Arc<Option<qs_parser::QSParserError>>,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[snafu(display("Could not read file"))]
|
||||||
|
DeviceNotFound {
|
||||||
|
mac: String
|
||||||
|
},
|
||||||
|
|
||||||
|
#[snafu(display("Could not read file"))]
|
||||||
|
TimeIsLongBehindNow
|
||||||
}
|
}
|
||||||
|
|
||||||
impl web::error::WebResponseError for AppError {
|
impl web::error::WebResponseError for AppError {
|
||||||
fn status_code(&self) -> StatusCode {
|
fn status_code(&self) -> StatusCode {
|
||||||
match self {
|
match self {
|
||||||
AppError::JsonError(_) => StatusCode::BAD_REQUEST,
|
AppError::JsonError { .. } => StatusCode::BAD_REQUEST,
|
||||||
AppError::UnknownMethod(_) => StatusCode::BAD_REQUEST,
|
AppError::UnknownMethod { .. } => StatusCode::BAD_REQUEST,
|
||||||
AppError::UnitValidationFailed { .. } => StatusCode::BAD_REQUEST,
|
AppError::UnitValidationFailed { .. } => StatusCode::BAD_REQUEST,
|
||||||
AppError::RequestTooLarge => StatusCode::PAYLOAD_TOO_LARGE,
|
AppError::RequestTooLarge => StatusCode::PAYLOAD_TOO_LARGE,
|
||||||
AppError::ServerRedisError(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
AppError::ServerRedisError { .. } => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
AppError::ApiKeyInvalid { .. } => StatusCode::BAD_REQUEST,
|
AppError::ApiKeyInvalid { .. } => StatusCode::BAD_REQUEST,
|
||||||
AppError::Utf8Error(_) => StatusCode::BAD_REQUEST,
|
AppError::Utf8Error { .. } => StatusCode::BAD_REQUEST,
|
||||||
AppError::UnknownBody { .. } => StatusCode::BAD_REQUEST,
|
AppError::UnknownBody { .. } => StatusCode::BAD_REQUEST,
|
||||||
AppError::QSError(..) => StatusCode::BAD_REQUEST,
|
AppError::QSError { .. } => StatusCode::BAD_REQUEST,
|
||||||
AppError::DeviceNotFound(..) => StatusCode::BAD_REQUEST,
|
AppError::DeviceNotFound { .. } => StatusCode::BAD_REQUEST,
|
||||||
|
AppError::TimeIsLongBehindNow { .. } => StatusCode::BAD_REQUEST
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn error_response(&self, _: &HttpRequest) -> HttpResponse {
|
fn error_response(&self, _: &HttpRequest) -> HttpResponse {
|
||||||
let error_message = match self {
|
let error_message = match self {
|
||||||
AppError::JsonError(_) => "Invalid JSON",
|
AppError::JsonError { .. } => "Invalid JSON",
|
||||||
AppError::UnknownMethod(_) => "Unknown command",
|
AppError::UnknownMethod { .. } => "Unknown command",
|
||||||
AppError::UnitValidationFailed { .. } => "Unknown command",
|
AppError::UnitValidationFailed { .. } => "Unknown command",
|
||||||
AppError::RequestTooLarge => "Request is too large",
|
AppError::RequestTooLarge => "Request is too large",
|
||||||
AppError::ServerRedisError(_) => "Internal server error",
|
AppError::ServerRedisError { .. } => "Internal server error",
|
||||||
AppError::ApiKeyInvalid { .. } => "API Key invalid",
|
AppError::ApiKeyInvalid { .. } => "API Key invalid",
|
||||||
AppError::Utf8Error(_) => "Invalid UTF8 sequence",
|
AppError::Utf8Error { .. } => "Invalid UTF8 sequence",
|
||||||
AppError::UnknownBody { .. } => {
|
AppError::UnknownBody { .. } => {
|
||||||
"Can't figure out where and in what encoding the main data is"
|
"Can't figure out where and in what encoding the main data is"
|
||||||
}
|
}
|
||||||
AppError::QSError(..) => "UrlEncoded body or query params are incorrect",
|
AppError::QSError { .. } => "UrlEncoded body or query params are incorrect",
|
||||||
AppError::DeviceNotFound(..) => "Device not found",
|
AppError::DeviceNotFound { .. } => "Device not found",
|
||||||
|
AppError::TimeIsLongBehindNow { .. } => "Time is long behind what it should be"
|
||||||
};
|
};
|
||||||
|
|
||||||
let status_code = self.status_code();
|
let status_code = self.status_code();
|
||||||
|
@ -99,19 +121,18 @@ impl web::error::WebResponseError for AppError {
|
||||||
let mut resp = HttpResponse::build(status_code).json(&body);
|
let mut resp = HttpResponse::build(status_code).json(&body);
|
||||||
let headers = resp.headers_mut();
|
let headers = resp.headers_mut();
|
||||||
|
|
||||||
let error_as_string = format!("{:?}", &self);
|
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
AppError::JsonError(json_err) => {
|
AppError::JsonError { source } => {
|
||||||
insert_header!(headers, "X-Error-Line", json_err.line());
|
insert_header!(headers, "X-Error-Line", source.line());
|
||||||
insert_header!(headers, "X-Error-Column", json_err.column());
|
insert_header!(headers, "X-Error-Column", source.column());
|
||||||
insert_header!(
|
insert_header!(
|
||||||
headers,
|
headers,
|
||||||
"X-Error-Description",
|
"X-Error-Description",
|
||||||
json_err.to_string().escape_default().collect::<String>()
|
source.to_string().escape_default().collect::<String>()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
AppError::UnknownMethod(method) => {
|
AppError::UnknownMethod {method} => {
|
||||||
insert_header!(
|
insert_header!(
|
||||||
headers,
|
headers,
|
||||||
"X-Unknown-Cmd",
|
"X-Unknown-Cmd",
|
||||||
|
@ -124,8 +145,8 @@ impl web::error::WebResponseError for AppError {
|
||||||
AppError::ApiKeyInvalid { reason } => {
|
AppError::ApiKeyInvalid { reason } => {
|
||||||
insert_header!(headers, "X-Error-Description", *reason);
|
insert_header!(headers, "X-Error-Description", *reason);
|
||||||
}
|
}
|
||||||
AppError::QSError(err) => {
|
AppError::QSError { source } => {
|
||||||
if let QSParserError::Parsing(desc) = err {
|
if let QSParserError::Parsing { context: desc } = source {
|
||||||
insert_header!(
|
insert_header!(
|
||||||
headers,
|
headers,
|
||||||
"X-Error-Description",
|
"X-Error-Description",
|
||||||
|
@ -137,6 +158,8 @@ impl web::error::WebResponseError for AppError {
|
||||||
};
|
};
|
||||||
|
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
|
let error_as_string = format!("{:?}", &self);
|
||||||
|
|
||||||
insert_header!(headers, "X-Full-Error", error_as_string);
|
insert_header!(headers, "X-Full-Error", error_as_string);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
|
|
||||||
use fred::bytes_utils::Str;
|
use fred::bytes_utils::Str;
|
||||||
use fred::prelude::*;
|
use fred::prelude::*;
|
||||||
|
use fred::clients::Client as RedisClient;
|
||||||
|
|
||||||
use old_app_api::old_api_handler;
|
use old_app_api::old_api_handler;
|
||||||
|
|
||||||
pub mod app_error;
|
pub mod app_error;
|
||||||
|
@ -27,7 +29,7 @@ pub async fn server_main() {
|
||||||
let client = RedisClient::default();
|
let client = RedisClient::default();
|
||||||
client.init().await.unwrap();
|
client.init().await.unwrap();
|
||||||
|
|
||||||
let asd: Str = client.ping().await.unwrap();
|
let asd: Str = client.ping(None).await.unwrap();
|
||||||
|
|
||||||
println!("Ping result: {}", asd);
|
println!("Ping result: {}", asd);
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use crate::web_server::app_error::AppError;
|
use crate::web_server::app_error::{self, AppError};
|
||||||
use crate::web_server::old_app_api::types::AppInitRequest;
|
use crate::web_server::old_app_api::types::AppInitRequest;
|
||||||
use crate::web_server::NMAppState;
|
use crate::web_server::NMAppState;
|
||||||
|
|
||||||
use serde_json::{json};
|
use serde_json::{json};
|
||||||
|
use snafu::ResultExt;
|
||||||
|
|
||||||
|
|
||||||
use crate::insert_header;
|
use crate::insert_header;
|
||||||
|
@ -19,7 +20,7 @@ pub async fn app_init(
|
||||||
let _: () = app_state
|
let _: () = app_state
|
||||||
.redis_client
|
.redis_client
|
||||||
.set("test", 123, None, None, true)
|
.set("test", 123, None, None, true)
|
||||||
.await?;
|
.await.context(app_error::ServerRedisSnafu)?;
|
||||||
|
|
||||||
Ok(web::HttpResponse::build(StatusCode::OK).body("Hello world!"))
|
Ok(web::HttpResponse::build(StatusCode::OK).body("Hello world!"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,12 +7,15 @@ use ntex::web::types::State;
|
||||||
use ntex::util::Bytes;
|
use ntex::util::Bytes;
|
||||||
use ntex::web;
|
use ntex::web;
|
||||||
use nom::AsBytes;
|
use nom::AsBytes;
|
||||||
|
use snafu::ResultExt;
|
||||||
use crate::web_server::app_error::AppError;
|
use crate::web_server::app_error::AppError;
|
||||||
use crate::web_server::NMAppState;
|
use crate::web_server::NMAppState;
|
||||||
use crate::web_server::old_app_api::handlers::{app_init, version};
|
use crate::web_server::old_app_api::handlers::{app_init, version};
|
||||||
use crate::web_server::old_app_api::types::{AppInitRequest, MandatoryParams};
|
use crate::web_server::old_app_api::types::{AppInitRequest, MandatoryParams};
|
||||||
use crate::web_server::utils::redis::is_api_key_valid;
|
use crate::web_server::utils::redis::is_api_key_valid;
|
||||||
|
|
||||||
|
use super::app_error;
|
||||||
|
|
||||||
|
|
||||||
/// Обработчик запросов от приложений.
|
/// Обработчик запросов от приложений.
|
||||||
///
|
///
|
||||||
|
@ -30,7 +33,7 @@ pub async fn old_api_handler(
|
||||||
|
|
||||||
let body_bytes = body_bytes.as_bytes();
|
let body_bytes = body_bytes.as_bytes();
|
||||||
|
|
||||||
let mandatory_params: MandatoryParams<'_> = serde_json::from_slice(body_bytes)?; // TODO: Simd-JSON
|
let mandatory_params: MandatoryParams<'_> = serde_json::from_slice(body_bytes).context(app_error::JsonSnafu {})?; // TODO: Simd-JSON
|
||||||
|
|
||||||
// Ignore clippy singlematch
|
// Ignore clippy singlematch
|
||||||
if mandatory_params.cmd.as_ref() == "version" { return version((), &app_state).await }
|
if mandatory_params.cmd.as_ref() == "version" { return version((), &app_state).await }
|
||||||
|
@ -39,11 +42,11 @@ pub async fn old_api_handler(
|
||||||
|
|
||||||
match mandatory_params.cmd.as_ref() {
|
match mandatory_params.cmd.as_ref() {
|
||||||
"appInit" => {
|
"appInit" => {
|
||||||
let body: AppInitRequest = serde_json::from_slice(body_bytes)?;
|
let body: AppInitRequest = serde_json::from_slice(body_bytes).context(app_error::JsonSnafu {})?;
|
||||||
|
|
||||||
app_init(body, &app_state).await
|
app_init(body, &app_state).await
|
||||||
}
|
}
|
||||||
_ => Err(AppError::UnknownMethod(mandatory_params.cmd.to_string())),
|
_ => Err(AppError::UnknownMethod { method: mandatory_params.cmd.to_string() }),
|
||||||
}
|
}
|
||||||
|
|
||||||
//Ok("fuck")
|
//Ok("fuck")
|
||||||
|
|
|
@ -2,26 +2,35 @@
|
||||||
|
|
||||||
pub mod qs_parser;
|
pub mod qs_parser;
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::ingest_protocol::NMJsonPacket;
|
use crate::ingest_protocol::NMJsonPacket;
|
||||||
use crate::web_server::app_error::AppError;
|
use crate::web_server::app_error::{self, AppError};
|
||||||
|
|
||||||
use ntex::http::{HttpMessage, StatusCode};
|
use ntex::http::{HttpMessage, StatusCode};
|
||||||
use ntex::util::Bytes;
|
use ntex::util::Bytes;
|
||||||
use ntex::web::types::State;
|
use ntex::web::types::State;
|
||||||
use ntex::{http, web};
|
use ntex::{http, web};
|
||||||
use qs_parser::QSParserError;
|
use qs_parser::QSParserError;
|
||||||
use thiserror::Error;
|
use snafu::{ResultExt, Snafu};
|
||||||
|
|
||||||
use super::NMAppState;
|
use super::NMAppState;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Snafu, Debug)]
|
||||||
|
#[snafu(visibility(pub))]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Device not found")]
|
#[snafu(display("Device not found"))]
|
||||||
DeviceNotFound(String),
|
DeviceNotFound {
|
||||||
#[error("Time sent with the device is way too behind now")]
|
mac: String
|
||||||
|
},
|
||||||
|
|
||||||
|
#[snafu(display("Time sent with the device is way too behind now"))]
|
||||||
TimeIsLongBehindNow,
|
TimeIsLongBehindNow,
|
||||||
#[error("{0}")]
|
|
||||||
QSParserError(#[from] QSParserError),
|
#[snafu(display("{source}"))]
|
||||||
|
QSParser {
|
||||||
|
source: QSParserError
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Обработчик данных датчиков с устройств.
|
/// Обработчик данных датчиков с устройств.
|
||||||
|
@ -30,7 +39,7 @@ pub enum Error {
|
||||||
/// Для того чтобы пользователям было легче, на оба пути можно отправлять и POST и GET.
|
/// Для того чтобы пользователям было легче, на оба пути можно отправлять и POST и GET.
|
||||||
///
|
///
|
||||||
/// На POST можно отправлять JSON или url-encoded тело, на GET - только через Query String.
|
/// На POST можно отправлять JSON или url-encoded тело, на GET - только через Query String.
|
||||||
pub async fn device_handler<'a>(
|
pub async fn device_handler(
|
||||||
request: web::HttpRequest,
|
request: web::HttpRequest,
|
||||||
body: Bytes,
|
body: Bytes,
|
||||||
app_state: State<NMAppState>,
|
app_state: State<NMAppState>,
|
||||||
|
@ -49,7 +58,7 @@ pub async fn device_handler<'a>(
|
||||||
Err(error) => json_error = Some(error),
|
Err(error) => json_error = Some(error),
|
||||||
},
|
},
|
||||||
"application/x-www-form-urlencoded" => {
|
"application/x-www-form-urlencoded" => {
|
||||||
let body = std::str::from_utf8(body.as_ref())?;
|
let body = std::str::from_utf8(body.as_ref()).context(app_error::Utf8Snafu)?;
|
||||||
match qs_parser::parse_nm_qs_format(body).await {
|
match qs_parser::parse_nm_qs_format(body).await {
|
||||||
Ok(qs_body) => {
|
Ok(qs_body) => {
|
||||||
real_body = Some(NMJsonPacket {
|
real_body = Some(NMJsonPacket {
|
||||||
|
@ -76,8 +85,8 @@ pub async fn device_handler<'a>(
|
||||||
real_body.save_to_db(&app_state.redis_client).await?;
|
real_body.save_to_db(&app_state.redis_client).await?;
|
||||||
} else {
|
} else {
|
||||||
return Err(AppError::UnknownBody {
|
return Err(AppError::UnknownBody {
|
||||||
json_err: json_error,
|
json_err: Arc::new(json_error),
|
||||||
query_error,
|
query_error: Arc::new(query_error),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,45 +1,57 @@
|
||||||
use crate::ingest_protocol::error::Error;
|
use crate::ingest_protocol::error::Error;
|
||||||
use crate::ingest_protocol::parser::parse_mac_address;
|
use crate::ingest_protocol::parser::parse_mac_address;
|
||||||
use crate::ingest_protocol::{NMDeviceDataPacket, SensorValue};
|
use crate::ingest_protocol::{NMDeviceDataPacket, SensorValue};
|
||||||
|
use crate::utils::convert_to_arc;
|
||||||
use hifitime::Epoch;
|
use hifitime::Epoch;
|
||||||
use ntex::util::HashMap;
|
use ntex::util::HashMap;
|
||||||
use rust_decimal::Decimal;
|
use rust_decimal::Decimal;
|
||||||
|
use snafu::{ResultExt, Snafu};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::num::ParseFloatError;
|
use std::num::ParseFloatError;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
/// В иделае было бы хорошо сделать всё как у [serde_json::Error], но это слишком большая морока
|
/// В иделае было бы хорошо сделать всё как у [serde_json::Error], но это слишком большая морока
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Error, Clone, Debug)]
|
#[derive(Snafu, Clone, Debug)]
|
||||||
|
#[snafu(visibility(pub))]
|
||||||
pub enum QSParserError {
|
pub enum QSParserError {
|
||||||
#[error("asd")]
|
#[snafu(display("asd"))]
|
||||||
SerdeQS(#[from] Arc<serde_qs::Error>),
|
SerdeQS {
|
||||||
#[error("asd")]
|
#[snafu(source(from(serde_qs::Error, convert_to_arc::<serde_qs::Error>)))]
|
||||||
Parsing(String),
|
source: Arc<serde_qs::Error>
|
||||||
|
},
|
||||||
|
#[snafu(display("asd"))]
|
||||||
|
Parsing {
|
||||||
|
context: String
|
||||||
|
},
|
||||||
|
|
||||||
#[error("asd")]
|
#[snafu(display("asd"))]
|
||||||
FloatParse(#[from] ParseFloatError),
|
FloatP {
|
||||||
|
#[snafu(source)]
|
||||||
|
source: ParseFloatError
|
||||||
|
},
|
||||||
|
|
||||||
#[error("failed to parse into decimal")]
|
#[snafu(display("failed to parse into decimal"))]
|
||||||
DecimalParse(#[from] rust_decimal::Error),
|
DecimalParse {
|
||||||
|
source: rust_decimal::Error
|
||||||
|
},
|
||||||
|
|
||||||
#[error("asd")]
|
#[snafu(display("asd"))]
|
||||||
NoMAC,
|
NoMAC,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Error<&str>> for QSParserError {
|
impl From<Error<&str>> for QSParserError {
|
||||||
fn from(value: Error<&str>) -> Self {
|
fn from(value: Error<&str>) -> Self {
|
||||||
QSParserError::Parsing(format!("{:?}", value))
|
QSParserError::Parsing { context: format!("{:?}", value)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<serde_qs::Error> for QSParserError {
|
// impl From<serde_qs::Error> for QSParserError {
|
||||||
fn from(value: serde_qs::Error) -> Self {
|
// fn from(value: serde_qs::Error) -> Self {
|
||||||
QSParserError::SerdeQS(Arc::new(value))
|
// QSParserError::SerdeQS(Arc::new(value))
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
/// Преобразование оставшихся параметров в urlencoded теле или query string в данные с датчиков
|
/// Преобразование оставшихся параметров в urlencoded теле или query string в данные с датчиков
|
||||||
/// [SensorValue].
|
/// [SensorValue].
|
||||||
|
@ -54,7 +66,7 @@ pub fn qs_rest_to_values(
|
||||||
for (key, value) in parsed {
|
for (key, value) in parsed {
|
||||||
hashset.insert(SensorValue {
|
hashset.insert(SensorValue {
|
||||||
mac: key,
|
mac: key,
|
||||||
value: Decimal::from_str(value.as_str())?,
|
value: Decimal::from_str(value.as_str()).context(DecimalParseSnafu{})?,
|
||||||
|
|
||||||
time: None,
|
time: None,
|
||||||
unit: None,
|
unit: None,
|
||||||
|
@ -70,7 +82,7 @@ pub fn parse_decimal_if_exists(
|
||||||
key: &str,
|
key: &str,
|
||||||
) -> Result<Option<Decimal>, QSParserError> {
|
) -> Result<Option<Decimal>, QSParserError> {
|
||||||
if let Some(unwrapped_value) = parsed.remove(key) {
|
if let Some(unwrapped_value) = parsed.remove(key) {
|
||||||
Ok(Some(Decimal::from_str(unwrapped_value.as_str())?))
|
Ok(Some(Decimal::from_str(unwrapped_value.as_str()).context(DecimalParseSnafu{})?))
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
@ -81,14 +93,14 @@ pub fn parse_epoch_if_exists(
|
||||||
key: &str,
|
key: &str,
|
||||||
) -> Result<Option<Epoch>, QSParserError> {
|
) -> Result<Option<Epoch>, QSParserError> {
|
||||||
if let Some(unwrapped_value) = parsed.remove(key) {
|
if let Some(unwrapped_value) = parsed.remove(key) {
|
||||||
Ok(Some(Epoch::from_unix_seconds(unwrapped_value.parse()?)))
|
Ok(Some(Epoch::from_unix_seconds(unwrapped_value.parse().context(FloatPSnafu{})?)))
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn parse_nm_qs_format(input: &str) -> Result<NMDeviceDataPacket, QSParserError> {
|
pub async fn parse_nm_qs_format(input: &str) -> Result<NMDeviceDataPacket, QSParserError> {
|
||||||
let mut parsed: HashMap<String, String> = serde_qs::from_str(input)?;
|
let mut parsed: HashMap<String, String> = serde_qs::from_str(input).context(SerdeQSSnafu{})?;
|
||||||
|
|
||||||
let (_, device_mac) = if let Some(id) = parsed.get("ID") {
|
let (_, device_mac) = if let Some(id) = parsed.get("ID") {
|
||||||
parse_mac_address(id)?
|
parse_mac_address(id)?
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
//! Сборник утилит для работы с Redis.
|
//! Сборник утилит для работы с Redis.
|
||||||
|
|
||||||
use crate::web_server::app_error::AppError;
|
use crate::web_server::app_error::{AppError, ServerRedisSnafu};
|
||||||
use fred::prelude::*;
|
use fred::prelude::*;
|
||||||
|
use fred::clients::Client as RedisClient;
|
||||||
use heapless::String as HeaplessString;
|
use heapless::String as HeaplessString;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use snafu::ResultExt;
|
||||||
use ufmt::uwrite;
|
use ufmt::uwrite;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
@ -34,7 +36,7 @@ pub async fn is_api_key_valid(
|
||||||
let mut key_buffer = HeaplessString::<{ 7 + 13 }>::new();
|
let mut key_buffer = HeaplessString::<{ 7 + 13 }>::new();
|
||||||
uwrite!(key_buffer, "apikey_{}", api_key).expect("TODO"); // TODO: Error handling
|
uwrite!(key_buffer, "apikey_{}", api_key).expect("TODO"); // TODO: Error handling
|
||||||
|
|
||||||
let valid: Option<i64> = client.hget(key_buffer.as_str(), "owner").await?;
|
let valid: Option<i64> = client.hget(key_buffer.as_str(), "owner").await.context(ServerRedisSnafu)?;
|
||||||
|
|
||||||
valid
|
valid
|
||||||
.map(|uid| ApiKeyDescription { apikey_owner: uid })
|
.map(|uid| ApiKeyDescription { apikey_owner: uid })
|
||||||
|
|
Loading…
Add table
Reference in a new issue