use serde_json::Value; pub(crate) fn extract_download_files( payload: &Value, ) -> Vec { let mut files = Vec::new(); collect_download_files(payload, &mut files); let mut deduped = Vec::new(); for file in files { if !deduped.iter().any( |entry: &shared_contracts::hyper3d::Hyper3dDownloadFilePayload| entry.url == file.url, ) { deduped.push(file); } } deduped } fn collect_download_files( value: &Value, output: &mut Vec, ) { match value { Value::Object(object) => { let maybe_url = object .get("url") .or_else(|| object.get("download_url")) .or_else(|| object.get("downloadUrl")) .or_else(|| object.get("file_url")) .or_else(|| object.get("fileUrl")) .or_else(|| object.get("signed_url")) .or_else(|| object.get("signedUrl")) .or_else(|| object.get("presigned_url")) .or_else(|| object.get("presignedUrl")) .and_then(Value::as_str) .map(str::trim) .filter(|value| value.starts_with("http://") || value.starts_with("https://")); if let Some(url) = maybe_url { let name = object .get("name") .or_else(|| object.get("file_name")) .or_else(|| object.get("filename")) .or_else(|| object.get("fileName")) .or_else(|| object.get("display_name")) .or_else(|| object.get("displayName")) .and_then(Value::as_str) .map(str::trim) .filter(|value| !value.is_empty()) .unwrap_or("model") .to_string(); output.push(shared_contracts::hyper3d::Hyper3dDownloadFilePayload { name, url: url.to_string(), }); } for nested in object.values() { collect_download_files(nested, output); } } Value::Array(items) => { for item in items { collect_download_files(item, output); } } _ => {} } }