feat: add uni-comfyui-sdk - Rust SDK for ComfyUI API

- Complete Rust SDK implementation based on OpenAPI specification
- All 23 endpoints implemented with type-safe interfaces
- Comprehensive error handling and async support
- Full documentation and examples included
- Client renamed to UniComfyUIClient for clear identification
This commit is contained in:
imeepos
2025-08-15 15:31:33 +08:00
parent 5380d9973f
commit 08b981c31f
12 changed files with 2193 additions and 0 deletions

20
cargos/uni-comfyui-sdk/.gitignore vendored Normal file
View File

@@ -0,0 +1,20 @@
# Rust
/target/
Cargo.lock
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Logs
*.log
# Environment
.env
.env.local

View File

@@ -0,0 +1,23 @@
[package]
name = "uni-comfyui-sdk"
version = "0.1.0"
edition = "2021"
description = "Rust SDK for ComfyUI Workflow Service & Management API"
license = "MIT"
repository = "https://github.com/your-repo/uni-comfyui-sdk"
keywords = ["comfyui", "api", "sdk", "workflow"]
categories = ["api-bindings", "web-programming::http-client"]
[workspace]
[dependencies]
reqwest = { version = "0.11", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1.0", features = ["full"] }
thiserror = "1.0"
url = "2.4"
chrono = { version = "0.4", features = ["serde"] }
[dev-dependencies]
tokio-test = "0.4"

View File

@@ -0,0 +1,124 @@
# uni-comfyui-sdk
A Rust SDK for ComfyUI Workflow Service & Management API.
## Features
- **Status Management**: Get metrics, server status, and file listings
- **Workflow Management**: Create, retrieve, and delete workflows
- **Workflow Execution**: Run workflows and monitor their status
- **Server Management**: Register, unregister, and manage ComfyUI servers
- **Monitoring**: Health checks, system stats, and task monitoring
- **Type Safety**: Strongly typed API with comprehensive error handling
## Installation
Add this to your `Cargo.toml`:
```toml
[dependencies]
uni-comfyui-sdk = "0.1.0"
```
## Quick Start
```rust
use uni_comfyui_sdk::{UniComfyUIClient, Result};
use std::collections::HashMap;
#[tokio::main]
async fn main() -> Result<()> {
// Create a client
let client = UniComfyUIClient::new("http://localhost:8000")?;
// Get system metrics
let metrics = client.get_metrics().await?;
println!("Metrics: {:#?}", metrics);
// Get all workflows
let workflows = client.get_all_workflows().await?;
println!("Found {} workflows", workflows.len());
// Run a workflow
let mut data = HashMap::new();
data.insert("input".to_string(), serde_json::json!("test input"));
let result = client.run_workflow("my_workflow", None, data).await?;
println!("Workflow result: {:#?}", result);
Ok(())
}
```
## API Coverage
### Status Endpoints
- `get_metrics()` - Get queue status overview
- `get_servers_status()` - Get all configured ComfyUI servers status
- `list_server_files(server_index)` - List files in server input/output folders
### Workflow Endpoints
- `get_all_workflows()` - Get all workflows
- `publish_workflow(data)` - Publish a new workflow
- `delete_workflow(workflow_name)` - Delete a workflow
- `get_one_workflow(base_name, version)` - Get workflow specification
### Run Endpoints
- `run_workflow(name, version, data)` - Execute workflow asynchronously
- `get_run_status(workflow_run_id)` - Get workflow execution status
### RunX Endpoints
- `model_with_multi_dress(version, data)` - Model with multiple dresses workflow
- `model_with_multi_dress_json_schema()` - Get workflow definition
### Monitor Endpoints
- `monitor_dashboard()` - Get monitoring dashboard HTML
- `get_system_stats()` - Get system statistics
- `get_task_stats()` - Get task statistics
- `get_monitor_server_status()` - Get server status information
- `get_recent_tasks(limit)` - Get recent tasks list
- `health_check()` - Health check endpoint
### ComfyUI Server Management Endpoints
- `register_server(request)` - Register ComfyUI server
- `update_heartbeat(request)` - Update server heartbeat
- `unregister_server(request)` - Unregister ComfyUI server
- `get_server_status(server_name)` - Get specific server status
- `list_all_servers()` - Get all servers list
- `get_system_health()` - Get system overall health status
- `force_health_check()` - Force health check execution
## Error Handling
The SDK provides comprehensive error handling through the `ComfyUIError` enum:
```rust
use uni_comfyui_sdk::{UniComfyUIClient, ComfyUIError};
match client.get_metrics().await {
Ok(metrics) => println!("Success: {:#?}", metrics),
Err(ComfyUIError::Http(e)) => println!("HTTP error: {}", e),
Err(ComfyUIError::Api { status, message }) => {
println!("API error {}: {}", status, message)
}
Err(e) => println!("Other error: {}", e),
}
```
## Data Models
The SDK includes strongly typed models for all API requests and responses:
- `ServerRegistrationRequest` - Server registration data
- `ServerHeartbeatRequest` - Server heartbeat data
- `ServerStatusResponse` - Server status information
- `ServerFiles` - Server file listings
- `FileDetails` - Individual file information
- And more...
## Examples
See the `examples/` directory for more usage examples.
## License
MIT

View File

@@ -0,0 +1,172 @@
use std::collections::HashMap;
use std::time::Duration;
use tokio::time::sleep;
use uni_comfyui_sdk::{
UniComfyUIClient, ComfyUIError, Result, ServerHeartbeatRequest, ServerRegistrationRequest,
ServerUnregisterRequest,
};
#[tokio::main]
async fn main() -> Result<()> {
let client = UniComfyUIClient::new("http://192.168.0.148:18000")?;
// Demonstrate comprehensive server management
println!("=== Server Management Demo ===");
// Register a new server
let server_name = "demo-server";
let registration = ServerRegistrationRequest {
name: server_name.to_string(),
http_url: "http://192.168.0.148:8188".to_string(),
ws_url: "ws://192.168.0.148:8188".to_string(),
};
match client.register_server(registration).await {
Ok(response) => println!("✓ Server registered: {:?}", response),
Err(ComfyUIError::Api { status: 409, .. }) => {
println!(" Server already exists (409 conflict)")
}
Err(e) => println!("✗ Failed to register server: {}", e),
}
// Send heartbeat
let heartbeat = ServerHeartbeatRequest {
name: server_name.to_string(),
};
match client.update_heartbeat(heartbeat).await {
Ok(response) => println!("✓ Heartbeat sent: {:?}", response),
Err(e) => println!("✗ Failed to send heartbeat: {}", e),
}
// List all servers
match client.list_all_servers().await {
Ok(servers) => {
println!("✓ Found {} servers:", servers.len());
for server in servers {
println!(" - {}: {} ({})", server.name, server.http_url, server.status);
}
}
Err(e) => println!("✗ Failed to list servers: {}", e),
}
// Demonstrate workflow management
println!("\n=== Workflow Management Demo ===");
// Get all workflows
match client.get_all_workflows().await {
Ok(workflows) => println!("✓ Found {} workflows", workflows.len()),
Err(e) => println!("✗ Failed to get workflows: {}", e),
}
// Publish a sample workflow
let mut sample_workflow = HashMap::new();
sample_workflow.insert("name".to_string(), serde_json::json!("demo_workflow"));
sample_workflow.insert("version".to_string(), serde_json::json!("1.0.0"));
sample_workflow.insert(
"description".to_string(),
serde_json::json!("A demo workflow for testing"),
);
match client.publish_workflow(sample_workflow).await {
Ok(_) => println!("✓ Workflow published successfully"),
Err(e) => println!("✗ Failed to publish workflow: {}", e),
}
// Demonstrate workflow execution
println!("\n=== Workflow Execution Demo ===");
let mut execution_data = HashMap::new();
execution_data.insert("input_text".to_string(), serde_json::json!("Hello, ComfyUI!"));
execution_data.insert("steps".to_string(), serde_json::json!(20));
execution_data.insert("seed".to_string(), serde_json::json!(42));
match client
.run_workflow("demo_workflow", Some("1.0.0"), execution_data)
.await
{
Ok(response) => {
println!("✓ Workflow execution started");
// Try to extract run ID from response for status checking
if let Some(run_id) = response.get("run_id").and_then(|v| v.as_str()) {
println!(" Run ID: {}", run_id);
// Poll for status (demo - in real usage you'd want better polling logic)
for i in 1..=3 {
sleep(Duration::from_secs(2)).await;
match client.get_run_status(run_id).await {
Ok(status) => {
println!(" Status check {}: {:?}", i, status);
}
Err(e) => println!(" Status check {} failed: {}", i, e),
}
}
}
}
Err(e) => println!("✗ Failed to start workflow: {}", e),
}
// Demonstrate monitoring
println!("\n=== Monitoring Demo ===");
// System health
match client.get_system_health().await {
Ok(health) => println!("✓ System health: {:?}", health),
Err(e) => println!("✗ Failed to get system health: {}", e),
}
// System stats
match client.get_system_stats().await {
Ok(stats) => println!("✓ System stats retrieved"),
Err(e) => println!("✗ Failed to get system stats: {}", e),
}
// Recent tasks
match client.get_recent_tasks(Some(5)).await {
Ok(tasks) => println!("✓ Found {} recent tasks", tasks.len()),
Err(e) => println!("✗ Failed to get recent tasks: {}", e),
}
// Force health check
match client.force_health_check().await {
Ok(response) => println!("✓ Health check forced: {:?}", response),
Err(e) => println!("✗ Failed to force health check: {}", e),
}
// Demonstrate RunX endpoints
println!("\n=== RunX Demo ===");
// Get schema for model_with_multi_dress
match client.model_with_multi_dress_json_schema().await {
Ok(schema) => println!("✓ Got model_with_multi_dress schema"),
Err(e) => println!("✗ Failed to get schema: {}", e),
}
// Try to run model_with_multi_dress workflow
let mut dress_data = HashMap::new();
dress_data.insert("model_image".to_string(), serde_json::json!("base64_image_data"));
dress_data.insert("dress_images".to_string(), serde_json::json!(["dress1.jpg", "dress2.jpg"]));
match client
.model_with_multi_dress(None, dress_data)
.await
{
Ok(response) => println!("✓ Model with multi dress executed: {:?}", response),
Err(e) => println!("✗ Failed to execute model with multi dress: {}", e),
}
// Cleanup: Unregister the demo server
println!("\n=== Cleanup ===");
let unregister = ServerUnregisterRequest {
name: server_name.to_string(),
};
match client.unregister_server(unregister).await {
Ok(response) => println!("✓ Server unregistered: {:?}", response),
Err(e) => println!("✗ Failed to unregister server: {}", e),
}
println!("\n=== Demo Complete ===");
Ok(())
}

View File

@@ -0,0 +1,56 @@
use std::collections::HashMap;
use uni_comfyui_sdk::{UniComfyUIClient, Result, ServerRegistrationRequest};
#[tokio::main]
async fn main() -> Result<()> {
// Create a new client
let client = UniComfyUIClient::new("http://192.168.0.148:18000")?;
// Get system metrics
println!("Getting system metrics...");
let metrics = client.get_metrics().await?;
println!("Metrics: {:#?}", metrics);
// Get all workflows
println!("\nGetting all workflows...");
let workflows = client.get_all_workflows().await?;
println!("Found {} workflows", workflows.len());
// Get servers status
println!("\nGetting servers status...");
let servers = client.get_servers_status().await?;
println!("Found {} servers", servers.len());
// Health check
println!("\nPerforming health check...");
let health = client.health_check().await?;
println!("Health status: {:#?}", health);
// Example: Register a new server
println!("\nRegistering a new server...");
let registration_request = ServerRegistrationRequest {
name: "test-server".to_string(),
http_url: "http://192.168.0.148:8188".to_string(),
ws_url: "ws://192.168.0.148:8188".to_string(),
};
match client.register_server(registration_request).await {
Ok(response) => println!("Server registered: {:#?}", response),
Err(e) => println!("Failed to register server: {}", e),
}
// Example: Run a workflow
println!("\nRunning a workflow...");
let mut workflow_data = HashMap::new();
workflow_data.insert("input".to_string(), serde_json::json!("test input"));
match client
.run_workflow("test_workflow", None, workflow_data)
.await
{
Ok(response) => println!("Workflow started: {:#?}", response),
Err(e) => println!("Failed to start workflow: {}", e),
}
Ok(())
}

View File

@@ -0,0 +1,291 @@
use crate::error::{ComfyUIError, Result};
use crate::models::*;
use crate::types::*;
use reqwest::{Client, Response};
use serde_json::Value;
use url::Url;
/// UniComfyUI API client
#[derive(Debug, Clone)]
pub struct UniComfyUIClient {
client: Client,
base_url: Url,
}
impl UniComfyUIClient {
/// Create a new UniComfyUI client
pub fn new(base_url: &str) -> Result<Self> {
let base_url = Url::parse(base_url)?;
let client = Client::new();
Ok(Self { client, base_url })
}
/// Create a new UniComfyUI client with custom reqwest client
pub fn with_client(base_url: &str, client: Client) -> Result<Self> {
let base_url = Url::parse(base_url)?;
Ok(Self { client, base_url })
}
/// Build URL for an endpoint
fn build_url(&self, path: &str) -> Result<Url> {
Ok(self.base_url.join(path)?)
}
/// Handle HTTP response and convert to appropriate result
async fn handle_response<T>(&self, response: Response) -> Result<T>
where
T: serde::de::DeserializeOwned,
{
let status = response.status();
if status.is_success() {
let text = response.text().await?;
if text.is_empty() {
// Handle empty responses for some endpoints
return Ok(serde_json::from_value(Value::Null)?);
}
Ok(serde_json::from_str(&text)?)
} else {
let error_text = response.text().await.unwrap_or_default();
Err(ComfyUIError::Api {
status: status.as_u16(),
message: error_text,
})
}
}
// Status endpoints
/// Get metrics - 获取队列状态概览
pub async fn get_metrics(&self) -> Result<GenericResponse> {
let url = self.build_url("/api/service/metrics")?;
let response = self.client.get(url).send().await?;
self.handle_response(response).await
}
/// Get servers status - 获取所有已配置的ComfyUI服务器的配置信息和实时状态
pub async fn get_servers_status(&self) -> Result<Vec<ServerStatus>> {
let url = self.build_url("/api/service/servers_status")?;
let response = self.client.get(url).send().await?;
self.handle_response(response).await
}
/// List server files - 获取指定ComfyUI服务器的输入和输出文件夹中的文件列表
pub async fn list_server_files(&self, server_index: u32) -> Result<ServerFiles> {
let url = self.build_url(&format!("/api/service/servers/{}/files", server_index))?;
let response = self.client.get(url).send().await?;
self.handle_response(response).await
}
// Workflow endpoints
/// Get all workflows - 获取所有工作流
pub async fn get_all_workflows(&self) -> Result<ObjectArrayResponse> {
let url = self.build_url("/api/workflow")?;
let response = self.client.get(url).send().await?;
self.handle_response(response).await
}
/// Publish workflow - 发布工作流
pub async fn publish_workflow(&self, data: JsonObject) -> Result<GenericResponse> {
let url = self.build_url("/api/workflow")?;
let response = self.client.post(url).json(&data).send().await?;
self.handle_response(response).await
}
/// Delete workflow - 删除工作流
pub async fn delete_workflow(&self, workflow_name: &str) -> Result<GenericResponse> {
let url = self.build_url(&format!("/api/workflow/{}", workflow_name))?;
let response = self.client.delete(url).send().await?;
self.handle_response(response).await
}
/// Get one workflow - 获取工作流规范
pub async fn get_one_workflow(
&self,
base_name: &str,
version: Option<&str>,
) -> Result<GenericResponse> {
let mut url = self.build_url(&format!("/api/workflow/{}", base_name))?;
if let Some(version) = version {
url.query_pairs_mut().append_pair("version", version);
}
let response = self.client.get(url).send().await?;
self.handle_response(response).await
}
// Run endpoints
/// Run workflow - 异步执行工作流
pub async fn run_workflow(
&self,
workflow_name: &str,
workflow_version: Option<&str>,
data: JsonObject,
) -> Result<GenericResponse> {
let mut url = self.build_url("/api/run")?;
url.query_pairs_mut().append_pair("workflow_name", workflow_name);
if let Some(version) = workflow_version {
url.query_pairs_mut().append_pair("workflow_version", version);
}
let response = self.client.post(url).json(&data).send().await?;
self.handle_response(response).await
}
/// Get run status - 获取工作流执行状态
pub async fn get_run_status(&self, workflow_run_id: &str) -> Result<GenericResponse> {
let url = self.build_url(&format!("/api/run/{}", workflow_run_id))?;
let response = self.client.get(url).send().await?;
self.handle_response(response).await
}
// RunX endpoints
/// Model with multi dress - 一个模特,穿多件不同的衣服
pub async fn model_with_multi_dress(
&self,
workflow_version: Option<&str>,
data: JsonObject,
) -> Result<GenericResponse> {
let mut url = self.build_url("/api/runx/model_with_multi_dress")?;
if let Some(version) = workflow_version {
url.query_pairs_mut().append_pair("workflow_version", version);
}
let response = self.client.post(url).json(&data).send().await?;
self.handle_response(response).await
}
/// Get model with multi dress JSON schema - 获取工作流定义
pub async fn model_with_multi_dress_json_schema(&self) -> Result<GenericResponse> {
let url = self.build_url("/api/runx/model_with_multi_dress/json_schema")?;
let response = self.client.get(url).send().await?;
self.handle_response(response).await
}
// Monitor endpoints
/// Monitor dashboard - 监控仪表板页面
pub async fn monitor_dashboard(&self) -> Result<String> {
let url = self.build_url("/monitor/")?;
let response = self.client.get(url).send().await?;
let status = response.status();
if status.is_success() {
Ok(response.text().await?)
} else {
let error_text = response.text().await.unwrap_or_default();
Err(ComfyUIError::Api {
status: status.as_u16(),
message: error_text,
})
}
}
/// Get system stats - 获取系统统计信息
pub async fn get_system_stats(&self) -> Result<AdditionalPropertiesResponse> {
let url = self.build_url("/monitor/system-stats")?;
let response = self.client.get(url).send().await?;
self.handle_response(response).await
}
/// Get task stats - 获取任务统计信息
pub async fn get_task_stats(&self) -> Result<AdditionalPropertiesResponse> {
let url = self.build_url("/monitor/task-stats")?;
let response = self.client.get(url).send().await?;
self.handle_response(response).await
}
/// Get server status (monitor) - 获取服务器状态信息
pub async fn get_monitor_server_status(&self) -> Result<ObjectArrayResponse> {
let url = self.build_url("/monitor/server-status")?;
let response = self.client.get(url).send().await?;
self.handle_response(response).await
}
/// Get recent tasks - 获取最近的任务列表
pub async fn get_recent_tasks(&self, limit: Option<i32>) -> Result<ObjectArrayResponse> {
let mut url = self.build_url("/monitor/recent-tasks")?;
if let Some(limit) = limit {
url.query_pairs_mut().append_pair("limit", &limit.to_string());
}
let response = self.client.get(url).send().await?;
self.handle_response(response).await
}
/// Health check - 健康检查端点
pub async fn health_check(&self) -> Result<AdditionalPropertiesResponse> {
let url = self.build_url("/monitor/health")?;
let response = self.client.get(url).send().await?;
self.handle_response(response).await
}
// ComfyUI Server Management endpoints
/// Register server - 注册ComfyUI服务器
pub async fn register_server(
&self,
request: ServerRegistrationRequest,
) -> Result<StringDictResponse> {
let url = self.build_url("/api/comfy/register")?;
let response = self.client.post(url).json(&request).send().await?;
self.handle_response(response).await
}
/// Update heartbeat - 更新服务器心跳
pub async fn update_heartbeat(
&self,
request: ServerHeartbeatRequest,
) -> Result<StringDictResponse> {
let url = self.build_url("/api/comfy/heartbeat")?;
let response = self.client.post(url).json(&request).send().await?;
self.handle_response(response).await
}
/// Unregister server - 注销ComfyUI服务器
pub async fn unregister_server(
&self,
request: ServerUnregisterRequest,
) -> Result<StringDictResponse> {
let url = self.build_url("/api/comfy/unregister")?;
let response = self.client.post(url).json(&request).send().await?;
self.handle_response(response).await
}
/// Get server status - 获取指定服务器状态
pub async fn get_server_status(&self, server_name: &str) -> Result<ServerStatusResponse> {
let url = self.build_url(&format!("/api/comfy/status/{}", server_name))?;
let response = self.client.get(url).send().await?;
self.handle_response(response).await
}
/// List all servers - 获取所有服务器列表
pub async fn list_all_servers(&self) -> Result<Vec<ServerStatusResponse>> {
let url = self.build_url("/api/comfy/list")?;
let response = self.client.get(url).send().await?;
self.handle_response(response).await
}
/// Get system health - 获取系统整体健康状态
pub async fn get_system_health(&self) -> Result<AdditionalPropertiesResponse> {
let url = self.build_url("/api/comfy/health")?;
let response = self.client.get(url).send().await?;
self.handle_response(response).await
}
/// Force health check - 强制执行健康检查
pub async fn force_health_check(&self) -> Result<StringDictResponse> {
let url = self.build_url("/api/comfy/force_health_check")?;
let response = self.client.post(url).send().await?;
self.handle_response(response).await
}
}

View File

@@ -0,0 +1,40 @@
use thiserror::Error;
/// Result type alias for ComfyUI SDK operations
pub type Result<T> = std::result::Result<T, ComfyUIError>;
/// Error types for ComfyUI SDK operations
#[derive(Error, Debug)]
pub enum ComfyUIError {
/// HTTP request error
#[error("HTTP request failed: {0}")]
Http(#[from] reqwest::Error),
/// JSON serialization/deserialization error
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
/// URL parsing error
#[error("URL parsing error: {0}")]
Url(#[from] url::ParseError),
/// API error response
#[error("API error: {status} - {message}")]
Api { status: u16, message: String },
/// Validation error
#[error("Validation error: {0}")]
Validation(String),
/// Server not found error
#[error("Server not found: {0}")]
ServerNotFound(String),
/// Workflow not found error
#[error("Workflow not found: {0}")]
WorkflowNotFound(String),
/// Generic error
#[error("Error: {0}")]
Generic(String),
}

View File

@@ -0,0 +1,58 @@
//! # uni-comfyui-sdk
//!
//! Rust SDK for ComfyUI Workflow Service & Management API
//!
//! This SDK provides a type-safe interface to interact with the ComfyUI API,
//! including workflow management, execution, monitoring, and server management.
//!
//! ## Features
//!
//! - **Status Management**: Get metrics, server status, and file listings
//! - **Workflow Management**: Create, retrieve, and delete workflows
//! - **Workflow Execution**: Run workflows and monitor their status
//! - **Server Management**: Register, unregister, and manage ComfyUI servers
//! - **Monitoring**: Health checks, system stats, and task monitoring
//! - **Type Safety**: Strongly typed API with comprehensive error handling
//!
//! ## Quick Start
//!
//! ```rust,no_run
//! use uni_comfyui_sdk::{UniComfyUIClient, Result};
//! use std::collections::HashMap;
//!
//! #[tokio::main]
//! async fn main() -> Result<()> {
//! // Create a client
//! let client = UniComfyUIClient::new("http://localhost:8000")?;
//!
//! // Get system metrics
//! let metrics = client.get_metrics().await?;
//! println!("Metrics: {:#?}", metrics);
//!
//! // Run a workflow
//! let mut data = HashMap::new();
//! data.insert("input".to_string(), serde_json::json!("test"));
//! let result = client.run_workflow("my_workflow", None, data).await?;
//! println!("Workflow result: {:#?}", result);
//!
//! Ok(())
//! }
//! ```
pub mod client;
pub mod error;
pub mod models;
pub mod types;
pub use client::UniComfyUIClient;
pub use error::{ComfyUIError, Result};
pub use models::*;
pub use types::*;
/// Re-export commonly used types
pub mod prelude {
pub use crate::client::UniComfyUIClient;
pub use crate::error::{ComfyUIError, Result};
pub use crate::models::*;
pub use crate::types::*;
}

View File

@@ -0,0 +1,130 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// File details information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileDetails {
/// File name
pub name: String,
/// File size in KB
pub size_kb: f64,
/// Last modified timestamp
pub modified_at: DateTime<Utc>,
}
/// HTTP validation error details
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HTTPValidationError {
/// List of validation errors
pub detail: Vec<ValidationError>,
}
/// Individual validation error
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValidationError {
/// Error location path
pub loc: Vec<ValidationErrorLocation>,
/// Error message
pub msg: String,
/// Error type
#[serde(rename = "type")]
pub error_type: String,
}
/// Validation error location (can be string or integer)
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ValidationErrorLocation {
String(String),
Integer(i32),
}
/// Server files information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerFiles {
/// Server index in configuration list
pub server_index: i32,
/// HTTP URL of the server
pub http_url: String,
/// List of input files
pub input_files: Vec<FileDetails>,
/// List of output files
pub output_files: Vec<FileDetails>,
}
/// Server heartbeat request
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerHeartbeatRequest {
/// Server name
pub name: String,
}
/// Server queue details
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerQueueDetails {
/// Number of running tasks
pub running_count: i32,
/// Number of pending tasks
pub pending_count: i32,
}
/// Server registration request
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerRegistrationRequest {
/// Server name
pub name: String,
/// HTTP URL of the server
pub http_url: String,
/// WebSocket URL of the server
pub ws_url: String,
}
/// Server status information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerStatus {
/// Server index in configuration list
pub server_index: i32,
/// HTTP URL of the server
pub http_url: String,
/// WebSocket URL of the server
pub ws_url: String,
/// Whether the server is reachable
pub is_reachable: bool,
/// Whether the server is free
pub is_free: bool,
/// Queue details
pub queue_details: ServerQueueDetails,
}
/// Server status response
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerStatusResponse {
/// Server name
pub name: String,
/// HTTP URL of the server
pub http_url: String,
/// WebSocket URL of the server
pub ws_url: String,
/// Server status
pub status: String,
/// Last heartbeat timestamp
pub last_heartbeat: Option<String>,
/// Last health check timestamp
pub last_health_check: Option<String>,
/// Current number of tasks
pub current_tasks: i32,
/// Maximum concurrent tasks
pub max_concurrent_tasks: i32,
/// Server capabilities
pub capabilities: HashMap<String, serde_json::Value>,
/// Server metadata
pub metadata: HashMap<String, serde_json::Value>,
}
/// Server unregister request
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerUnregisterRequest {
/// Server name
pub name: String,
}

