use oxios_gateway::format::ChannelFormatter;
use oxios_gateway::message::{ErrorKind, OutgoingMessage};
pub struct CliFormatter;
impl ChannelFormatter for CliFormatter {
fn format_success(&self, msg: &OutgoingMessage) -> String {
let mut out = msg.content.clone();
if let Some(meta) = &msg.meta {
let eval_icon = if meta.evaluation_passed {
"✅"
} else {
"⚠️"
};
if !meta.phase.is_empty() {
out.push_str(&format!(
"\n{} {} | {}",
eval_icon,
meta.phase,
if meta.evaluation_passed {
"통과"
} else {
"미통과"
}
));
}
if let Some(tag) = &meta.project_tag {
out.push_str(&format!(" | {tag}"));
}
if let Some(dur) = meta.duration_ms {
if dur >= 1000 {
out.push_str(&format!(" | {:.1}s", dur as f64 / 1000.0));
} else {
out.push_str(&format!(" | {dur}ms"));
}
}
}
out
}
fn format_error(&self, msg: &OutgoingMessage) -> String {
let meta = msg.meta.as_ref();
let kind = meta.and_then(|m| m.error.as_ref()).map(|e| e.kind);
let icon = match kind {
Some(ErrorKind::ExecutionFailed) => "❌",
Some(ErrorKind::ProviderError) => "🔌",
Some(ErrorKind::Timeout) => "⏱️",
Some(ErrorKind::PermissionDenied) => "🔒",
Some(ErrorKind::ValidationError) => "⚠️",
_ => "💥",
};
let mut out = format!("{} {}", icon, msg.content);
if let Some(err) = meta.and_then(|m| m.error.as_ref()) {
if let Some(s) = &err.suggestion {
out.push_str(&format!("\n💡 {s}"));
}
}
out
}
fn format_progress(&self, phase: &str) -> String {
match phase {
"Interview" => "🔍 분석 중...".into(),
"Seed" => "📋 계획 수립 중...".into(),
"Execute" => "⚡ 실행 중...".into(),
"Evaluate" => "📊 평가 중...".into(),
"Evolve" => "🔄 개선 중...".into(),
_ => "⏳ 처리 중...".into(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use oxios_gateway::message::{ResponseMeta, UserFacingError};
use std::collections::HashMap;
fn make_msg(content: &str, meta: Option<ResponseMeta>) -> OutgoingMessage {
OutgoingMessage {
id: uuid::Uuid::new_v4(),
channel: "cli".into(),
user_id: "test-user".into(),
content: content.into(),
timestamp: chrono::Utc::now(),
metadata: HashMap::new(),
meta,
}
}
#[test]
fn format_success_no_meta() {
let msg = make_msg("Hello", None);
let formatter = CliFormatter;
assert_eq!(formatter.format_success(&msg), "Hello");
}
#[test]
fn format_success_with_phase_and_eval() {
let meta = ResponseMeta {
session_id: None,
project_id: None,
project_tag: Some("[🔧 oxios]".into()),
seed_id: None,
phase: "Execute".into(),
evaluation_passed: true,
duration_ms: Some(1500),
error: None,
};
let msg = make_msg("Done!", Some(meta));
let formatter = CliFormatter;
let output = formatter.format_success(&msg);
assert!(output.contains("✅ Execute | 통과"));
assert!(output.contains("[🔧 oxios]"));
assert!(output.contains("1.5s"));
}
#[test]
fn format_success_failed_eval() {
let meta = ResponseMeta {
session_id: None,
project_id: None,
project_tag: None,
seed_id: None,
phase: "Evaluate".into(),
evaluation_passed: false,
duration_ms: Some(500),
error: None,
};
let msg = make_msg("Partial", Some(meta));
let formatter = CliFormatter;
let output = formatter.format_success(&msg);
assert!(output.contains("⚠️ Evaluate | 미통과"));
assert!(output.contains("500ms"));
}
#[test]
fn format_error_timeout() {
let meta = ResponseMeta {
session_id: None,
project_id: None,
project_tag: None,
seed_id: None,
phase: String::new(),
evaluation_passed: false,
duration_ms: None,
error: Some(UserFacingError {
message: "시간이 초과되었습니다.".into(),
kind: ErrorKind::Timeout,
suggestion: Some("더 간단한 요청으로 시도하세요.".into()),
}),
};
let msg = make_msg("시간이 초과되었습니다.", Some(meta));
let formatter = CliFormatter;
let output = formatter.format_error(&msg);
assert!(output.starts_with("⏱️"));
assert!(output.contains("💡 더 간단한 요청으로 시도하세요."));
}
#[test]
fn format_error_provider() {
let meta = ResponseMeta {
session_id: None,
project_id: None,
project_tag: None,
seed_id: None,
phase: String::new(),
evaluation_passed: false,
duration_ms: None,
error: Some(UserFacingError {
message: "AI 서비스 오류.".into(),
kind: ErrorKind::ProviderError,
suggestion: None,
}),
};
let msg = make_msg("AI 서비스 오류.", Some(meta));
let formatter = CliFormatter;
let output = formatter.format_error(&msg);
assert!(output.starts_with("🔌"));
assert!(!output.contains("💡")); }
#[test]
fn format_progress_phases() {
let formatter = CliFormatter;
assert_eq!(formatter.format_progress("Interview"), "🔍 분석 중...");
assert_eq!(formatter.format_progress("Seed"), "📋 계획 수립 중...");
assert_eq!(formatter.format_progress("Execute"), "⚡ 실행 중...");
assert_eq!(formatter.format_progress("Evaluate"), "📊 평가 중...");
assert_eq!(formatter.format_progress("Evolve"), "🔄 개선 중...");
assert_eq!(formatter.format_progress("Unknown"), "⏳ 처리 중...");
}
}