use super::ast::{DataFormat, HttpMethod, WebCTESpec};
use super::lexer::Token;
pub struct WebCteParser<'a> {
_tokens: &'a mut dyn Iterator<Item = Token>,
_current_token: Token,
}
impl<'a> WebCteParser<'a> {
pub fn new(tokens: &'a mut dyn Iterator<Item = Token>, current_token: Token) -> Self {
Self {
_tokens: tokens,
_current_token: current_token,
}
}
pub fn parse(parser: &mut crate::sql::recursive_parser::Parser) -> Result<WebCTESpec, String> {
if let Token::Identifier(id) = &parser.current_token {
if id.to_uppercase() != "URL" {
return Err("Expected URL keyword in WEB CTE".to_string());
}
} else {
return Err("Expected URL keyword in WEB CTE".to_string());
}
parser.advance();
let url = match &parser.current_token {
Token::StringLiteral(url) => url.clone(),
_ => return Err("Expected URL string after URL keyword".to_string()),
};
parser.advance();
let mut format = None;
let mut headers = Vec::new();
let mut cache_seconds = None;
let mut method = None;
let mut body = None;
let mut json_path = None;
let mut form_files = Vec::new();
let mut form_fields = Vec::new();
while !matches!(parser.current_token, Token::RightParen)
&& !matches!(parser.current_token, Token::Eof)
{
if let Token::Identifier(id) = &parser.current_token {
match id.to_uppercase().as_str() {
"FORMAT" => {
parser.advance();
format = Some(Self::parse_data_format(parser)?);
}
"CACHE" => {
parser.advance();
cache_seconds = Some(Self::parse_cache_duration(parser)?);
}
"HEADERS" => {
parser.advance();
headers = Self::parse_headers(parser)?;
}
"METHOD" => {
parser.advance();
method = Some(Self::parse_http_method(parser)?);
}
"BODY" => {
parser.advance();
body = Some(Self::parse_body(parser)?);
}
"JSON_PATH" => {
parser.advance();
json_path = Some(Self::parse_json_path(parser)?);
}
"FORM_FILE" => {
parser.advance();
let (field_name, file_path) = Self::parse_form_file(parser)?;
form_files.push((field_name, file_path));
}
"FORM_FIELD" => {
parser.advance();
let (field_name, value) = Self::parse_form_field(parser)?;
form_fields.push((field_name, value));
}
_ => {
return Err(format!(
"Unexpected keyword '{}' in WEB CTE specification",
id
));
}
}
} else {
break;
}
}
Ok(WebCTESpec {
url,
format,
headers,
cache_seconds,
method,
body,
json_path,
form_files,
form_fields,
template_vars: Vec::new(), })
}
fn parse_data_format(
parser: &mut crate::sql::recursive_parser::Parser,
) -> Result<DataFormat, String> {
if let Token::Identifier(id) = &parser.current_token {
let format = match id.to_uppercase().as_str() {
"CSV" => DataFormat::CSV,
"JSON" => DataFormat::JSON,
"AUTO" => DataFormat::Auto,
_ => return Err(format!("Unknown data format: {}", id)),
};
parser.advance();
Ok(format)
} else {
Err("Expected data format (CSV, JSON, or AUTO)".to_string())
}
}
fn parse_cache_duration(
parser: &mut crate::sql::recursive_parser::Parser,
) -> Result<u64, String> {
match &parser.current_token {
Token::NumberLiteral(n) => {
let duration = n
.parse::<u64>()
.map_err(|_| format!("Invalid cache duration: {}", n))?;
parser.advance();
Ok(duration)
}
_ => Err("Expected number for cache duration".to_string()),
}
}
fn parse_http_method(
parser: &mut crate::sql::recursive_parser::Parser,
) -> Result<HttpMethod, String> {
if let Token::Identifier(id) = &parser.current_token {
let method = match id.to_uppercase().as_str() {
"GET" => HttpMethod::GET,
"POST" => HttpMethod::POST,
"PUT" => HttpMethod::PUT,
"DELETE" => HttpMethod::DELETE,
"PATCH" => HttpMethod::PATCH,
_ => return Err(format!("Unknown HTTP method: {}", id)),
};
parser.advance();
Ok(method)
} else {
Err("Expected HTTP method (GET, POST, PUT, DELETE, PATCH)".to_string())
}
}
fn parse_body(parser: &mut crate::sql::recursive_parser::Parser) -> Result<String, String> {
match &parser.current_token {
Token::StringLiteral(body) | Token::JsonBlock(body) => {
let body = body.clone();
parser.advance();
Ok(body)
}
_ => Err("Expected string literal or $JSON$ block for BODY clause".to_string()),
}
}
fn parse_json_path(
parser: &mut crate::sql::recursive_parser::Parser,
) -> Result<String, String> {
match &parser.current_token {
Token::StringLiteral(path) => {
let path = path.clone();
parser.advance();
Ok(path)
}
_ => Err("Expected string literal for JSON_PATH clause".to_string()),
}
}
fn parse_form_file(
parser: &mut crate::sql::recursive_parser::Parser,
) -> Result<(String, String), String> {
let field_name = match &parser.current_token {
Token::StringLiteral(name) => name.clone(),
_ => return Err("Expected field name string after FORM_FILE".to_string()),
};
parser.advance();
let file_path = match &parser.current_token {
Token::StringLiteral(path) => path.clone(),
_ => return Err("Expected file path string after field name".to_string()),
};
parser.advance();
Ok((field_name, file_path))
}
fn parse_form_field(
parser: &mut crate::sql::recursive_parser::Parser,
) -> Result<(String, String), String> {
let field_name = match &parser.current_token {
Token::StringLiteral(name) | Token::JsonBlock(name) => name.clone(),
_ => return Err("Expected field name string after FORM_FIELD".to_string()),
};
parser.advance();
let value = match &parser.current_token {
Token::StringLiteral(val) | Token::JsonBlock(val) => val.clone(),
_ => {
return Err(
"Expected field value string or $JSON$ block after field name".to_string(),
)
}
};
parser.advance();
Ok((field_name, value))
}
fn parse_headers(
parser: &mut crate::sql::recursive_parser::Parser,
) -> Result<Vec<(String, String)>, String> {
parser.consume(Token::LeftParen)?;
let mut headers = Vec::new();
loop {
let key = match &parser.current_token {
Token::Identifier(id) => id.clone(),
Token::StringLiteral(s) => s.clone(),
_ => return Err("Expected header name".to_string()),
};
parser.advance();
if !matches!(parser.current_token, Token::Colon) {
if matches!(parser.current_token, Token::Equal) {
parser.advance();
} else {
return Err("Expected ':' or '=' after header name".to_string());
}
} else {
parser.advance(); }
let value = match &parser.current_token {
Token::StringLiteral(s) => s.clone(),
_ => return Err("Expected header value as string".to_string()),
};
parser.advance();
headers.push((key, value));
if matches!(parser.current_token, Token::Comma) {
parser.advance();
} else if matches!(parser.current_token, Token::RightParen) {
parser.advance();
break;
} else {
return Err("Expected ',' or ')' after header value".to_string());
}
}
Ok(headers)
}
}