use serde::Serialize;
use sylphx_sdk_core::SdkError;
pub fn print_out<T: Serialize>(json: bool, value: &T, human: impl FnOnce()) {
if json {
println!(
"{}",
serde_json::to_string_pretty(value).unwrap_or_else(|_| "{}".into())
);
} else {
human();
}
}
pub fn emit_json_or<T: Serialize>(
json: bool,
value: &T,
human: impl FnOnce(),
) -> anyhow::Result<()> {
print_out(json, value, human);
Ok(())
}
pub fn map_sdk_err(err: SdkError) -> anyhow::Error {
match &err {
SdkError::Api {
status: 404,
code,
message,
} if message.contains("/whoami") || message.contains("\"path\":\"/whoami\"") => {
anyhow::anyhow!(
"api 404: {code}: Management whoami route not found.\n\
\n\
Likely cause: API base URL is missing `/v1`\n\
wrong: https://api.sylphx.com/whoami\n\
right: https://api.sylphx.com/v1/whoami\n\
\n\
Fix:\n\
• sylphx doctor\n\
• sylphx login --token \"$SYLPHX_TOKEN\" --api-url https://api.sylphx.com/v1\n\
• or: export SYLPHX_API_URL=https://api.sylphx.com/v1\n\
\n\
Raw: {message}"
)
}
SdkError::Api {
status: 403,
code,
message,
} if message.contains("user_context_required") => anyhow::anyhow!(
"api 403: {code}: this endpoint needs a user-scoped credential.\n\
\n\
You are likely using a service token (`svc_*`).\n\
• whoami / user profile → use `sylphx login` (device flow)\n\
• deploy / projects / automation → `svc_*` is correct\n\
\n\
Raw: {message}"
),
SdkError::Api {
status: 401,
code,
message,
} => anyhow::anyhow!(
"api 401: {code}: not authenticated.\n\
\n\
Fix:\n\
• sylphx login\n\
• sylphx login --token svc_…\n\
• export SYLPHX_TOKEN=…\n\
\n\
Then: sylphx doctor\n\
Raw: {message}"
),
SdkError::Api {
status: 404,
code,
message,
} => anyhow::anyhow!(
"api 404: {code}: resource not found.\n\
\n\
Check:\n\
• project/org id spelling\n\
• preferred org (`sylphx context show` / `--org-id`)\n\
• sylphx doctor\n\
\n\
Raw: {message}"
),
other => anyhow::anyhow!("{other}"),
}
}
pub fn login_verification_err(err: SdkError) -> anyhow::Error {
anyhow::anyhow!(
"login verification failed — credentials were NOT saved: {}",
map_sdk_err(err)
)
}
pub fn ok_line(msg: &str) {
println!("✓ {msg}");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn login_verification_error_keeps_sanitized_api_cause() {
let error = login_verification_err(SdkError::Api {
status: 401,
code: "invalid_audience".into(),
message: "credential audience rejected".into(),
})
.to_string();
assert!(error.contains("credentials were NOT saved"));
assert!(error.contains("api 401: invalid_audience"));
assert!(error.contains("credential audience rejected"));
}
}