Переделка ошибок на snafu #19
15 changed files with 874 additions and 855 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(
|
||||
"Получение значений до тега терминатора",
|
||||
|
|
|
@ -8,7 +8,6 @@ mod utils;
|
|||
mod web_server;
|
||||
use crate::web_server::server_main;
|
||||
|
||||
struct Params {}
|
||||
|
||||
#[ntex::main]
|
||||
async fn main() {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
use fred::bytes_utils::Str;
|
||||
use fred::prelude::*;
|
||||
use fred::clients::Client as RedisClient;
|
||||
|
||||
use old_app_api::old_api_handler;
|
||||
|
||||
pub mod app_error;
|
||||
|
@ -27,7 +29,7 @@ pub async fn server_main() {
|
|||
let client = RedisClient::default();
|
||||
client.init().await.unwrap();
|
||||
|
||||
let asd: Str = client.ping().await.unwrap();
|
||||
let asd: Str = client.ping(None).await.unwrap();
|
||||
|
||||
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::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