initial frontend command stuff
This commit is contained in:
36
server/src/command/definition.rs
Normal file
36
server/src/command/definition.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use crate::command::service::RegisteredCommand;
|
||||
use crate::core::TelemetryDataType;
|
||||
use crate::telemetry::data_type::tlm_data_type_deserializer;
|
||||
use crate::telemetry::data_type::tlm_data_type_serializer;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct CommandParameterDefinition {
|
||||
pub name: String,
|
||||
#[serde(serialize_with = "tlm_data_type_serializer")]
|
||||
#[serde(deserialize_with = "tlm_data_type_deserializer")]
|
||||
pub data_type: TelemetryDataType,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct CommandDefinition {
|
||||
pub name: String,
|
||||
pub parameters: Vec<CommandParameterDefinition>,
|
||||
}
|
||||
|
||||
impl From<RegisteredCommand> for CommandDefinition {
|
||||
fn from(value: RegisteredCommand) -> Self {
|
||||
Self {
|
||||
name: value.name,
|
||||
parameters: value
|
||||
.definition
|
||||
.parameters
|
||||
.into_iter()
|
||||
.map(|param| CommandParameterDefinition {
|
||||
data_type: param.data_type(),
|
||||
name: param.name,
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
36
server/src/command/error.rs
Normal file
36
server/src/command/error.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use crate::core::TelemetryDataType;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::ResponseError;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("Command Not Found {0}")]
|
||||
CommandNotFound(String),
|
||||
#[error("Incorrect Number of Parameters Specified. {expected} expected. {actual} found.")]
|
||||
IncorrectParameterCount { expected: usize, actual: usize },
|
||||
#[error("Missing Parameter {0}.")]
|
||||
MisingParameter(String),
|
||||
#[error("Incorrect Parameter Type for {name}. {expected_type:?} expected.")]
|
||||
WrongParameterType {
|
||||
name: String,
|
||||
expected_type: TelemetryDataType,
|
||||
},
|
||||
#[error("No Command Receiver")]
|
||||
NoCommandReceiver,
|
||||
#[error("Failed to Send")]
|
||||
FailedToSend,
|
||||
}
|
||||
|
||||
impl ResponseError for Error {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match *self {
|
||||
Error::CommandNotFound(_) => StatusCode::NOT_FOUND,
|
||||
Error::IncorrectParameterCount { .. } => StatusCode::BAD_REQUEST,
|
||||
Error::MisingParameter(_) => StatusCode::BAD_REQUEST,
|
||||
Error::WrongParameterType { .. } => StatusCode::BAD_REQUEST,
|
||||
Error::NoCommandReceiver => StatusCode::SERVICE_UNAVAILABLE,
|
||||
Error::FailedToSend => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +1,3 @@
|
||||
mod definition;
|
||||
pub mod error;
|
||||
pub mod service;
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
use crate::command::definition::CommandDefinition;
|
||||
use crate::command::error::Error as CmdError;
|
||||
use crate::command::error::Error::{
|
||||
CommandNotFound, FailedToSend, IncorrectParameterCount, MisingParameter, NoCommandReceiver,
|
||||
WrongParameterType,
|
||||
};
|
||||
use crate::core::telemetry_value::Value;
|
||||
use crate::core::{
|
||||
Command, CommandDefinitionRequest, TelemetryDataType, TelemetryValue, Timestamp,
|
||||
};
|
||||
use anyhow::{bail, ensure};
|
||||
use chrono::{DateTime, Utc};
|
||||
use log::error;
|
||||
use papaya::HashMap;
|
||||
use std::collections::HashMap as StdHashMap;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::mpsc::{Receiver, Sender};
|
||||
|
||||
struct RegisteredCommand {
|
||||
definition: CommandDefinitionRequest,
|
||||
#[derive(Clone)]
|
||||
pub(super) struct RegisteredCommand {
|
||||
pub(super) name: String,
|
||||
pub(super) definition: CommandDefinitionRequest,
|
||||
tx: Sender<Option<Command>>,
|
||||
}
|
||||
|
||||
@@ -25,6 +33,24 @@ impl CommandManagementService {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_commands(&self) -> anyhow::Result<Vec<CommandDefinition>> {
|
||||
let mut result = vec![];
|
||||
|
||||
let registered_commands = self.registered_commands.pin();
|
||||
for registration in registered_commands.values() {
|
||||
result.push(registration.clone().into());
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn get_command_definition(&self, name: &String) -> Option<CommandDefinition> {
|
||||
self.registered_commands
|
||||
.pin()
|
||||
.get(name)
|
||||
.map(|registration| registration.clone().into())
|
||||
}
|
||||
|
||||
pub async fn register_command(
|
||||
&self,
|
||||
command: CommandDefinitionRequest,
|
||||
@@ -35,6 +61,7 @@ impl CommandManagementService {
|
||||
if let Some(previous) = registered_commands.insert(
|
||||
command.name.clone(),
|
||||
RegisteredCommand {
|
||||
name: command.name.clone(),
|
||||
definition: command,
|
||||
tx,
|
||||
},
|
||||
@@ -50,7 +77,7 @@ impl CommandManagementService {
|
||||
&self,
|
||||
name: impl Into<String>,
|
||||
parameters: serde_json::Map<String, serde_json::Value>,
|
||||
) -> anyhow::Result<()> {
|
||||
) -> Result<(), CmdError> {
|
||||
let timestamp = Utc::now();
|
||||
let offset_from_unix_epoch =
|
||||
timestamp - DateTime::from_timestamp(0, 0).expect("Could not get Unix epoch");
|
||||
@@ -58,19 +85,19 @@ impl CommandManagementService {
|
||||
let name = name.into();
|
||||
let registered_commands = self.registered_commands.pin();
|
||||
let Some(registration) = registered_commands.get(&name) else {
|
||||
bail!("Command Not Found {name}");
|
||||
return Err(CommandNotFound(name));
|
||||
};
|
||||
|
||||
ensure!(
|
||||
parameters.len() == registration.definition.parameters.len(),
|
||||
"Command has {} parameters. {} expected",
|
||||
parameters.len(),
|
||||
registration.definition.parameters.len()
|
||||
);
|
||||
if parameters.len() != registration.definition.parameters.len() {
|
||||
return Err(IncorrectParameterCount {
|
||||
expected: registration.definition.parameters.len(),
|
||||
actual: parameters.len(),
|
||||
});
|
||||
}
|
||||
let mut result_parameters = StdHashMap::new();
|
||||
for parameter in ®istration.definition.parameters {
|
||||
let Some(param_value) = parameters.get(¶meter.name) else {
|
||||
bail!("Command Missing Parameter: {}", parameter.name);
|
||||
return Err(MisingParameter(parameter.name.clone()));
|
||||
};
|
||||
let Some(param_value) = (match parameter.data_type() {
|
||||
TelemetryDataType::Float32 => {
|
||||
@@ -79,11 +106,10 @@ impl CommandManagementService {
|
||||
TelemetryDataType::Float64 => param_value.as_f64().map(Value::Float64),
|
||||
TelemetryDataType::Boolean => param_value.as_bool().map(Value::Boolean),
|
||||
}) else {
|
||||
bail!(
|
||||
"Parameter {} has the wrong type. {:?} expected",
|
||||
parameter.name,
|
||||
parameter.data_type()
|
||||
);
|
||||
return Err(WrongParameterType {
|
||||
name: parameter.name.clone(),
|
||||
expected_type: parameter.data_type(),
|
||||
});
|
||||
};
|
||||
result_parameters.insert(
|
||||
parameter.name.clone(),
|
||||
@@ -97,6 +123,10 @@ impl CommandManagementService {
|
||||
let tx = registration.tx.clone();
|
||||
drop(registered_commands);
|
||||
|
||||
if tx.is_closed() {
|
||||
return Err(NoCommandReceiver);
|
||||
}
|
||||
|
||||
if let Err(e) = tx
|
||||
.send(Some(Command {
|
||||
timestamp: Some(Timestamp {
|
||||
@@ -107,7 +137,8 @@ impl CommandManagementService {
|
||||
}))
|
||||
.await
|
||||
{
|
||||
bail!("Failed to send command {e}");
|
||||
error!("Failed to Send Command: {e}");
|
||||
return Err(FailedToSend);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::command::service::CommandManagementService;
|
||||
use crate::http::error::HttpServerResultError;
|
||||
use actix_web::{post, web, Responder};
|
||||
use actix_web::{get, post, web, Responder};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[post("/cmd/{name:[\\w\\d/_-]+}")]
|
||||
@@ -13,5 +13,22 @@ pub(super) async fn send_command(
|
||||
.send_command(name.to_string(), parameters.into_inner())
|
||||
.await?;
|
||||
|
||||
Ok(web::Json(()))
|
||||
Ok(web::Json("Command Sent Successfully."))
|
||||
}
|
||||
|
||||
#[get("/cmd")]
|
||||
pub(super) async fn get_all(
|
||||
command_service: web::Data<Arc<CommandManagementService>>,
|
||||
) -> Result<impl Responder, HttpServerResultError> {
|
||||
Ok(web::Json(command_service.get_commands()?))
|
||||
}
|
||||
|
||||
#[get("/cmd/{name:[\\w\\d/_-]+}")]
|
||||
pub(super) async fn get_one(
|
||||
command_service: web::Data<Arc<CommandManagementService>>,
|
||||
name: web::Path<String>,
|
||||
) -> Result<impl Responder, HttpServerResultError> {
|
||||
Ok(web::Json(
|
||||
command_service.get_command_definition(&name.to_string()),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -13,5 +13,7 @@ pub fn setup_api(cfg: &mut web::ServiceConfig) {
|
||||
.service(panels::get_one)
|
||||
.service(panels::set)
|
||||
.service(panels::delete)
|
||||
.service(cmd::send_command);
|
||||
.service(cmd::send_command)
|
||||
.service(cmd::get_all)
|
||||
.service(cmd::get_one);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ use actix_web::error::ResponseError;
|
||||
use actix_web::http::header::ContentType;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::HttpResponse;
|
||||
use anyhow::Error;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
@@ -16,31 +15,28 @@ pub enum HttpServerResultError {
|
||||
#[error("Timed out")]
|
||||
Timeout,
|
||||
#[error("Internal Error")]
|
||||
InternalError(anyhow::Error),
|
||||
InternalError(#[from] anyhow::Error),
|
||||
#[error("Panel Uuid Not Found: {uuid}")]
|
||||
PanelUuidNotFound { uuid: String },
|
||||
#[error(transparent)]
|
||||
Command(#[from] crate::command::error::Error),
|
||||
}
|
||||
|
||||
impl ResponseError for HttpServerResultError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match *self {
|
||||
match self {
|
||||
HttpServerResultError::TlmNameNotFound { .. } => StatusCode::NOT_FOUND,
|
||||
HttpServerResultError::TlmUuidNotFound { .. } => StatusCode::NOT_FOUND,
|
||||
HttpServerResultError::InvalidDateTime { .. } => StatusCode::BAD_REQUEST,
|
||||
HttpServerResultError::Timeout => StatusCode::GATEWAY_TIMEOUT,
|
||||
HttpServerResultError::InternalError { .. } => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
HttpServerResultError::PanelUuidNotFound { .. } => StatusCode::NOT_FOUND,
|
||||
HttpServerResultError::Command(inner) => inner.status_code(),
|
||||
}
|
||||
}
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
HttpResponse::build(self.status_code())
|
||||
.insert_header(ContentType::html())
|
||||
.insert_header(ContentType::plaintext())
|
||||
.body(self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<anyhow::Error> for HttpServerResultError {
|
||||
fn from(value: Error) -> Self {
|
||||
Self::InternalError(value)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user