Skip to content

Commit

Permalink
[SDK] Small changes from review of Rust client quickstart draft (#78)
Browse files Browse the repository at this point in the history
* 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`.
  • Loading branch information
gefjon authored Jul 13, 2023
1 parent 882d4cf commit c15dbdd
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 36 deletions.
83 changes: 55 additions & 28 deletions crates/cli/src/subcommands/generate/rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -723,21 +748,25 @@ fn iter_table_items(items: &[GenItem]) -> impl Iterator<Item = &TableDef> {
})
}

fn iter_module_names(items: &[GenItem]) -> impl Iterator<Item = String> + '_ {
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();
}
}

Expand Down Expand Up @@ -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!(
Expand Down Expand Up @@ -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();
}
Expand Down
23 changes: 15 additions & 8 deletions crates/sdk/src/websocket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -19,20 +19,27 @@ pub(crate) struct DbConnection {
pub(crate) write: SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, WebSocketMessage>,
}

fn parse_scheme(scheme: Option<Scheme>) -> Result<Scheme> {
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: Host, db_name: &str) -> Result<Uri>
where
Host: TryInto<Uri>,
<Host as TryInto<Uri>>::Error: std::error::Error + Send + Sync + 'static,
{
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);
Expand Down

0 comments on commit c15dbdd

Please sign in to comment.