1use spacetimedb_data_structures::error_stream::ErrorStream;
2use spacetimedb_lib::db::raw_def::v9::{Lifecycle, RawScopedTypeNameV9};
3use spacetimedb_lib::{ProductType, SumType};
4use spacetimedb_primitives::{ColId, ColList, ColSet};
5use spacetimedb_sats::algebraic_type::fmt::fmt_algebraic_type;
6use spacetimedb_sats::{bsatn::DecodeError, raw_identifier::RawIdentifier, AlgebraicType, AlgebraicTypeRef};
7use std::fmt;
8
9use crate::def::{FunctionKind, ScopedTypeName};
10use crate::identifier::Identifier;
11use crate::type_for_generate::ClientCodegenError;
12
13pub type ValidationErrors = ErrorStream<ValidationError>;
15
16#[derive(thiserror::Error, Debug, PartialOrd, Ord, PartialEq, Eq)]
21#[non_exhaustive]
22pub enum ValidationError {
23 #[error("name `{name}` is used for multiple entities")]
24 DuplicateName { name: RawIdentifier },
25 #[error("name `{name}` is used for multiple types")]
26 DuplicateTypeName { name: ScopedTypeName },
27 #[error("Multiple reducers defined for lifecycle event {lifecycle:?}")]
28 DuplicateLifecycle { lifecycle: Lifecycle },
29 #[error("module contains invalid identifier: {error}")]
30 IdentifierError { error: IdentifierError },
31 #[error("table `{}` has unnamed column `{}`, which is forbidden.", column.table, column.column)]
32 UnnamedColumn { column: RawColumnName },
33 #[error("type `{type_name:?}` is not annotated with a custom ordering, but uses one: {bad_type}")]
34 TypeHasIncorrectOrdering {
35 type_name: RawScopedTypeNameV9,
36 ref_: AlgebraicTypeRef,
37 bad_type: PrettyAlgebraicType,
39 },
40 #[error("column `{column}` referenced by def {def} not found in table `{table}`")]
41 ColumnNotFound {
42 table: RawIdentifier,
43 def: RawIdentifier,
44 column: ColId,
45 },
46 #[error(
47 "{column} type {ty} does not match corresponding element at position {pos} in product type `{product_type}`"
48 )]
49 ColumnDefMalformed {
50 column: RawColumnName,
51 ty: PrettyAlgebraicType,
52 pos: ColId,
53 product_type: PrettyAlgebraicType,
54 },
55 #[error("table `{table}` has multiple primary key annotations")]
56 RepeatedPrimaryKey { table: RawIdentifier },
57 #[error("Attempt to define {column} with more than 1 auto_inc sequence")]
58 OneAutoInc { column: RawColumnName },
59 #[error("No index found to support unique constraint `{constraint}` for columns `{columns:?}`")]
60 UniqueConstraintWithoutIndex { constraint: RawIdentifier, columns: ColSet },
61 #[error("Direct index does not support type `{ty}` in column `{column}` in index `{index}`")]
62 DirectIndexOnBadType {
63 index: RawIdentifier,
64 column: RawIdentifier,
65 ty: PrettyAlgebraicType,
66 },
67 #[error("def `{def}` has duplicate columns: {columns:?}")]
68 DuplicateColumns { def: RawIdentifier, columns: ColList },
69 #[error("invalid sequence column type: {column} with type `{column_type:?}` in sequence `{sequence}`")]
70 InvalidSequenceColumnType {
71 sequence: RawIdentifier,
72 column: RawColumnName,
73 column_type: PrettyAlgebraicType,
74 },
75 #[error("invalid sequence range information: expected {min_value:?} <= {start:?} <= {max_value:?} in sequence `{sequence}`")]
76 InvalidSequenceRange {
77 sequence: RawIdentifier,
78 min_value: Option<i128>,
79 start: Option<i128>,
80 max_value: Option<i128>,
81 },
82 #[error("View {view} has invalid return type {ty}")]
83 InvalidViewReturnType {
84 view: RawIdentifier,
85 ty: PrettyAlgebraicType,
86 },
87 #[error("Table {table} has invalid product_type_ref {ref_}")]
88 InvalidProductTypeRef {
89 table: RawIdentifier,
90 ref_: AlgebraicTypeRef,
91 },
92 #[error("Type {type_name:?} has invalid ref: {ref_}")]
93 InvalidTypeRef {
94 type_name: RawScopedTypeNameV9,
95 ref_: AlgebraicTypeRef,
96 },
97 #[error("A scheduled table must have columns `scheduled_id: u64` and `scheduled_at: ScheduledAt`, but table `{table}` has columns {columns:?}")]
98 ScheduledIncorrectColumns { table: RawIdentifier, columns: ProductType },
99 #[error("error at {location}: {error}")]
100 ClientCodegenError {
101 location: TypeLocation,
102 error: ClientCodegenError,
103 },
104 #[error("Missing type definition for ref: {ref_}, holds type: {ty}")]
105 MissingTypeDef {
106 ref_: AlgebraicTypeRef,
107 ty: PrettyAlgebraicType,
108 },
109 #[error("{column} is primary key but has no unique constraint")]
110 MissingPrimaryKeyUniqueConstraint { column: RawColumnName },
111 #[error("Table {table} should have a type definition for its product_type_element, but does not")]
112 TableTypeNameMismatch { table: Identifier },
113 #[error("Schedule {schedule} refers to a scheduled reducer or procedure {function} that does not exist")]
114 MissingScheduledFunction { schedule: Identifier, function: Identifier },
115 #[error("Scheduled {function_kind} {function_name} expected to have type {expected}, but has type {actual}")]
116 IncorrectScheduledFunctionParams {
117 function_name: RawIdentifier,
118 function_kind: FunctionKind,
119 expected: PrettyAlgebraicType,
120 actual: PrettyAlgebraicType,
121 },
122 #[error("Table name is reserved for system use: {table}")]
123 TableNameReserved { table: Identifier },
124 #[error("Row-level security invalid: `{error}`, query: `{sql}")]
125 InvalidRowLevelQuery { sql: String, error: String },
126 #[error("Failed to deserialize default value for table {table} column {col_id}: {err}")]
127 ColumnDefaultValueMalformed {
128 table: RawIdentifier,
129 col_id: ColId,
130 err: DecodeError,
131 },
132 #[error("Multiple default values for table {table} column {col_id}")]
133 MultipleColumnDefaultValues { table: RawIdentifier, col_id: ColId },
134 #[error("Table {table} not found")]
135 TableNotFound { table: RawIdentifier },
136 #[error("Name {name} is used for multiple reducers, procedures and/or views")]
137 DuplicateFunctionName { name: Identifier },
138 #[error("lifecycle event {lifecycle:?} without reducer")]
139 LifecycleWithoutReducer { lifecycle: Lifecycle },
140 #[error("lifecycle event {lifecycle:?} assigned multiple reducers")]
141 DuplicateLifeCycle { lifecycle: Lifecycle },
142 #[error("table {table} is assigned in multiple schedules")]
143 DuplicateSchedule { table: Identifier },
144 #[error("table {} corresponding to schedule {} not found", table_name, schedule_name)]
145 MissingScheduleTable {
146 table_name: RawIdentifier,
147 schedule_name: Identifier,
148 },
149 #[error("reducer {reducer_name} has invalid return type: found Result<{ok_type}, {err_type}>")]
150 InvalidReducerReturnType {
151 reducer_name: RawIdentifier,
152 ok_type: PrettyAlgebraicType,
153 err_type: PrettyAlgebraicType,
154 },
155}
156
157#[derive(PartialOrd, Ord, PartialEq, Eq)]
159pub struct PrettyAlgebraicType(pub AlgebraicType);
160
161impl fmt::Display for PrettyAlgebraicType {
162 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163 fmt_algebraic_type(&self.0).fmt(f)
164 }
165}
166impl fmt::Debug for PrettyAlgebraicType {
167 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168 <Self as fmt::Display>::fmt(self, f)
169 }
170}
171impl From<AlgebraicType> for PrettyAlgebraicType {
172 fn from(ty: AlgebraicType) -> Self {
173 Self(ty)
174 }
175}
176impl From<ProductType> for PrettyAlgebraicType {
177 fn from(ty: ProductType) -> Self {
178 let ty: AlgebraicType = ty.into();
179 Self(ty)
180 }
181}
182impl From<SumType> for PrettyAlgebraicType {
183 fn from(ty: SumType) -> Self {
184 let ty: AlgebraicType = ty.into();
185 Self(ty)
186 }
187}
188
189#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
191pub enum TypeLocation {
192 ReducerArg {
194 reducer_name: RawIdentifier,
195 position: usize,
196 arg_name: Option<RawIdentifier>,
197 },
198 ProcedureArg {
200 procedure_name: RawIdentifier,
201 position: usize,
202 arg_name: Option<RawIdentifier>,
203 },
204 ViewArg {
206 view_name: RawIdentifier,
207 position: usize,
208 arg_name: Option<RawIdentifier>,
209 },
210 ProcedureReturn { procedure_name: RawIdentifier },
212 ViewReturn { view_name: RawIdentifier },
214 InTypespace {
216 ref_: AlgebraicTypeRef,
218 },
219}
220
221impl fmt::Display for TypeLocation {
222 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223 match self {
224 TypeLocation::ReducerArg {
225 reducer_name,
226 position,
227 arg_name,
228 } => {
229 write!(f, "reducer `{reducer_name}` argument {position}")?;
230 if let Some(arg_name) = arg_name {
231 write!(f, " (`{arg_name}`)")?;
232 }
233 Ok(())
234 }
235 TypeLocation::ProcedureArg {
236 procedure_name,
237 position,
238 arg_name,
239 } => {
240 write!(f, "procedure `{procedure_name}` argument {position}")?;
241 if let Some(arg_name) = arg_name {
242 write!(f, " (`{arg_name}`)")?;
243 }
244 Ok(())
245 }
246 TypeLocation::ViewArg {
247 view_name,
248 position,
249 arg_name,
250 } => {
251 write!(f, "view `{view_name}` argument {position}")?;
252 if let Some(arg_name) = arg_name {
253 write!(f, " (`{arg_name}`)")?;
254 }
255 Ok(())
256 }
257 TypeLocation::ProcedureReturn { procedure_name } => {
258 write!(f, "procedure `{procedure_name}` return value")
259 }
260 TypeLocation::ViewReturn { view_name } => {
261 write!(f, "view `{view_name}` return value")
262 }
263 TypeLocation::InTypespace { ref_ } => {
264 write!(f, "typespace ref `{ref_}`")
265 }
266 }
267 }
268}
269
270#[derive(Debug, PartialOrd, Ord, PartialEq, Eq)]
272pub struct RawColumnName {
273 pub table: RawIdentifier,
275 pub column: RawIdentifier,
277}
278
279impl RawColumnName {
280 pub fn new(table: impl Into<RawIdentifier>, column: impl Into<RawIdentifier>) -> Self {
282 Self {
283 table: table.into(),
284 column: column.into(),
285 }
286 }
287}
288
289impl fmt::Display for RawColumnName {
290 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291 write!(f, "table `{}` column `{}`", self.table, self.column)
292 }
293}
294
295#[derive(thiserror::Error, Debug, PartialEq, Eq, PartialOrd, Ord)]
297pub enum IdentifierError {
298 #[error(
306 "Identifier `{name}` is not in normalization form C according to Unicode Standard Annex 15 \
307 (http://www.unicode.org/reports/tr15/) and cannot be used for entities in a module."
308 )]
309 NotCanonicalized { name: RawIdentifier },
310
311 #[error("Identifier `{name}` is reserved by spacetimedb and cannot be used for entities in a module.")]
313 Reserved { name: RawIdentifier },
314
315 #[error(
316 "Identifier `{name}`'s starting character '{invalid_start}' is neither an underscore ('_') nor a \
317 Unicode XID_start character (according to Unicode Standard Annex 31, https://www.unicode.org/reports/tr31/) \
318 and cannot be used for entities in a module."
319 )]
320 InvalidStart { name: RawIdentifier, invalid_start: char },
321
322 #[error(
323 "Identifier `{name}` contains a character '{invalid_continue}' that is not an XID_continue character \
324 (according to Unicode Standard Annex 31, https://www.unicode.org/reports/tr31/) \
325 and cannot be used for entities in a module."
326 )]
327 InvalidContinue {
328 name: RawIdentifier,
329 invalid_continue: char,
330 },
331 #[error("Empty identifiers are forbidden.")]
333 Empty {},
334}