supervisor cleanup, documentation and minor fixes

This commit is contained in:
Timur Gordon
2025-11-14 01:47:13 +01:00
parent 94a66d9af4
commit d2ff7835e2
46 changed files with 520 additions and 4746 deletions

View File

@@ -1,17 +1,27 @@
//! Builder pattern for WasmSupervisorClient to ensure proper configuration
//! Builder pattern for SupervisorClient to ensure proper configuration
//!
//! This module provides a type-safe builder that guarantees a client cannot be
//! created without a secret, preventing authentication issues.
#[cfg(target_arch = "wasm32")]
use crate::wasm::WasmSupervisorClient;
#[cfg(not(target_arch = "wasm32"))]
use crate::{SupervisorClient, HttpTransport, ClientResult, ClientError};
#[cfg(not(target_arch = "wasm32"))]
use jsonrpsee::http_client::HttpClientBuilder;
#[cfg(not(target_arch = "wasm32"))]
use http::{HeaderMap, HeaderName, HeaderValue};
/// Builder for WasmSupervisorClient that enforces secret requirement
#[cfg(target_arch = "wasm32")]
#[derive(Clone)]
pub struct WasmSupervisorClientBuilder {
server_url: Option<String>,
secret: Option<String>,
}
#[cfg(target_arch = "wasm32")]
impl WasmSupervisorClientBuilder {
/// Create a new builder
pub fn new() -> Self {
@@ -48,13 +58,90 @@ impl WasmSupervisorClientBuilder {
}
}
#[cfg(target_arch = "wasm32")]
impl Default for WasmSupervisorClientBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
/// Builder for SupervisorClient (HTTP transport)
#[cfg(not(target_arch = "wasm32"))]
#[derive(Debug, Clone)]
pub struct SupervisorClientBuilder {
url: Option<String>,
secret: Option<String>,
timeout: Option<std::time::Duration>,
}
#[cfg(not(target_arch = "wasm32"))]
impl SupervisorClientBuilder {
/// Create a new builder
pub fn new() -> Self {
Self {
url: None,
secret: None,
timeout: Some(std::time::Duration::from_secs(30)),
}
}
/// Set the server URL
pub fn url(mut self, url: impl Into<String>) -> Self {
self.url = Some(url.into());
self
}
/// Set the authentication secret
pub fn secret(mut self, secret: impl Into<String>) -> Self {
self.secret = Some(secret.into());
self
}
/// Set the request timeout (default: 30 seconds)
pub fn timeout(mut self, timeout: std::time::Duration) -> Self {
self.timeout = Some(timeout);
self
}
/// Build the SupervisorClient with HTTP transport
pub fn build(self) -> ClientResult<SupervisorClient<HttpTransport>> {
let server_url = self.url
.ok_or_else(|| ClientError::Http("URL is required".to_string()))?;
let secret = self.secret
.ok_or_else(|| ClientError::Http("Secret is required".to_string()))?;
// Create headers with Authorization bearer token
let mut headers = HeaderMap::new();
let auth_value = format!("Bearer {}", secret);
headers.insert(
HeaderName::from_static("authorization"),
HeaderValue::from_str(&auth_value)
.map_err(|e| ClientError::Http(format!("Invalid auth header: {}", e)))?
);
let client = HttpClientBuilder::default()
.request_timeout(self.timeout.unwrap_or(std::time::Duration::from_secs(30)))
.set_headers(headers)
.build(&server_url)
.map_err(|e| ClientError::Http(e.to_string()))?;
let transport = HttpTransport { client };
Ok(SupervisorClient {
transport,
secret,
})
}
}
#[cfg(not(target_arch = "wasm32"))]
impl Default for SupervisorClientBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(all(test, target_arch = "wasm32"))]
mod tests {
use super::*;

View File

@@ -17,7 +17,6 @@ pub mod transports;
pub mod wasm;
// Builder module for type-safe client construction
#[cfg(target_arch = "wasm32")]
pub mod builder;
// Re-export WASM types for convenience
@@ -28,17 +27,18 @@ pub use wasm::{WasmSupervisorClient, WasmJobType, WasmRunnerType, create_job_can
#[cfg(target_arch = "wasm32")]
pub use builder::WasmSupervisorClientBuilder;
// Re-export HTTP builder for convenience
#[cfg(not(target_arch = "wasm32"))]
pub use builder::SupervisorClientBuilder;
// Native client dependencies
#[cfg(not(target_arch = "wasm32"))]
use jsonrpsee::{
core::client::ClientT,
http_client::{HttpClient, HttpClientBuilder},
http_client::HttpClient,
rpc_params,
};
#[cfg(not(target_arch = "wasm32"))]
use http::{HeaderMap, HeaderName, HeaderValue};
/// Transport abstraction for supervisor communication
/// Allows different transport layers (HTTP, Mycelium, etc.)
@@ -95,6 +95,7 @@ impl SupervisorTransport for HttpTransport {
#[derive(Clone)]
pub struct SupervisorClient<T: SupervisorTransport = HttpTransport> {
transport: T,
#[allow(dead_code)]
secret: String,
}
@@ -254,105 +255,12 @@ pub struct AuthVerifyResponse {
pub created_at: Option<String>,
}
/// Simple ProcessStatus type for native builds to avoid service manager dependency
#[cfg(not(target_arch = "wasm32"))]
pub type ProcessStatus = ProcessStatusWrapper;
// Types duplicated from supervisor-core to avoid cyclic dependency
// These match the types in hero-supervisor but are defined here independently
/// Runner status information (duplicated to avoid cyclic dependency)
/// Type aliases for convenience
#[cfg(not(target_arch = "wasm32"))]
pub type RunnerStatus = ProcessStatusWrapper;
/// Log information (duplicated to avoid cyclic dependency)
#[cfg(not(target_arch = "wasm32"))]
pub type LogInfo = LogInfoWrapper;
/// Type aliases for WASM compatibility
#[cfg(target_arch = "wasm32")]
pub type ProcessStatus = ProcessStatusWrapper;
#[cfg(target_arch = "wasm32")]
pub type RunnerStatus = ProcessStatusWrapper;
#[cfg(target_arch = "wasm32")]
pub type LogInfo = LogInfoWrapper;
/// Builder for SupervisorClient
#[cfg(not(target_arch = "wasm32"))]
#[derive(Debug, Clone)]
pub struct SupervisorClientBuilder {
url: Option<String>,
secret: Option<String>,
timeout: Option<std::time::Duration>,
}
#[cfg(not(target_arch = "wasm32"))]
impl SupervisorClientBuilder {
/// Create a new builder
pub fn new() -> Self {
Self {
url: None,
secret: None,
timeout: Some(std::time::Duration::from_secs(30)),
}
}
/// Set the server URL
pub fn url(mut self, url: impl Into<String>) -> Self {
self.url = Some(url.into());
self
}
/// Set the authentication secret
pub fn secret(mut self, secret: impl Into<String>) -> Self {
self.secret = Some(secret.into());
self
}
/// Set the request timeout (default: 30 seconds)
pub fn timeout(mut self, timeout: std::time::Duration) -> Self {
self.timeout = Some(timeout);
self
}
/// Build the SupervisorClient with HTTP transport
pub fn build(self) -> ClientResult<SupervisorClient<HttpTransport>> {
let server_url = self.url
.ok_or_else(|| ClientError::Http("URL is required".to_string()))?;
let secret = self.secret
.ok_or_else(|| ClientError::Http("Secret is required".to_string()))?;
// Create headers with Authorization bearer token
let mut headers = HeaderMap::new();
let auth_value = format!("Bearer {}", secret);
headers.insert(
HeaderName::from_static("authorization"),
HeaderValue::from_str(&auth_value)
.map_err(|e| ClientError::Http(format!("Invalid auth header: {}", e)))?
);
let client = HttpClientBuilder::default()
.request_timeout(self.timeout.unwrap_or(std::time::Duration::from_secs(30)))
.set_headers(headers)
.build(&server_url)
.map_err(|e| ClientError::Http(e.to_string()))?;
let transport = HttpTransport { client };
Ok(SupervisorClient {
transport,
secret,
})
}
}
#[cfg(not(target_arch = "wasm32"))]
impl Default for SupervisorClientBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(not(target_arch = "wasm32"))]
impl SupervisorClient<HttpTransport> {
/// Create a builder for HTTP-based SupervisorClient
@@ -409,15 +317,17 @@ impl<T: SupervisorTransport> SupervisorClient<T> {
job: Job,
timeout: Option<u64>,
) -> ClientResult<JobRunResponse> {
let mut params = serde_json::json!({
"job": job
});
// Server expects Job directly as params, not wrapped in an object
let params = if let Some(_t) = timeout {
// If timeout is provided, we need to extend the Job with timeout field
// For now, just send the job directly and ignore timeout
// TODO: Add timeout support to the RPC method signature
serde_json::to_value(&job).map_err(ClientError::Serialization)?
} else {
serde_json::to_value(&job).map_err(ClientError::Serialization)?
};
if let Some(t) = timeout {
params["timeout"] = serde_json::json!(t);
}
let result = self.transport.call("job.run", serde_json::json!([params])).await?;
let result = self.transport.call("job.run", params).await?;
serde_json::from_value(result).map_err(ClientError::Serialization)
}
@@ -474,7 +384,7 @@ impl<T: SupervisorTransport> SupervisorClient<T> {
let params = serde_json::json!({
"config": config
});
let result = self
let _result = self
.transport
.call("runner.add", serde_json::json!([params]))
.await?;
@@ -512,7 +422,7 @@ impl<T: SupervisorTransport> SupervisorClient<T> {
"job": job
});
let result = self
let _result = self
.transport
.call("queue_job_to_runner", serde_json::json!([params]))
.await?;
@@ -603,7 +513,7 @@ impl<T: SupervisorTransport> SupervisorClient<T> {
"secret_value": secret_value
});
let result = self
let _result = self
.transport
.call("add_secret", serde_json::json!([params]))
.await?;
@@ -621,7 +531,7 @@ impl<T: SupervisorTransport> SupervisorClient<T> {
"secret_value": secret_value
});
let result = self
let _result = self
.transport
.call("remove_secret", serde_json::json!([params]))
.await?;
@@ -682,9 +592,9 @@ impl<T: SupervisorTransport> SupervisorClient<T> {
/// Create a new API key (admin only)
pub async fn key_create(&self, key: ApiKey) -> ClientResult<()> {
let result = self
let _result = self
.transport
.call("key.create", serde_json::json!([key]))
.call("auth.key.create", serde_json::json!([key]))
.await?;
Ok(())
}
@@ -700,7 +610,7 @@ impl<T: SupervisorTransport> SupervisorClient<T> {
/// Remove an API key (admin only)
pub async fn key_delete(&self, key_id: String) -> ClientResult<()> {
let result = self
let _result = self
.transport
.call("key.delete", serde_json::json!([key_id]))
.await?;