use super::code_indenter::CodeIndenter;
use super::{GenCtx, GenItem};
use convert_case::{Case, Casing};
use spacetimedb_data_structures::map::HashSet;
use spacetimedb_lib::sats::db::def::TableSchema;
use spacetimedb_lib::sats::{
AlgebraicType, AlgebraicTypeRef, ArrayType, BuiltinType, MapType, ProductType, ProductTypeElement, SumType,
SumTypeVariant,
};
use spacetimedb_lib::{ReducerDef, TableDesc};
use spacetimedb_primitives::ColList;
use std::fmt::{self, Write};
use std::ops::Deref;
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).unwrap()
}
pub fn write_type<W: Write>(ctx: &impl Fn(AlgebraicTypeRef) -> String, out: &mut W, ty: &AlgebraicType) -> fmt::Result {
match ty {
AlgebraicType::Sum(sum_type) => {
if let Some(inner_ty) = sum_type.as_option() {
write!(out, "Option::<")?;
write_type(ctx, out, inner_ty)?;
write!(out, ">")?;
} else {
write!(out, "enum ")?;
print_comma_sep_braced(out, &sum_type.variants, |out: &mut W, elem: &_| {
if let Some(name) = &elem.name {
write!(out, "{name}: ")?;
}
write_type(ctx, out, &elem.algebraic_type)
})?;
}
}
AlgebraicType::Product(p) if p.is_identity() => {
write!(out, "Identity")?;
}
AlgebraicType::Product(p) if p.is_address() => {
write!(out, "Address")?;
}
AlgebraicType::Product(ProductType { elements }) => {
print_comma_sep_braced(out, elements, |out: &mut W, elem: &ProductTypeElement| {
if let Some(name) = &elem.name {
write!(out, "{name}: ")?;
}
write_type(ctx, out, &elem.algebraic_type)
})?;
}
AlgebraicType::Builtin(b) => match maybe_primitive(b) {
MaybePrimitive::Primitive(p) => write!(out, "{p}")?,
MaybePrimitive::Array(ArrayType { elem_ty }) => {
write!(out, "Vec::<")?;
write_type(ctx, out, elem_ty)?;
write!(out, ">")?;
}
MaybePrimitive::Map(ty) => {
write!(out, "HashMap::<")?;
write_type(ctx, out, &ty.key_ty)?;
write!(out, ", ")?;
write_type(ctx, out, &ty.ty)?;
write!(out, ">")?;
}
},
AlgebraicType::Ref(r) => {
write!(out, "{}", ctx(*r))?;
}
}
Ok(())
}
fn print_comma_sep_braced<W: Write, T>(
out: &mut W,
elems: &[T],
on: impl Fn(&mut W, &T) -> fmt::Result,
) -> fmt::Result {
write!(out, "{{")?;
let mut iter = elems.iter();
if let Some(elem) = iter.next() {
write!(out, " ")?;
on(out, elem)?;
}
for elem in iter {
write!(out, ", ")?;
on(out, elem)?;
}
if !elems.is_empty() {
write!(out, " ")?;
}
write!(out, "}}")?;
Ok(())
}
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}");
}
}
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 ALLOW_UNUSED_IMPORTS: &str = "#![allow(unused_imports)]";
const SPACETIMEDB_IMPORTS: &[&str] = &[
"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);
write!(output, "{ALLOW_UNUSED_IMPORTS}");
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} ");
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.deref().to_case(Case::Pascal);
write!(out, "{name}");
match &variant.algebraic_type {
AlgebraicType::Product(ProductType { elements }) if elements.is_empty() => {
writeln!(out, ",");
}
otherwise => {
write!(out, "(");
write_type_ctx(ctx, out, otherwise);
write!(out, "),");
}
}
}
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).unwrap()
}
pub fn write_arglist_no_delimiters(
ctx: &impl Fn(AlgebraicTypeRef) -> String,
out: &mut impl Write,
elements: &[ProductTypeElement],
prefix: Option<&str>,
) -> fmt::Result {
for elt in elements {
if let Some(prefix) = prefix {
write!(out, "{prefix} ")?;
}
let Some(name) = &elt.name else {
panic!("Product type element has no name: {elt:?}");
};
let name = name.deref().to_case(Case::Snake);
write!(out, "{name}: ")?;
write_type(ctx, out, &elt.algebraic_type)?;
writeln!(out, ",")?;
}
Ok(())
}
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: &TableDesc) -> String {
let mut output = CodeIndenter::new(String::new());
let out = &mut output;
let type_name = table.schema.table_name.deref().to_case(Case::Pascal);
begin_rust_struct_def_shared(ctx, out, &type_name, &find_product_type(ctx, table.data).elements);
out.newline();
let table = table
.schema
.clone()
.into_schema(0.into())
.validated()
.expect("Failed to generate table due to validation errors");
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_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, elements, generate_imports_elements, this_file);
out.newline();
print_struct_derives(out);
write!(out, "pub struct {name} ");
write_struct_type_fields_in_braces(
ctx, out, elements, true,
);
out.newline();
}
fn find_primary_key_column_index(table: &TableSchema) -> Option<usize> {
table.pk().map(|x| x.col_pos.into())
}
fn print_impl_tabletype(ctx: &GenCtx, out: &mut Indenter, table: &TableSchema) {
let type_name = table.table_name.deref().to_case(Case::Pascal);
write!(out, "impl TableType for {type_name} ");
out.delimited_block(
"{",
|out| {
writeln!(out, "const TABLE_NAME: &'static str = {:?};", table.table_name);
writeln!(out, "type ReducerEvent = super::ReducerEvent;");
},
"}\n",
);
out.newline();
if let Some(pk_field) = table.pk() {
let pk_field_name = pk_field.col_name.deref().to_case(Case::Snake);
write!(out, "impl TableWithPrimaryKey for {type_name} ");
out.delimited_block(
"{",
|out| {
write!(out, "type PrimaryKey = ");
write_type_ctx(ctx, out, &pk_field.col_type);
writeln!(out, ";");
out.delimited_block(
"fn primary_key(&self) -> &Self::PrimaryKey {",
|out| writeln!(out, "&self.{pk_field_name}"),
"}\n",
)
},
"}\n",
);
}
out.newline();
print_table_filter_methods(ctx, out, &type_name, table);
}
fn print_table_filter_methods(ctx: &GenCtx, out: &mut Indenter, table_type_name: &str, table: &TableSchema) {
write!(out, "impl {table_type_name} ");
let constraints = table.column_constraints();
out.delimited_block(
"{",
|out| {
for field in table.columns() {
let field_name = field.col_name.deref().to_case(Case::Snake);
writeln!(out, "{ALLOW_UNUSED}");
write!(out, "pub fn filter_by_{field_name}({field_name}: ");
write_type_ctx(ctx, out, &field.col_type);
write!(out, ") -> ");
let ct = constraints[&ColList::new(field.col_pos)];
if ct.has_unique() {
write!(out, "Option<Self>");
} else {
write!(out, "TableIter<Self>");
}
out.delimited_block(
" {",
|out| {
writeln!(
out,
"Self::{}(|row| row.{} == {})",
if ct.has_unique() { "find" } else { "filter" },
field_name,
field_name,
)
},
"}\n",
);
}
},
"}\n",
)
}
fn reducer_type_name(reducer: &ReducerDef) -> String {
let mut name = reducer.name.deref().to_case(Case::Pascal);
name.push_str("Args");
name
}
fn reducer_variant_name(reducer: &ReducerDef) -> String {
reducer.name.deref().to_case(Case::Pascal)
}
fn reducer_module_name(reducer: &ReducerDef) -> String {
let mut name = reducer.name.deref().to_case(Case::Snake);
name.push_str("_reducer");
name
}
fn reducer_function_name(reducer: &ReducerDef) -> String {
reducer.name.deref().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.deref().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));
out.delimited_block(
"{",
|out| {
for arg_name in iter_reducer_arg_names(reducer) {
let name = arg_name.unwrap();
writeln!(out, "{name},");
}
},
"}",
);
}
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} ");
out.delimited_block(
"{",
|out| writeln!(out, "const REDUCER_NAME: &'static str = {:?};", &reducer.name),
"}\n",
);
out.newline();
writeln!(out, "{ALLOW_UNUSED}");
write!(out, "pub fn {func_name}");
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();");
},
"}\n",
);
out.newline();
writeln!(out, "{ALLOW_UNUSED}");
write!(
out,
"pub fn on_{func_name}(mut __callback: impl FnMut(&Identity, Option<Address>, &Status"
);
for arg_type in iter_reducer_arg_types(reducer) {
write!(out, ", &");
write_type_ctx(ctx, out, arg_type);
}
writeln!(out, ") + Send + 'static) -> ReducerCallbackId<{type_name}> ");
out.delimited_block(
"{",
|out| {
write!(out, "{type_name}");
out.delimited_block(
"::on_reducer(move |__identity, __addr, __status, __args| {",
|out| {
write!(out, "let ");
print_reducer_struct_literal(out, reducer);
writeln!(out, " = __args;");
out.delimited_block(
"__callback(",
|out| {
writeln!(out, "__identity,");
writeln!(out, "__addr,");
writeln!(out, "__status,");
for arg_name in iter_reducer_arg_names(reducer) {
writeln!(out, "{},", arg_name.unwrap());
}
},
");\n",
);
},
"})\n",
);
},
"}\n",
);
out.newline();
writeln!(out, "{ALLOW_UNUSED}");
write!(
out,
"pub fn once_on_{func_name}(__callback: impl FnOnce(&Identity, Option<Address>, &Status"
);
for arg_type in iter_reducer_arg_types(reducer) {
write!(out, ", &");
write_type_ctx(ctx, out, arg_type);
}
writeln!(out, ") + Send + 'static) -> ReducerCallbackId<{type_name}> ");
out.delimited_block(
"{",
|out| {
write!(out, "{type_name}");
out.delimited_block(
"::once_on_reducer(move |__identity, __addr, __status, __args| {",
|out| {
write!(out, "let ");
print_reducer_struct_literal(out, reducer);
writeln!(out, " = __args;");
out.delimited_block(
"__callback(",
|out| {
writeln!(out, "__identity,");
writeln!(out, "__addr,");
writeln!(out, "__status,");
for arg_name in iter_reducer_arg_names(reducer) {
writeln!(out, "{},", arg_name.unwrap());
}
},
");\n",
);
},
"})\n",
)
},
"}\n",
);
out.newline();
writeln!(out, "{ALLOW_UNUSED}");
write!(out, "pub fn remove_on_{func_name}(id: ReducerCallbackId<{type_name}>) ");
out.delimited_block(
"{",
|out| {
writeln!(out, "{type_name}::remove_on_reducer(id);");
},
"}\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_file_header(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 = &TableDesc> {
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.schema.table_name.deref().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};");
}
}
fn print_module_reexports(out: &mut Indenter, items: &[GenItem]) {
for module_name in iter_module_names(items) {
writeln!(out, "pub use {module_name}::*;");
}
}
fn print_spacetime_module_struct_defn(ctx: &GenCtx, out: &mut Indenter, items: &[GenItem]) {
writeln!(out, "{ALLOW_UNUSED}");
writeln!(out, "pub struct Module;");
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[..];");
out.delimited_block(
"match table_name {",
|out| {
for table in iter_table_items(items) {
let table = table.schema.clone().into_schema(0.into()).validated().unwrap();
writeln!(
out,
"{:?} => client_cache.{}::<{}::{}>(callbacks, table_update),",
table.table_name,
if find_primary_key_column_index(&table).is_some() {
"handle_table_update_with_primary_key"
} else {
"handle_table_update_no_primary_key"
},
table.table_name.deref().to_case(Case::Snake),
table.table_name.deref().to_case(Case::Pascal),
);
}
writeln!(
out,
"_ => spacetimedb_sdk::log::error!(\"TableRowOperation on unknown table {{:?}}\", table_name),"
);
},
"}\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.schema.table_name.deref().to_case(Case::Snake),
table.schema.table_name.deref().to_case(Case::Pascal),
);
}
},
"}\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[..];");
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.schema.table_name,
table.schema.table_name.deref().to_case(Case::Snake),
table.schema.table_name.deref().to_case(Case::Pascal),
);
}
writeln!(
out,
"_ => spacetimedb_sdk::log::error!(\"TableRowOperation on unknown table {{:?}}\", table_name),"
);
},
"}\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;"),
"};\n",
);
writeln!(out, "#[allow(clippy::match_single_binding)]");
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),
);
}
writeln!(
out,
"unknown => {{ spacetimedb_sdk::log::error!(\"Event on an unknown reducer: {{:?}}\", unknown); None }}"
);
},
"}\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))?;"
);
writeln!(out, "Ok(())");
},
"})\n",
),
"}\n",
);
}
fn print_reducer_event_defn(out: &mut Indenter, items: &[GenItem]) {
writeln!(out, "{ALLOW_UNUSED}");
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),
);
}
},
"}\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};");
}
}
}
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);
}