use crate::error::SynaError;
use thiserror::Error;
pub const QUERY_SUCCESS: i32 = 1;
pub const QUERY_ERR_PARSE: i32 = -10;
pub const QUERY_ERR_TYPE: i32 = -11;
pub const QUERY_ERR_UNKNOWN_FUNC: i32 = -12;
pub const QUERY_ERR_TIMEOUT: i32 = -13;
pub const QUERY_ERR_INVALID_REGEX: i32 = -14;
pub const QUERY_ERR_NON_NUMERIC: i32 = -15;
pub const QUERY_ERR_INSUFFICIENT_DATA: i32 = -16;
pub const QUERY_ERR_DATABASE: i32 = -17;
pub const QUERY_ERR_INTERNAL: i32 = -100;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseError {
pub message: String,
pub line: usize,
pub column: usize,
pub snippet: String,
}
impl std::fmt::Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Parse error at {}:{}: {}\n {}",
self.line, self.column, self.message, self.snippet
)
}
}
impl std::error::Error for ParseError {}
#[derive(Debug, Error)]
pub enum QueryError {
#[error("Parse error at {line}:{column}: {message}")]
Parse {
message: String,
line: usize,
column: usize,
},
#[error("Unknown function: {0}")]
UnknownFunction(String),
#[error("Type error: expected {expected}, got {actual}")]
TypeError {
expected: String,
actual: String,
},
#[error("Query timeout after {0}ms")]
Timeout(u64),
#[error("Invalid regex: {0}")]
InvalidRegex(String),
#[error("Non-numeric aggregation on type: {0}")]
NonNumericAggregation(String),
#[error("Insufficient data: need {required}, have {actual}")]
InsufficientData {
required: usize,
actual: usize,
},
#[error("Database error: {0}")]
Database(#[from] SynaError),
#[error("Internal error: {0}")]
Internal(String),
}
impl From<ParseError> for QueryError {
fn from(err: ParseError) -> Self {
QueryError::Parse {
message: err.message,
line: err.line,
column: err.column,
}
}
}
impl From<&QueryError> for i32 {
fn from(err: &QueryError) -> Self {
match err {
QueryError::Parse { .. } => QUERY_ERR_PARSE,
QueryError::TypeError { .. } => QUERY_ERR_TYPE,
QueryError::UnknownFunction(_) => QUERY_ERR_UNKNOWN_FUNC,
QueryError::Timeout(_) => QUERY_ERR_TIMEOUT,
QueryError::InvalidRegex(_) => QUERY_ERR_INVALID_REGEX,
QueryError::NonNumericAggregation(_) => QUERY_ERR_NON_NUMERIC,
QueryError::InsufficientData { .. } => QUERY_ERR_INSUFFICIENT_DATA,
QueryError::Database(_) => QUERY_ERR_DATABASE,
QueryError::Internal(_) => QUERY_ERR_INTERNAL,
}
}
}
impl From<QueryError> for i32 {
fn from(err: QueryError) -> Self {
(&err).into()
}
}
pub type QueryResultT<T> = Result<T, QueryError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ffi_error_codes_are_stable() {
let e = QueryError::Timeout(500);
let code: i32 = (&e).into();
assert_eq!(code, QUERY_ERR_TIMEOUT);
}
#[test]
fn parse_error_into_query_error() {
let pe = ParseError {
message: "expected SELECT".into(),
line: 1,
column: 1,
snippet: "SELEC * FROM ...".into(),
};
let qe: QueryError = pe.into();
let code: i32 = qe.into();
assert_eq!(code, QUERY_ERR_PARSE);
}
#[test]
fn parse_error_display_includes_location() {
let pe = ParseError {
message: "expected keyword".into(),
line: 3,
column: 7,
snippet: "...".into(),
};
let s = format!("{}", pe);
assert!(s.contains("3:7"));
assert!(s.contains("expected keyword"));
}
}