use super::code_indenter::CodeIndenter;
use super::{GenCtx, GenItem};
use convert_case::{Case, Casing};
use spacetimedb_lib::sats::{
AlgebraicType, AlgebraicTypeRef, ArrayType, BuiltinType, MapType, ProductType, ProductTypeElement, SumType,
SumTypeVariant,
};
use spacetimedb_lib::{ColumnIndexAttribute, ReducerDef, TableDef};
use std::collections::HashSet;
use std::fmt::Write;
type Indenter = CodeIndenter<String>;
type Imports = HashSet<(String, String)>;
enum MaybePrimitive<'a> {
Primitive(&'a str),
Array(&'a ArrayType),
Map(&'a MapType),
}
fn maybe_primitive(b: &BuiltinType) -> MaybePrimitive {
MaybePrimitive::Primitive(match b {
BuiltinType::Bool => "bool",
BuiltinType::I8 => "i8",
BuiltinType::U8 => "u8",
BuiltinType::I16 => "i16",
BuiltinType::U16 => "u16",
BuiltinType::I32 => "i32",
BuiltinType::U32 => "u32",
BuiltinType::I64 => "i64",
BuiltinType::U64 => "u64",
BuiltinType::I128 => "i128",
BuiltinType::U128 => "u128",
BuiltinType::String => "String",
BuiltinType::F32 => "f32",
BuiltinType::F64 => "f64",
BuiltinType::Array(ty) => return MaybePrimitive::Array(ty),
BuiltinType::Map(m) => return MaybePrimitive::Map(m),
})
}
fn write_type_ctx(ctx: &GenCtx, out: &mut Indenter, ty: &AlgebraicType) {
write_type(&|r| type_name(ctx, r), out, ty)
}
pub fn write_type<W: Write>(ctx: &impl Fn(AlgebraicTypeRef) -> String, out: &mut W, ty: &AlgebraicType) {
match ty {
AlgebraicType::Sum(sum_type) => {
if let Some(inner_ty) = sum_type.as_option() {
write!(out, "Option::<").unwrap();
write_type(ctx, out, inner_ty);
write!(out, ">").unwrap();
} else {
write!(out, "enum ").unwrap();
print_comma_sep_braced(out, &sum_type.variants, |out: &mut W, elem: &_| {
if let Some(name) = &elem.name {
write!(out, "{}: ", name).unwrap();
}
write_type(ctx, out, &elem.algebraic_type);
});
}
}
AlgebraicType::Product(p) if p.is_identity() => {
write!(out, "Identity").unwrap();
}
AlgebraicType::Product(p) if p.is_address() => {
write!(out, "Address").unwrap();
}
AlgebraicType::Product(ProductType { elements }) => {
print_comma_sep_braced(out, elements, |out: &mut W, elem: &ProductTypeElement| {
if let Some(name) = &elem.name {
write!(out, "{}: ", name).unwrap();
}
write_type(ctx, out, &elem.algebraic_type);
});
}
AlgebraicType::Builtin(b) => match maybe_primitive(b) {
MaybePrimitive::Primitive(p) => write!(out, "{}", p).unwrap(),
MaybePrimitive::Array(ArrayType { elem_ty }) => {
write!(out, "Vec::<").unwrap();
write_type(ctx, out, elem_ty);
write!(out, ">").unwrap();
}
MaybePrimitive::Map(ty) => {
write!(out, "HashMap::<").unwrap();
write_type(ctx, out, &ty.key_ty);
write!(out, ", ").unwrap();
write_type(ctx, out, &ty.ty);
write!(out, ">").unwrap();
}
},
AlgebraicType::Ref(r) => {
write!(out, "{}", ctx(*r)).unwrap();
}
}
}
fn print_comma_sep_braced<W: Write, T>(out: &mut W, elems: &[T], on: impl Fn(&mut W, &T)) {
write!(out, "{{").unwrap();
let mut iter = elems.iter();
if let Some(elem) = iter.next() {
write!(out, " ").unwrap();
on(out, elem);
}
for elem in iter {
write!(out, ", ").unwrap();
on(out, elem);
}
if !elems.is_empty() {
write!(out, " ").unwrap();
}
write!(out, "}}").unwrap();
}
fn type_name(ctx: &GenCtx, typeref: AlgebraicTypeRef) -> String {
ctx.names[typeref.idx()]
.as_deref()
.expect("TypeRefs should have names")
.to_case(Case::Pascal)
}
fn print_lines(output: &mut Indenter, lines: &[&str]) {
for line in lines {
writeln!(output, "{}", line).unwrap();
}
}
const AUTO_GENERATED_FILE_COMMENT: &[&str] = &[
"// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE",
"// WILL NOT BE SAVED. MODIFY TABLES IN RUST INSTEAD.",
"",
];
fn print_auto_generated_file_comment(output: &mut Indenter) {
print_lines(output, AUTO_GENERATED_FILE_COMMENT);
}
const ALLOW_UNUSED: &str = "#[allow(unused)]";
const SPACETIMEDB_IMPORTS: &[&str] = &[
ALLOW_UNUSED,
"use spacetimedb_sdk::{",
"\tAddress,",
"\tsats::{ser::Serialize, de::Deserialize},",
"\ttable::{TableType, TableIter, TableWithPrimaryKey},",
"\treducer::{Reducer, ReducerCallbackId, Status},",
"\tidentity::Identity,",
"\tspacetimedb_lib,",
"\tanyhow::{Result, anyhow},",
"};",
];
fn print_spacetimedb_imports(output: &mut Indenter) {
print_lines(output, SPACETIMEDB_IMPORTS);
}
fn print_file_header(output: &mut Indenter) {
print_auto_generated_file_comment(output);
print_spacetimedb_imports(output);
}
const ENUM_DERIVES: &[&str] = &["#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]"];
fn print_enum_derives(output: &mut Indenter) {
print_lines(output, ENUM_DERIVES);
}
pub fn autogen_rust_sum(ctx: &GenCtx, name: &str, sum_type: &SumType) -> String {
let mut output = CodeIndenter::new(String::new());
let out = &mut output;
let sum_type_name = name.replace("r#", "").to_case(Case::Pascal);
print_file_header(out);
let file_name = name.to_case(Case::Snake);
let this_file = (file_name.as_str(), name);
gen_and_print_imports(ctx, out, &sum_type.variants[..], generate_imports_variants, this_file);
out.newline();
print_enum_derives(out);
write!(out, "pub enum {} ", sum_type_name).unwrap();
out.delimited_block(
"{",
|out| {
for variant in &sum_type.variants {
write_enum_variant(ctx, out, variant);
out.newline();
}
},
"}\n",
);
output.into_inner()
}
fn write_enum_variant(ctx: &GenCtx, out: &mut Indenter, variant: &SumTypeVariant) {
let Some(name) = &variant.name else {
panic!("Sum type variant has no name: {:?}", variant);
};
let name = name.to_case(Case::Pascal);
write!(out, "{}", name).unwrap();
match &variant.algebraic_type {
AlgebraicType::Product(ProductType { elements }) if elements.is_empty() => {
writeln!(out, ",").unwrap();
}
otherwise => {
write!(out, "(").unwrap();
write_type_ctx(ctx, out, otherwise);
write!(out, "),").unwrap();
}
}
}
fn write_struct_type_fields_in_braces(
ctx: &GenCtx,
out: &mut Indenter,
elements: &[ProductTypeElement],
pub_qualifier: bool,
) {
out.delimited_block(
"{",
|out| write_arglist_no_delimiters_ctx(ctx, out, elements, pub_qualifier.then_some("pub")),
"}",
);
}
fn write_arglist_no_delimiters_ctx(
ctx: &GenCtx,
out: &mut Indenter,
elements: &[ProductTypeElement],
prefix: Option<&str>,
) {
write_arglist_no_delimiters(&|r| type_name(ctx, r), out, elements, prefix)
}
pub fn write_arglist_no_delimiters(
ctx: &impl Fn(AlgebraicTypeRef) -> String,
out: &mut impl Write,
elements: &[ProductTypeElement],
prefix: Option<&str>,
) {
for elt in elements {
if let Some(prefix) = prefix {
write!(out, "{} ", prefix).unwrap();
}
let Some(name) = &elt.name else {
panic!("Product type element has no name: {:?}", elt);
};
let name = name.to_case(Case::Snake);
write!(out, "{}: ", name).unwrap();
write_type(ctx, out, &elt.algebraic_type);
writeln!(out, ",").unwrap();
}
}
pub fn autogen_rust_tuple(ctx: &GenCtx, name: &str, product: &ProductType) -> String {
let mut output = CodeIndenter::new(String::new());
let out = &mut output;
let type_name = name.to_case(Case::Pascal);
begin_rust_struct_def_shared(ctx, out, &type_name, &product.elements);
output.into_inner()
}
fn find_product_type(ctx: &GenCtx, ty: AlgebraicTypeRef) -> &ProductType {
ctx.typespace[ty].as_product().unwrap()
}
pub fn autogen_rust_table(ctx: &GenCtx, table: &TableDef) -> String {
let mut output = CodeIndenter::new(String::new());
let out = &mut output;
let type_name = table.name.to_case(Case::Pascal);
begin_rust_struct_def_shared(ctx, out, &type_name, &find_product_type(ctx, table.data).elements);
out.newline();
print_impl_tabletype(ctx, out, table);
output.into_inner()
}
pub fn rust_type_file_name(type_name: &str) -> String {
let filename = type_name.replace('.', "").to_case(Case::Snake);
filename + ".rs"
}
pub fn rust_reducer_file_name(type_name: &str) -> String {
let filename = type_name.replace('.', "").to_case(Case::Snake);
filename + "_reducer.rs"
}
const STRUCT_DERIVES: &[&str] = &["#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]"];
fn print_struct_derives(output: &mut Indenter) {
print_lines(output, STRUCT_DERIVES);
}
fn begin_rust_struct_def_shared(ctx: &GenCtx, out: &mut Indenter, name: &str, elements: &[ProductTypeElement]) {
print_auto_generated_file_comment(out);
print_spacetimedb_imports(out);
let file_name = name.to_case(Case::Snake);
let this_file = (file_name.as_str(), name);
gen_and_print_imports(ctx, out, elements, generate_imports_elements, this_file);
out.newline();
print_struct_derives(out);
write!(out, "pub struct {} ", name).unwrap();
write_struct_type_fields_in_braces(
ctx, out, elements, true,
);
out.newline();
}
fn find_primary_key_column_index(ctx: &GenCtx, table: &TableDef) -> Option<usize> {
let primaries = table
.column_attrs
.iter()
.enumerate()
.filter_map(|(i, attr)| attr.is_primary().then_some(i))
.collect::<Vec<_>>();
match primaries.len() {
2.. => {
let names = primaries
.iter()
.map(|&i| &find_product_type(ctx, table.data).elements[i])
.collect::<Vec<_>>();
panic!(
"Multiple primary columns defined for table {:?}: {:?}",
table.name, names
);
}
1 => Some(primaries[0]),
0 => None,
_ => unreachable!(),
}
}
fn print_impl_tabletype(ctx: &GenCtx, out: &mut Indenter, table: &TableDef) {
let type_name = table.name.to_case(Case::Pascal);
write!(out, "impl TableType for {} ", type_name).unwrap();
out.delimited_block(
"{",
|out| {
writeln!(out, "const TABLE_NAME: &'static str = {:?};", table.name).unwrap();
writeln!(out, "type ReducerEvent = super::ReducerEvent;").unwrap();
},
"}\n",
);
out.newline();
if let Some(primary_column_index) = find_primary_key_column_index(ctx, table) {
let pk_field = &find_product_type(ctx, table.data).elements[primary_column_index];
let pk_field_name = pk_field
.name
.as_ref()
.expect("Fields designated as primary key should have names!")
.to_case(Case::Snake);
write!(out, "impl TableWithPrimaryKey for {} ", type_name).unwrap();
out.delimited_block(
"{",
|out| {
write!(out, "type PrimaryKey = ").unwrap();
write_type_ctx(ctx, out, &pk_field.algebraic_type);
writeln!(out, ";").unwrap();
out.delimited_block(
"fn primary_key(&self) -> &Self::PrimaryKey {",
|out| writeln!(out, "&self.{}", pk_field_name).unwrap(),
"}\n",
)
},
"}\n",
);
}
out.newline();
print_table_filter_methods(
ctx,
out,
&type_name,
&find_product_type(ctx, table.data).elements,
&table.column_attrs,
);
}
fn print_table_filter_methods(
ctx: &GenCtx,
out: &mut Indenter,
table_type_name: &str,
elements: &[ProductTypeElement],
attrs: &[ColumnIndexAttribute],
) {
write!(out, "impl {} ", table_type_name).unwrap();
out.delimited_block(
"{",
|out| {
for (elt, attr) in elements.iter().zip(attrs) {
let field_name = elt
.name
.as_ref()
.expect("Table columns should have names!")
.to_case(Case::Snake);
writeln!(out, "{}", ALLOW_UNUSED).unwrap();
write!(out, "pub fn filter_by_{}({}: ", field_name, field_name).unwrap();
write_type_ctx(ctx, out, &elt.algebraic_type);
write!(out, ") -> ").unwrap();
if attr.is_unique() {
write!(out, "Option<Self>").unwrap();
} else {
write!(out, "TableIter<Self>").unwrap();
}
out.delimited_block(
" {",
|out| {
writeln!(
out,
"Self::{}(|row| row.{} == {})",
if attr.is_unique() { "find" } else { "filter" },
field_name,
field_name,
)
.unwrap()
},
"}\n",
);
}
},
"}\n",
)
}
fn reducer_type_name(reducer: &ReducerDef) -> String {
let mut name = reducer.name.to_case(Case::Pascal);
name.push_str("Args");
name
}
fn reducer_variant_name(reducer: &ReducerDef) -> String {
reducer.name.to_case(Case::Pascal)
}
fn reducer_module_name(reducer: &ReducerDef) -> String {
let mut name = reducer.name.to_case(Case::Snake);
name.push_str("_reducer");
name
}
fn reducer_function_name(reducer: &ReducerDef) -> String {
reducer.name.to_case(Case::Snake)
}
fn iter_reducer_arg_names(reducer: &ReducerDef) -> impl Iterator<Item = Option<String>> + '_ {
reducer
.args
.iter()
.map(|elt| elt.name.as_ref().map(|name| name.to_case(Case::Snake)))
}
fn iter_reducer_arg_types(reducer: &'_ ReducerDef) -> impl Iterator<Item = &'_ AlgebraicType> {
reducer.args.iter().map(|elt| &elt.algebraic_type)
}
fn print_reducer_struct_literal(out: &mut Indenter, reducer: &ReducerDef) {
write!(out, "{} ", reducer_type_name(reducer)).unwrap();
out.delimited_block(
"{",
|out| {
for arg_name in iter_reducer_arg_names(reducer) {
let name = arg_name.unwrap();
writeln!(out, "{},", name).unwrap();
}
},
"}",
);
}
pub fn autogen_rust_reducer(ctx: &GenCtx, reducer: &ReducerDef) -> String {
let func_name = reducer_function_name(reducer);
let type_name = reducer_type_name(reducer);
let mut output = CodeIndenter::new(String::new());
let out = &mut output;
begin_rust_struct_def_shared(ctx, out, &type_name, &reducer.args);
out.newline();
write!(out, "impl Reducer for {} ", type_name).unwrap();
out.delimited_block(
"{",
|out| writeln!(out, "const REDUCER_NAME: &'static str = {:?};", &reducer.name).unwrap(),
"}\n",
);
out.newline();
writeln!(out, "{}", ALLOW_UNUSED).unwrap();
write!(out, "pub fn {}", func_name).unwrap();
out.delimited_block(
"(",
|out| write_arglist_no_delimiters_ctx(ctx, out, &reducer.args, None),
") ",
);
out.delimited_block(
"{",
|out| {
print_reducer_struct_literal(out, reducer);
writeln!(out, ".invoke();").unwrap();
},
"}\n",
);
out.newline();
writeln!(out, "{}", ALLOW_UNUSED).unwrap();
write!(
out,
"pub fn on_{}(mut __callback: impl FnMut(&Identity, Option<Address>, &Status",
func_name
)
.unwrap();
for arg_type in iter_reducer_arg_types(reducer) {
write!(out, ", &").unwrap();
write_type_ctx(ctx, out, arg_type);
}
writeln!(out, ") + Send + 'static) -> ReducerCallbackId<{}> ", type_name).unwrap();
out.delimited_block(
"{",
|out| {
write!(out, "{}", type_name).unwrap();
out.delimited_block(
"::on_reducer(move |__identity, __addr, __status, __args| {",
|out| {
write!(out, "let ").unwrap();
print_reducer_struct_literal(out, reducer);
writeln!(out, " = __args;").unwrap();
out.delimited_block(
"__callback(",
|out| {
writeln!(out, "__identity,").unwrap();
writeln!(out, "__addr,").unwrap();
writeln!(out, "__status,").unwrap();
for arg_name in iter_reducer_arg_names(reducer) {
writeln!(out, "{},", arg_name.unwrap()).unwrap();
}
},
");\n",
);
},
"})\n",
);
},
"}\n",
);
out.newline();
writeln!(out, "{}", ALLOW_UNUSED).unwrap();
write!(
out,
"pub fn once_on_{}(__callback: impl FnOnce(&Identity, Option<Address>, &Status",
func_name
)
.unwrap();
for arg_type in iter_reducer_arg_types(reducer) {
write!(out, ", &").unwrap();
write_type_ctx(ctx, out, arg_type);
}
writeln!(out, ") + Send + 'static) -> ReducerCallbackId<{}> ", type_name).unwrap();
out.delimited_block(
"{",
|out| {
write!(out, "{}", type_name).unwrap();
out.delimited_block(
"::once_on_reducer(move |__identity, __addr, __status, __args| {",
|out| {
write!(out, "let ").unwrap();
print_reducer_struct_literal(out, reducer);
writeln!(out, " = __args;").unwrap();
out.delimited_block(
"__callback(",
|out| {
writeln!(out, "__identity,").unwrap();
writeln!(out, "__addr,").unwrap();
writeln!(out, "__status,").unwrap();
for arg_name in iter_reducer_arg_names(reducer) {
writeln!(out, "{},", arg_name.unwrap()).unwrap();
}
},
");\n",
);
},
"})\n",
)
},
"}\n",
);
out.newline();
writeln!(out, "{}", ALLOW_UNUSED).unwrap();
write!(
out,
"pub fn remove_on_{}(id: ReducerCallbackId<{}>) ",
func_name, type_name,
)
.unwrap();
out.delimited_block(
"{",
|out| {
writeln!(out, "{}::remove_on_reducer(id);", type_name,).unwrap();
},
"}\n",
);
output.into_inner()
}
pub fn autogen_rust_globals(ctx: &GenCtx, items: &[GenItem]) -> Vec<Vec<(String, String)>> {
let mut output = CodeIndenter::new(String::new());
let out = &mut output;
print_auto_generated_file_comment(out);
print_spacetimedb_imports(out);
print_dispatch_imports(out);
out.newline();
print_module_decls(out, items);
out.newline();
print_module_reexports(out, items);
out.newline();
print_reducer_event_defn(out, items);
out.newline();
print_spacetime_module_struct_defn(ctx, out, items);
out.newline();
print_connect_defn(out);
vec![vec![("mod.rs".to_string(), output.into_inner())]]
}
const DISPATCH_IMPORTS: &[&str] = &[
"use spacetimedb_sdk::client_api_messages::{TableUpdate, Event};",
"use spacetimedb_sdk::client_cache::{ClientCache, RowCallbackReminders};",
"use spacetimedb_sdk::identity::Credentials;",
"use spacetimedb_sdk::callbacks::{DbCallbacks, ReducerCallbacks};",
"use spacetimedb_sdk::reducer::AnyReducerEvent;",
"use spacetimedb_sdk::global_connection::with_connection_mut;",
"use spacetimedb_sdk::spacetime_module::SpacetimeModule;",
"use std::sync::Arc;",
];
fn print_dispatch_imports(out: &mut Indenter) {
print_lines(out, DISPATCH_IMPORTS);
}
fn iter_reducer_items(items: &[GenItem]) -> impl Iterator<Item = &ReducerDef> {
items.iter().filter_map(|item| match item {
GenItem::Reducer(reducer) => Some(reducer),
_ => None,
})
}
fn iter_table_items(items: &[GenItem]) -> impl Iterator<Item = &TableDef> {
items.iter().filter_map(|item| match item {
GenItem::Table(table) => Some(table),
_ => None,
})
}
fn iter_module_names(items: &[GenItem]) -> impl Iterator<Item = String> + '_ {
items.iter().map(|item| match item {
GenItem::Table(table) => table.name.to_case(Case::Snake),
GenItem::TypeAlias(ty) => ty.name.to_case(Case::Snake),
GenItem::Reducer(reducer) => reducer_module_name(reducer),
})
}
fn print_module_decls(out: &mut Indenter, items: &[GenItem]) {
for module_name in iter_module_names(items) {
writeln!(out, "pub mod {};", module_name).unwrap();
}
}
fn print_module_reexports(out: &mut Indenter, items: &[GenItem]) {
for module_name in iter_module_names(items) {
writeln!(out, "pub use {}::*;", module_name).unwrap();
}
}
fn print_spacetime_module_struct_defn(ctx: &GenCtx, out: &mut Indenter, items: &[GenItem]) {
writeln!(out, "{}", ALLOW_UNUSED).unwrap();
writeln!(out, "pub struct Module;").unwrap();
out.delimited_block(
"impl SpacetimeModule for Module {",
|out| {
print_handle_table_update_defn(ctx, out, items);
print_invoke_row_callbacks_defn(out, items);
print_handle_event_defn(out, items);
print_handle_resubscribe_defn(out, items);
},
"}\n",
);
}
fn print_handle_table_update_defn(ctx: &GenCtx, out: &mut Indenter, items: &[GenItem]) {
out.delimited_block(
"fn handle_table_update(&self, table_update: TableUpdate, client_cache: &mut ClientCache, callbacks: &mut RowCallbackReminders) {",
|out| {
writeln!(out, "let table_name = &table_update.table_name[..];").unwrap();
out.delimited_block(
"match table_name {",
|out| {
for table in iter_table_items(items) {
writeln!(
out,
"{:?} => client_cache.{}::<{}::{}>(callbacks, table_update),",
table.name,
if find_primary_key_column_index(ctx, table).is_some() {
"handle_table_update_with_primary_key"
} else {
"handle_table_update_no_primary_key"
},
table.name.to_case(Case::Snake),
table.name.to_case(Case::Pascal),
).unwrap();
}
writeln!(
out,
"_ => spacetimedb_sdk::log::error!(\"TableRowOperation on unknown table {{:?}}\", table_name),",
).unwrap();
},
"}\n",
);
},
"}\n",
);
}
fn print_invoke_row_callbacks_defn(out: &mut Indenter, items: &[GenItem]) {
out.delimited_block(
"fn invoke_row_callbacks(&self, reminders: &mut RowCallbackReminders, worker: &mut DbCallbacks, reducer_event: Option<Arc<AnyReducerEvent>>, state: &Arc<ClientCache>) {",
|out| {
for table in iter_table_items(items) {
writeln!(
out,
"reminders.invoke_callbacks::<{}::{}>(worker, &reducer_event, state);",
table.name.to_case(Case::Snake),
table.name.to_case(Case::Pascal),
).unwrap();
}
},
"}\n",
);
}
fn print_handle_resubscribe_defn(out: &mut Indenter, items: &[GenItem]) {
out.delimited_block(
"fn handle_resubscribe(&self, new_subs: TableUpdate, client_cache: &mut ClientCache, callbacks: &mut RowCallbackReminders) {",
|out| {
writeln!(out, "let table_name = &new_subs.table_name[..];").unwrap();
out.delimited_block(
"match table_name {",
|out| {
for table in iter_table_items(items) {
writeln!(
out,
"{:?} => client_cache.handle_resubscribe_for_type::<{}::{}>(callbacks, new_subs),",
table.name,
table.name.to_case(Case::Snake),
table.name.to_case(Case::Pascal),
).unwrap();
}
writeln!(
out,
"_ => spacetimedb_sdk::log::error!(\"TableRowOperation on unknown table {{:?}}\", table_name)," ,
).unwrap();
},
"}\n",
);
},
"}\n"
);
}
fn print_handle_event_defn(out: &mut Indenter, items: &[GenItem]) {
out.delimited_block(
"fn handle_event(&self, event: Event, _reducer_callbacks: &mut ReducerCallbacks, _state: Arc<ClientCache>) -> Option<Arc<AnyReducerEvent>> {",
|out| {
out.delimited_block(
"let Some(function_call) = &event.function_call else {",
|out| writeln!(out, "spacetimedb_sdk::log::warn!(\"Received Event with None function_call\"); return None;")
.unwrap(),
"};\n",
);
writeln!(out, "#[allow(clippy::match_single_binding)]").unwrap();
out.delimited_block(
"match &function_call.reducer[..] {",
|out| {
for reducer in iter_reducer_items(items) {
writeln!(
out,
"{:?} => _reducer_callbacks.handle_event_of_type::<{}::{}, ReducerEvent>(event, _state, ReducerEvent::{}),",
reducer.name,
reducer_module_name(reducer),
reducer_type_name(reducer),
reducer_variant_name(reducer),
).unwrap();
}
writeln!(
out,
"unknown => {{ spacetimedb_sdk::log::error!(\"Event on an unknown reducer: {{:?}}\", unknown); None }}",
).unwrap();
},
"}\n",
);
},
"}\n",
);
}
const CONNECT_DOCSTRING: &[&str] = &[
"/// Connect to a database named `db_name` accessible over the internet at the URI `spacetimedb_uri`.",
"///",
"/// If `credentials` are supplied, they will be passed to the new connection to",
"/// identify and authenticate the user. Otherwise, a set of `Credentials` will be",
"/// generated by the server.",
];
fn print_connect_docstring(out: &mut Indenter) {
print_lines(out, CONNECT_DOCSTRING);
}
fn print_connect_defn(out: &mut Indenter) {
print_connect_docstring(out);
out.delimited_block(
"pub fn connect<IntoUri>(spacetimedb_uri: IntoUri, db_name: &str, credentials: Option<Credentials>) -> Result<()>
where
\tIntoUri: TryInto<spacetimedb_sdk::http::Uri>,
\t<IntoUri as TryInto<spacetimedb_sdk::http::Uri>>::Error: std::error::Error + Send + Sync + 'static,
{",
|out| out.delimited_block(
"with_connection_mut(|connection| {",
|out| {
writeln!(
out,
"connection.connect(spacetimedb_uri, db_name, credentials, Arc::new(Module))?;"
).unwrap();
writeln!(out, "Ok(())").unwrap();
},
"})\n",
),
"}\n",
);
}
fn print_reducer_event_defn(out: &mut Indenter, items: &[GenItem]) {
writeln!(out, "{}", ALLOW_UNUSED).unwrap();
print_enum_derives(out);
out.delimited_block(
"pub enum ReducerEvent {",
|out| {
for reducer in iter_reducer_items(items) {
writeln!(
out,
"{}({}::{}),",
reducer_variant_name(reducer),
reducer_module_name(reducer),
reducer_type_name(reducer),
)
.unwrap();
}
},
"}\n",
);
}
fn generate_imports_variants(ctx: &GenCtx, imports: &mut Imports, variants: &[SumTypeVariant]) {
for variant in variants {
generate_imports(ctx, imports, &variant.algebraic_type);
}
}
fn generate_imports_elements(ctx: &GenCtx, imports: &mut Imports, elements: &[ProductTypeElement]) {
for element in elements {
generate_imports(ctx, imports, &element.algebraic_type);
}
}
fn module_name(name: &str) -> String {
name.to_case(Case::Snake)
}
fn generate_imports(ctx: &GenCtx, imports: &mut Imports, ty: &AlgebraicType) {
match ty {
AlgebraicType::Builtin(BuiltinType::Array(ArrayType { elem_ty })) => generate_imports(ctx, imports, elem_ty),
AlgebraicType::Builtin(BuiltinType::Map(map_type)) => {
generate_imports(ctx, imports, &map_type.key_ty);
generate_imports(ctx, imports, &map_type.ty);
}
AlgebraicType::Builtin(_) => (),
AlgebraicType::Ref(r) => {
let type_name = type_name(ctx, *r);
let module_name = module_name(&type_name);
imports.insert((module_name, type_name));
}
AlgebraicType::Sum(s) => generate_imports_variants(ctx, imports, &s.variants),
_ => (),
}
}
fn print_imports(out: &mut Indenter, imports: Imports, this_file: (&str, &str)) {
for (module_name, type_name) in imports {
if (module_name.as_str(), type_name.as_str()) != this_file {
writeln!(out, "use super::{}::{};", module_name, type_name).unwrap();
}
}
}
fn gen_and_print_imports<Roots, SearchFn>(
ctx: &GenCtx,
out: &mut Indenter,
roots: Roots,
search_fn: SearchFn,
this_file: (&str, &str),
) where
SearchFn: FnOnce(&GenCtx, &mut Imports, Roots),
{
let mut imports = HashSet::new();
search_fn(ctx, &mut imports, roots);
print_imports(out, imports, this_file);
}