View File

@@ -0,0 +1,16 @@
use std::collections::HashMap;
/// Generic JSON object type
pub type JsonObject = HashMap<String, serde_json::Value>;
/// Generic response type for endpoints that return arbitrary JSON
pub type GenericResponse = serde_json::Value;
/// Response type for endpoints that return a dictionary of strings
pub type StringDictResponse = HashMap<String, String>;
/// Response type for endpoints that return an array of objects
pub type ObjectArrayResponse = Vec<JsonObject>;
/// Response type for endpoints that return an object with additional properties
pub type AdditionalPropertiesResponse = JsonObject;

View File

@@ -0,0 +1,90 @@
use std::collections::HashMap;
use uni_comfyui_sdk::{UniComfyUIClient, Result, ServerRegistrationRequest};
// Note: These tests require a running ComfyUI service
// They are disabled by default and should be run manually when needed
#[tokio::test]
#[ignore]
async fn test_client_creation() -> Result<()> {
let _client = UniComfyUIClient::new("http://192.168.0.148:18000")?;
assert!(true); // Just test that client creation doesn't panic
Ok(())
}
#[tokio::test]
#[ignore]
async fn test_get_metrics() -> Result<()> {
let client = UniComfyUIClient::new("http://192.168.0.148:18000")?;
let _metrics = client.get_metrics().await?;
// Just test that the call doesn't fail
Ok(())
}
#[tokio::test]
#[ignore]
async fn test_get_all_workflows() -> Result<()> {
let client = UniComfyUIClient::new("http://192.168.0.148:18000")?;
let workflows = client.get_all_workflows().await?;
assert!(workflows.is_empty() || !workflows.is_empty()); // Just test the call
Ok(())
}
#[tokio::test]
#[ignore]
async fn test_health_check() -> Result<()> {
let client = UniComfyUIClient::new("http://192.168.0.148:18000")?;
let _health = client.health_check().await?;
Ok(())
}
#[tokio::test]
#[ignore]
async fn test_server_registration() -> Result<()> {
let client = UniComfyUIClient::new("http://192.168.0.148:18000")?;
let registration_request = ServerRegistrationRequest {
name: "test-server".to_string(),
http_url: "http://192.168.0.148:8188".to_string(),
ws_url: "ws://192.168.0.148:8188".to_string(),
};
// This might fail if server already exists, which is fine for testing
let _result = client.register_server(registration_request).await;
Ok(())
}
#[tokio::test]
#[ignore]
async fn test_workflow_execution() -> Result<()> {
let client = UniComfyUIClient::new("http://192.168.0.148:18000")?;
let mut workflow_data = HashMap::new();
workflow_data.insert("test_input".to_string(), serde_json::json!("test_value"));
// This will likely fail without a real workflow, but tests the API call
let _result = client
.run_workflow("test_workflow", None, workflow_data)
.await;
Ok(())
}
#[test]
fn test_url_building() {
let _client = UniComfyUIClient::new("http://192.168.0.148:18000").unwrap();
// Test that URL building works correctly
assert!(true); // Basic test that client creation works
}
#[test]
fn test_error_types() {
use uni_comfyui_sdk::ComfyUIError;
// Test that error types can be created
let _error = ComfyUIError::Validation("test error".to_string());
let _error = ComfyUIError::ServerNotFound("test server".to_string());
let _error = ComfyUIError::WorkflowNotFound("test workflow".to_string());
assert!(true);
}

File diff suppressed because it is too large Load Diff