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 AmbiguousFunction,
21 AmbiguousColumn,
22 TypeMismatch,
23 MissingAnnotation,
24 InvalidAnnotation,
25 ColumnCountMismatch,
26 DuplicateAlias,
27 InvalidRecursion,
28 InvalidConfig,
32 UnresolvedType,
45 InternalError,
46}
47
48impl fmt::Display for ErrorCode {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 match self {
51 ErrorCode::SyntaxError => write!(f, "SYNTAX_ERROR"),
52 ErrorCode::UnknownTable => write!(f, "UNKNOWN_TABLE"),
53 ErrorCode::UnknownColumn => write!(f, "UNKNOWN_COLUMN"),
54 ErrorCode::UnknownFunction => write!(f, "UNKNOWN_FUNCTION"),
55 ErrorCode::AmbiguousFunction => write!(f, "AMBIGUOUS_FUNCTION"),
56 ErrorCode::AmbiguousColumn => write!(f, "AMBIGUOUS_COLUMN"),
57 ErrorCode::TypeMismatch => write!(f, "TYPE_MISMATCH"),
58 ErrorCode::MissingAnnotation => write!(f, "MISSING_ANNOTATION"),
59 ErrorCode::InvalidAnnotation => write!(f, "INVALID_ANNOTATION"),
60 ErrorCode::ColumnCountMismatch => write!(f, "COLUMN_COUNT_MISMATCH"),
61 ErrorCode::DuplicateAlias => write!(f, "DUPLICATE_ALIAS"),
62 ErrorCode::InvalidRecursion => write!(f, "INVALID_RECURSION"),
63 ErrorCode::InvalidConfig => write!(f, "INVALID_CONFIG"),
64 ErrorCode::UnresolvedType => write!(f, "UNRESOLVED_TYPE"),
65 ErrorCode::InternalError => write!(f, "INTERNAL_ERROR"),
66 }
67 }
68}
69
70impl fmt::Display for ScytheError {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 write!(f, "{}: {}", self.code, self.message)
73 }
74}
75
76impl std::error::Error for ScytheError {}
77
78impl ScytheError {
79 pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
80 Self {
81 code,
82 message: message.into(),
83 }
84 }
85
86 pub fn syntax(msg: impl Into<String>) -> Self {
87 Self::new(ErrorCode::SyntaxError, msg)
88 }
89
90 pub fn unknown_table(name: &str) -> Self {
91 Self::new(ErrorCode::UnknownTable, format!("table \"{name}\" does not exist"))
92 }
93
94 pub fn unknown_column(name: &str) -> Self {
95 Self::new(ErrorCode::UnknownColumn, format!("column \"{name}\" does not exist"))
96 }
97
98 pub fn unknown_function(name: &str) -> Self {
99 Self::new(
100 ErrorCode::UnknownFunction,
101 format!("function \"{name}\" does not exist"),
102 )
103 }
104
105 pub fn ambiguous_function(name: &str) -> Self {
106 Self::new(
107 ErrorCode::AmbiguousFunction,
108 format!("function call \"{name}\" is ambiguous"),
109 )
110 }
111
112 pub fn ambiguous_column(name: &str) -> Self {
113 Self::new(ErrorCode::AmbiguousColumn, format!("column \"{name}\" is ambiguous"))
114 }
115
116 pub fn type_mismatch(msg: impl Into<String>) -> Self {
117 Self::new(ErrorCode::TypeMismatch, msg)
118 }
119
120 pub fn missing_annotation(what: &str) -> Self {
121 Self::new(ErrorCode::MissingAnnotation, format!("missing @{what} annotation"))
122 }
123
124 pub fn invalid_annotation(msg: impl Into<String>) -> Self {
125 Self::new(ErrorCode::InvalidAnnotation, msg)
126 }
127
128 pub fn column_count_mismatch(left: usize, right: usize) -> Self {
129 Self::new(
130 ErrorCode::ColumnCountMismatch,
131 format!("column count mismatch: {left} vs {right}"),
132 )
133 }
134
135 pub fn duplicate_alias(name: &str) -> Self {
136 Self::new(ErrorCode::DuplicateAlias, format!("duplicate column alias \"{name}\""))
137 }
138
139 pub fn cte_column_alias_mismatch(alias_count: usize, column_count: usize) -> Self {
144 Self::new(
145 ErrorCode::ColumnCountMismatch,
146 format!("CTE column alias list has {alias_count} entries but the CTE body produces {column_count} columns"),
147 )
148 }
149
150 pub fn invalid_recursion(msg: impl Into<String>) -> Self {
151 Self::new(ErrorCode::InvalidRecursion, msg)
152 }
153
154 pub fn invalid_config(msg: impl Into<String>) -> Self {
161 Self::new(ErrorCode::InvalidConfig, msg)
162 }
163
164 pub fn untypeable_record(subject: &str, column: &str, function: &str) -> Self {
180 Self::new(
181 ErrorCode::UnresolvedType,
182 format!(
183 "{subject} \"{column}\": {function}(...) in the select list returns PostgreSQL's anonymous \
184 `record` type, which scythe cannot resolve to a type it can generate code for -- move it \
185 into the FROM clause instead (e.g. `FROM ..., {function}(...) AS {column}`) and select \
186 {column}'s fields directly instead of the whole record"
187 ),
188 )
189 }
190
191 pub fn untypeable_row_constructor(subject: &str, column: &str) -> Self {
195 Self::new(
196 ErrorCode::UnresolvedType,
197 format!(
198 "{subject} \"{column}\": ROW(...) builds a row with no single neutral type scythe can name -- \
199 CAST it to a named composite type, or select its fields individually instead of one ROW(...) value"
200 ),
201 )
202 }
203
204 pub fn unresolved_expression_type(subject: &str, column: &str) -> Self {
210 Self::new(
211 ErrorCode::UnresolvedType,
212 format!("{subject} \"{column}\": scythe could not determine a type for this expression"),
213 )
214 }
215}