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};
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: Identifier },
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("Only Btree Indexes are supported: index `{index}` is not a btree")]
61 OnlyBtree { index: RawIdentifier },
62 #[error("def `{def}` has duplicate columns: {columns:?}")]
63 DuplicateColumns { def: RawIdentifier, columns: ColList },
64 #[error("invalid sequence column type: {column} with type `{column_type:?}` in sequence `{sequence}`")]
65 InvalidSequenceColumnType {
66 sequence: RawIdentifier,
67 column: RawColumnName,
68 column_type: PrettyAlgebraicType,
69 },
70 #[error("invalid sequence range information: expected {min_value:?} <= {start:?} <= {max_value:?} in sequence `{sequence}`")]
71 InvalidSequenceRange {
72 sequence: RawIdentifier,
73 min_value: Option<i128>,
74 start: Option<i128>,
75 max_value: Option<i128>,
76 },
77 #[error("Table {table} has invalid product_type_ref {ref_}")]
78 InvalidProductTypeRef {
79 table: RawIdentifier,
80 ref_: AlgebraicTypeRef,
81 },
82 #[error("Type {type_name:?} has invalid ref: {ref_}")]
83 InvalidTypeRef {
84 type_name: RawScopedTypeNameV9,
85 ref_: AlgebraicTypeRef,
86 },
87 #[error("A scheduled table must have columns `scheduled_id: u64` and `scheduled_at: ScheduledAt`, but table `{table}` has columns {columns:?}")]
88 ScheduledIncorrectColumns { table: RawIdentifier, columns: ProductType },
89 #[error("error at {location}: {error}")]
90 ClientCodegenError {
91 location: TypeLocation<'static>,
92 error: ClientCodegenError,
93 },
94 #[error("Missing type definition for ref: {ref_}, holds type: {ty}")]
95 MissingTypeDef {
96 ref_: AlgebraicTypeRef,
97 ty: PrettyAlgebraicType,
98 },
99 #[error("{column} is primary key but has no unique constraint")]
100 MissingPrimaryKeyUniqueConstraint { column: RawColumnName },
101 #[error("Table {table} should have a type definition for its product_type_element, but does not")]
102 TableTypeNameMismatch { table: Identifier },
103 #[error("Schedule {schedule} refers to a scheduled reducer {reducer} that does not exist")]
104 MissingScheduledReducer { schedule: Identifier, reducer: Identifier },
105 #[error("Scheduled reducer {reducer} expected to have type {expected}, but has type {actual}")]
106 IncorrectScheduledReducerParams {
107 reducer: RawIdentifier,
108 expected: PrettyAlgebraicType,
109 actual: PrettyAlgebraicType,
110 },
111 #[error("Table name is reserved for system use: {table}")]
112 TableNameReserved { table: Identifier },
113 #[error("Row-level security invalid: `{error}`, query: `{sql}")]
114 InvalidRowLevelQuery { sql: String, error: String },
115}
116
117#[derive(PartialOrd, Ord, PartialEq, Eq)]
119pub struct PrettyAlgebraicType(pub AlgebraicType);
120
121impl fmt::Display for PrettyAlgebraicType {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 fmt_algebraic_type(&self.0).fmt(f)
124 }
125}
126impl fmt::Debug for PrettyAlgebraicType {
127 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128 <Self as fmt::Display>::fmt(self, f)
129 }
130}
131impl From<AlgebraicType> for PrettyAlgebraicType {
132 fn from(ty: AlgebraicType) -> Self {
133 Self(ty)
134 }
135}
136impl From<ProductType> for PrettyAlgebraicType {
137 fn from(ty: ProductType) -> Self {
138 let ty: AlgebraicType = ty.into();
139 Self(ty)
140 }
141}
142impl From<SumType> for PrettyAlgebraicType {
143 fn from(ty: SumType) -> Self {
144 let ty: AlgebraicType = ty.into();
145 Self(ty)
146 }
147}
148
149#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
151pub enum TypeLocation<'a> {
152 ReducerArg {
154 reducer_name: Cow<'a, str>,
155 position: usize,
156 arg_name: Option<Cow<'a, str>>,
157 },
158 InTypespace {
160 ref_: AlgebraicTypeRef,
162 },
163}
164impl TypeLocation<'_> {
165 pub fn make_static(self) -> TypeLocation<'static> {
168 match self {
169 TypeLocation::ReducerArg {
170 reducer_name,
171 position,
172 arg_name,
173 } => TypeLocation::ReducerArg {
174 reducer_name: reducer_name.to_string().into(),
175 position,
176 arg_name: arg_name.map(|s| s.to_string().into()),
177 },
178 TypeLocation::InTypespace { ref_ } => TypeLocation::InTypespace { ref_ },
180 }
181 }
182}
183
184impl fmt::Display for TypeLocation<'_> {
185 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186 match self {
187 TypeLocation::ReducerArg {
188 reducer_name,
189 position,
190 arg_name,
191 } => {
192 write!(f, "reducer `{}` argument {}", reducer_name, position)?;
193 if let Some(arg_name) = arg_name {
194 write!(f, " (`{}`)", arg_name)?;
195 }
196 Ok(())
197 }
198 TypeLocation::InTypespace { ref_ } => {
199 write!(f, "typespace ref `{}`", ref_)
200 }
201 }
202 }
203}
204
205#[derive(Debug, PartialOrd, Ord, PartialEq, Eq)]
207pub struct RawColumnName {
208 pub table: RawIdentifier,
210 pub column: RawIdentifier,
212}
213
214impl RawColumnName {
215 pub fn new(table: impl Into<RawIdentifier>, column: impl Into<RawIdentifier>) -> Self {
217 Self {
218 table: table.into(),
219 column: column.into(),
220 }
221 }
222}
223
224impl fmt::Display for RawColumnName {
225 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226 write!(f, "table `{}` column `{}`", self.table, self.column)
227 }
228}
229
230#[derive(thiserror::Error, Debug, PartialEq, Eq, PartialOrd, Ord)]
232pub enum IdentifierError {
233 #[error(
241 "Identifier `{name}` is not in normalization form C according to Unicode Standard Annex 15 \
242 (http://www.unicode.org/reports/tr15/) and cannot be used for entities in a module."
243 )]
244 NotCanonicalized { name: RawIdentifier },
245
246 #[error("Identifier `{name}` is reserved by spacetimedb and cannot be used for entities in a module.")]
248 Reserved { name: RawIdentifier },
249
250 #[error(
251 "Identifier `{name}`'s starting character '{invalid_start}' is neither an underscore ('_') nor a \
252 Unicode XID_start character (according to Unicode Standard Annex 31, https://www.unicode.org/reports/tr31/) \
253 and cannot be used for entities in a module."
254 )]
255 InvalidStart { name: RawIdentifier, invalid_start: char },
256
257 #[error(
258 "Identifier `{name}` contains a character '{invalid_continue}' that is not an XID_continue character \
259 (according to Unicode Standard Annex 31, https://www.unicode.org/reports/tr31/) \
260 and cannot be used for entities in a module."
261 )]
262 InvalidContinue {
263 name: RawIdentifier,
264 invalid_continue: char,
265 },
266 #[error("Empty identifiers are forbidden.")]
268 Empty {},
269}