1use serde::de::DeserializeOwned;
2
3pub fn parse_json_response<T: DeserializeOwned>(raw: &str) -> Result<T, String> {
8 let trimmed = raw.trim();
9 let cleaned = if trimmed.starts_with("```json") {
10 trimmed
11 .trim_start_matches("```json")
12 .trim_end_matches("```")
13 .trim()
14 } else if trimmed.starts_with("```") {
15 trimmed
16 .trim_start_matches("```")
17 .trim_end_matches("```")
18 .trim()
19 } else {
20 trimmed
21 };
22
23 serde_json::from_str(cleaned).map_err(|e| format!("JSON parse error: {e}\nRaw: {raw}"))
24}
25
26#[cfg(test)]
27mod tests {
28 use super::*;
29 use serde::Deserialize;
30
31 #[derive(Debug, Deserialize)]
32 struct Sample {
33 value: String,
34 }
35
36 #[test]
37 fn plain_json() {
38 let r: Sample = parse_json_response(r#"{"value": "ok"}"#).unwrap();
39 assert_eq!(r.value, "ok");
40 }
41
42 #[test]
43 fn json_with_code_fence() {
44 let r: Sample = parse_json_response("```json\n{\"value\": \"fenced\"}\n```").unwrap();
45 assert_eq!(r.value, "fenced");
46 }
47
48 #[test]
49 fn json_with_generic_fence() {
50 let r: Sample = parse_json_response("```\n{\"value\": \"generic\"}\n```").unwrap();
51 assert_eq!(r.value, "generic");
52 }
53
54 #[test]
55 fn bad_json_returns_err() {
56 let r = parse_json_response::<Sample>("not json");
57 assert!(r.is_err());
58 assert!(r.unwrap_err().contains("JSON parse error"));
59 }
60}