Переделка ошибок на snafu #19

Manually merged
DarkCat09 merged 2 commits from snafu-errors-remake into master 2025-01-09 17:07:16 +03:00
15 changed files with 874 additions and 855 deletions

1455
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -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"

View file

@ -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> {

View file

@ -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)?;
} }
} }

View file

@ -111,8 +111,10 @@ pub fn parse_packet(input: &str) -> MyIError<&str, NMDeviceDataPacket> {
let (input, opt_name) = opt(delimited(tag("#"), take_while(|c| c != '\n'), tag("\n")))(input)?; let (input, opt_name) = opt(delimited(tag("#"), take_while(|c| c != '\n'), tag("\n")))(input)?;
let mut packet = NMDeviceDataPacket::default(); let mut packet = NMDeviceDataPacket {
packet.mac = device_mac; mac: device_mac,
..Default::default()
};
let (input, lines) = context( let (input, lines) = context(
"Получение значений до тега терминатора", "Получение значений до тега терминатора",

View file

@ -8,7 +8,6 @@ mod utils;
mod web_server; mod web_server;
use crate::web_server::server_main; use crate::web_server::server_main;
struct Params {}
#[ntex::main] #[ntex::main]
async fn main() { async fn main() {

View file

@ -1,7 +1,7 @@
use hifitime::Epoch; use hifitime::Epoch;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use std::fmt; use std::fmt;
use std::fmt::{Formatter, Write}; use std::fmt::Formatter;
#[derive(PartialOrd, PartialEq, Ord, Eq, Clone, Copy, Debug, Default)] #[derive(PartialOrd, PartialEq, Ord, Eq, Clone, Copy, Debug, Default)]
#[repr(transparent)] #[repr(transparent)]
@ -30,7 +30,7 @@ impl<'de> Deserialize<'de> for EpochUTC {
{ {
struct EpochVisitor; struct EpochVisitor;
impl<'de> de::Visitor<'de> for EpochVisitor { impl de::Visitor<'_> for EpochVisitor {
type Value = EpochUTC; type Value = EpochUTC;
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {

View file

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

View file

@ -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);
} }

View file

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

View file

@ -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!"))
} }

View file

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

View file

@ -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),
}); });
} }

View file

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

View file

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