feat: A lot of changes
This commit is contained in:
parent
824e382e89
commit
b0f3343a27
20 changed files with 1256 additions and 532 deletions
1067
Cargo.lock
generated
1067
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -7,10 +7,11 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
anyhow = "1.0.71"
|
||||
axum = { version = "0.6.18", features = ["http2", "headers", "macros"] }
|
||||
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 = "6.3.0", features = ["nom"] }
|
||||
heapless = { version = "0.7.16", features = ["ufmt-impl"] }
|
||||
|
@ -18,11 +19,14 @@ hex = { version = "0.4.3", default-features = false }
|
|||
hifitime = "3.8.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"] }
|
||||
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_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"] }
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
use std::borrow::Cow;
|
||||
use phf::phf_map;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
#[repr(u64)]
|
||||
pub enum SupportedUnit {
|
||||
Celsius, // Needs verification > 273.15
|
||||
Percentage, // Needs verification >= 100 && <= 0
|
||||
Celsius, // Needs verification > 273.15
|
||||
Percentage, // Needs verification >= 100 && <= 0
|
||||
MillimeterHg, // Needs verification
|
||||
UVIndex, // Needs verification
|
||||
Boolean, // Needs verification
|
||||
UVIndex, // Needs verification
|
||||
Boolean, // Needs verification and possible parsing
|
||||
Kbps,
|
||||
Volume,
|
||||
KWh,
|
||||
|
@ -17,12 +17,13 @@ pub enum SupportedUnit {
|
|||
Volts,
|
||||
Watts,
|
||||
Seconds,
|
||||
Hertz,
|
||||
}
|
||||
|
||||
impl Serialize for SupportedUnit {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(match self {
|
||||
SupportedUnit::Celsius => "C",
|
||||
|
@ -36,15 +37,16 @@ impl Serialize for SupportedUnit {
|
|||
SupportedUnit::Volts => "V",
|
||||
SupportedUnit::Watts => "W",
|
||||
SupportedUnit::Seconds => "s",
|
||||
SupportedUnit::KWh => "KWh"
|
||||
SupportedUnit::KWh => "KWh",
|
||||
SupportedUnit::Hertz => "Hz",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for SupportedUnit {
|
||||
fn deserialize<'a, D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let color_str = Cow::<'a, str>::deserialize(deserializer)?;
|
||||
match STR_TO_UNITS.get(color_str.as_ref()) {
|
||||
|
@ -54,7 +56,6 @@ impl<'de> Deserialize<'de> for SupportedUnit {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
static STR_TO_UNITS: phf::Map<&'static str, SupportedUnit> = phf_map! {
|
||||
"C" => SupportedUnit::Celsius,
|
||||
"%" => SupportedUnit::Percentage,
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
use std::fmt::{Display, Formatter};
|
||||
use std::num::ParseFloatError;
|
||||
use bstr::BStr;
|
||||
use nom::error::VerboseError;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::num::ParseFloatError;
|
||||
use thiserror::Error as ThisError;
|
||||
|
||||
#[derive(Debug, ThisError)]
|
||||
pub enum Error<I: std::fmt::Debug> {
|
||||
#[error("Oops it blew up")]
|
||||
pub enum Error<I: Debug> {
|
||||
#[error("Nom error: {0}")]
|
||||
NomError(#[from] nom::Err<VerboseError<I>>),
|
||||
|
||||
#[error("Oops it blew up")]
|
||||
#[error("Failed to parse a timestamp")]
|
||||
TimestampParseError(ParseFloatError),
|
||||
|
||||
#[error("Oops it blew up")]
|
||||
#[error("Unknown unit")]
|
||||
UnknownUnit(I),
|
||||
|
||||
#[error("Oops it blew up")]
|
||||
DecimalParseError(#[from] rust_decimal::Error)
|
||||
#[error("Failed to parse a number")]
|
||||
DecimalParseError(#[from] rust_decimal::Error),
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
mod packet_types;
|
||||
pub mod error;
|
||||
mod packet_types;
|
||||
pub mod parser;
|
||||
mod server;
|
||||
|
||||
|
@ -7,4 +7,3 @@ mod server;
|
|||
mod tests;
|
||||
|
||||
pub use packet_types::*;
|
||||
|
||||
|
|
|
@ -1,42 +1,76 @@
|
|||
use crate::hashes::SupportedUnit;
|
||||
use crate::ingest_protocol::error::Error;
|
||||
use crate::ingest_protocol::parser::parse_mac_address;
|
||||
use bstr::BStr;
|
||||
use hifitime::Epoch;
|
||||
use rust_decimal::Decimal;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::formats::Separator;
|
||||
use serde_with::serde_as;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashSet;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use hifitime::Epoch;
|
||||
use rust_decimal::Decimal;
|
||||
use crate::hashes::SupportedUnit;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SensorValue<'a> {
|
||||
pub mac: Cow<'a, str>,
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SensorValue {
|
||||
pub mac: String,
|
||||
pub value: Decimal,
|
||||
pub time: Option<Epoch>,
|
||||
pub unit: Option<SupportedUnit>,
|
||||
pub name: Option<Cow<'a, str>>,
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
impl<'a> Hash for SensorValue<'a> {
|
||||
impl Hash for SensorValue {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.mac.hash(state);
|
||||
self.mac.hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PartialEq for SensorValue<'a> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.mac == other.mac
|
||||
pub struct DashSeparator {}
|
||||
|
||||
impl Separator for DashSeparator {
|
||||
fn separator() -> &'static str {
|
||||
"-"
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Eq for SensorValue<'a> {
|
||||
|
||||
fn mac_as_array(value: &str) -> Result<[u8; 6], Error<&str>> {
|
||||
Ok(parse_mac_address(value)?.1)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct NarodMonPacket<'a> {
|
||||
serde_with::serde_conv!(
|
||||
MacAsArray,
|
||||
[u8; 6],
|
||||
|rgb: &[u8; 6]| rgb.to_owned(),
|
||||
mac_as_array
|
||||
);
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize)]
|
||||
pub struct NMDeviceDataPacket {
|
||||
#[serde(alias = "ID")]
|
||||
#[serde_as(as = "MacAsArray")]
|
||||
pub mac: [u8; 6],
|
||||
pub name: Option<Cow<'a, str>>,
|
||||
pub values: HashSet<SensorValue<'a>>,
|
||||
pub owner: Option<Cow<'a, str>>,
|
||||
|
||||
pub name: Option<String>,
|
||||
|
||||
pub values: HashSet<SensorValue>,
|
||||
|
||||
pub owner: Option<String>,
|
||||
|
||||
pub lat: Option<Decimal>,
|
||||
pub lon: Option<Decimal>,
|
||||
pub alt: Option<Decimal>
|
||||
pub alt: Option<Decimal>,
|
||||
|
||||
// HTTP GET/POST url-encode specific parameters
|
||||
/// TODO: Желательное поведение в будущем:
|
||||
/// - Если в values есть хотябы один times и этот time не None => Игнорируем этот time
|
||||
/// - Если time нет у values => используем этот time (если он None, то соотв. считаем что информации о времени нет)
|
||||
/// TODO: В базе всё должно храниться как секунды по TAI спустя J1900 для возможности сортировки по времени позже.
|
||||
pub time: Option<Epoch>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
|
||||
pub struct NMJsonPacket {
|
||||
pub devices: Vec<NMDeviceDataPacket>,
|
||||
}
|
|
@ -1,56 +1,52 @@
|
|||
use std::borrow::Cow;
|
||||
use std::collections::HashSet;
|
||||
use std::str::FromStr;
|
||||
use hifitime::Epoch;
|
||||
use nom::{InputTake, Needed, Parser};
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::{take_until, take_until1, take_while_m_n};
|
||||
use nom::bytes::complete::{take_till1, take, take_while, take_while1};
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::bytes::streaming::take_till;
|
||||
use nom::character::streaming::{anychar, char, hex_digit0, newline};
|
||||
use nom::bytes::complete::take_until1;
|
||||
use nom::bytes::complete::{take, take_while, take_while1};
|
||||
use nom::character::complete::hex_digit1;
|
||||
use nom::character::is_digit;
|
||||
use nom::{InputTake, Parser};
|
||||
use std::str::FromStr;
|
||||
|
||||
use nom::combinator::{map, map_opt, map_parser, opt, recognize, rest};
|
||||
use nom::Err as NomErr;
|
||||
use nom::error::{context, ContextError, ErrorKind as NomErrorKind, ParseError as NomParseError, Error as NomError, VerboseError};
|
||||
use nom::multi::{count, separated_list0, separated_list1};
|
||||
use nom::sequence::{delimited, preceded, separated_pair};
|
||||
use rust_decimal::Decimal;
|
||||
use crate::ingest_protocol::error::Error;
|
||||
use crate::ingest_protocol::{NarodMonPacket, SensorValue};
|
||||
use crate::ingest_protocol::error::Error::TimestampParseError;
|
||||
use crate::ingest_protocol::{NMDeviceDataPacket, SensorValue};
|
||||
use nom::combinator::{map_parser, opt};
|
||||
use nom::error::context;
|
||||
use nom::multi::{count, separated_list0};
|
||||
use nom::sequence::{delimited, preceded};
|
||||
use rust_decimal::Decimal;
|
||||
|
||||
type MyIError<I, O> = Result<(I, O), Error<I>>;
|
||||
|
||||
|
||||
pub fn parse_mac_address(input: &str) -> MyIError<&str, [u8; 6]> {
|
||||
let mut mac = [0u8; 6];
|
||||
|
||||
let mut counter = 0;
|
||||
|
||||
let (leftovers, i) = context("17 символов для MAC адреса", take(17usize))(input)?;
|
||||
let (leftovers, i) =
|
||||
context("17 символов для MAC адреса", take(17usize))(input)?;
|
||||
|
||||
let (_, out) = count(|inp| {
|
||||
let (mut i, o) = context("Октет", map_parser(take(2usize), hex_digit1))(inp)?;
|
||||
if counter != 5 {
|
||||
(i, _) = tag("-")(i)?;
|
||||
}
|
||||
counter += 1;
|
||||
Ok((i, o))
|
||||
}, 6)(i)?;
|
||||
let (_, out) = count(
|
||||
|inp| {
|
||||
let (mut i, o) = context("Октет", map_parser(take(2usize), hex_digit1))(inp)?;
|
||||
if counter != 5 {
|
||||
(i, _) = tag("-")(i)?;
|
||||
}
|
||||
counter += 1;
|
||||
Ok((i, o))
|
||||
},
|
||||
6,
|
||||
)(i)?;
|
||||
|
||||
hex::decode_to_slice(out.join(""), &mut mac).unwrap();
|
||||
|
||||
Ok((leftovers, mac))
|
||||
}
|
||||
|
||||
fn handle_special_sensor_macs<I: std::fmt::Debug> (
|
||||
mac: &str,
|
||||
fn handle_special_sensor_macs<'a>(
|
||||
mac: &'a str,
|
||||
sensor_value: &str,
|
||||
packet: &mut NarodMonPacket,
|
||||
) -> Result<bool, Error<I>> {
|
||||
packet: &mut NMDeviceDataPacket,
|
||||
) -> Result<bool, Error<&'a str>> {
|
||||
match mac.to_uppercase().as_str() {
|
||||
"LAT" => packet.lat = Some(Decimal::from_str(sensor_value)?),
|
||||
"LON" => packet.lon = Some(Decimal::from_str(sensor_value)?),
|
||||
|
@ -62,7 +58,10 @@ fn handle_special_sensor_macs<I: std::fmt::Debug> (
|
|||
return Ok(true);
|
||||
}
|
||||
|
||||
pub fn parse_packet_body<'a>(line: &'a str, packet: &mut NarodMonPacket<'a>) -> MyIError<&'a str, ()> {
|
||||
pub fn parse_packet_body<'a>(
|
||||
line: &'a str,
|
||||
packet: &mut NMDeviceDataPacket,
|
||||
) -> MyIError<&'a str, ()> {
|
||||
let (line, _) = tag("#")(line)?;
|
||||
let (line, sensor_mac) = take_while1(|c| c != '\n' && c != '#')(line)?;
|
||||
|
||||
|
@ -79,17 +78,17 @@ pub fn parse_packet_body<'a>(line: &'a str, packet: &mut NarodMonPacket<'a>) ->
|
|||
}
|
||||
_ => {
|
||||
let (line, sensor_value) = take_while1(|c| c != '\n' && c != '#')(line)?;
|
||||
let (line, sensor_time) = opt(preceded(tag("#"), take_while1(|c| c != '\n' && c != '#' && "1234567890.".contains(c))))(line)?;
|
||||
let (line, sensor_time) = opt(preceded(
|
||||
tag("#"),
|
||||
take_while1(|c| c != '\n' && c != '#' && "1234567890.".contains(c)),
|
||||
))(line)?;
|
||||
let (line, sensor_name) = opt(preceded(tag("#"), take_while1(|c| c != '\n')))(line)?;
|
||||
|
||||
|
||||
let sensor_time = match sensor_time {
|
||||
Some(v) => Some(
|
||||
Epoch::from_unix_seconds(
|
||||
v.parse().map_err(|e| TimestampParseError(e))?
|
||||
)
|
||||
),
|
||||
None => None
|
||||
Some(v) => Some(Epoch::from_unix_seconds(
|
||||
v.parse().map_err(|e| TimestampParseError(e))?,
|
||||
)),
|
||||
None => None,
|
||||
};
|
||||
|
||||
if !handle_special_sensor_macs(sensor_mac, sensor_value, packet)? {
|
||||
|
@ -98,31 +97,31 @@ pub fn parse_packet_body<'a>(line: &'a str, packet: &mut NarodMonPacket<'a>) ->
|
|||
value: Decimal::from_str(sensor_value)?,
|
||||
time: sensor_time, // TODO
|
||||
unit: None,
|
||||
name: sensor_name.map(|v| Cow::from(v)),
|
||||
name: sensor_name.map(|v| v.to_string()),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Ok((line, ()))
|
||||
return Ok((line, ()));
|
||||
}
|
||||
|
||||
pub fn parse_packet(input: &str) -> MyIError<&str, NarodMonPacket> {
|
||||
pub fn parse_packet(input: &str) -> MyIError<&str, NMDeviceDataPacket> {
|
||||
let (input, _) = tag("#")(input)?;
|
||||
|
||||
let (input, device_mac) = parse_mac_address(input)?;
|
||||
|
||||
let (input, opt_name) = opt(delimited(tag("#"), take_while(|c| c != '\n'), tag("\n")))(input)?;
|
||||
|
||||
let mut packet = NarodMonPacket::default();
|
||||
let mut packet = NMDeviceDataPacket::default();
|
||||
packet.mac = device_mac;
|
||||
|
||||
let (input, lines) = context(
|
||||
"Получение значений до тега терминатора",
|
||||
map_parser(
|
||||
take_until1("##"),
|
||||
separated_list0(tag("\n"), take_while1(|c| c != '\n'))
|
||||
)
|
||||
separated_list0(tag("\n"), take_while1(|c| c != '\n')),
|
||||
),
|
||||
)(input)?;
|
||||
|
||||
for line in lines {
|
||||
|
@ -131,7 +130,7 @@ pub fn parse_packet(input: &str) -> MyIError<&str, NarodMonPacket> {
|
|||
|
||||
let (input, _) = tag("##")(input)?;
|
||||
|
||||
packet.name = opt_name.map(|v| Cow::from(v));
|
||||
packet.name = opt_name.map(|v| v.to_string());
|
||||
|
||||
Ok((input, packet))
|
||||
}
|
|
@ -1,3 +1 @@
|
|||
async fn main() {
|
||||
|
||||
}
|
||||
async fn main() {}
|
||||
|
|
|
@ -12,7 +12,8 @@ fn test_asd() {
|
|||
#LON#37.6068
|
||||
#ALT#38
|
||||
##
|
||||
"#.trim();
|
||||
"#
|
||||
.trim();
|
||||
dbg!(parse_packet(asd));
|
||||
}
|
||||
|
||||
|
@ -32,6 +33,5 @@ fn test_packet() {
|
|||
#T2#1.2#3400005345
|
||||
##"#;
|
||||
|
||||
|
||||
println!("{:#?}", parse_packet(inp));
|
||||
}
|
30
src/main.rs
30
src/main.rs
|
@ -37,25 +37,23 @@ C названием и координатами:
|
|||
extern crate core;
|
||||
|
||||
mod hashes;
|
||||
mod web_server;
|
||||
mod ingest_protocol;
|
||||
mod web_server;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::collections::{BTreeSet, HashMap, HashSet};
|
||||
use std::mem::{size_of, size_of_val};
|
||||
use std::num::NonZeroUsize;
|
||||
use std::str::FromStr;
|
||||
use axum::Router;
|
||||
use axum::routing::post;
|
||||
|
||||
use hifitime::Epoch;
|
||||
|
||||
use rust_decimal::Decimal;
|
||||
use crate::ingest_protocol::{NarodMonPacket, SensorValue};
|
||||
use crate::ingest_protocol::error::Error;
|
||||
use crate::ingest_protocol::error::Error::TimestampParseError;
|
||||
use crate::ingest_protocol::{NMDeviceDataPacket, SensorValue};
|
||||
use crate::web_server::old_app_api::old_api_handler;
|
||||
use crate::web_server::server_main;
|
||||
|
||||
use rust_decimal::Decimal;
|
||||
|
||||
/*fn parse_sensor_value(input: Vec<&str>) -> MyIError<Vec<&str>, NarodMonValues> {
|
||||
Ok(
|
||||
|
@ -68,20 +66,14 @@ use crate::web_server::server_main;
|
|||
)
|
||||
}*/
|
||||
|
||||
struct Params {}
|
||||
|
||||
struct Params {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[tokio::main]
|
||||
#[ntex::main]
|
||||
async fn main() {
|
||||
//dotenvy::dotenv().unwrap();
|
||||
|
||||
let web_server_hndl = tokio::spawn(server_main());
|
||||
|
||||
web_server_hndl.await.unwrap();
|
||||
//
|
||||
// let web_server_hndl = tokio::spawn(server_main());
|
||||
//
|
||||
// web_server_hndl.await.unwrap();
|
||||
server_main().await;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,97 +1,133 @@
|
|||
use std::borrow::Cow;
|
||||
use axum::headers::HeaderValue;
|
||||
use axum::http::StatusCode;
|
||||
use axum::Json;
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use derive_more::Display;
|
||||
use fred::prelude::*;
|
||||
use ntex::http::header::{HeaderName, HeaderValue};
|
||||
use ntex::http::{HeaderMap, Response, StatusCode};
|
||||
use ntex::web;
|
||||
use ntex::web::types::Json;
|
||||
use ntex::web::{App, HttpRequest, HttpResponse};
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::format;
|
||||
use std::str::FromStr;
|
||||
use thiserror::Error;
|
||||
|
||||
|
||||
use crate::ingest_protocol::error::Error;
|
||||
use crate::insert_header;
|
||||
use crate::web_server::old_devices_api::QSParserError;
|
||||
use rust_decimal::Decimal;
|
||||
use serde_json::json;
|
||||
use thiserror::Error;
|
||||
use ufmt::derive::uDebug;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug, Error, Display)]
|
||||
pub enum AppError {
|
||||
#[error("IDK")]
|
||||
#[display(fmt = "IDK")]
|
||||
JsonError(#[from] serde_json::Error),
|
||||
|
||||
#[error("IDK")]
|
||||
#[display(fmt = "IDK")]
|
||||
QSError(QSParserError),
|
||||
|
||||
#[display(fmt = "IDK")]
|
||||
ServerRedisError(#[from] RedisError),
|
||||
|
||||
#[error("Fuck")]
|
||||
#[display(fmt = "IDK")]
|
||||
UnknownMethod(String),
|
||||
|
||||
#[error("Fuck")]
|
||||
#[display(fmt = "IDK")]
|
||||
RequestTooLarge,
|
||||
|
||||
#[error("Api")]
|
||||
ApiKeyInvalid {
|
||||
reason: &'static str
|
||||
},
|
||||
#[display(fmt = "IDK")]
|
||||
ApiKeyInvalid { reason: &'static str },
|
||||
|
||||
#[error("Fuck")]
|
||||
#[display(fmt = "IDK")]
|
||||
UnitValidationFailed {
|
||||
max: Option<Decimal>,
|
||||
min: Option<Decimal>
|
||||
}
|
||||
min: Option<Decimal>,
|
||||
},
|
||||
|
||||
#[display(fmt = "IDK")]
|
||||
UnknownBody {
|
||||
json_err: Option<serde_json::Error>,
|
||||
query_error: Option<serde_qs::Error>,
|
||||
},
|
||||
}
|
||||
|
||||
impl IntoResponse for AppError {
|
||||
fn into_response(self) -> Response {
|
||||
impl web::error::WebResponseError for AppError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match self {
|
||||
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::ApiKeyInvalid { .. } => StatusCode::BAD_REQUEST,
|
||||
AppError::UnknownBody { .. } => StatusCode::BAD_REQUEST,
|
||||
AppError::QSError(..) => StatusCode::BAD_REQUEST,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let (status, error_message) = match self {
|
||||
AppError::JsonError(_) => {
|
||||
(StatusCode::BAD_REQUEST, "Invalid JSON")
|
||||
},
|
||||
AppError::UnknownMethod(_) => {
|
||||
(StatusCode::BAD_REQUEST, "Unknown command")
|
||||
},
|
||||
AppError::UnitValidationFailed { .. } => {
|
||||
(StatusCode::BAD_REQUEST, "Unknown command")
|
||||
},
|
||||
AppError::RequestTooLarge => {
|
||||
(StatusCode::PAYLOAD_TOO_LARGE, "Request is too large")
|
||||
},
|
||||
AppError::ServerRedisError(_) => {
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, "Internal server error")
|
||||
},
|
||||
AppError::ApiKeyInvalid { .. } => {
|
||||
(StatusCode::BAD_REQUEST, "API Key invalid")
|
||||
fn error_response(&self, _: &HttpRequest) -> HttpResponse {
|
||||
let error_message = match self {
|
||||
AppError::JsonError(_) => "Invalid JSON",
|
||||
AppError::UnknownMethod(_) => "Unknown command",
|
||||
AppError::UnitValidationFailed { .. } => "Unknown command",
|
||||
AppError::RequestTooLarge => "Request is too large",
|
||||
AppError::ServerRedisError(_) => "Internal server error",
|
||||
AppError::ApiKeyInvalid { .. } => "API Key invalid",
|
||||
AppError::UnknownBody { .. } => {
|
||||
"Can't figure out where and in what encoding the main data is"
|
||||
}
|
||||
AppError::QSError(..) => "UrlEncoded body or query params are incorrect",
|
||||
};
|
||||
|
||||
let body = Json(json!({
|
||||
"errno": status.as_u16(),
|
||||
"error": error_message,
|
||||
}));
|
||||
let status_code = self.status_code();
|
||||
|
||||
let mut resp = (status, body).into_response();
|
||||
let body = json!({
|
||||
"errno": status_code.as_u16(),
|
||||
"error": error_message,
|
||||
});
|
||||
|
||||
let mut resp = HttpResponse::build(status_code).json(&body);
|
||||
let headers = resp.headers_mut();
|
||||
|
||||
let error_as_string= format!("{:?}", &self);
|
||||
let error_as_string = format!("{:?}", &self);
|
||||
|
||||
match self {
|
||||
AppError::JsonError(json_err) => {
|
||||
headers.insert("X-Error-Line", HeaderValue::from(json_err.line()));
|
||||
headers.insert("X-Error-Column", HeaderValue::from(json_err.column()));
|
||||
headers.insert("X-Error-Description", HeaderValue::try_from(json_err.to_string().escape_default().collect::<String>()).unwrap());
|
||||
},
|
||||
insert_header!(headers, "X-Error-Line", json_err.line());
|
||||
insert_header!(headers, "X-Error-Column", json_err.column());
|
||||
insert_header!(
|
||||
headers,
|
||||
"X-Error-Description",
|
||||
json_err.to_string().escape_default().collect::<String>()
|
||||
);
|
||||
}
|
||||
AppError::UnknownMethod(method) => {
|
||||
headers.insert("X-Unknown-Cmd", HeaderValue::try_from(method.escape_default().collect::<String>()).unwrap());
|
||||
},
|
||||
insert_header!(
|
||||
headers,
|
||||
"X-Unknown-Cmd",
|
||||
method.escape_default().collect::<String>()
|
||||
);
|
||||
}
|
||||
AppError::RequestTooLarge => {
|
||||
headers.insert("X-Max-Request-Size", HeaderValue::try_from("10 KiB = 10240 bytes").unwrap());
|
||||
},
|
||||
insert_header!(headers, "X-Max-Request-Size", "10 KiB = 10240 bytes");
|
||||
}
|
||||
AppError::ApiKeyInvalid { reason } => {
|
||||
headers.insert("X-Error-Description", HeaderValue::try_from(reason).unwrap());
|
||||
insert_header!(headers, "X-Error-Description", *reason);
|
||||
}
|
||||
AppError::QSError(err) => match err {
|
||||
QSParserError::ParsingError(desc) => {
|
||||
insert_header!(
|
||||
headers,
|
||||
"X-Error-Description",
|
||||
desc.escape_default().to_string()
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
};
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
headers.insert("X-Full-Error", HeaderValue::try_from(error_as_string).unwrap());
|
||||
insert_header!(headers, "X-Full-Error", error_as_string);
|
||||
}
|
||||
|
||||
resp
|
||||
|
|
|
@ -1,26 +1,22 @@
|
|||
use std::time::Duration;
|
||||
use tokio;
|
||||
use axum::{
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use axum::error_handling::{HandleError, HandleErrorLayer};
|
||||
use axum::http::StatusCode;
|
||||
use axum::routing::post;
|
||||
use crate::web_server::old_app_api::old_api_handler;
|
||||
use fred::bytes_utils::Str;
|
||||
use fred::prelude::*;
|
||||
use crate::web_server::old_app_api::old_api_handler;
|
||||
use std::time::Duration;
|
||||
use tokio;
|
||||
|
||||
pub mod old_app_api;
|
||||
mod utils;
|
||||
pub(crate) mod app_error;
|
||||
pub mod old_app_api;
|
||||
mod old_devices_api;
|
||||
pub mod utils;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NMAppState {
|
||||
pub redis_client: RedisClient
|
||||
pub redis_client: RedisClient,
|
||||
}
|
||||
|
||||
use crate::web_server::old_devices_api::device_handler;
|
||||
use heapless::String as HeaplessString;
|
||||
use ntex::web;
|
||||
|
||||
pub async fn server_main() {
|
||||
let config = RedisConfig::default();
|
||||
|
@ -37,16 +33,19 @@ pub async fn server_main() {
|
|||
println!("Ping result: {}", asd);
|
||||
|
||||
let state = NMAppState {
|
||||
redis_client: client
|
||||
redis_client: client,
|
||||
};
|
||||
|
||||
let app = Router::new()
|
||||
.route("/api", post(old_api_handler))
|
||||
.with_state(state);
|
||||
|
||||
|
||||
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||
.serve(app.into_make_service())
|
||||
.await
|
||||
.unwrap();
|
||||
web::HttpServer::new(move || {
|
||||
web::App::new()
|
||||
.state(state.clone())
|
||||
.route("/api", web::post().to(old_api_handler))
|
||||
.route("/get", web::route().to(device_handler))
|
||||
.route("/post", web::route().to(device_handler))
|
||||
})
|
||||
.bind(("127.0.0.1", 8080))
|
||||
.unwrap()
|
||||
.run()
|
||||
.await
|
||||
.unwrap(); // TODO figure out how to handle what
|
||||
}
|
||||
|
|
|
@ -1,41 +1,44 @@
|
|||
use std::borrow::Cow;
|
||||
use axum::body::{Body, Bytes, HttpBody};
|
||||
use axum::extract::State;
|
||||
use axum::http::Request;
|
||||
use axum::Json;
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use nom::AsBytes;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::web_server::app_error::AppError;
|
||||
use crate::web_server::NMAppState;
|
||||
use crate::web_server::old_app_api::handlers::app_init;
|
||||
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 crate::web_server::NMAppState;
|
||||
use nom::AsBytes;
|
||||
use ntex::util::Bytes;
|
||||
use ntex::web;
|
||||
use ntex::web::types::State;
|
||||
use serde_json::Value;
|
||||
|
||||
pub async fn old_api_handler(
|
||||
app_state: State<NMAppState>,
|
||||
body_bytes: Bytes,
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
if body_bytes.len() > 10 * 1024 { // 10 KiB
|
||||
return Err(AppError::RequestTooLarge)
|
||||
) -> Result<impl web::Responder, AppError> {
|
||||
if body_bytes.len() > 10 * 1024 {
|
||||
// 10 KiB
|
||||
return Err(AppError::RequestTooLarge);
|
||||
}
|
||||
|
||||
let mandatory_params: MandatoryParams<'_> = serde_json::from_slice(body_bytes.as_bytes())?; // TODO: Simd-JSON
|
||||
let body_bytes = body_bytes.as_bytes();
|
||||
|
||||
let mandatory_params: MandatoryParams<'_> = serde_json::from_slice(body_bytes)?; // TODO: Simd-JSON
|
||||
|
||||
match mandatory_params.cmd.as_ref() {
|
||||
"version" => return Ok(version((), &app_state).await?),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
is_api_key_valid(&app_state.redis_client, mandatory_params.api_key.as_ref()).await?;
|
||||
|
||||
return match mandatory_params.cmd.as_ref() {
|
||||
match mandatory_params.cmd.as_ref() {
|
||||
"appInit" => {
|
||||
let body: AppInitRequest = serde_json::from_slice(body_bytes.as_bytes())?;
|
||||
let body: AppInitRequest = serde_json::from_slice(body_bytes)?;
|
||||
|
||||
Ok(app_init(body, app_state).await)
|
||||
}
|
||||
_ => {
|
||||
Err(AppError::UnknownMethod(mandatory_params.cmd.to_string()))
|
||||
return Ok(app_init(body, &app_state).await?);
|
||||
}
|
||||
_ => Err(AppError::UnknownMethod(mandatory_params.cmd.to_string())),
|
||||
}
|
||||
|
||||
//Ok("fuck")
|
||||
}
|
||||
|
||||
|
|
|
@ -1,20 +1,36 @@
|
|||
use axum::body::Body;
|
||||
use axum::extract::State;
|
||||
use axum::http::{Request, StatusCode};
|
||||
use axum::Json;
|
||||
use axum::response::IntoResponse;
|
||||
|
||||
use serde_json::Value as JsonValue;
|
||||
use crate::web_server::old_app_api::types::AppInitRequest;
|
||||
use heapless::String as HeaplessString;
|
||||
use ufmt::uwrite;
|
||||
use crate::web_server::NMAppState;
|
||||
use crate::web_server::app_error::AppError;
|
||||
use crate::web_server::old_app_api::types::AppInitRequest;
|
||||
use crate::web_server::NMAppState;
|
||||
use heapless::String as HeaplessString;
|
||||
use serde_json::{json, Value as JsonValue};
|
||||
use ufmt::uwrite;
|
||||
|
||||
use crate::insert_header;
|
||||
use fred::interfaces::KeysInterface;
|
||||
use ntex::http::StatusCode;
|
||||
use ntex::web;
|
||||
use ntex::web::types::State;
|
||||
use ntex::web::Responder;
|
||||
|
||||
pub async fn app_init(body: AppInitRequest<'_>, State(appState): State<NMAppState>) -> Result<impl IntoResponse, AppError> {
|
||||
let _: () = appState.redis_client.set("test", 123, None, None, true).await?;
|
||||
pub async fn app_init(
|
||||
body: AppInitRequest<'_>,
|
||||
app_state: &NMAppState,
|
||||
) -> Result<web::HttpResponse, AppError> {
|
||||
let _: () = app_state
|
||||
.redis_client
|
||||
.set("test", 123, None, None, true)
|
||||
.await?;
|
||||
|
||||
Ok((StatusCode::OK, "Hello, World!").into_response())
|
||||
Ok(web::HttpResponse::build(StatusCode::OK).body("Hello world!"))
|
||||
}
|
||||
|
||||
pub async fn version(body: (), app_state: &NMAppState) -> Result<web::HttpResponse, AppError> {
|
||||
let mut resp = web::HttpResponse::build(StatusCode::OK).json(&json!({
|
||||
"version": "indev",
|
||||
"iotishnik": true
|
||||
}));
|
||||
|
||||
insert_header!(resp.headers_mut(), "Cache-Control", "no-cache");
|
||||
|
||||
Ok(resp)
|
||||
}
|
|
@ -1,10 +1,7 @@
|
|||
mod methods;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use axum::Json;
|
||||
use axum::response::IntoResponse;
|
||||
use phf::phf_map;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::hashes::SupportedUnit;
|
||||
pub use methods::*;
|
||||
|
||||
use phf::phf_map;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
mod types;
|
||||
mod handlers;
|
||||
mod config_app;
|
||||
mod handlers;
|
||||
mod types;
|
||||
|
||||
pub use config_app::old_api_handler;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::borrow::Cow;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use crate::hashes::SupportedUnit;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use std::borrow::Cow;
|
||||
|
||||
// fn<'de, D>(D) -> Result<T, D::Error> where D: Deserializer<'de>
|
||||
|
||||
|
@ -15,7 +15,7 @@ pub struct AppInitRequest<'a> {
|
|||
#[serde(borrow)]
|
||||
pub model: Cow<'a, str>,
|
||||
|
||||
pub width: u64
|
||||
pub width: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
|
@ -36,5 +36,5 @@ pub struct MandatoryParams<'a> {
|
|||
pub uuid: Cow<'a, str>,
|
||||
|
||||
#[serde(borrow)]
|
||||
pub api_key: Cow<'a, str>
|
||||
pub api_key: Cow<'a, str>,
|
||||
}
|
109
src/web_server/old_devices_api/mod.rs
Normal file
109
src/web_server/old_devices_api/mod.rs
Normal file
|
@ -0,0 +1,109 @@
|
|||
use crate::ingest_protocol::error::Error;
|
||||
use crate::ingest_protocol::parser::parse_mac_address;
|
||||
use crate::ingest_protocol::{NMDeviceDataPacket, NMJsonPacket};
|
||||
use crate::web_server::app_error::AppError;
|
||||
use bstr::BStr;
|
||||
use ntex::http::error::DecodeError::Method;
|
||||
use ntex::http::{HttpMessage, Payload, StatusCode};
|
||||
use ntex::util::{Bytes, HashMap};
|
||||
use ntex::{http, web};
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
|
||||
/// В иделае было бы хорошо сделать всё как у [serde_json::Error], но это слишком большая морока
|
||||
#[derive(Error, Clone, Debug)]
|
||||
pub enum QSParserError {
|
||||
#[error("asd")]
|
||||
SerdeQSError(#[from] Arc<serde_qs::Error>),
|
||||
#[error("asd")]
|
||||
ParsingError(String),
|
||||
#[error("asd")]
|
||||
NoMAC,
|
||||
}
|
||||
|
||||
impl From<Error<&str>> for QSParserError {
|
||||
fn from(value: Error<&str>) -> Self {
|
||||
QSParserError::ParsingError(format!("{:?}", value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_qs::Error> for QSParserError {
|
||||
fn from(value: serde_qs::Error) -> Self {
|
||||
QSParserError::SerdeQSError(Arc::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn parse_nm_qs_format<'a>(input: &'a str) -> Result<NMDeviceDataPacket, QSParserError> {
|
||||
let parsed: HashMap<&str, &str> = serde_qs::from_str(input)?;
|
||||
|
||||
let (_, device_mac) = if let Some(id) = parsed.get("ID") {
|
||||
parse_mac_address(*id)?
|
||||
} else {
|
||||
return Err(QSParserError::NoMAC);
|
||||
};
|
||||
|
||||
let device_data = NMDeviceDataPacket {
|
||||
mac: [0u8; 6],
|
||||
name: None,
|
||||
values: Default::default(),
|
||||
owner: None,
|
||||
lat: None,
|
||||
lon: None,
|
||||
alt: None,
|
||||
time: None,
|
||||
};
|
||||
|
||||
return Ok(device_data);
|
||||
}
|
||||
|
||||
pub async fn device_handler<'a>(
|
||||
request: web::HttpRequest,
|
||||
body: Bytes,
|
||||
) -> Result<web::HttpResponse, AppError> {
|
||||
let mut real_body: Option<NMJsonPacket> = None;
|
||||
let mut json_error = None;
|
||||
let mut query_error = None;
|
||||
|
||||
dbg!(&request.content_type());
|
||||
dbg!(&request.query_string());
|
||||
|
||||
if request.method() == http::Method::POST {
|
||||
match request.content_type() {
|
||||
"application/json" => match serde_json::from_slice::<NMJsonPacket>(body.as_ref()) {
|
||||
Ok(json_body) => real_body = Some(json_body),
|
||||
Err(error) => json_error = Some(error),
|
||||
},
|
||||
"application/x-www-form-urlencoded" => {
|
||||
match serde_qs::from_bytes::<NMDeviceDataPacket>(body.as_ref()) {
|
||||
Ok(qs_body) => {
|
||||
real_body = Some(NMJsonPacket {
|
||||
devices: Vec::from([qs_body]),
|
||||
})
|
||||
}
|
||||
Err(error) => query_error = Some(error),
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else if request.method() == http::Method::GET {
|
||||
match serde_qs::from_str::<NMDeviceDataPacket>(request.query_string()) {
|
||||
Ok(qs_body) => {
|
||||
real_body = Some(NMJsonPacket {
|
||||
devices: Vec::from([qs_body]),
|
||||
})
|
||||
}
|
||||
Err(error) => query_error = Some(error),
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(body) = real_body {
|
||||
// TODO
|
||||
} else {
|
||||
return Err(AppError::UnknownBody {
|
||||
json_err: json_error,
|
||||
query_error: query_error,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(web::HttpResponse::build(StatusCode::OK).finish())
|
||||
}
|
|
@ -1 +1,8 @@
|
|||
pub mod redis;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! insert_header {
|
||||
($headers: expr, $name: expr, $value: expr) => {
|
||||
$headers.insert($name.try_into().unwrap(), $value.try_into().unwrap());
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,31 +1,36 @@
|
|||
use crate::web_server::app_error::AppError;
|
||||
use fred::prelude::*;
|
||||
use heapless::String as HeaplessString;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use ufmt::uwrite;
|
||||
use crate::web_server::app_error::AppError;
|
||||
|
||||
lazy_static! {
|
||||
static ref ALLOWED_API_KEY_CHARACTERS: Regex = Regex::new("[a-zA-Z0-9]{13}").unwrap();
|
||||
}
|
||||
|
||||
pub struct ApiKeyDescription {
|
||||
apikey_owner: i64
|
||||
apikey_owner: i64,
|
||||
}
|
||||
|
||||
pub async fn is_api_key_valid(client: &RedisClient, api_key: &str) -> Result<ApiKeyDescription, AppError> {
|
||||
pub async fn is_api_key_valid(
|
||||
client: &RedisClient,
|
||||
api_key: &str,
|
||||
) -> Result<ApiKeyDescription, AppError> {
|
||||
if !ALLOWED_API_KEY_CHARACTERS.is_match(api_key) {
|
||||
return Err(AppError::ApiKeyInvalid { reason: "Invalid characters present in the API key." })
|
||||
return Err(AppError::ApiKeyInvalid {
|
||||
reason: "Invalid characters present in the API key.",
|
||||
});
|
||||
}
|
||||
|
||||
let mut key_buffer = HeaplessString::<{7 + 13}>::new();
|
||||
uwrite!(key_buffer, "apikey_{}", api_key);
|
||||
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?;
|
||||
|
||||
valid.map(|uid| {
|
||||
ApiKeyDescription {
|
||||
apikey_owner: uid
|
||||
}
|
||||
}).ok_or(AppError::ApiKeyInvalid { reason: "Unknown API key" })
|
||||
valid
|
||||
.map(|uid| ApiKeyDescription { apikey_owner: uid })
|
||||
.ok_or(AppError::ApiKeyInvalid {
|
||||
reason: "Unknown API key",
|
||||
})
|
||||
}
|
Loading…
Add table
Reference in a new issue