use serde_json::Value;
use crate::types::{
BasicCheckResult, ChimeraResult, DetailedCheckResult, NsfwCheckResult, RawJson,
};
pub(crate) fn ensure_payload(value: Value) -> crate::Result<RawJson> {
let payload = match value {
Value::Object(map) => map,
other => {
return Err(crate::LinkShieldAIError::Response {
message: "LinkShieldAI API returned a non-object JSON response.".to_string(),
payload: Some(other),
});
}
};
if payload.get("Error").is_some() || payload.get("error").is_some() {
return Err(crate::LinkShieldAIError::Response {
message: error_message_from_payload(&payload, "LinkShieldAI API request failed."),
payload: Some(Value::Object(payload)),
});
}
Ok(payload)
}
pub(crate) fn parse_basic(payload: RawJson) -> BasicCheckResult {
let result = string_value(payload.get("result"));
BasicCheckResult {
is_malicious: is_malicious_text(result.as_deref()),
is_safe: is_safe_text(result.as_deref()),
result,
raw: payload,
}
}
pub(crate) fn parse_detailed(payload: RawJson) -> DetailedCheckResult {
let result = string_value(payload.get("result"));
let screenshot_url = first_string(
&payload,
&["screenshot url", "screenshot_url", "screenshotUrl"],
);
let tag = string_value(payload.get("tag"));
DetailedCheckResult {
is_malicious: is_malicious_text(result.as_deref()),
result,
screenshot_url,
tag,
raw: payload,
}
}
pub(crate) fn parse_nsfw(payload: RawJson) -> NsfwCheckResult {
let result = string_value(payload.get("result"));
NsfwCheckResult {
is_nsfw: result
.as_deref()
.unwrap_or_default()
.trim()
.eq_ignore_ascii_case("true"),
result,
raw: payload,
}
}
pub(crate) fn parse_chimera(payload: RawJson) -> ChimeraResult {
let result = string_value(payload.get("result"));
let probability = number_value(payload.get("probability"));
let detection_method = first_string(&payload, &["detection_method", "detectionMethod"]);
let matched_signatures = integer_value(first_value(
&payload,
&["matched_signatures", "matchedSignatures"],
));
let url = string_value(payload.get("url"));
ChimeraResult {
is_malicious: is_malicious_text(result.as_deref()),
result,
probability,
detection_method,
matched_signatures,
url,
raw: payload,
}
}
pub(crate) fn error_message_from_payload(payload: &RawJson, fallback: &str) -> String {
if let Some(message) = payload
.get("error")
.and_then(Value::as_object)
.and_then(|error| error.get("message"))
.and_then(Value::as_str)
{
return message.to_string();
}
for key in ["Error", "error", "message", "detail"] {
if let Some(value) = payload.get(key) {
if let Some(text) = value.as_str() {
if !text.is_empty() {
return text.to_string();
}
} else if !value.is_null() {
return value.to_string();
}
}
}
fallback.to_string()
}
fn first_value<'a>(payload: &'a RawJson, keys: &[&str]) -> Option<&'a Value> {
keys.iter().find_map(|key| payload.get(*key))
}
fn first_string(payload: &RawJson, keys: &[&str]) -> Option<String> {
string_value(first_value(payload, keys))
}
fn string_value(value: Option<&Value>) -> Option<String> {
match value {
Some(Value::String(text)) => Some(text.clone()),
Some(Value::Number(number)) => Some(number.to_string()),
Some(Value::Bool(boolean)) => Some(boolean.to_string()),
_ => None,
}
}
fn number_value(value: Option<&Value>) -> Option<f64> {
match value {
Some(Value::Number(number)) => number.as_f64(),
Some(Value::String(text)) => text.parse().ok(),
_ => None,
}
}
fn integer_value(value: Option<&Value>) -> Option<i64> {
match value {
Some(Value::Number(number)) => number
.as_i64()
.or_else(|| number.as_u64().map(|value| value as i64)),
Some(Value::String(text)) => text.parse().ok(),
_ => None,
}
}
fn is_malicious_text(result: Option<&str>) -> bool {
let value = result.unwrap_or_default().trim().to_lowercase();
for phrase in [
"didn't detect",
"did not detect",
"no malicious",
"not malicious",
] {
if value.contains(phrase) {
return false;
}
}
matches!(value.as_str(), "might be malicious" | "malicious" | "true")
|| value.contains("malicious")
}
fn is_safe_text(result: Option<&str>) -> bool {
let value = result.unwrap_or_default().trim().to_lowercase();
matches!(
value.as_str(),
"likely safe" | "the system didn't detect anything malicious." | "false" | "benign"
) || value.contains("safe")
|| value.contains("benign")
}