1use spacetimedb_data_structures::error_stream::ErrorStream;
2use spacetimedb_lib::db::raw_def::v9::{Lifecycle, RawIdentifier, RawScopedTypeNameV9};
3use spacetimedb_lib::{ProductType, SumType};
4use spacetimedb_primitives::{ColId, ColList, ColSet};
5use spacetimedb_sats::algebraic_type::fmt::fmt_algebraic_type;
6use spacetimedb_sats::{AlgebraicType, AlgebraicTypeRef};
7use std::borrow::Cow;
8use std::fmt;
9
10use crate::def::ScopedTypeName;
11use crate::identifier::Identifier;
12use crate::type_for_generate::ClientCodegenError;
13
14pub type ValidationErrors = ErrorStream<ValidationError>;
16
17#[derive(thiserror::Error, Debug, PartialOrd, Ord, PartialEq, Eq)]
22#[non_exhaustive]
23pub enum ValidationError {
24 #[error("name `{name}` is used for multiple entities")]
25 DuplicateName { name: Box<str> },
26 #[error("name `{name}` is used for multiple types")]
27 DuplicateTypeName { name: ScopedTypeName },
28 #[error("Multiple reducers defined for lifecycle event {lifecycle:?}")]
29 DuplicateLifecycle { lifecycle: Lifecycle },
30 #[error("module contains invalid identifier: {error}")]
31 IdentifierError { error: IdentifierError },
32 #[error("table `{}` has unnamed column `{}`, which is forbidden.", column.table, column.column)]
33 UnnamedColumn { column: RawColumnName },
34 #[error("type `{type_name:?}` is not annotated with a custom ordering, but uses one: {bad_type}")]
35 TypeHasIncorrectOrdering {
36 type_name: RawScopedTypeNameV9,
37 ref_: AlgebraicTypeRef,
38 bad_type: PrettyAlgebraicType,
40 },
41 #[error("column `{column}` referenced by def {def} not found in table `{table}`")]
42 ColumnNotFound {
43 table: RawIdentifier,
44 def: RawIdentifier,
45 column: ColId,
46 },
47 #[error(
48 "{column} type {ty} does not match corresponding element at position {pos} in product type `{product_type}`"
49 )]
50 ColumnDefMalformed {
51 column: RawColumnName,
52 ty: PrettyAlgebraicType,
53 pos: ColId,
54 product_type: PrettyAlgebraicType,
55 },
56 #[error("table `{table}` has multiple primary key annotations")]
57 RepeatedPrimaryKey { table: RawIdentifier },
58 #[error("Attempt to define {column} with more than 1 auto_inc sequence")]
59 OneAutoInc { column: RawColumnName },
60 #[error("Hash indexes are not supported: `{index}` is a hash index")]
61 HashIndexUnsupported { index: RawIdentifier },
62 #[error("No index found to support unique constraint `{constraint}` for columns `{columns:?}`")]
63 UniqueConstraintWithoutIndex { constraint: Box<str>, columns: ColSet },
64 #[error("Direct index does not support type `{ty}` in column `{column}` in index `{index}`")]
65 DirectIndexOnBadType {
66 index: RawIdentifier,
67 column: RawIdentifier,
68 ty: PrettyAlgebraicType,
69 },
70 #[error("def `{def}` has duplicate columns: {columns:?}")]
71 DuplicateColumns { def: RawIdentifier, columns: ColList },
72 #[error("invalid sequence column type: {column} with type `{column_type:?}` in sequence `{sequence}`")]
73 InvalidSequenceColumnType {
74 sequence: RawIdentifier,
75 column: RawColumnName,
76 column_type: PrettyAlgebraicType,
77 },
78 #[error("invalid sequence range information: expected {min_value:?} <= {start:?} <= {max_value:?} in sequence `{sequence}`")]
79 InvalidSequenceRange {
80 sequence: RawIdentifier,
81 min_value: Option<i128>,
82 start: Option<i128>,
83 max_value: Option<i128>,
84 },
85 #[error("Table {table} has invalid product_type_ref {ref_}")]
86 InvalidProductTypeRef {
87 table: RawIdentifier,
88 ref_: AlgebraicTypeRef,
89 },
90 #[error("Type {type_name:?} has invalid ref: {ref_}")]
91 InvalidTypeRef {
92 type_name: RawScopedTypeNameV9,
93 ref_: AlgebraicTypeRef,
94 },
95 #[error("A scheduled table must have columns `scheduled_id: u64` and `scheduled_at: ScheduledAt`, but table `{table}` has columns {columns:?}")]
96 ScheduledIncorrectColumns { table: RawIdentifier, columns: ProductType },
97 #[error("error at {location}: {error}")]
98 ClientCodegenError {
99 location: TypeLocation<'static>,
100 error: ClientCodegenError,
101 },
102 #[error("Missing type definition for ref: {ref_}, holds type: {ty}")]
103 MissingTypeDef {
104 ref_: AlgebraicTypeRef,
105 ty: PrettyAlgebraicType,
106 },
107 #[error("{column} is primary key but has no unique constraint")]
108 MissingPrimaryKeyUniqueConstraint { column: RawColumnName },
109 #[error("Table {table} should have a type definition for its product_type_element, but does not")]
110 TableTypeNameMismatch { table: Identifier },
111 #[error("Schedule {schedule} refers to a scheduled reducer {reducer} that does not exist")]
112 MissingScheduledReducer { schedule: Box<str>, reducer: Identifier },
113 #[error("Scheduled reducer {reducer} expected to have type {expected}, but has type {actual}")]
114 IncorrectScheduledReducerParams {
115 reducer: RawIdentifier,
116 expected: PrettyAlgebraicType,
117 actual: PrettyAlgebraicType,
118 },
119 #[error("Table name is reserved for system use: {table}")]
120 TableNameReserved { table: Identifier },
121 #[error("Row-level security invalid: `{error}`, query: `{sql}")]
122 InvalidRowLevelQuery { sql: String, error: String },
123}
124
125#[derive(PartialOrd, Ord, PartialEq, Eq)]
127pub struct PrettyAlgebraicType(pub AlgebraicType);
128
129impl fmt::Display for PrettyAlgebraicType {
130 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131 fmt_algebraic_type(&self.0).fmt(f)
132 }
133}
134impl fmt::Debug for PrettyAlgebraicType {
135 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136 <Self as fmt::Display>::fmt(self, f)
137 }
138}
139impl From<AlgebraicType> for PrettyAlgebraicType {
140 fn from(ty: AlgebraicType) -> Self {
141 Self(ty)
142 }
143}
144impl From<ProductType> for PrettyAlgebraicType {
145 fn from(ty: ProductType) -> Self {
146 let ty: AlgebraicType = ty.into();
147 Self(ty)
148 }
149}
150impl From<SumType> for PrettyAlgebraicType {
151 fn from(ty: SumType) -> Self {
152 let ty: AlgebraicType = ty.into();
153 Self(ty)
154 }
155}
156
157#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
159pub enum TypeLocation<'a> {
160 ReducerArg {
162 reducer_name: Cow<'a, str>,
163 position: usize,
164 arg_name: Option<Cow<'a, str>>,
165 },
166 InTypespace {
168 ref_: AlgebraicTypeRef,
170 },
171}
172impl TypeLocation<'_> {
173 pub fn make_static(self) -> TypeLocation<'static> {
176 match self {
177 TypeLocation::ReducerArg {
178 reducer_name,
179 position,
180 arg_name,
181 } => TypeLocation::ReducerArg {
182 reducer_name: reducer_name.to_string().into(),
183 position,
184 arg_name: arg_name.map(|s| s.to_string().into()),
185 },
186 TypeLocation::InTypespace { ref_ } => TypeLocation::InTypespace { ref_ },
188 }
189 }
190}
191
192impl fmt::Display for TypeLocation<'_> {
193 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194 match self {
195 TypeLocation::ReducerArg {
196 reducer_name,
197 position,
198 arg_name,
199 } => {
200 write!(f, "reducer `{}` argument {}", reducer_name, position)?;
201 if let Some(arg_name) = arg_name {
202 write!(f, " (`{}`)", arg_name)?;
203 }
204 Ok(())
205 }
206 TypeLocation::InTypespace { ref_ } => {
207 write!(f, "typespace ref `{}`", ref_)
208 }
209 }
210 }
211}
212
213#[derive(Debug, PartialOrd, Ord, PartialEq, Eq)]
215pub struct RawColumnName {
216 pub table: RawIdentifier,
218 pub column: RawIdentifier,
220}
221
222impl RawColumnName {
223 pub fn new(table: impl Into<RawIdentifier>, column: impl Into<RawIdentifier>) -> Self {
225 Self {
226 table: table.into(),
227 column: column.into(),
228 }
229 }
230}
231
232impl fmt::Display for RawColumnName {
233 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234 write!(f, "table `{}` column `{}`", self.table, self.column)
235 }
236}
237
238#[derive(thiserror::Error, Debug, PartialEq, Eq, PartialOrd, Ord)]
240pub enum IdentifierError {
241 #[error(
249 "Identifier `{name}` is not in normalization form C according to Unicode Standard Annex 15 \
250 (http://www.unicode.org/reports/tr15/) and cannot be used for entities in a module."
251 )]
252 NotCanonicalized { name: RawIdentifier },
253
254 #[error("Identifier `{name}` is reserved by spacetimedb and cannot be used for entities in a module.")]
256 Reserved { name: RawIdentifier },
257
258 #[error(
259 "Identifier `{name}`'s starting character '{invalid_start}' is neither an underscore ('_') nor a \
260 Unicode XID_start character (according to Unicode Standard Annex 31, https://www.unicode.org/reports/tr31/) \
261 and cannot be used for entities in a module."
262 )]
263 InvalidStart { name: RawIdentifier, invalid_start: char },
264
265 #[error(
266 "Identifier `{name}` contains a character '{invalid_continue}' that is not an XID_continue character \
267 (according to Unicode Standard Annex 31, https://www.unicode.org/reports/tr31/) \
268 and cannot be used for entities in a module."
269 )]
270 InvalidContinue {
271 name: RawIdentifier,
272 invalid_continue: char,
273 },
274 #[error("Empty identifiers are forbidden.")]
276 Empty {},
277}