Переделка ошибок на snafu #19
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