Updates
This commit is contained in:
@@ -38,12 +38,6 @@ enum Commands {
|
||||
#[arg(long, default_value = "ws://127.0.0.1:9944")]
|
||||
url: String,
|
||||
},
|
||||
/// Connect to Unix socket server
|
||||
Unix {
|
||||
/// Unix socket path
|
||||
#[arg(long, default_value = "/tmp/hero-openrpc.sock")]
|
||||
socket_path: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
/// Available RPC methods with descriptions
|
||||
@@ -161,10 +155,6 @@ async fn main() -> Result<()> {
|
||||
println!("{} {}", "Connecting to WebSocket server:".green(), url.cyan());
|
||||
ClientTransport::WebSocket(url)
|
||||
}
|
||||
Commands::Unix { socket_path } => {
|
||||
println!("{} {:?}", "Connecting to Unix socket server:".green(), socket_path);
|
||||
ClientTransport::Unix(socket_path)
|
||||
}
|
||||
};
|
||||
|
||||
// Connect to the server
|
||||
@@ -282,15 +272,18 @@ async fn execute_method(client: &HeroOpenRpcClient, method_name: &str) -> Result
|
||||
.with_prompt("Signature (hex)")
|
||||
.interact_text()?;
|
||||
|
||||
let nonce: String = Input::new()
|
||||
.with_prompt("Nonce (hex) - fetch via fetch_nonce first")
|
||||
.interact_text()?;
|
||||
|
||||
let result = client.authenticate(pubkey, signature, nonce).await?;
|
||||
println!("{} {}", "Authentication result:".green().bold(),
|
||||
println!("{} {}", "Authentication result:".green().bold(),
|
||||
if result { "Success".green() } else { "Failed".red() });
|
||||
}
|
||||
|
||||
"whoami" => {
|
||||
let result = client.whoami().await?;
|
||||
println!("{} {}", "User info:".green().bold(),
|
||||
serde_json::to_string_pretty(&result)?.cyan());
|
||||
println!("{} {}", "User info:".green().bold(), result.cyan());
|
||||
}
|
||||
|
||||
"play" => {
|
||||
@@ -307,7 +300,7 @@ async fn execute_method(client: &HeroOpenRpcClient, method_name: &str) -> Result
|
||||
.with_prompt("Script content")
|
||||
.interact_text()?;
|
||||
|
||||
let script_types = ["HeroScript", "RhaiSAL", "RhaiDSL"];
|
||||
let script_types = ["OSIS", "SAL", "V", "Python"];
|
||||
let script_type_selection = Select::new()
|
||||
.with_prompt("Script type")
|
||||
.items(&script_types)
|
||||
@@ -315,10 +308,10 @@ async fn execute_method(client: &HeroOpenRpcClient, method_name: &str) -> Result
|
||||
.interact()?;
|
||||
|
||||
let script_type = match script_type_selection {
|
||||
0 => ScriptType::HeroScript,
|
||||
1 => ScriptType::RhaiSAL,
|
||||
2 => ScriptType::RhaiDSL,
|
||||
_ => ScriptType::HeroScript,
|
||||
0 => ScriptType::OSIS,
|
||||
1 => ScriptType::SAL,
|
||||
2 => ScriptType::V,
|
||||
_ => ScriptType::Python,
|
||||
};
|
||||
|
||||
let add_prerequisites = Confirm::new()
|
||||
@@ -335,9 +328,34 @@ async fn execute_method(client: &HeroOpenRpcClient, method_name: &str) -> Result
|
||||
None
|
||||
};
|
||||
|
||||
let caller_id: String = Input::new()
|
||||
.with_prompt("Caller ID")
|
||||
.interact_text()?;
|
||||
|
||||
let context_id: String = Input::new()
|
||||
.with_prompt("Context ID")
|
||||
.interact_text()?;
|
||||
|
||||
let specify_timeout = Confirm::new()
|
||||
.with_prompt("Specify timeout (seconds)?")
|
||||
.default(false)
|
||||
.interact()?;
|
||||
|
||||
let timeout = if specify_timeout {
|
||||
let t: u64 = Input::new()
|
||||
.with_prompt("Timeout (seconds)")
|
||||
.interact_text()?;
|
||||
Some(t)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let job_params = JobParams {
|
||||
script,
|
||||
script_type,
|
||||
caller_id,
|
||||
context_id,
|
||||
timeout,
|
||||
prerequisites,
|
||||
};
|
||||
|
||||
@@ -360,7 +378,7 @@ async fn execute_method(client: &HeroOpenRpcClient, method_name: &str) -> Result
|
||||
.with_prompt("Script content")
|
||||
.interact_text()?;
|
||||
|
||||
let script_types = ["HeroScript", "RhaiSAL", "RhaiDSL"];
|
||||
let script_types = ["OSIS", "SAL", "V", "Python"];
|
||||
let script_type_selection = Select::new()
|
||||
.with_prompt("Script type")
|
||||
.items(&script_types)
|
||||
@@ -368,10 +386,10 @@ async fn execute_method(client: &HeroOpenRpcClient, method_name: &str) -> Result
|
||||
.interact()?;
|
||||
|
||||
let script_type = match script_type_selection {
|
||||
0 => ScriptType::HeroScript,
|
||||
1 => ScriptType::RhaiSAL,
|
||||
2 => ScriptType::RhaiDSL,
|
||||
_ => ScriptType::HeroScript,
|
||||
0 => ScriptType::OSIS,
|
||||
1 => ScriptType::SAL,
|
||||
2 => ScriptType::V,
|
||||
_ => ScriptType::Python,
|
||||
};
|
||||
|
||||
let add_prerequisites = Confirm::new()
|
||||
@@ -416,18 +434,17 @@ async fn execute_method(client: &HeroOpenRpcClient, method_name: &str) -> Result
|
||||
.interact_text()?;
|
||||
|
||||
let result = client.get_job_logs(job_id).await?;
|
||||
println!("{} {}", "Job logs:".green().bold(), result.logs.cyan());
|
||||
match result.logs {
|
||||
Some(logs) => println!("{} {}", "Job logs:".green().bold(), logs.cyan()),
|
||||
None => println!("{} {}", "Job logs:".green().bold(), "(no logs)".yellow()),
|
||||
}
|
||||
}
|
||||
|
||||
"list_jobs" => {
|
||||
let result = client.list_jobs().await?;
|
||||
println!("{}", "Jobs:".green().bold());
|
||||
for job in result {
|
||||
println!(" {} - {} ({:?})",
|
||||
job.id().yellow(),
|
||||
job.script_type(),
|
||||
job.status()
|
||||
);
|
||||
println!("{}", "Job IDs:".green().bold());
|
||||
for id in result {
|
||||
println!(" {}", id.yellow());
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use hero_job::{Job, JobStatus, ScriptType};
|
||||
use hero_job::{JobStatus, ScriptType};
|
||||
use jsonrpsee::core::client::ClientT;
|
||||
use jsonrpsee::core::ClientError;
|
||||
use jsonrpsee::proc_macros::rpc;
|
||||
@@ -37,7 +37,7 @@ pub trait OpenRpcClient {
|
||||
) -> Result<bool, ClientError>;
|
||||
|
||||
#[method(name = "whoami")]
|
||||
async fn whoami(&self) -> Result<serde_json::Value, ClientError>;
|
||||
async fn whoami(&self) -> Result<String, ClientError>;
|
||||
|
||||
// Script execution
|
||||
#[method(name = "play")]
|
||||
@@ -68,7 +68,7 @@ pub trait OpenRpcClient {
|
||||
async fn get_job_logs(&self, job_id: String) -> Result<JobLogsResult, ClientError>;
|
||||
|
||||
#[method(name = "list_jobs")]
|
||||
async fn list_jobs(&self) -> Result<Vec<Job>, ClientError>;
|
||||
async fn list_jobs(&self) -> Result<Vec<String>, ClientError>;
|
||||
|
||||
#[method(name = "stop_job")]
|
||||
async fn stop_job(&self, job_id: String) -> Result<(), ClientError>;
|
||||
@@ -146,7 +146,7 @@ impl HeroOpenRpcClient {
|
||||
}
|
||||
|
||||
/// Delegate to whoami on the underlying client
|
||||
pub async fn whoami(&self) -> Result<serde_json::Value, ClientError> {
|
||||
pub async fn whoami(&self) -> Result<String, ClientError> {
|
||||
self.client.whoami().await
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ impl HeroOpenRpcClient {
|
||||
}
|
||||
|
||||
/// Delegate to list_jobs on the underlying client
|
||||
pub async fn list_jobs(&self) -> Result<Vec<Job>, ClientError> {
|
||||
pub async fn list_jobs(&self) -> Result<Vec<String>, ClientError> {
|
||||
self.client.list_jobs().await
|
||||
}
|
||||
|
||||
|
@@ -1,11 +1,14 @@
|
||||
use hero_job::ScriptType;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Parameters for creating a job
|
||||
/** Parameters for creating a job (must mirror server DTO) */
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct JobParams {
|
||||
pub script: String,
|
||||
pub script_type: ScriptType,
|
||||
pub caller_id: String,
|
||||
pub context_id: String,
|
||||
pub timeout: Option<u64>, // seconds
|
||||
pub prerequisites: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
@@ -21,8 +24,8 @@ pub struct StartJobResult {
|
||||
pub success: bool,
|
||||
}
|
||||
|
||||
/// Result of getting job logs
|
||||
/** Result of getting job logs */
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct JobLogsResult {
|
||||
pub logs: String,
|
||||
pub logs: Option<String>,
|
||||
}
|
||||
|
@@ -19,10 +19,7 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
clap = { version = "4.0", features = ["derive"] }
|
||||
|
||||
# JSON-RPC dependencies
|
||||
jsonrpsee = { version = "0.21", features = [
|
||||
"server",
|
||||
"macros"
|
||||
] }
|
||||
jsonrpsee = { version = "0.21", features = ["server", "macros"] }
|
||||
jsonrpsee-types = "0.21"
|
||||
uuid = { version = "1.6", features = ["v4", "serde"] }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
|
@@ -8,7 +8,7 @@ use tracing_subscriber;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "hero-openrpc-server")]
|
||||
#[command(about = "Hero OpenRPC Server - WebSocket and Unix socket JSON-RPC server")]
|
||||
#[command(about = "Hero OpenRPC Server - JSON-RPC over HTTP/WS")]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
@@ -34,12 +34,6 @@ enum Commands {
|
||||
#[arg(long, default_value = "127.0.0.1:9944")]
|
||||
addr: SocketAddr,
|
||||
},
|
||||
/// Start Unix socket server
|
||||
Unix {
|
||||
/// Unix socket path
|
||||
#[arg(long, default_value = "/tmp/hero-openrpc.sock")]
|
||||
socket_path: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
@@ -65,14 +59,6 @@ async fn main() -> Result<()> {
|
||||
info!("Starting WebSocket server on {}", addr);
|
||||
Transport::WebSocket(addr)
|
||||
}
|
||||
Commands::Unix { socket_path } => {
|
||||
info!("Starting Unix socket server on {:?}", socket_path);
|
||||
// Remove existing socket file if it exists
|
||||
if socket_path.exists() {
|
||||
std::fs::remove_file(&socket_path)?;
|
||||
}
|
||||
Transport::Unix(socket_path)
|
||||
}
|
||||
};
|
||||
|
||||
let config = OpenRpcServerConfig {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
use anyhow::Result;
|
||||
use hero_job::{Job, JobBuilder, JobStatus, ScriptType};
|
||||
use hero_supervisor::{Supervisor, SupervisorBuilder};
|
||||
use hero_supervisor::{Supervisor, SupervisorBuilder, SupervisorError};
|
||||
use jsonrpsee::core::async_trait;
|
||||
use jsonrpsee::proc_macros::rpc;
|
||||
use jsonrpsee::server::{ServerBuilder, ServerHandle};
|
||||
@@ -12,17 +12,24 @@ use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::error;
|
||||
|
||||
fn map_sup_error_to_rpc(e: &SupervisorError) -> ErrorCode {
|
||||
match e {
|
||||
SupervisorError::InvalidInput(_) | SupervisorError::JobError(_) => ErrorCode::InvalidParams,
|
||||
SupervisorError::Timeout(_) => ErrorCode::ServerError(-32002),
|
||||
_ => ErrorCode::InternalError,
|
||||
}
|
||||
}
|
||||
|
||||
mod auth;
|
||||
pub mod types;
|
||||
|
||||
pub use auth::*;
|
||||
pub use types::*;
|
||||
|
||||
/// Transport type for the OpenRPC server
|
||||
/** Transport type for the OpenRPC server */
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Transport {
|
||||
WebSocket(SocketAddr),
|
||||
Unix(PathBuf),
|
||||
}
|
||||
|
||||
/// OpenRPC server configuration
|
||||
@@ -82,7 +89,7 @@ pub trait OpenRpcApi {
|
||||
async fn get_job_logs(&self, job_id: String) -> Result<JobLogsResult, ErrorCode>;
|
||||
|
||||
#[method(name = "list_jobs")]
|
||||
async fn list_jobs(&self) -> Result<Vec<Job>, ErrorCode>;
|
||||
async fn list_jobs(&self) -> Result<Vec<String>, ErrorCode>;
|
||||
|
||||
#[method(name = "stop_job")]
|
||||
async fn stop_job(&self, job_id: String) -> Result<(), ErrorCode>;
|
||||
@@ -114,8 +121,8 @@ impl OpenRpcServer {
|
||||
})
|
||||
}
|
||||
|
||||
/// Start the OpenRPC server
|
||||
pub async fn start(self, config: OpenRpcServerConfig) -> Result<ServerHandle> {
|
||||
/// Start the OpenRPC server on the given SocketAddr (HTTP/WS only)
|
||||
pub async fn start_on(self, addr: SocketAddr) -> Result<ServerHandle> {
|
||||
let mut module = RpcModule::new(());
|
||||
|
||||
// Register all the RPC methods
|
||||
@@ -244,18 +251,17 @@ impl OpenRpcServer {
|
||||
}
|
||||
})?;
|
||||
|
||||
let server = ServerBuilder::default()
|
||||
.build(addr)
|
||||
.await?;
|
||||
let handle = server.start(module);
|
||||
Ok(handle)
|
||||
}
|
||||
|
||||
/// Start the OpenRPC server (config wrapper)
|
||||
pub async fn start(self, config: OpenRpcServerConfig) -> Result<ServerHandle> {
|
||||
match config.transport {
|
||||
Transport::WebSocket(addr) => {
|
||||
let server = ServerBuilder::default()
|
||||
.build(addr)
|
||||
.await?;
|
||||
let handle = server.start(module);
|
||||
Ok(handle)
|
||||
}
|
||||
Transport::Unix(_path) => {
|
||||
// Unix socket transport not yet implemented in jsonrpsee 0.21
|
||||
return Err(anyhow::anyhow!("Unix socket transport not yet supported").into());
|
||||
}
|
||||
Transport::WebSocket(addr) => self.start_on(addr).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -295,12 +301,8 @@ impl OpenRpcApiServer for OpenRpcServer {
|
||||
}
|
||||
|
||||
async fn play(&self, script: String) -> Result<PlayResult, ErrorCode> {
|
||||
let _supervisor = self.supervisor.read().await;
|
||||
|
||||
// For now, return a simple result since we need to implement execute_script method
|
||||
Ok(PlayResult {
|
||||
output: format!("Script executed: {}", script)
|
||||
})
|
||||
let output = self.run_job(script, ScriptType::SAL, None).await?;
|
||||
Ok(PlayResult { output })
|
||||
}
|
||||
|
||||
async fn create_job(&self, job_params: JobParams) -> Result<String, ErrorCode> {
|
||||
@@ -360,10 +362,37 @@ impl OpenRpcApiServer for OpenRpcServer {
|
||||
&self,
|
||||
script: String,
|
||||
script_type: ScriptType,
|
||||
_prerequisites: Option<Vec<String>>,
|
||||
prerequisites: Option<Vec<String>>,
|
||||
) -> Result<String, ErrorCode> {
|
||||
// For now, return a simple result
|
||||
Ok(format!("Job executed with script: {} (type: {:?})", script, script_type))
|
||||
let supervisor = self.supervisor.read().await;
|
||||
|
||||
// Build job with defaults and optional prerequisites
|
||||
let mut builder = JobBuilder::new()
|
||||
.caller_id("rpc-caller")
|
||||
.context_id("rpc-context")
|
||||
.script(&script)
|
||||
.script_type(script_type)
|
||||
.timeout(std::time::Duration::from_secs(30));
|
||||
|
||||
if let Some(prs) = prerequisites {
|
||||
builder = builder.prerequisites(prs);
|
||||
}
|
||||
|
||||
let job = match builder.build() {
|
||||
Ok(j) => j,
|
||||
Err(e) => {
|
||||
error!("Failed to build job in run_job: {}", e);
|
||||
return Err(ErrorCode::InvalidParams);
|
||||
}
|
||||
};
|
||||
|
||||
match supervisor.run_job_and_await_result(&job).await {
|
||||
Ok(output) => Ok(output),
|
||||
Err(e) => {
|
||||
error!("run_job failed: {}", e);
|
||||
Err(map_sup_error_to_rpc(&e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_job_status(&self, job_id: String) -> Result<JobStatus, ErrorCode> {
|
||||
@@ -373,7 +402,7 @@ impl OpenRpcApiServer for OpenRpcServer {
|
||||
Ok(status) => Ok(status),
|
||||
Err(e) => {
|
||||
error!("Failed to get job status for {}: {}", job_id, e);
|
||||
Err(ErrorCode::InvalidParams)
|
||||
Err(map_sup_error_to_rpc(&e))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -385,50 +414,29 @@ impl OpenRpcApiServer for OpenRpcServer {
|
||||
Ok(output) => Ok(output.unwrap_or_else(|| "No output available".to_string())),
|
||||
Err(e) => {
|
||||
error!("Failed to get job output for {}: {}", job_id, e);
|
||||
Err(ErrorCode::InvalidParams)
|
||||
Err(map_sup_error_to_rpc(&e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_job_logs(&self, job_id: String) -> Result<JobLogsResult, ErrorCode> {
|
||||
// For now, return mock logs
|
||||
Ok(JobLogsResult {
|
||||
logs: format!("Logs for job {}", job_id),
|
||||
})
|
||||
let supervisor = self.supervisor.read().await;
|
||||
match supervisor.get_job_logs(&job_id).await {
|
||||
Ok(logs_opt) => Ok(JobLogsResult { logs: logs_opt }),
|
||||
Err(e) => {
|
||||
error!("Failed to get job logs for {}: {}", job_id, e);
|
||||
Err(map_sup_error_to_rpc(&e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_jobs(&self) -> Result<Vec<Job>, ErrorCode> {
|
||||
async fn list_jobs(&self) -> Result<Vec<String>, ErrorCode> {
|
||||
let supervisor = self.supervisor.read().await;
|
||||
|
||||
match supervisor.list_jobs().await {
|
||||
Ok(job_ids) => {
|
||||
// For now, create minimal Job objects with just the IDs
|
||||
// In a real implementation, we'd need a supervisor.get_job() method
|
||||
let jobs: Vec<Job> = job_ids.into_iter().map(|job_id| {
|
||||
// Create a minimal job object - this is a temporary solution
|
||||
// until supervisor.get_job() is implemented
|
||||
Job {
|
||||
id: job_id,
|
||||
caller_id: "unknown".to_string(),
|
||||
context_id: "unknown".to_string(),
|
||||
script: "unknown".to_string(),
|
||||
script_type: ScriptType::OSIS,
|
||||
timeout: std::time::Duration::from_secs(30),
|
||||
retries: 0,
|
||||
concurrent: false,
|
||||
log_path: None,
|
||||
env_vars: std::collections::HashMap::new(),
|
||||
prerequisites: Vec::new(),
|
||||
dependents: Vec::new(),
|
||||
created_at: chrono::Utc::now(),
|
||||
updated_at: chrono::Utc::now(),
|
||||
}
|
||||
}).collect();
|
||||
Ok(jobs)
|
||||
},
|
||||
Ok(job_ids) => Ok(job_ids),
|
||||
Err(e) => {
|
||||
error!("Failed to list jobs: {}", e);
|
||||
Err(ErrorCode::InternalError)
|
||||
Err(map_sup_error_to_rpc(&e))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -440,7 +448,7 @@ impl OpenRpcApiServer for OpenRpcServer {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => {
|
||||
error!("Failed to stop job {}: {}", job_id, e);
|
||||
Err(ErrorCode::InvalidParams)
|
||||
Err(map_sup_error_to_rpc(&e))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -452,7 +460,7 @@ impl OpenRpcApiServer for OpenRpcServer {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => {
|
||||
error!("Failed to delete job {}: {}", job_id, e);
|
||||
Err(ErrorCode::InvalidParams)
|
||||
Err(map_sup_error_to_rpc(&e))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -464,7 +472,7 @@ impl OpenRpcApiServer for OpenRpcServer {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => {
|
||||
error!("Failed to clear all jobs: {}", e);
|
||||
Err(ErrorCode::InternalError)
|
||||
Err(map_sup_error_to_rpc(&e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -24,8 +24,8 @@ pub struct StartJobResult {
|
||||
pub success: bool,
|
||||
}
|
||||
|
||||
/// Result of getting job logs
|
||||
/** Result of getting job logs */
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct JobLogsResult {
|
||||
pub logs: String,
|
||||
pub logs: Option<String>,
|
||||
}
|
||||
|
@@ -204,13 +204,13 @@ async fn test_list_jobs() {
|
||||
let result = server.list_jobs().await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
let jobs = result.unwrap();
|
||||
assert!(jobs.len() >= 3); // Should have at least the 3 jobs we created
|
||||
let job_ids = result.unwrap();
|
||||
assert!(job_ids.len() >= 3); // Should have at least the 3 jobs we created
|
||||
|
||||
// Verify job structure
|
||||
for job in jobs {
|
||||
assert!(!job.id.is_empty());
|
||||
assert!(uuid::Uuid::parse_str(&job.id).is_ok());
|
||||
// Verify job IDs are valid UUIDs
|
||||
for id in job_ids {
|
||||
assert!(!id.is_empty());
|
||||
assert!(uuid::Uuid::parse_str(&id).is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,7 +337,10 @@ async fn test_get_job_logs() {
|
||||
assert!(result.is_ok());
|
||||
|
||||
let logs_result = result.unwrap();
|
||||
assert!(!logs_result.logs.is_empty());
|
||||
match logs_result.logs {
|
||||
Some(ref logs) => assert!(!logs.is_empty()),
|
||||
None => {} // acceptable when no logs are available
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
Reference in New Issue
Block a user