1use std::fmt;
2
3#[derive(Debug)]
4pub struct ScytheError {
5 pub code: ErrorCode,
6 pub message: String,
7}
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14#[non_exhaustive]
15pub enum ErrorCode {
16 SyntaxError,
17 UnknownTable,
18 UnknownColumn,
19 UnknownFunction,
20 AmbiguousColumn,
21 TypeMismatch,
22 MissingAnnotation,
23 InvalidAnnotation,
24 ColumnCountMismatch,
25 DuplicateAlias,
26 InvalidRecursion,
27 InvalidConfig,
31 UnresolvedType,
44 InternalError,
45}
46
47impl fmt::Display for ErrorCode {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 match self {
50 ErrorCode::SyntaxError => write!(f, "SYNTAX_ERROR"),
51 ErrorCode::UnknownTable => write!(f, "UNKNOWN_TABLE"),
52 ErrorCode::UnknownColumn => write!(f, "UNKNOWN_COLUMN"),
53 ErrorCode::UnknownFunction => write!(f, "UNKNOWN_FUNCTION"),
54 ErrorCode::AmbiguousColumn => write!(f, "AMBIGUOUS_COLUMN"),
55 ErrorCode::TypeMismatch => write!(f, "TYPE_MISMATCH"),
56 ErrorCode::MissingAnnotation => write!(f, "MISSING_ANNOTATION"),
57 ErrorCode::InvalidAnnotation => write!(f, "INVALID_ANNOTATION"),
58 ErrorCode::ColumnCountMismatch => write!(f, "COLUMN_COUNT_MISMATCH"),
59 ErrorCode::DuplicateAlias => write!(f, "DUPLICATE_ALIAS"),
60 ErrorCode::InvalidRecursion => write!(f, "INVALID_RECURSION"),
61 ErrorCode::InvalidConfig => write!(f, "INVALID_CONFIG"),
62 ErrorCode::UnresolvedType => write!(f, "UNRESOLVED_TYPE"),
63 ErrorCode::InternalError => write!(f, "INTERNAL_ERROR"),
64 }
65 }
66}
67
68impl fmt::Display for ScytheError {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 write!(f, "{}: {}", self.code, self.message)
71 }
72}
73
74impl std::error::Error for ScytheError {}
75
76impl ScytheError {
77 pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
78 Self {
79 code,
80 message: message.into(),
81 }
82 }
83
84 pub fn syntax(msg: impl Into<String>) -> Self {
85 Self::new(ErrorCode::SyntaxError, msg)
86 }
87
88 pub fn unknown_table(name: &str) -> Self {
89 Self::new(ErrorCode::UnknownTable, format!("table \"{name}\" does not exist"))
90 }
91
92 pub fn unknown_column(name: &str) -> Self {
93 Self::new(ErrorCode::UnknownColumn, format!("column \"{name}\" does not exist"))
94 }
95
96 pub fn unknown_function(name: &str) -> Self {
97 Self::new(
98 ErrorCode::UnknownFunction,
99 format!("function \"{name}\" does not exist"),
100 )
101 }
102
103 pub fn ambiguous_column(name: &str) -> Self {
104 Self::new(ErrorCode::AmbiguousColumn, format!("column \"{name}\" is ambiguous"))
105 }
106
107 pub fn type_mismatch(msg: impl Into<String>) -> Self {
108 Self::new(ErrorCode::TypeMismatch, msg)
109 }
110
111 pub fn missing_annotation(what: &str) -> Self {
112 Self::new(ErrorCode::MissingAnnotation, format!("missing @{what} annotation"))
113 }
114
115 pub fn invalid_annotation(msg: impl Into<String>) -> Self {
116 Self::new(ErrorCode::InvalidAnnotation, msg)
117 }
118
119 pub fn column_count_mismatch(left: usize, right: usize) -> Self {
120 Self::new(
121 ErrorCode::ColumnCountMismatch,
122 format!("column count mismatch: {left} vs {right}"),
123 )
124 }
125
126 pub fn duplicate_alias(name: &str) -> Self {
127 Self::new(ErrorCode::DuplicateAlias, format!("duplicate column alias \"{name}\""))
128 }
129
130 pub fn cte_column_alias_mismatch(alias_count: usize, column_count: usize) -> Self {
135 Self::new(
136 ErrorCode::ColumnCountMismatch,
137 format!("CTE column alias list has {alias_count} entries but the CTE body produces {column_count} columns"),
138 )
139 }
140
141 pub fn invalid_recursion(msg: impl Into<String>) -> Self {
142 Self::new(ErrorCode::InvalidRecursion, msg)
143 }
144
145 pub fn invalid_config(msg: impl Into<String>) -> Self {
152 Self::new(ErrorCode::InvalidConfig, msg)
153 }
154
155 pub fn untypeable_record(subject: &str, column: &str, function: &str) -> Self {
171 Self::new(
172 ErrorCode::UnresolvedType,
173 format!(
174 "{subject} \"{column}\": {function}(...) in the select list returns PostgreSQL's anonymous \
175 `record` type, which scythe cannot resolve to a type it can generate code for -- move it \
176 into the FROM clause instead (e.g. `FROM ..., {function}(...) AS {column}`) and select \
177 {column}'s fields directly instead of the whole record"
178 ),
179 )
180 }
181
182 pub fn untypeable_row_constructor(subject: &str, column: &str) -> Self {
186 Self::new(
187 ErrorCode::UnresolvedType,
188 format!(
189 "{subject} \"{column}\": ROW(...) builds a row with no single neutral type scythe can name -- \
190 CAST it to a named composite type, or select its fields individually instead of one ROW(...) value"
191 ),
192 )
193 }
194
195 pub fn unresolved_expression_type(subject: &str, column: &str) -> Self {
201 Self::new(
202 ErrorCode::UnresolvedType,
203 format!("{subject} \"{column}\": scythe could not determine a type for this expression"),
204 )
205 }
206}