use super::*;
pub(in crate::commands::ops) fn load_status_snapshot(
args: &[String],
) -> Result<(StatusSnapshot, OpsSource), String> {
if let Some(raw_endpoint) = option_value(args, "--endpoint") {
let timeout_ms = parse_u64_option(args, "--timeout-ms", 1000)?;
let (snapshot, endpoint) = fetch_remote_status_snapshot(&raw_endpoint, timeout_ms)?;
let source = OpsSource {
label: format!("remote:{endpoint}"),
json: serde_json::json!({
"mode": "remote_service",
"service": STATUS_SERVICE_NAME,
"endpoint": endpoint,
"timeout_ms": timeout_ms,
}),
};
return Ok((snapshot, source));
}
let report_path = parse_report_path(args, DEFAULT_STATUS_REPORT_PATH)?;
if has_flag(args, "--refresh-demo") {
capture_demo_status_report(&report_path).map_err(|err| {
format!(
"refresh demo status report to {} failed: {err}",
report_path.display()
)
})?;
}
let snapshot = read_status_snapshot(&report_path).map_err(|err| {
format!(
"read status snapshot from {} failed: {err}. Hint: run `cargo run -p local-loop` first, or pass --refresh-demo.",
report_path.display()
)
})?;
let source = OpsSource {
label: report_path.display().to_string(),
json: serde_json::json!({
"mode": "file",
"report": report_path,
}),
};
Ok((snapshot, source))
}
pub(in crate::commands::ops) fn load_baseline_snapshot(
args: &[String],
) -> Result<(StatusSnapshot, OpsSource), String> {
if let Some(raw_endpoint) = option_value(args, "--baseline-endpoint") {
let timeout_ms = parse_u64_option(args, "--baseline-timeout-ms", 1000)?;
let (snapshot, endpoint) = fetch_remote_status_snapshot(&raw_endpoint, timeout_ms)?;
let source = OpsSource {
label: format!("remote:{endpoint}"),
json: serde_json::json!({
"mode": "remote_service",
"service": STATUS_SERVICE_NAME,
"endpoint": endpoint,
"timeout_ms": timeout_ms,
}),
};
return Ok((snapshot, source));
}
let Some(raw_path) = option_value(args, "--baseline-report") else {
return Err(String::from(
"ops diff requires --baseline-report <path> or --baseline-endpoint <addr>",
));
};
let report_path = PathBuf::from(raw_path);
let snapshot = read_status_snapshot(&report_path).map_err(|err| {
format!(
"read baseline status snapshot from {} failed: {err}",
report_path.display()
)
})?;
let source = OpsSource {
label: report_path.display().to_string(),
json: serde_json::json!({
"mode": "file",
"report": report_path,
}),
};
Ok((snapshot, source))
}
pub(in crate::commands::ops) fn load_fleet_snapshots(
args: &[String],
baseline: bool,
) -> Result<Vec<(StatusSnapshot, OpsSource)>, String> {
let reports_option = if baseline {
"--baseline-reports"
} else {
"--reports"
};
let endpoints_option = if baseline {
"--baseline-endpoints"
} else {
"--endpoints"
};
let timeout_option = if baseline {
"--baseline-timeout-ms"
} else {
"--timeout-ms"
};
let mut items = Vec::new();
let timeout_ms = parse_u64_option(args, timeout_option, 1000)?;
if let Some(raw_reports) = option_value(args, reports_option) {
for raw in parse_csv_list(&raw_reports) {
let report_path = PathBuf::from(raw);
let snapshot = read_status_snapshot(&report_path).map_err(|err| {
format!(
"read fleet status snapshot from {} failed: {err}",
report_path.display()
)
})?;
items.push((
snapshot,
OpsSource {
label: report_path.display().to_string(),
json: serde_json::json!({
"mode": "file",
"report": report_path,
}),
},
));
}
}
if let Some(raw_endpoints) = option_value(args, endpoints_option) {
for raw in parse_csv_list(&raw_endpoints) {
let (snapshot, endpoint) = fetch_remote_status_snapshot(&raw, timeout_ms)?;
items.push((
snapshot,
OpsSource {
label: format!("remote:{endpoint}"),
json: serde_json::json!({
"mode": "remote_service",
"service": STATUS_SERVICE_NAME,
"endpoint": endpoint,
"timeout_ms": timeout_ms,
}),
},
));
}
}
if baseline {
if items.is_empty()
&& (option_value(args, "--baseline-report").is_some()
|| option_value(args, "--baseline-endpoint").is_some())
{
let (snapshot, source) = load_baseline_snapshot(args)?;
items.push((snapshot, source));
}
return Ok(items);
}
if items.is_empty() {
let (snapshot, source) = load_status_snapshot(args)?;
items.push((snapshot, source));
}
Ok(items)
}
pub(in crate::commands::ops) fn parse_csv_list(raw: &str) -> Vec<String> {
raw.split(',')
.map(str::trim)
.filter(|item| !item.is_empty())
.map(ToString::to_string)
.collect()
}
pub(in crate::commands::ops) fn write_json_payload_file(
path: &Path,
payload: &serde_json::Value,
) -> Result<(), String> {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).map_err(|err| {
format!(
"create ops diff output dir {} failed: {err}",
parent.display()
)
})?;
}
let body = serde_json::to_string_pretty(payload)
.map_err(|err| format!("serialize ops diff report failed: {err}"))?;
fs::write(path, body)
.map_err(|err| format!("write ops diff report to {} failed: {err}", path.display()))
}
pub(in crate::commands::ops) fn fetch_remote_status_snapshot(
endpoint: &str,
timeout_ms: u64,
) -> Result<(StatusSnapshot, String), String> {
let endpoint = normalize_udp_endpoint(endpoint)?;
let client = make_udp_service_client(endpoint.clone(), timeout_ms)?;
let request_id = next_request_id();
let request = build_snapshot_request(request_id);
let response: StatusServiceResponse =
call_status_service(&client, request_id, &request, &endpoint)?;
validate_response(&response, request_id)?;
Ok((response.snapshot, endpoint))
}
pub(in crate::commands::ops) fn call_status_service(
client: &core_api::UdpServiceClient,
request_id: ServiceRequestId,
request: &crate::status_api::StatusServiceRequest,
endpoint: &str,
) -> Result<StatusServiceResponse, String> {
client
.call_json(STATUS_SERVICE_NAME, request_id, request)
.map_err(|err| format!("status query to {endpoint} failed: {err}"))
}
pub(in crate::commands::ops) fn normalize_udp_endpoint(raw: &str) -> Result<String, String> {
if let Some(rest) = raw.strip_prefix("udp://") {
if rest.is_empty() {
return Err(String::from("invalid --endpoint value: udp://"));
}
return Ok(rest.to_string());
}
if raw.contains("://") {
return Err(format!("unsupported endpoint scheme: {raw}"));
}
Ok(raw.to_string())
}