From c15dbdd955129555e322561a48e5f85a107bfaa6 Mon Sep 17 00:00:00 2001 From: Phoebe Goldman Date: Thu, 13 Jul 2023 14:31:36 -0400 Subject: [PATCH] [SDK] Small changes from review of Rust client quickstart draft (#78) * SDK: Convert `http[s]` uri schemes to `ws[s]` Prior to this commit, the Rust SDK would reject host URIs which began with `http://` or `https://`, requiring exactly one of `ws://` or `wss://`. Now, the SDK converts `http` URI schemes to their websocket equivalents, so clients can do `connect("http://localhost:3000", "my-database", my-creds)`. * SDK: Append `Args` suffix to generated reducer args structs This reduces the liklihood of name collisions, e.g. when a user has both a table `Guess` and a reducer `guess`. Prior to this commit, in that case, the SDK would generate a `struct guess::Guess` for the table, and a `struct guess_reducer::Guess` for the reducer args. Now, the latter is `struct guess_reducer::GuessArgs`. This will allow the CLI's codegen to re-export all generated structs from its main `mod.rs` file. * SDK: generated `mod.rs` re-exports all other files The `mod.rs` generated by `spacetime generate --lang rust` now re-exports all of the types and functions defined in other files it generates. This allows users to write e.g. `spacetime_types::Player`, rather than `spacetime_types::player::Player`. --- crates/cli/src/subcommands/generate/rust.rs | 83 ++++++++++++++------- crates/sdk/src/websocket.rs | 23 ++++-- 2 files changed, 70 insertions(+), 36 deletions(-) diff --git a/crates/cli/src/subcommands/generate/rust.rs b/crates/cli/src/subcommands/generate/rust.rs index 3fc29bdebc..b1ef278206 100644 --- a/crates/cli/src/subcommands/generate/rust.rs +++ b/crates/cli/src/subcommands/generate/rust.rs @@ -535,12 +535,32 @@ fn print_table_filter_methods( ) } +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) +} + /// Generate a file which defines a struct corresponding to the `reducer`'s arguments, /// implements `spacetimedb_sdk::table::Reducer` for it, and defines a helper /// function which invokes the reducer. pub fn autogen_rust_reducer(ctx: &GenCtx, reducer: &ReducerDef) -> String { - let func_name = reducer.name.to_case(Case::Snake); - let type_name = reducer.name.to_case(Case::Pascal); + 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; @@ -610,9 +630,9 @@ pub fn autogen_rust_reducer(ctx: &GenCtx, reducer: &ReducerDef) -> String { /// /// The `mod.rs` contains several things: /// -/// 1. `pub mod` declarations for all the other files generated. Without these, either the -/// other files wouldn't get compiled, or users would have to `mod`-declare each file -/// manually. +/// 1. `pub mod` and `pub use` declarations for all the other files generated. +/// Without these, either the other files wouldn't get compiled, +/// or users would have to `mod`-declare each file manually. /// /// 2. `enum ReducerEvent`, which has variants for each reducer in the module. /// Row callbacks are passed an optional `ReducerEvent` as an additional argument, @@ -659,6 +679,11 @@ pub fn autogen_rust_globals(ctx: &GenCtx, items: &[GenItem]) -> Vec<(String, Str out.newline(); + // Re-export all the modules for the generated files. + print_module_reexports(out, items); + + out.newline(); + // Define `enum ReducerEvent`. print_reducer_event_defn(out, items); @@ -723,21 +748,25 @@ fn iter_table_items(items: &[GenItem]) -> impl Iterator { }) } +fn iter_module_names(items: &[GenItem]) -> impl Iterator + '_ { + items.iter().filter_map(|item| match item { + GenItem::Table(table) => Some(table.name.to_case(Case::Snake)), + GenItem::TypeAlias(ty) => Some(ty.name.to_case(Case::Snake)), + GenItem::Reducer(reducer) => (!is_init(reducer)).then_some(reducer_module_name(reducer)), + }) +} + /// Print `pub mod` declarations for all the files that will be generated for `items`. fn print_module_decls(out: &mut Indenter, items: &[GenItem]) { - for item in items { - let (name, suffix) = match item { - GenItem::Table(table) => (&table.name, ""), - GenItem::TypeAlias(ty) => (&ty.name, ""), - GenItem::Reducer(reducer) => { - if is_init(reducer) { - continue; - } - (&reducer.name, "_reducer") - } - }; - let module_name = name.to_case(Case::Snake); - writeln!(out, "pub mod {}{};", module_name, suffix).unwrap(); + for module_name in iter_module_names(items) { + writeln!(out, "pub mod {};", module_name).unwrap(); + } +} + +/// Print `pub use *` declarations for all the files that will be generated for `items`. +fn print_module_reexports(out: &mut Indenter, items: &[GenItem]) { + for module_name in iter_module_names(items) { + writeln!(out, "pub use {}::*;", module_name).unwrap(); } } @@ -856,14 +885,13 @@ fn print_handle_event_defn(out: &mut Indenter, items: &[GenItem]) { "match &function_call.reducer[..] {", |out| { for reducer in iter_reducer_items(items) { - let type_or_variant_name = reducer.name.to_case(Case::Pascal); writeln!( out, - "{:?} => reducer_callbacks.handle_event_of_type::<{}_reducer::{}, ReducerEvent>(event, state, ReducerEvent::{}),", + "{:?} => reducer_callbacks.handle_event_of_type::<{}::{}, ReducerEvent>(event, state, ReducerEvent::{}),", reducer.name, - reducer.name.to_case(Case::Snake), - type_or_variant_name, - type_or_variant_name, + reducer_module_name(reducer), + reducer_type_name(reducer), + reducer_variant_name(reducer), ).unwrap(); } writeln!( @@ -925,13 +953,12 @@ fn print_reducer_event_defn(out: &mut Indenter, items: &[GenItem]) { for item in items { if let GenItem::Reducer(reducer) = item { if !is_init(reducer) { - let type_name = reducer.name.to_case(Case::Pascal); writeln!( out, - "{}({}_reducer::{}),", - type_name, - reducer.name.to_case(Case::Snake), - type_name, + "{}({}::{}),", + reducer_variant_name(reducer), + reducer_module_name(reducer), + reducer_type_name(reducer), ) .unwrap(); } diff --git a/crates/sdk/src/websocket.rs b/crates/sdk/src/websocket.rs index a98373effb..da7d914bc5 100644 --- a/crates/sdk/src/websocket.rs +++ b/crates/sdk/src/websocket.rs @@ -5,7 +5,7 @@ use futures::{ SinkExt, StreamExt, }; use futures_channel::mpsc; -use http::uri::{Parts, Uri}; +use http::uri::{Parts, Scheme, Uri}; use prost::Message as ProtobufMessage; use spacetimedb_client_api_messages::client_api::Message; use tokio::{net::TcpStream, runtime, task::JoinHandle}; @@ -19,6 +19,18 @@ pub(crate) struct DbConnection { pub(crate) write: SplitSink>, WebSocketMessage>, } +fn parse_scheme(scheme: Option) -> Result { + Ok(match scheme { + Some(s) => match s.as_str() { + "ws" | "wss" => s, + "http" => "ws".parse()?, + "https" => "wss".parse()?, + unknown_scheme => bail!("Unknown URI scheme {}", unknown_scheme), + }, + None => "ws".parse()?, + }) +} + fn make_uri(host: Host, db_name: &str) -> Result where Host: TryInto, @@ -26,13 +38,8 @@ where { let host: Uri = host.try_into()?; let mut parts = Parts::try_from(host)?; - match &parts.scheme { - Some(s) => match s.as_str() { - "ws" | "wss" => (), - unknown_scheme => bail!("Unknown URI scheme {}", unknown_scheme), - }, - None => parts.scheme = Some("ws".parse()?), - } + let scheme = parse_scheme(parts.scheme.take())?; + parts.scheme = Some(scheme); let mut path = if let Some(path_and_query) = parts.path_and_query { if let Some(query) = path_and_query.query() { bail!("Unexpected query {}", query);