spacetimedb_codegen/
util.rs1use std::{
4 fmt::{Display, Formatter, Result},
5 ops::Deref,
6};
7
8use super::code_indenter::Indenter;
9use convert_case::{Case, Casing};
10use itertools::Itertools;
11use spacetimedb_lib::sats::layout::PrimitiveType;
12use spacetimedb_lib::version;
13use spacetimedb_lib::{db::raw_def::v9::Lifecycle, sats::AlgebraicTypeRef};
14use spacetimedb_primitives::ColList;
15use spacetimedb_schema::schema::TableSchema;
16use spacetimedb_schema::type_for_generate::ProductTypeDef;
17use spacetimedb_schema::{
18 def::{IndexDef, TableDef, TypeDef},
19 type_for_generate::TypespaceForGenerate,
20};
21use spacetimedb_schema::{
22 def::{ModuleDef, ReducerDef},
23 identifier::Identifier,
24 type_for_generate::AlgebraicTypeUse,
25};
26
27pub(super) fn fmt_fn(f: impl Fn(&mut Formatter) -> Result) -> impl Display {
29 struct FDisplay<F>(F);
30 impl<F: Fn(&mut Formatter) -> Result> Display for FDisplay<F> {
31 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
32 (self.0)(f)
33 }
34 }
35 FDisplay(f)
36}
37
38pub(super) fn collect_case<'a>(case: Case, segs: impl Iterator<Item = &'a Identifier>) -> String {
39 segs.map(|s| s.deref().to_case(case)).join(case.delim())
40}
41
42pub(super) fn print_lines(output: &mut Indenter, lines: &[&str]) {
43 for line in lines {
44 writeln!(output, "{line}");
45 }
46}
47
48pub const AUTO_GENERATED_PREFIX: &str = "// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB";
49
50const AUTO_GENERATED_FILE_COMMENT: &[&str] = &[
51 "// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE",
52 "// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.",
53 "",
54];
55
56pub(super) fn print_auto_generated_file_comment(output: &mut Indenter) {
57 print_lines(output, AUTO_GENERATED_FILE_COMMENT);
58 writeln!(
59 output,
60 "// This was generated using spacetimedb cli version {} (commit {}).",
61 version::spacetimedb_lib_version(),
62 version::GIT_HASH
63 );
64 writeln!(output);
65}
66
67pub(super) fn type_ref_name(module: &ModuleDef, typeref: AlgebraicTypeRef) -> String {
68 let (name, _def) = module.type_def_from_ref(typeref).unwrap();
69 collect_case(Case::Pascal, name.name_segments())
70}
71
72pub(super) fn is_type_filterable(typespace: &TypespaceForGenerate, ty: &AlgebraicTypeUse) -> bool {
73 match ty {
74 AlgebraicTypeUse::Primitive(prim) => !matches!(prim, PrimitiveType::F32 | PrimitiveType::F64),
75 AlgebraicTypeUse::String | AlgebraicTypeUse::Identity | AlgebraicTypeUse::ConnectionId => true,
76 AlgebraicTypeUse::Never => true,
78 AlgebraicTypeUse::Option(inner) => matches!(&**inner, AlgebraicTypeUse::Unit),
79 AlgebraicTypeUse::Ref(r) => typespace[r].is_plain_enum(),
80 _ => false,
81 }
82}
83
84pub(super) fn is_reducer_invokable(reducer: &ReducerDef) -> bool {
85 reducer.lifecycle.is_none()
86}
87
88pub(super) fn iter_reducers(module: &ModuleDef) -> impl Iterator<Item = &ReducerDef> {
93 module
94 .reducers()
95 .filter(|reducer| reducer.lifecycle != Some(Lifecycle::Init))
96}
97
98pub(super) fn iter_tables(module: &ModuleDef) -> impl Iterator<Item = &TableDef> {
102 module.tables().sorted_by_key(|table| &table.name)
103}
104
105pub(super) fn iter_unique_cols<'a>(
106 typespace: &'a TypespaceForGenerate,
107 schema: &'a TableSchema,
108 product_def: &'a ProductTypeDef,
109) -> impl Iterator<Item = &'a (Identifier, AlgebraicTypeUse)> + 'a {
110 let constraints = schema.backcompat_column_constraints();
111 schema.columns().iter().filter_map(move |field| {
112 constraints[&ColList::from(field.col_pos)]
113 .has_unique()
114 .then(|| {
115 let res @ (_, ref ty) = &product_def.elements[field.col_pos.idx()];
116 is_type_filterable(typespace, ty).then_some(res)
117 })
118 .flatten()
119 })
120}
121
122pub(super) fn iter_indexes(table: &TableDef) -> impl Iterator<Item = &IndexDef> {
123 table.indexes.values().sorted_by_key(|index| &index.name)
124}
125
126pub fn iter_types(module: &ModuleDef) -> impl Iterator<Item = &TypeDef> {
130 module.types().sorted_by_key(|table| &table.name)
131}