use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use thiserror::Error;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModuleInfo {
pub name: String,
pub version: String,
pub description: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<Uuid>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<HashMap<String, String>>,
}
impl ModuleInfo {
pub fn new<S: Into<String>>(name: S, version: S, description: S) -> Self {
Self {
name: name.into(),
version: version.into(),
description: description.into(),
id: None,
metadata: None,
}
}
pub fn with_id(mut self, id: Uuid) -> Self {
self.id = Some(id);
self
}
pub fn with_metadata<S: Into<String>>(mut self, key: S, value: S) -> Self {
if self.metadata.is_none() {
self.metadata = Some(HashMap::new());
}
if let Some(metadata) = self.metadata.as_mut() {
metadata.insert(key.into(), value.into());
}
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Endpoint {
pub path: String,
pub methods: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub auth: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<HashMap<String, String>>,
}
impl Endpoint {
pub fn new<S: Into<String>>(path: S, methods: Vec<&str>) -> Self {
Self {
path: path.into(),
methods: methods.into_iter().map(String::from).collect(),
auth: None,
metadata: None,
}
}
pub fn with_auth<S: Into<String>>(mut self, auth: S) -> Self {
self.auth = Some(auth.into());
self
}
pub fn with_metadata<S: Into<String>>(mut self, key: S, value: S) -> Self {
if self.metadata.is_none() {
self.metadata = Some(HashMap::new());
}
if let Some(metadata) = self.metadata.as_mut() {
metadata.insert(key.into(), value.into());
}
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Capabilities {
pub endpoints: Vec<Endpoint>,
#[serde(skip_serializing_if = "Option::is_none")]
pub message_types: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub additional_capabilities: Option<HashMap<String, serde_json::Value>>,
}
impl Capabilities {
pub fn new() -> Self {
Self {
endpoints: Vec::new(),
message_types: None,
additional_capabilities: None,
}
}
pub fn with_http_endpoint<S: Into<String>>(mut self, path: S, methods: Vec<&str>) -> Self {
self.endpoints.push(Endpoint::new(path, methods));
self
}
pub fn with_message_type<S: Into<String>>(mut self, message_type: S) -> Self {
if self.message_types.is_none() {
self.message_types = Some(Vec::new());
}
if let Some(message_types) = self.message_types.as_mut() {
message_types.push(message_type.into());
}
self
}
pub fn with_capability<S: Into<String>, V: Serialize>(
mut self,
key: S,
value: V,
) -> Result<Self, serde_json::Error> {
if self.additional_capabilities.is_none() {
self.additional_capabilities = Some(HashMap::new());
}
if let Some(capabilities) = self.additional_capabilities.as_mut() {
capabilities.insert(key.into(), serde_json::to_value(value)?);
}
Ok(self)
}
}
impl Default for Capabilities {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum HealthStatus {
Healthy,
Degraded,
Unhealthy,
}
impl std::fmt::Display for HealthStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
HealthStatus::Healthy => write!(f, "healthy"),
HealthStatus::Degraded => write!(f, "degraded"),
HealthStatus::Unhealthy => write!(f, "unhealthy"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegisteredModule {
pub info: ModuleInfo,
pub id: Uuid,
pub token: String,
pub orchestrator_host: String,
pub orchestrator_port: u16,
#[serde(skip_serializing_if = "Option::is_none")]
pub env: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub additional_data: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Error)]
pub enum RegistrationError {
#[error("Module name '{0}' is already taken")]
NameTaken(String),
#[error("Connection error: {0}")]
ConnectionError(String),
#[error("Registration rejected: {0}")]
Rejected(String),
#[error("Authentication failed: {0}")]
AuthenticationFailed(String),
#[error("Serialization error: {0}")]
SerializationError(String),
#[error("Channel error: {0}")]
ChannelError(String),
#[error("Registration timed out after {0:?}")]
Timeout(std::time::Duration),
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegistrationRequest {
pub request_type: String,
pub module_info: ModuleInfo,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegistrationResponse {
pub response_type: String,
pub success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub module: Option<RegisteredModule>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HeartbeatRequest {
pub request_type: String,
pub module_id: Uuid,
pub token: String,
pub status: HealthStatus,
#[serde(skip_serializing_if = "Option::is_none")]
pub details: Option<HashMap<String, String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HeartbeatResponse {
pub response_type: String,
pub success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
pub status: HealthStatus,
#[serde(skip_serializing_if = "Option::is_none")]
pub context: Option<HashMap<String, String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CapabilitiesRequest {
pub request_type: String,
pub module_id: Uuid,
pub token: String,
pub capabilities: Capabilities,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CapabilitiesResponse {
pub response_type: String,
pub success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UnregistrationRequest {
pub request_type: String,
pub module_id: Uuid,
pub token: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UnregistrationResponse {
pub response_type: String,
pub success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}