sql_cli/sql/parser/
web_cte_parser.rs

1/// Web CTE Parser Module
2/// Handles parsing of WEB CTEs for HTTP data fetching with custom selectors
3use super::ast::{DataFormat, HttpMethod, WebCTESpec};
4use super::lexer::Token;
5
6pub struct WebCteParser<'a> {
7    _tokens: &'a mut dyn Iterator<Item = Token>,
8    _current_token: Token,
9}
10
11impl<'a> WebCteParser<'a> {
12    pub fn new(tokens: &'a mut dyn Iterator<Item = Token>, current_token: Token) -> Self {
13        Self {
14            _tokens: tokens,
15            _current_token: current_token,
16        }
17    }
18
19    /// Main entry point to parse WEB CTE specification
20    /// Expects: URL 'url' [METHOD method] [FORMAT format] [HEADERS (...)] [CACHE n] [BODY 'body'] [FORM_FILE 'field' 'path'] [FORM_FIELD 'field' 'value']
21    pub fn parse(parser: &mut crate::sql::recursive_parser::Parser) -> Result<WebCTESpec, String> {
22        // Expect URL keyword
23        if let Token::Identifier(id) = &parser.current_token {
24            if id.to_uppercase() != "URL" {
25                return Err("Expected URL keyword in WEB CTE".to_string());
26            }
27        } else {
28            return Err("Expected URL keyword in WEB CTE".to_string());
29        }
30        parser.advance();
31
32        // Parse URL string
33        let url = match &parser.current_token {
34            Token::StringLiteral(url) => url.clone(),
35            _ => return Err("Expected URL string after URL keyword".to_string()),
36        };
37        parser.advance();
38
39        // Initialize optional fields
40        let mut format = None;
41        let mut headers = Vec::new();
42        let mut cache_seconds = None;
43        let mut method = None;
44        let mut body = None;
45        let mut json_path = None;
46        let mut form_files = Vec::new();
47        let mut form_fields = Vec::new();
48
49        // Parse optional clauses until we hit the closing parenthesis
50        while !matches!(parser.current_token, Token::RightParen)
51            && !matches!(parser.current_token, Token::Eof)
52        {
53            if let Token::Identifier(id) = &parser.current_token {
54                match id.to_uppercase().as_str() {
55                    "FORMAT" => {
56                        parser.advance();
57                        format = Some(Self::parse_data_format(parser)?);
58                    }
59                    "CACHE" => {
60                        parser.advance();
61                        cache_seconds = Some(Self::parse_cache_duration(parser)?);
62                    }
63                    "HEADERS" => {
64                        parser.advance();
65                        headers = Self::parse_headers(parser)?;
66                    }
67                    "METHOD" => {
68                        parser.advance();
69                        method = Some(Self::parse_http_method(parser)?);
70                    }
71                    "BODY" => {
72                        parser.advance();
73                        body = Some(Self::parse_body(parser)?);
74                    }
75                    "JSON_PATH" => {
76                        parser.advance();
77                        json_path = Some(Self::parse_json_path(parser)?);
78                    }
79                    "FORM_FILE" => {
80                        parser.advance();
81                        let (field_name, file_path) = Self::parse_form_file(parser)?;
82                        form_files.push((field_name, file_path));
83                    }
84                    "FORM_FIELD" => {
85                        parser.advance();
86                        let (field_name, value) = Self::parse_form_field(parser)?;
87                        form_fields.push((field_name, value));
88                    }
89                    _ => {
90                        return Err(format!(
91                            "Unexpected keyword '{}' in WEB CTE specification",
92                            id
93                        ));
94                    }
95                }
96            } else {
97                break;
98            }
99        }
100
101        Ok(WebCTESpec {
102            url,
103            format,
104            headers,
105            cache_seconds,
106            method,
107            body,
108            json_path,
109            form_files,
110            form_fields,
111            template_vars: Vec::new(), // Will be populated by template expander
112        })
113    }
114
115    fn parse_data_format(
116        parser: &mut crate::sql::recursive_parser::Parser,
117    ) -> Result<DataFormat, String> {
118        if let Token::Identifier(id) = &parser.current_token {
119            let format = match id.to_uppercase().as_str() {
120                "CSV" => DataFormat::CSV,
121                "JSON" => DataFormat::JSON,
122                "AUTO" => DataFormat::Auto,
123                _ => return Err(format!("Unknown data format: {}", id)),
124            };
125            parser.advance();
126            Ok(format)
127        } else {
128            Err("Expected data format (CSV, JSON, or AUTO)".to_string())
129        }
130    }
131
132    fn parse_cache_duration(
133        parser: &mut crate::sql::recursive_parser::Parser,
134    ) -> Result<u64, String> {
135        match &parser.current_token {
136            Token::NumberLiteral(n) => {
137                let duration = n
138                    .parse::<u64>()
139                    .map_err(|_| format!("Invalid cache duration: {}", n))?;
140                parser.advance();
141                Ok(duration)
142            }
143            _ => Err("Expected number for cache duration".to_string()),
144        }
145    }
146
147    fn parse_http_method(
148        parser: &mut crate::sql::recursive_parser::Parser,
149    ) -> Result<HttpMethod, String> {
150        if let Token::Identifier(id) = &parser.current_token {
151            let method = match id.to_uppercase().as_str() {
152                "GET" => HttpMethod::GET,
153                "POST" => HttpMethod::POST,
154                "PUT" => HttpMethod::PUT,
155                "DELETE" => HttpMethod::DELETE,
156                "PATCH" => HttpMethod::PATCH,
157                _ => return Err(format!("Unknown HTTP method: {}", id)),
158            };
159            parser.advance();
160            Ok(method)
161        } else {
162            Err("Expected HTTP method (GET, POST, PUT, DELETE, PATCH)".to_string())
163        }
164    }
165
166    fn parse_body(parser: &mut crate::sql::recursive_parser::Parser) -> Result<String, String> {
167        match &parser.current_token {
168            Token::StringLiteral(body) | Token::JsonBlock(body) => {
169                let body = body.clone();
170                parser.advance();
171                Ok(body)
172            }
173            _ => Err("Expected string literal or $JSON$ block for BODY clause".to_string()),
174        }
175    }
176
177    fn parse_json_path(
178        parser: &mut crate::sql::recursive_parser::Parser,
179    ) -> Result<String, String> {
180        match &parser.current_token {
181            Token::StringLiteral(path) => {
182                let path = path.clone();
183                parser.advance();
184                Ok(path)
185            }
186            _ => Err("Expected string literal for JSON_PATH clause".to_string()),
187        }
188    }
189
190    fn parse_form_file(
191        parser: &mut crate::sql::recursive_parser::Parser,
192    ) -> Result<(String, String), String> {
193        // Parse field name
194        let field_name = match &parser.current_token {
195            Token::StringLiteral(name) => name.clone(),
196            _ => return Err("Expected field name string after FORM_FILE".to_string()),
197        };
198        parser.advance();
199
200        // Parse file path
201        let file_path = match &parser.current_token {
202            Token::StringLiteral(path) => path.clone(),
203            _ => return Err("Expected file path string after field name".to_string()),
204        };
205        parser.advance();
206
207        Ok((field_name, file_path))
208    }
209
210    fn parse_form_field(
211        parser: &mut crate::sql::recursive_parser::Parser,
212    ) -> Result<(String, String), String> {
213        // Parse field name
214        let field_name = match &parser.current_token {
215            Token::StringLiteral(name) | Token::JsonBlock(name) => name.clone(),
216            _ => return Err("Expected field name string after FORM_FIELD".to_string()),
217        };
218        parser.advance();
219
220        // Parse field value (can be regular string or JSON block)
221        let value = match &parser.current_token {
222            Token::StringLiteral(val) | Token::JsonBlock(val) => val.clone(),
223            _ => {
224                return Err(
225                    "Expected field value string or $JSON$ block after field name".to_string(),
226                )
227            }
228        };
229        parser.advance();
230
231        Ok((field_name, value))
232    }
233
234    fn parse_headers(
235        parser: &mut crate::sql::recursive_parser::Parser,
236    ) -> Result<Vec<(String, String)>, String> {
237        parser.consume(Token::LeftParen)?;
238        let mut headers = Vec::new();
239
240        loop {
241            // Parse header name
242            let key = match &parser.current_token {
243                Token::Identifier(id) => id.clone(),
244                Token::StringLiteral(s) => s.clone(),
245                _ => return Err("Expected header name".to_string()),
246            };
247            parser.advance();
248
249            // Expect : (colon) for header key-value separator
250            if !matches!(parser.current_token, Token::Colon) {
251                // For backwards compatibility, also accept =
252                if matches!(parser.current_token, Token::Equal) {
253                    parser.advance();
254                } else {
255                    return Err("Expected ':' or '=' after header name".to_string());
256                }
257            } else {
258                parser.advance(); // consume the colon
259            }
260
261            // Parse header value
262            let value = match &parser.current_token {
263                Token::StringLiteral(s) => s.clone(),
264                _ => return Err("Expected header value as string".to_string()),
265            };
266            parser.advance();
267
268            headers.push((key, value));
269
270            // Check for comma (more headers) or closing paren (end)
271            if matches!(parser.current_token, Token::Comma) {
272                parser.advance();
273            } else if matches!(parser.current_token, Token::RightParen) {
274                parser.advance();
275                break;
276            } else {
277                return Err("Expected ',' or ')' after header value".to_string());
278            }
279        }
280
281        Ok(headers)
282    }
283}