Merge branch 'snafu-errors-remake'
This commit is contained in:
commit
c820b95a2d
15 changed files with 883 additions and 857 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
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.71"
|
||||
bstr = { version = "1.9.0", features = ["serde"] }
|
||||
bytes = { version = "1.4.0", features = ["serde"] }
|
||||
chrono = { version = "0.4.26", features = ["serde"] }
|
||||
clap = { version = "4.3.8", features = ["derive", "env"] }
|
||||
derive_more = "0.99.17"
|
||||
dotenvy = "0.15.7"
|
||||
fred = { version = "9.0.3", features = ["nom"] }
|
||||
heapless = { version = "0.7.16", features = ["ufmt-impl"] }
|
||||
fred = { version = "10.0.3", features = ["nom"] }
|
||||
heapless = { version = "0.8.0", features = ["ufmt"] }
|
||||
hex = { version = "0.4.3", default-features = false, features = ["std", "alloc"] }
|
||||
hifitime = "3.8.2"
|
||||
hifitime = "4.0.2"
|
||||
lazy_static = "1.4.0"
|
||||
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"] }
|
||||
regex = "1.8.4"
|
||||
rust_decimal = { version = "1.30.0", features = ["rkyv", "rkyv-safe"] }
|
||||
serde = { version = "1.0.164", features = ["derive", "alloc"] }
|
||||
serde_json = "1.0.99"
|
||||
serde_qs = "0.12.0"
|
||||
serde_qs = "0.13.0"
|
||||
serde_with = { version = "3.6.1", features = ["hex"] }
|
||||
smallstr = { version = "0.3.0", features = ["std", "union"] }
|
||||
thiserror = "1.0.40"
|
||||
tokio = { version = "1.28.2", features = ["full"] }
|
||||
ufmt = { version = "0.2.0", features = ["std"] }
|
||||
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 thiserror::Error as ThisError;
|
||||
|
||||
///
|
||||
/// Тип ошибки Ingest протокола
|
||||
///
|
||||
/// К сожалению, не может быть переделан на Snafu, так как
|
||||
/// Snafu не поддерживает generic типы как source ошибки,
|
||||
/// не приделывая 'static лайфтайм.
|
||||
///
|
||||
/// См. https://github.com/shepmaster/snafu/issues/99
|
||||
///
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, ThisError)]
|
||||
pub enum Error<I: Debug> {
|
||||
|
|
|
@ -9,14 +9,15 @@
|
|||
use crate::ingest_protocol::error::Error;
|
||||
use crate::ingest_protocol::parser::parse_mac_address;
|
||||
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 hifitime::Epoch;
|
||||
use rust_decimal::Decimal;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::serde_as;
|
||||
use snafu::ResultExt;
|
||||
use ufmt::uwrite;
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
@ -116,10 +117,10 @@ impl NMDeviceDataPacket {
|
|||
|
||||
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) {
|
||||
return Err(AppError::DeviceNotFound(hex::encode(self.mac)));
|
||||
return Err(AppError::DeviceNotFound { mac: hex::encode(self.mac) });
|
||||
}
|
||||
|
||||
// devices_{device_id}_{tai_timestamp}_{sensor_id}
|
||||
|
@ -136,7 +137,7 @@ impl NMDeviceDataPacket {
|
|||
|
||||
redis
|
||||
.set(key.as_str(), sensor.value.to_string(), None, None, false)
|
||||
.await?;
|
||||
.await.context(ServerRedisSnafu)?;
|
||||
}
|
||||
|
||||
if let Some(commands) = &self.commands {
|
||||
|
@ -146,7 +147,7 @@ impl NMDeviceDataPacket {
|
|||
|
||||
redis
|
||||
.set(key.as_str(), cmd_value, None, None, false)
|
||||
.await?;
|
||||
.await.context(ServerRedisSnafu)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 mut packet = NMDeviceDataPacket::default();
|
||||
packet.mac = device_mac;
|
||||
let mut packet = NMDeviceDataPacket {
|
||||
mac: device_mac,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let (input, lines) = context(
|
||||
"Получение значений до тега терминатора",
|
||||
|
|
12
src/main.rs
12
src/main.rs
|
@ -1,16 +1,20 @@
|
|||
#![doc = include_str!("../README.md")]
|
||||
extern crate core;
|
||||
|
||||
mod cli;
|
||||
mod ingest_protocol;
|
||||
mod ingest_socket_server;
|
||||
mod utils;
|
||||
mod web_server;
|
||||
mod cli;
|
||||
|
||||
use clap::Parser;
|
||||
use cli::{Cli, MyCommand};
|
||||
use fred::{
|
||||
clients::RedisClient, prelude::ClientLike, types::{ConnectionConfig, PerformanceConfig, ReconnectPolicy, RedisConfig, Server, ServerConfig}
|
||||
clients::RedisClient,
|
||||
prelude::ClientLike,
|
||||
types::{
|
||||
ConnectionConfig, PerformanceConfig, ReconnectPolicy, RedisConfig, Server, ServerConfig,
|
||||
},
|
||||
};
|
||||
use ingest_socket_server::socketserv_main;
|
||||
|
||||
|
@ -22,7 +26,9 @@ async fn main() {
|
|||
|
||||
let mut config = RedisConfig::default();
|
||||
|
||||
config.server = ServerConfig::Centralized { server: Server::new(result.redis_host.clone(), result.redis_port.clone()) };
|
||||
config.server = ServerConfig::Centralized {
|
||||
server: Server::new(result.redis_host.clone(), result.redis_port.clone()),
|
||||
};
|
||||
|
||||
let perf = PerformanceConfig::default();
|
||||
let policy = ReconnectPolicy::default();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use hifitime::Epoch;
|
||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::fmt;
|
||||
use std::fmt::{Formatter, Write};
|
||||
use std::fmt::Formatter;
|
||||
|
||||
#[derive(PartialOrd, PartialEq, Ord, Eq, Clone, Copy, Debug, Default)]
|
||||
#[repr(transparent)]
|
||||
|
@ -30,7 +30,7 @@ impl<'de> Deserialize<'de> for EpochUTC {
|
|||
{
|
||||
struct EpochVisitor;
|
||||
|
||||
impl<'de> de::Visitor<'de> for EpochVisitor {
|
||||
impl de::Visitor<'_> for EpochVisitor {
|
||||
type Value = EpochUTC;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
|
||||
|
|
|
@ -5,7 +5,7 @@ mod hifitime_serde;
|
|||
|
||||
use phf::phf_map;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::borrow::Cow;
|
||||
use std::{borrow::Cow, sync::Arc};
|
||||
|
||||
pub use hifitime_serde::EpochUTC;
|
||||
|
||||
|
@ -83,3 +83,7 @@ static STR_TO_UNITS: phf::Map<&'static str, SupportedUnit> = phf_map! {
|
|||
"s" => SupportedUnit::Seconds,
|
||||
"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 fred::prelude::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
use ntex::http::StatusCode;
|
||||
use ntex::web;
|
||||
|
||||
use ntex::web::{HttpRequest, HttpResponse};
|
||||
|
||||
use thiserror::Error;
|
||||
use snafu::Snafu;
|
||||
|
||||
use crate::insert_header;
|
||||
use crate::utils::convert_to_arc;
|
||||
use crate::web_server::old_device_sensor_api::qs_parser::QSParserError;
|
||||
use rust_decimal::Decimal;
|
||||
use serde_json::json;
|
||||
|
||||
use super::old_device_sensor_api::qs_parser;
|
||||
|
||||
|
||||
|
||||
|
||||
/// Главный объект ошибки [std::error::Error] для всего Web API.
|
||||
///
|
||||
/// В целом, все Result у Web сервера должны использовать этот Error.
|
||||
#[derive(Debug, Error, Display)]
|
||||
#[derive(Debug, Snafu, Clone)]
|
||||
#[snafu(visibility(pub))]
|
||||
pub enum AppError {
|
||||
#[display(fmt = "IDK")]
|
||||
JsonError(#[from] serde_json::Error),
|
||||
#[snafu(display("Could not read file"))]
|
||||
JsonError {
|
||||
#[snafu(source(from(serde_json::Error, convert_to_arc::<serde_json::Error>)))]
|
||||
source: Arc<serde_json::Error>
|
||||
},
|
||||
|
||||
#[display(fmt = "IDK")]
|
||||
QSError(QSParserError),
|
||||
#[snafu(display("Could not read file"))]
|
||||
QSError {
|
||||
source: QSParserError
|
||||
},
|
||||
|
||||
#[display(fmt = "IDK")]
|
||||
ServerRedisError(#[from] RedisError),
|
||||
#[snafu(display("Could not read file"))]
|
||||
ServerRedisError {
|
||||
source: fred::error::Error
|
||||
},
|
||||
|
||||
#[display(fmt = "IDK")]
|
||||
UnknownMethod(String),
|
||||
#[snafu(display("Could not read file"))]
|
||||
UnknownMethod {
|
||||
method: String
|
||||
},
|
||||
|
||||
#[display(fmt = "IDK")]
|
||||
#[snafu(display("Could not read file"))]
|
||||
RequestTooLarge,
|
||||
|
||||
#[display(fmt = "IDK")]
|
||||
#[snafu(display("API key invalid: {reason}"))]
|
||||
ApiKeyInvalid { reason: &'static str },
|
||||
|
||||
#[display(fmt = "IDK")]
|
||||
#[snafu(display("Could not read file"))]
|
||||
UnitValidationFailed {
|
||||
max: Option<Decimal>,
|
||||
min: Option<Decimal>,
|
||||
},
|
||||
|
||||
#[display(fmt = "UTF8")]
|
||||
Utf8Error(#[from] std::str::Utf8Error),
|
||||
|
||||
#[display(fmt = "IDK")]
|
||||
UnknownBody {
|
||||
json_err: Option<serde_json::Error>,
|
||||
query_error: Option<qs_parser::QSParserError>,
|
||||
#[snafu(display("UTF-8 Error"))]
|
||||
Utf8Error {
|
||||
source: std::str::Utf8Error
|
||||
},
|
||||
|
||||
#[display(fmt = "IDK")]
|
||||
DeviceNotFound(String),
|
||||
#[snafu(display("Could not read file"))]
|
||||
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 {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match self {
|
||||
AppError::JsonError(_) => StatusCode::BAD_REQUEST,
|
||||
AppError::UnknownMethod(_) => StatusCode::BAD_REQUEST,
|
||||
AppError::JsonError { .. } => StatusCode::BAD_REQUEST,
|
||||
AppError::UnknownMethod { .. } => StatusCode::BAD_REQUEST,
|
||||
AppError::UnitValidationFailed { .. } => StatusCode::BAD_REQUEST,
|
||||
AppError::RequestTooLarge => StatusCode::PAYLOAD_TOO_LARGE,
|
||||
AppError::ServerRedisError(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
AppError::ServerRedisError { .. } => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
AppError::ApiKeyInvalid { .. } => StatusCode::BAD_REQUEST,
|
||||
AppError::Utf8Error(_) => StatusCode::BAD_REQUEST,
|
||||
AppError::Utf8Error { .. } => StatusCode::BAD_REQUEST,
|
||||
AppError::UnknownBody { .. } => StatusCode::BAD_REQUEST,
|
||||
AppError::QSError(..) => StatusCode::BAD_REQUEST,
|
||||
AppError::DeviceNotFound(..) => StatusCode::BAD_REQUEST,
|
||||
AppError::QSError { .. } => StatusCode::BAD_REQUEST,
|
||||
AppError::DeviceNotFound { .. } => StatusCode::BAD_REQUEST,
|
||||
AppError::TimeIsLongBehindNow { .. } => StatusCode::BAD_REQUEST
|
||||
}
|
||||
}
|
||||
|
||||
fn error_response(&self, _: &HttpRequest) -> HttpResponse {
|
||||
let error_message = match self {
|
||||
AppError::JsonError(_) => "Invalid JSON",
|
||||
AppError::UnknownMethod(_) => "Unknown command",
|
||||
AppError::JsonError { .. } => "Invalid JSON",
|
||||
AppError::UnknownMethod { .. } => "Unknown command",
|
||||
AppError::UnitValidationFailed { .. } => "Unknown command",
|
||||
AppError::RequestTooLarge => "Request is too large",
|
||||
AppError::ServerRedisError(_) => "Internal server error",
|
||||
AppError::ServerRedisError { .. } => "Internal server error",
|
||||
AppError::ApiKeyInvalid { .. } => "API Key invalid",
|
||||
AppError::Utf8Error(_) => "Invalid UTF8 sequence",
|
||||
AppError::Utf8Error { .. } => "Invalid UTF8 sequence",
|
||||
AppError::UnknownBody { .. } => {
|
||||
"Can't figure out where and in what encoding the main data is"
|
||||
}
|
||||
AppError::QSError(..) => "UrlEncoded body or query params are incorrect",
|
||||
AppError::DeviceNotFound(..) => "Device not found",
|
||||
AppError::QSError { .. } => "UrlEncoded body or query params are incorrect",
|
||||
AppError::DeviceNotFound { .. } => "Device not found",
|
||||
AppError::TimeIsLongBehindNow { .. } => "Time is long behind what it should be"
|
||||
};
|
||||
|
||||
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 headers = resp.headers_mut();
|
||||
|
||||
let error_as_string = format!("{:?}", &self);
|
||||
|
||||
match self {
|
||||
AppError::JsonError(json_err) => {
|
||||
insert_header!(headers, "X-Error-Line", json_err.line());
|
||||
insert_header!(headers, "X-Error-Column", json_err.column());
|
||||
AppError::JsonError { source } => {
|
||||
insert_header!(headers, "X-Error-Line", source.line());
|
||||
insert_header!(headers, "X-Error-Column", source.column());
|
||||
insert_header!(
|
||||
headers,
|
||||
"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!(
|
||||
headers,
|
||||
"X-Unknown-Cmd",
|
||||
|
@ -124,8 +145,8 @@ impl web::error::WebResponseError for AppError {
|
|||
AppError::ApiKeyInvalid { reason } => {
|
||||
insert_header!(headers, "X-Error-Description", *reason);
|
||||
}
|
||||
AppError::QSError(err) => {
|
||||
if let QSParserError::Parsing(desc) = err {
|
||||
AppError::QSError { source } => {
|
||||
if let QSParserError::Parsing { context: desc } = source {
|
||||
insert_header!(
|
||||
headers,
|
||||
"X-Error-Description",
|
||||
|
@ -137,6 +158,8 @@ impl web::error::WebResponseError for AppError {
|
|||
};
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
let error_as_string = format!("{:?}", &self);
|
||||
|
||||
insert_header!(headers, "X-Full-Error", error_as_string);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,9 @@
|
|||
//! TODO: Начать работу над TCP/UDP и MQTT сервером
|
||||
|
||||
use fred::bytes_utils::Str;
|
||||
use fred::clients::Client as RedisClient;
|
||||
use fred::prelude::*;
|
||||
|
||||
use old_app_api::old_api_handler;
|
||||
|
||||
pub mod app_error;
|
||||
|
|
|
@ -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::NMAppState;
|
||||
|
||||
use serde_json::{json};
|
||||
use snafu::ResultExt;
|
||||
|
||||
|
||||
use crate::insert_header;
|
||||
|
@ -19,7 +20,7 @@ pub async fn app_init(
|
|||
let _: () = app_state
|
||||
.redis_client
|
||||
.set("test", 123, None, None, true)
|
||||
.await?;
|
||||
.await.context(app_error::ServerRedisSnafu)?;
|
||||
|
||||
Ok(web::HttpResponse::build(StatusCode::OK).body("Hello world!"))
|
||||
}
|
||||
|
|
|
@ -7,12 +7,15 @@ use ntex::web::types::State;
|
|||
use ntex::util::Bytes;
|
||||
use ntex::web;
|
||||
use nom::AsBytes;
|
||||
use snafu::ResultExt;
|
||||
use crate::web_server::app_error::AppError;
|
||||
use crate::web_server::NMAppState;
|
||||
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::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 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
|
||||
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() {
|
||||
"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
|
||||
}
|
||||
_ => Err(AppError::UnknownMethod(mandatory_params.cmd.to_string())),
|
||||
_ => Err(AppError::UnknownMethod { method: mandatory_params.cmd.to_string() }),
|
||||
}
|
||||
|
||||
//Ok("fuck")
|
||||
|
|
|
@ -2,26 +2,35 @@
|
|||
|
||||
pub mod qs_parser;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
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::util::Bytes;
|
||||
use ntex::web::types::State;
|
||||
use ntex::{http, web};
|
||||
use qs_parser::QSParserError;
|
||||
use thiserror::Error;
|
||||
use snafu::{ResultExt, Snafu};
|
||||
|
||||
use super::NMAppState;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[derive(Snafu, Debug)]
|
||||
#[snafu(visibility(pub))]
|
||||
pub enum Error {
|
||||
#[error("Device not found")]
|
||||
DeviceNotFound(String),
|
||||
#[error("Time sent with the device is way too behind now")]
|
||||
#[snafu(display("Device not found"))]
|
||||
DeviceNotFound {
|
||||
mac: String
|
||||
},
|
||||
|
||||
#[snafu(display("Time sent with the device is way too behind now"))]
|
||||
TimeIsLongBehindNow,
|
||||
#[error("{0}")]
|
||||
QSParserError(#[from] QSParserError),
|
||||
|
||||
#[snafu(display("{source}"))]
|
||||
QSParser {
|
||||
source: QSParserError
|
||||
},
|
||||
}
|
||||
|
||||
/// Обработчик данных датчиков с устройств.
|
||||
|
@ -30,7 +39,7 @@ pub enum Error {
|
|||
/// Для того чтобы пользователям было легче, на оба пути можно отправлять и POST и GET.
|
||||
///
|
||||
/// На POST можно отправлять JSON или url-encoded тело, на GET - только через Query String.
|
||||
pub async fn device_handler<'a>(
|
||||
pub async fn device_handler(
|
||||
request: web::HttpRequest,
|
||||
body: Bytes,
|
||||
app_state: State<NMAppState>,
|
||||
|
@ -49,7 +58,7 @@ pub async fn device_handler<'a>(
|
|||
Err(error) => json_error = Some(error),
|
||||
},
|
||||
"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 {
|
||||
Ok(qs_body) => {
|
||||
real_body = Some(NMJsonPacket {
|
||||
|
@ -76,8 +85,8 @@ pub async fn device_handler<'a>(
|
|||
real_body.save_to_db(&app_state.redis_client).await?;
|
||||
} else {
|
||||
return Err(AppError::UnknownBody {
|
||||
json_err: json_error,
|
||||
query_error,
|
||||
json_err: Arc::new(json_error),
|
||||
query_error: Arc::new(query_error),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,45 +1,57 @@
|
|||
use crate::ingest_protocol::error::Error;
|
||||
use crate::ingest_protocol::parser::parse_mac_address;
|
||||
use crate::ingest_protocol::{NMDeviceDataPacket, SensorValue};
|
||||
use crate::utils::convert_to_arc;
|
||||
use hifitime::Epoch;
|
||||
use ntex::util::HashMap;
|
||||
use rust_decimal::Decimal;
|
||||
use snafu::{ResultExt, Snafu};
|
||||
use std::collections::HashSet;
|
||||
use std::num::ParseFloatError;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
|
||||
/// В иделае было бы хорошо сделать всё как у [serde_json::Error], но это слишком большая морока
|
||||
#[allow(dead_code)]
|
||||
#[derive(Error, Clone, Debug)]
|
||||
#[derive(Snafu, Clone, Debug)]
|
||||
#[snafu(visibility(pub))]
|
||||
pub enum QSParserError {
|
||||
#[error("asd")]
|
||||
SerdeQS(#[from] Arc<serde_qs::Error>),
|
||||
#[error("asd")]
|
||||
Parsing(String),
|
||||
#[snafu(display("asd"))]
|
||||
SerdeQS {
|
||||
#[snafu(source(from(serde_qs::Error, convert_to_arc::<serde_qs::Error>)))]
|
||||
source: Arc<serde_qs::Error>
|
||||
},
|
||||
#[snafu(display("asd"))]
|
||||
Parsing {
|
||||
context: String
|
||||
},
|
||||
|
||||
#[error("asd")]
|
||||
FloatParse(#[from] ParseFloatError),
|
||||
#[snafu(display("asd"))]
|
||||
FloatP {
|
||||
#[snafu(source)]
|
||||
source: ParseFloatError
|
||||
},
|
||||
|
||||
#[error("failed to parse into decimal")]
|
||||
DecimalParse(#[from] rust_decimal::Error),
|
||||
#[snafu(display("failed to parse into decimal"))]
|
||||
DecimalParse {
|
||||
source: rust_decimal::Error
|
||||
},
|
||||
|
||||
#[error("asd")]
|
||||
#[snafu(display("asd"))]
|
||||
NoMAC,
|
||||
}
|
||||
|
||||
impl From<Error<&str>> for QSParserError {
|
||||
fn from(value: Error<&str>) -> Self {
|
||||
QSParserError::Parsing(format!("{:?}", value))
|
||||
QSParserError::Parsing { context: format!("{:?}", value)}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_qs::Error> for QSParserError {
|
||||
fn from(value: serde_qs::Error) -> Self {
|
||||
QSParserError::SerdeQS(Arc::new(value))
|
||||
}
|
||||
}
|
||||
// impl From<serde_qs::Error> for QSParserError {
|
||||
// fn from(value: serde_qs::Error) -> Self {
|
||||
// QSParserError::SerdeQS(Arc::new(value))
|
||||
// }
|
||||
// }
|
||||
|
||||
/// Преобразование оставшихся параметров в urlencoded теле или query string в данные с датчиков
|
||||
/// [SensorValue].
|
||||
|
@ -54,7 +66,7 @@ pub fn qs_rest_to_values(
|
|||
for (key, value) in parsed {
|
||||
hashset.insert(SensorValue {
|
||||
mac: key,
|
||||
value: Decimal::from_str(value.as_str())?,
|
||||
value: Decimal::from_str(value.as_str()).context(DecimalParseSnafu{})?,
|
||||
|
||||
time: None,
|
||||
unit: None,
|
||||
|
@ -70,7 +82,7 @@ pub fn parse_decimal_if_exists(
|
|||
key: &str,
|
||||
) -> Result<Option<Decimal>, QSParserError> {
|
||||
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 {
|
||||
Ok(None)
|
||||
}
|
||||
|
@ -81,14 +93,14 @@ pub fn parse_epoch_if_exists(
|
|||
key: &str,
|
||||
) -> Result<Option<Epoch>, QSParserError> {
|
||||
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 {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
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") {
|
||||
parse_mac_address(id)?
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
//! Сборник утилит для работы с Redis.
|
||||
|
||||
use crate::web_server::app_error::AppError;
|
||||
use crate::web_server::app_error::{AppError, ServerRedisSnafu};
|
||||
use fred::prelude::*;
|
||||
use fred::clients::Client as RedisClient;
|
||||
use heapless::String as HeaplessString;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use snafu::ResultExt;
|
||||
use ufmt::uwrite;
|
||||
|
||||
lazy_static! {
|
||||
|
@ -34,7 +36,7 @@ pub async fn is_api_key_valid(
|
|||
let mut key_buffer = HeaplessString::<{ 7 + 13 }>::new();
|
||||
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
|
||||
.map(|uid| ApiKeyDescription { apikey_owner: uid })
|
||||
|
|
Loading…
Add table
Reference in a new issue