use std::collections::HashMap;
use algocline_core::{EngineApi, QueryResponse};
use async_trait::async_trait;
use super::list_opts::ListOpts;
use super::AppService;
#[async_trait]
impl EngineApi for AppService {
async fn run(
&self,
code: Option<String>,
code_file: Option<String>,
ctx: Option<serde_json::Value>,
project_root: Option<String>,
) -> Result<String, String> {
AppService::run(self, code, code_file, ctx, project_root).await
}
async fn advice(
&self,
strategy: &str,
task: Option<String>,
opts: Option<serde_json::Value>,
project_root: Option<String>,
) -> Result<String, String> {
AppService::advice(self, strategy, task, opts, project_root).await
}
async fn continue_single(
&self,
session_id: &str,
response: String,
query_id: Option<&str>,
usage: Option<algocline_core::TokenUsage>,
) -> Result<String, String> {
AppService::continue_single(self, session_id, response, query_id, usage).await
}
async fn continue_batch(
&self,
session_id: &str,
responses: Vec<QueryResponse>,
) -> Result<String, String> {
AppService::continue_batch(self, session_id, responses).await
}
async fn status(
&self,
session_id: Option<&str>,
pending_filter: Option<serde_json::Value>,
include_history: bool,
) -> Result<String, String> {
AppService::status(self, session_id, pending_filter, include_history).await
}
async fn eval(
&self,
scenario: Option<String>,
scenario_file: Option<String>,
scenario_name: Option<String>,
strategy: &str,
strategy_opts: Option<serde_json::Value>,
auto_card: bool,
) -> Result<String, String> {
AppService::eval(
self,
scenario,
scenario_file,
scenario_name,
strategy,
strategy_opts,
auto_card,
)
.await
}
async fn eval_history(&self, strategy: Option<&str>, limit: usize) -> Result<String, String> {
AppService::eval_history(self, strategy, limit)
}
async fn eval_detail(&self, eval_id: &str) -> Result<String, String> {
AppService::eval_detail(self, eval_id)
}
async fn eval_compare(&self, eval_id_a: &str, eval_id_b: &str) -> Result<String, String> {
AppService::eval_compare(self, eval_id_a, eval_id_b).await
}
async fn scenario_list(&self) -> Result<String, String> {
AppService::scenario_list(self)
}
async fn scenario_show(&self, name: &str) -> Result<String, String> {
AppService::scenario_show(self, name)
}
async fn scenario_install(&self, url: String) -> Result<String, String> {
AppService::scenario_install(self, url).await
}
async fn pkg_link(
&self,
path: String,
name: Option<String>,
force: Option<bool>,
scope: Option<String>,
project_root: Option<String>,
) -> Result<String, String> {
AppService::pkg_link(self, path, name, force, scope, project_root).await
}
async fn pkg_unlink(&self, name: String) -> Result<String, String> {
AppService::pkg_unlink(self, name).await
}
#[allow(clippy::too_many_arguments)]
async fn pkg_list(
&self,
project_root: Option<String>,
limit: Option<i32>,
sort: Option<String>,
filter: Option<serde_json::Value>,
fields: Option<Vec<String>>,
verbose: Option<String>,
) -> Result<String, String> {
let filter_map = match filter {
None => None,
Some(v) => match serde_json::from_value::<HashMap<String, serde_json::Value>>(v) {
Ok(map) => Some(map),
Err(e) => {
tracing::warn!(error = %e, "pkg_list: filter value is not a JSON object — treating as no filter");
None
}
},
};
let opts = ListOpts {
limit: limit.map(|n| n.max(0) as usize),
sort,
filter: filter_map,
fields,
verbose,
};
AppService::pkg_list(self, project_root, opts)
.await
.map_err(|e| e.to_string())
}
async fn pkg_install(&self, url: String, name: Option<String>) -> Result<String, String> {
AppService::pkg_install(self, url, name).await
}
async fn pkg_remove(
&self,
name: &str,
project_root: Option<String>,
version: Option<String>,
scope: Option<String>,
) -> Result<String, String> {
AppService::pkg_remove(self, name, project_root, version, scope).await
}
async fn pkg_repair(
&self,
name: Option<String>,
project_root: Option<String>,
) -> Result<String, String> {
AppService::pkg_repair(self, name, project_root).await
}
async fn pkg_doctor(
&self,
name: Option<String>,
project_root: Option<String>,
) -> Result<String, String> {
AppService::pkg_doctor(self, name, project_root).await
}
async fn add_note(
&self,
session_id: &str,
content: &str,
title: Option<&str>,
) -> Result<String, String> {
AppService::add_note(self, session_id, content, title).await
}
async fn log_view(
&self,
session_id: Option<&str>,
limit: Option<usize>,
max_chars: Option<usize>,
) -> Result<String, String> {
AppService::log_view(self, session_id, limit, max_chars).await
}
async fn stats(
&self,
strategy_filter: Option<&str>,
days: Option<u64>,
) -> Result<String, String> {
AppService::stats(self, strategy_filter, days)
}
async fn init(&self, project_root: Option<String>) -> Result<String, String> {
AppService::init(self, project_root).await
}
async fn update(&self, project_root: Option<String>) -> Result<String, String> {
AppService::update(self, project_root).await
}
async fn migrate(&self, project_root: Option<String>) -> Result<String, String> {
AppService::migrate(self, project_root).await
}
async fn card_list(&self, pkg: Option<String>) -> Result<String, String> {
AppService::card_list(self, pkg.as_deref())
}
async fn card_get(&self, card_id: &str) -> Result<String, String> {
AppService::card_get(self, card_id)
}
async fn card_find(
&self,
pkg: Option<String>,
where_: Option<serde_json::Value>,
order_by: Option<serde_json::Value>,
limit: Option<usize>,
offset: Option<usize>,
) -> Result<String, String> {
AppService::card_find(self, pkg, where_, order_by, limit, offset)
}
async fn card_alias_list(&self, pkg: Option<String>) -> Result<String, String> {
AppService::card_alias_list(self, pkg.as_deref())
}
async fn card_get_by_alias(&self, name: &str) -> Result<String, String> {
AppService::card_get_by_alias(self, name)
}
async fn card_alias_set(
&self,
name: &str,
card_id: &str,
pkg: Option<String>,
note: Option<String>,
) -> Result<String, String> {
AppService::card_alias_set(self, name, card_id, pkg.as_deref(), note.as_deref())
}
async fn card_append(
&self,
card_id: &str,
fields: serde_json::Value,
) -> Result<String, String> {
AppService::card_append(self, card_id, fields)
}
async fn card_install(&self, url: String) -> Result<String, String> {
AppService::card_install(self, url).await
}
async fn card_samples(
&self,
card_id: &str,
offset: Option<usize>,
limit: Option<usize>,
where_: Option<serde_json::Value>,
) -> Result<String, String> {
AppService::card_samples(self, card_id, offset.unwrap_or(0), limit, where_)
}
async fn card_lineage(
&self,
card_id: &str,
direction: Option<String>,
depth: Option<usize>,
include_stats: Option<bool>,
relation_filter: Option<Vec<String>>,
) -> Result<String, String> {
AppService::card_lineage(
self,
card_id,
direction.as_deref(),
depth,
include_stats,
relation_filter,
)
}
async fn card_sink_backfill(&self, sink: String, dry_run: bool) -> Result<String, String> {
AppService::card_sink_backfill(self, super::card::SinkBackfillParams { sink, dry_run })
}
async fn hub_reindex(
&self,
output_path: Option<String>,
source_dir: Option<String>,
) -> Result<String, String> {
let svc = self.clone();
tokio::task::spawn_blocking(move || {
AppService::hub_reindex(&svc, output_path.as_deref(), source_dir.as_deref())
})
.await
.map_err(|e| format!("hub_reindex task panicked: {e}"))?
}
async fn hub_gendoc(
&self,
source_dir: String,
out_dir: Option<String>,
projections: Option<Vec<String>>,
config_path: Option<String>,
lint_strict: Option<bool>,
) -> Result<String, String> {
let svc = self.clone();
tokio::task::spawn_blocking(move || {
crate::AppService::hub_gendoc(
&svc,
&source_dir,
out_dir.as_deref(),
projections.as_deref(),
config_path.as_deref(),
lint_strict,
)
})
.await
.map_err(|e| format!("hub_gendoc task panicked: {e}"))?
}
async fn hub_dist(
&self,
source_dir: String,
output_path: Option<String>,
out_dir: Option<String>,
preset: Option<String>,
project_root: Option<String>,
projections: Option<Vec<String>>,
config_path: Option<String>,
lint_strict: Option<bool>,
) -> Result<String, String> {
let svc = self.clone();
tokio::task::spawn_blocking(move || {
AppService::hub_dist(
&svc,
&source_dir,
output_path.as_deref(),
out_dir.as_deref(),
preset.as_deref(),
project_root.as_deref(),
projections.as_deref(),
config_path.as_deref(),
lint_strict,
)
})
.await
.map_err(|e| format!("hub_dist task panicked: {e}"))?
}
async fn hub_info(&self, pkg: String) -> Result<String, String> {
let svc = self.clone();
tokio::task::spawn_blocking(move || AppService::hub_info(&svc, &pkg))
.await
.map_err(|e| format!("hub_info task panicked: {e}"))?
}
#[allow(clippy::too_many_arguments)]
async fn hub_search(
&self,
query: Option<String>,
category: Option<String>,
installed_only: Option<bool>,
limit: Option<i32>,
sort: Option<String>,
filter: Option<serde_json::Value>,
fields: Option<Vec<String>>,
verbose: Option<String>,
) -> Result<String, String> {
let svc = self.clone();
let filter_map = match filter {
None => None,
Some(v) => match serde_json::from_value::<HashMap<String, serde_json::Value>>(v) {
Ok(map) => Some(map),
Err(e) => {
tracing::warn!(error = %e, "hub_search: filter value is not a JSON object — treating as no filter");
None
}
},
};
let opts = ListOpts {
limit: limit.map(|n| n.max(0) as usize),
sort,
filter: filter_map,
fields,
verbose,
};
tokio::task::spawn_blocking(move || {
AppService::hub_search(
&svc,
query.as_deref(),
category.as_deref(),
installed_only,
opts,
)
})
.await
.map_err(|e| format!("hub_search task panicked: {e}"))?
}
async fn pkg_read_init_lua(&self, name: &str) -> Result<String, String> {
AppService::pkg_read_init_lua(self, name, None)
}
async fn pkg_meta(&self, name: &str) -> Result<String, String> {
let filter = serde_json::json!({ "name": name });
let json_str = EngineApi::pkg_list(
self,
None,
None,
None,
Some(filter),
None,
Some("full".to_string()),
)
.await?;
let val: serde_json::Value = serde_json::from_str(&json_str)
.map_err(|e| format!("pkg_meta: failed to parse pkg_list response: {e}"))?;
let pkgs = val
.get("packages")
.and_then(|p| p.as_array())
.ok_or_else(|| "pkg_meta: pkg_list response missing 'packages' field".to_string())?;
if pkgs.is_empty() {
return Err(format!("pkg not found: {name}"));
}
serde_json::to_string(&pkgs[0]).map_err(|e| format!("pkg_meta: serialize entry: {e}"))
}
async fn pkg_scaffold(
&self,
name: String,
target_dir: Option<String>,
category: Option<String>,
description: Option<String>,
) -> Result<String, String> {
let svc = self.clone();
tokio::task::spawn_blocking(move || {
AppService::pkg_scaffold(
&svc,
&name,
target_dir.as_deref(),
category.as_deref(),
description.as_deref(),
)
})
.await
.map_err(|e| format!("pkg_scaffold task panicked: {e}"))?
}
async fn hub_index_aggregate(&self) -> Result<String, String> {
let svc = self.clone();
let (index, warnings) = tokio::task::spawn_blocking(move || {
AppService::aggregate_index(&svc).map_err(|e| e.to_string())
})
.await
.map_err(|e| format!("hub_index_aggregate task panicked: {e}"))??;
let mut json = serde_json::to_value(&index)
.map_err(|e| format!("hub_index_aggregate: serialize index: {e}"))?;
if !warnings.is_empty() {
if let Some(obj) = json.as_object_mut() {
obj.insert("warnings".to_string(), serde_json::json!(warnings));
}
}
serde_json::to_string(&json)
.map_err(|e| format!("hub_index_aggregate: serialize final: {e}"))
}
async fn info(&self) -> String {
AppService::info(self)
}
}