uses a proc-macro to automate command definitions

This commit is contained in:
2025-12-31 00:23:30 -05:00
parent 6fdbb868b7
commit 0e28b0416a
26 changed files with 757 additions and 177 deletions

View File

@@ -6,14 +6,16 @@ version = "0.1.0"
authors = ["Sergey <me@sergeysav.com>"]
[dependencies]
log = { workspace = true }
thiserror = { workspace = true }
serde = { workspace = true, features = ["derive"] }
derive_more = { workspace = true, features = ["from", "try_into"] }
uuid = { workspace = true, features = ["serde"] }
api-core = { path = "../api-core" }
api-proc-macro = { path = "../api-proc-macro" }
chrono = { workspace = true, features = ["serde"] }
derive_more = { workspace = true, features = ["from", "try_into"] }
futures-util = { workspace = true }
log = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["rt", "macros", "time"] }
tokio-tungstenite = { workspace = true, features = ["rustls-tls-native-roots"] }
tokio-util = { workspace = true }
futures-util = { workspace = true }
serde_json = { workspace = true }
uuid = { workspace = true, features = ["serde"] }

73
api/src/client/command.rs Normal file
View File

@@ -0,0 +1,73 @@
use crate::client::Client;
use crate::messages::command::CommandResponse;
use api_core::command::{CommandHeader, IntoCommandDefinition};
use std::fmt::Display;
use std::sync::Arc;
use tokio_util::sync::CancellationToken;
pub struct Commanding;
impl Commanding {
pub fn register_handler<C: IntoCommandDefinition, F, E: Display>(
client: Arc<Client>,
command_name: String,
mut callback: F,
) -> CommandHandle
where
F: FnMut(CommandHeader, C) -> Result<String, E> + Send + 'static,
{
let cancellation_token = CancellationToken::new();
let result = CommandHandle {
cancellation_token: cancellation_token.clone(),
};
let command_definition = C::create(command_name);
tokio::spawn(async move {
while !cancellation_token.is_cancelled() {
// This would only fail if the sender closed while trying to insert data
// It would wait until space is made
let Ok(mut rx) = client
.register_callback_channel(command_definition.clone())
.await
else {
continue;
};
while let Some((cmd, responder)) = rx.recv().await {
let header = cmd.header.clone();
let response = match C::parse(cmd) {
Ok(cmd) => match callback(header, cmd) {
Ok(response) => CommandResponse {
success: true,
response,
},
Err(err) => CommandResponse {
success: false,
response: err.to_string(),
},
},
Err(err) => CommandResponse {
success: false,
response: err.to_string(),
},
};
// This should only err if we had an error elsewhere
let _ = responder.send(response);
}
}
});
result
}
}
pub struct CommandHandle {
cancellation_token: CancellationToken,
}
impl Drop for CommandHandle {
fn drop(&mut self) {
self.cancellation_token.cancel();
}
}

View File

@@ -1,3 +1,4 @@
pub mod command;
mod context;
pub mod error;
pub mod telemetry;

View File

@@ -1,8 +1 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum DataType {
Float32,
Float64,
Boolean,
}
pub use api_core::data_type::*;

View File

@@ -1,9 +1 @@
use derive_more::TryInto;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Serialize, Deserialize, TryInto)]
pub enum DataValue {
Float32(f32),
Float64(f64),
Boolean(bool),
}
pub use api_core::data_value::*;

View File

@@ -2,3 +2,7 @@ pub mod client;
pub mod data_type;
pub mod data_value;
pub mod messages;
pub mod macros {
pub use api_proc_macro::IntoCommandDefinition;
}

View File

@@ -1,33 +1,13 @@
use crate::data_type::DataType;
use crate::data_value::DataValue;
use crate::messages::RegisterCallback;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommandParameterDefinition {
pub name: String,
pub data_type: DataType,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommandDefinition {
pub name: String,
pub parameters: Vec<CommandParameterDefinition>,
}
pub use api_core::command::*;
impl RegisterCallback for CommandDefinition {
type Callback = Command;
type Response = CommandResponse;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Command {
pub timestamp: DateTime<Utc>,
pub parameters: HashMap<String, DataValue>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommandResponse {
pub success: bool,