fix(cli): keep total-failure JSON errors parseable

Remove obsolete unsupported-import diagnostics and defer comparison warnings until partial success. Preserve per-symbol diagnostics and quiet behavior when results remain available.
This commit is contained in:
hermes 2026-10-03 16:21:35 +00:00
commit b4dcfceef6
3 changed files with 20 additions and 30 deletions

View file

@ -137,6 +137,7 @@ CLI flags > environment variables > config file > defaults
- Table mode: human-readable error on stderr
- JSON mode: `{"error": true, "code": "...", "message": "..."}`
- Exit code 0 on success, non-zero on failure
- A totally failed `stocks compare` emits one structured error in JSON mode; per-symbol warnings are retained for partial success, not printed ahead of a total-failure error.
## CLI Output and Offline Cache
- `version`, `cache info`, `cache clear`, and `ownership resolve map|merge` emit

View file

@ -18,7 +18,6 @@ use crate::ownership::types::{
ChangeType, FlowSignal, HolderRow, KseiHolding, OwnershipRelease, OwnershipSource,
};
use crate::ownership::{archive, db, entities, graph, parser, remote, search, snapshot, xlsx};
use crate::runtime;
#[derive(Debug, Args)]
#[command(
@ -786,28 +785,15 @@ fn handle_import(args: &ImportArgs, config: &IdxConfig) -> Result<(), IdxError>
));
}
if let Some(symbols) = &args.fetch_bing {
let clean: Vec<String> = symbols
if args.fetch_bing.as_ref().is_some_and(|symbols| {
symbols
.iter()
.flat_map(|v| v.split(','))
.map(str::trim)
.filter(|s| !s.is_empty())
.map(|s| s.to_uppercase())
.collect();
if !clean.is_empty() {
runtime::info(format!(
"--fetch-bing requested for {} symbol(s), implementation deferred for Sprint 6",
clean.len()
));
for symbol in &clean {
runtime::info(format!(" - {symbol}"));
}
return Err(IdxError::Unsupported(
"--fetch-bing import is not implemented yet".to_string(),
));
}
.flat_map(|value| value.split(','))
.any(|value| !value.trim().is_empty())
}) {
return Err(IdxError::Unsupported(
"--fetch-bing import is not implemented yet".to_string(),
));
}
let Some(import_input) = resolve_import_input(args)? else {

View file

@ -647,7 +647,7 @@ pub fn handle(
}
StocksSubcommand::Compare { symbols } => {
let mut reports: Vec<FundamentalReport> = Vec::new();
let mut last_error = None;
let mut failures = Vec::new();
for sym in symbols.iter().flat_map(|s| s.split(',')) {
let resolved = crate::api::resolve_symbol(sym, &config.exchange)?;
@ -664,21 +664,24 @@ pub fn handle(
analyze_fundamental,
) {
Ok(report) => reports.push(report),
Err(err) => {
runtime::warn(format!(
"failed to fetch fundamentals for {resolved}: {err}"
));
last_error = Some(err);
}
Err(err) => failures.push((resolved, err)),
}
}
if reports.is_empty() {
return Err(last_error.unwrap_or_else(|| {
return Err(failures.pop().map(|(_, err)| err).unwrap_or_else(|| {
IdxError::CacheMiss("fundamental/no symbols could be compared".to_string())
}));
}
// Partial success needs diagnostics; total failure must leave stderr
// available for the single structured error emitted by the caller.
for (resolved, err) in failures {
runtime::warn(format!(
"failed to fetch fundamentals for {resolved}: {err}"
));
}
render_compare(&reports, &config.output, config.no_color)
}
}