use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use std::collections::HashMap;
#[async_trait::async_trait]
pub trait ResponseParser {
type Output;
type Error: std::fmt::Display + Send + Sync;
async fn parse(&self, response: &str) -> Result<Self::Output, Self::Error>;
fn can_parse(&self, response: &str) -> bool;
}
pub struct MarkdownSectionParser;
impl MarkdownSectionParser {
pub fn new() -> Self {
Self
}
pub fn extract_sections(&self, text: &str) -> HashMap<String, String> {
let mut sections = HashMap::new();
let lines: Vec<&str> = text.lines().collect();
let mut current_section = String::new();
let mut current_content = String::new();
let mut in_section = false;
for line in lines {
if line.starts_with("## ") {
if in_section && !current_section.is_empty() {
sections.insert(current_section.clone(), current_content.trim().to_string());
}
current_section = line.trim_start_matches("## ").to_string();
current_content = String::new();
in_section = true;
} else if in_section {
current_content.push_str(line);
current_content.push('\n');
}
}
if in_section && !current_section.is_empty() {
sections.insert(current_section, current_content.trim().to_string());
}
sections
}
}
#[async_trait::async_trait]
impl ResponseParser for MarkdownSectionParser {
type Output = HashMap<String, String>;
type Error = ParseError;
async fn parse(&self, response: &str) -> Result<Self::Output, Self::Error> {
let sections = self.extract_sections(response);
Ok(sections)
}
fn can_parse(&self, response: &str) -> bool {
response.contains("## ")
}
}
pub struct JsonResponseParser<T> {
_phantom: std::marker::PhantomData<T>,
}
impl<T> JsonResponseParser<T> {
pub fn new() -> Self {
Self {
_phantom: std::marker::PhantomData,
}
}
}
#[async_trait::async_trait]
impl<T> ResponseParser for JsonResponseParser<T>
where
T: for<'de> Deserialize<'de> + Send + Sync,
{
type Output = T;
type Error = ParseError;
async fn parse(&self, response: &str) -> Result<Self::Output, Self::Error> {
if let Ok(result) = serde_json::from_str::<T>(response) {
return Ok(result);
}
if let Some(json_str) = extract_json_from_text(response) {
if let Ok(result) = serde_json::from_str::<T>(&json_str) {
return Ok(result);
}
}
Err(ParseError::InvalidJson(
"Could not parse JSON from response".to_string(),
))
}
fn can_parse(&self, response: &str) -> bool {
serde_json::from_str::<JsonValue>(response).is_ok()
|| response.contains("```json")
|| (response.contains('{') && response.contains('}'))
}
}
pub struct AutoParser {
pub json_first: bool,
}
impl Default for AutoParser {
fn default() -> Self {
Self { json_first: true }
}
}
#[async_trait::async_trait]
impl ResponseParser for AutoParser {
type Output = ParsedResponse;
type Error = ParseError;
async fn parse(&self, response: &str) -> Result<Self::Output, Self::Error> {
if self.json_first {
if let Ok(json) = serde_json::from_str::<JsonValue>(response) {
return Ok(ParsedResponse::Json(json));
}
if let Some(json_str) = extract_json_from_text(response) {
if let Ok(json) = serde_json::from_str::<JsonValue>(&json_str) {
return Ok(ParsedResponse::Json(json));
}
}
}
let section_parser = MarkdownSectionParser::new();
let sections = section_parser.extract_sections(response);
if !sections.is_empty() {
return Ok(ParsedResponse::Sections(sections));
}
let json_lines_parser = JsonLinesResponseParser::<JsonValue>::new();
if json_lines_parser.can_parse(response) {
if let Ok(lines) = json_lines_parser.parse(response).await {
if lines.len() > 1 || (lines.len() == 1 && !response.trim().starts_with('[')) {
return Ok(ParsedResponse::JsonLines(lines));
}
}
}
let blocks = extract_code_blocks(response);
if !blocks.is_empty() {
return Ok(ParsedResponse::CodeBlocks(blocks));
}
Ok(ParsedResponse::Text(response.to_string()))
}
fn can_parse(&self, _response: &str) -> bool {
true
}
}
#[derive(Debug, thiserror::Error)]
pub enum ParseError {
#[error("Invalid JSON: {0}")]
InvalidJson(String),
#[error("Invalid format: {0}")]
InvalidFormat(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ParsedResponse {
Json(JsonValue),
JsonLines(Vec<JsonValue>),
Sections(HashMap<String, String>),
CodeBlocks(Vec<(String, String)>),
Text(String),
}
pub struct JsonLinesResponseParser<T> {
_phantom: std::marker::PhantomData<T>,
}
impl<T> JsonLinesResponseParser<T> {
pub fn new() -> Self {
Self {
_phantom: std::marker::PhantomData,
}
}
}
#[async_trait::async_trait]
impl<T> ResponseParser for JsonLinesResponseParser<T>
where
T: for<'de> Deserialize<'de> + Send + Sync,
{
type Output = Vec<T>;
type Error = ParseError;
async fn parse(&self, response: &str) -> Result<Self::Output, Self::Error> {
let mut results = Vec::new();
for line in response.lines() {
let line = line.trim();
if line.is_empty() {
continue;
}
if line.starts_with("```") {
continue;
}
if let Ok(val) = serde_json::from_str::<T>(line) {
results.push(val);
}
}
if results.is_empty() {
return Err(ParseError::InvalidFormat(
"No valid JSON lines found".to_string(),
));
}
Ok(results)
}
fn can_parse(&self, response: &str) -> bool {
let lines: Vec<&str> = response
.lines()
.map(|l| l.trim())
.filter(|l| !l.is_empty() && !l.starts_with("```"))
.collect();
if lines.is_empty() {
return false;
}
lines
.iter()
.any(|l| serde_json::from_str::<JsonValue>(l).is_ok())
}
}
pub fn extract_json_from_text(text: &str) -> Option<String> {
if let Some(start) = text.find("```json") {
if let Some(end) = text[start + 7..].find("```") {
let json_content = &text[start + 7..start + 7 + end];
return Some(json_content.trim().to_string());
}
}
if let Some(start) = text.find("```") {
if let Some(end) = text[start + 3..].find("```") {
let content = &text[start + 3..start + 3 + end];
if content.trim().starts_with('{') || content.trim().starts_with('[') {
return Some(content.trim().to_string());
}
}
}
None
}
pub fn extract_code_blocks(text: &str) -> Vec<(String, String)> {
let mut blocks = Vec::new();
let mut lines = text.lines().peekable();
while let Some(line) = lines.next() {
if line.starts_with("```") {
let lang = line.trim_start_matches("```").trim().to_string();
let mut code = String::new();
while let Some(code_line) = lines.next() {
if code_line.starts_with("```") {
break;
}
code.push_str(code_line);
code.push('\n');
}
blocks.push((lang, code.trim().to_string()));
}
}
blocks
}