use anyhow::{bail, Context, Result};
use std::path::PathBuf;
use super::manifest_schema::Manifest;
pub fn extract_manifest_from_package(package_path: &PathBuf) -> Result<Manifest> {
let tmp = tempfile::TempDir::new().context("Failed to create temporary directory")?;
let extract_dir = tmp.path().join("extract");
std::fs::create_dir_all(&extract_dir).context("Failed to create extract directory")?;
let source_dir = fidius_core::package::unpack_package(package_path, &extract_dir)
.with_context(|| format!("Failed to unpack source archive: {:?}", package_path))?;
let fidius_manifest = fidius_core::package::load_manifest::<
cloacina_workflow_plugin::CloacinaMetadata,
>(&source_dir)
.with_context(|| format!("Failed to parse package.toml in: {:?}", source_dir))?;
let pkg = &fidius_manifest.package;
let meta = &fidius_manifest.metadata;
let language = match meta.language.as_str() {
"python" => super::manifest_schema::PackageLanguage::Python,
_ => super::manifest_schema::PackageLanguage::Rust,
};
let python_runtime = if meta.language == "python" {
Some(super::manifest_schema::PythonRuntime {
requires_python: meta.requires_python.clone().unwrap_or_default(),
entry_module: meta.entry_module.clone().unwrap_or_default(),
})
} else {
None
};
let manifest = Manifest {
format_version: "2".to_string(),
package: super::manifest_schema::PackageInfo {
name: pkg.name.clone(),
version: pkg.version.clone(),
description: meta.description.clone(),
fingerprint: format!("sha256:{}:{}", pkg.name, pkg.version),
targets: vec![super::manifest::get_current_platform()],
},
language,
python: python_runtime,
rust: None,
tasks: vec![],
triggers: vec![],
created_at: chrono::Utc::now(),
signature: None,
};
Ok(manifest)
}
pub fn execute_task_from_library(
library_path: &PathBuf,
task_name: &str,
context_json: &str,
) -> Result<String> {
let loaded = fidius_host::loader::load_library(library_path)
.with_context(|| format!("Failed to load library: {:?}", library_path))?;
let plugin = loaded
.plugins
.into_iter()
.next()
.ok_or_else(|| anyhow::anyhow!("Plugin library contains no plugins"))?;
let handle = fidius_host::PluginHandle::from_loaded(plugin);
let request = cloacina_workflow_plugin::TaskExecutionRequest {
task_name: task_name.to_string(),
context_json: context_json.to_string(),
};
let result: cloacina_workflow_plugin::TaskExecutionResult = handle
.call_method(1, &(request,))
.with_context(|| format!("Failed to execute task '{}' via plugin API", task_name))?;
Ok(result.context_json.unwrap_or_default())
}
pub fn resolve_task_name(manifest: &Manifest, task_identifier: &str) -> Result<String> {
if let Ok(index) = task_identifier.parse::<u32>() {
let index = index as usize;
if index < manifest.tasks.len() {
return Ok(manifest.tasks[index].id.clone());
} else {
bail!(
"Task index {} is out of range. Available tasks: 0-{}",
index,
manifest.tasks.len().saturating_sub(1)
);
}
}
for task in &manifest.tasks {
if task.id == task_identifier {
return Ok(task.id.clone());
}
}
bail!(
"Task '{}' not found. Available tasks: {:?}",
task_identifier,
manifest.tasks.iter().map(|t| &t.id).collect::<Vec<_>>()
);
}
pub fn debug_package(
package_path: &PathBuf,
task_identifier: Option<&str>,
context_json: Option<&str>,
) -> Result<DebugResult> {
if !package_path.exists() {
bail!("Package file does not exist: {:?}", package_path);
}
if !package_path.is_file() {
bail!("Package path is not a file: {:?}", package_path);
}
let manifest = extract_manifest_from_package(package_path)?;
match task_identifier {
None => {
let tasks: Vec<TaskDebugInfo> = manifest
.tasks
.iter()
.enumerate()
.map(|(index, task)| TaskDebugInfo {
index,
id: task.id.clone(),
description: task.description.clone().unwrap_or_default(),
dependencies: task.dependencies.clone(),
})
.collect();
Ok(DebugResult::TaskList { tasks })
}
Some(task_id) => {
let task_name = resolve_task_name(&manifest, task_id)?;
let _context = context_json.unwrap_or("{}");
bail!(
"Cannot directly execute task '{}' from source package {:?}. \
The package must be registered with the workflow registry and compiled \
by the reconciler before tasks can be executed.",
task_name,
package_path
);
}
}
}
#[derive(Debug)]
pub enum DebugResult {
TaskList { tasks: Vec<TaskDebugInfo> },
TaskExecution { output: String },
}
#[derive(Debug, Clone)]
pub struct TaskDebugInfo {
pub index: usize,
pub id: String,
pub description: String,
pub dependencies: Vec<String>,
}