diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 2c69e0ef06e..4d8fe135192 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -184,7 +184,8 @@ jobs: runs-on: spacetimedb-linux timeout-minutes: 20 # on a successful run, runs in 8 minutes container: - image: rust:1.93.0 + # !rust-toolchain-sync + image: rust:1.96.1 options: --privileged # disable until we fix the benchmarks if: false diff --git a/Cargo.lock b/Cargo.lock index 07e571bf069..13aba752ae5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -360,6 +360,7 @@ checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" dependencies = [ "async-trait", "axum-core", + "axum-macros", "bytes", "futures-util", "http", @@ -431,6 +432,17 @@ dependencies = [ "tower-service", ] +[[package]] +name = "axum-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.107", +] + [[package]] name = "backtrace" version = "0.3.76" @@ -7647,6 +7659,17 @@ dependencies = [ "syn 2.0.107", ] +[[package]] +name = "sfv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "890ef0e5532daf21fdaa3dbaf991417187f3a87ab69231fe69be942495746480" +dependencies = [ + "base64 0.22.1", + "indexmap 2.12.0", + "ref-cast", +] + [[package]] name = "sha1" version = "0.10.6" @@ -8113,12 +8136,15 @@ dependencies = [ "bytestring", "derive_more 0.99.20", "enum-as-inner", + "headers", "hex", + "http", "itertools 0.12.1", "proptest", "serde", "serde_json", "serde_with", + "sfv", "smallvec", "spacetimedb-lib", "spacetimedb-primitives", diff --git a/Cargo.toml b/Cargo.toml index a2b27d448c4..f990e3162f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -144,7 +144,7 @@ debug = true version = "2.10.1" edition = "2024" # update rust-toolchain.toml too! -rust-version = "1.93.0" +rust-version = "1.96.0" [workspace.dependencies] spacetimedb = { path = "crates/bindings", version = "=2.10.1" } @@ -193,7 +193,7 @@ arrayvec = "0.7.2" async-channel = "2.5" async-stream = "0.3.6" async-trait = "0.1.68" -axum = { version = "0.7", features = ["tracing", "http2"] } +axum = { version = "0.7", features = ["tracing", "http2", "macros"] } axum-extra = { version = "0.9", features = ["typed-header"] } base64 = "0.21.2" bigdecimal = "0.4.7" @@ -305,6 +305,7 @@ serde_json = { version = "1.0.128", features = ["raw_value"] } serde_path_to_error = "0.1.9" serde_with = { version = "3.3.0", features = ["base64", "hex"] } serial_test = "2.0.0" +sfv = "0.15" sha3 = "0.10.0" slab = "0.4.7" sled = "0.34.7" diff --git a/crates/bench/Dockerfile b/crates/bench/Dockerfile index 0f64f819c82..2e01d684110 100644 --- a/crates/bench/Dockerfile +++ b/crates/bench/Dockerfile @@ -2,8 +2,8 @@ # Set up to run from linux / WSL (running from a windows file system will be extremely slow). # See the README for commands to run. -# sync with: ../../rust-toolchain.toml -FROM rust:1.93.0 +# !rust-toolchain-sync +FROM rust:1.96.1 RUN apt-get update && \ apt-get install -y valgrind bash && \ diff --git a/crates/bindings/tests/ui/environment_enum.stderr b/crates/bindings/tests/ui/environment_enum.stderr index f3f3d25ddc0..cb64199e845 100644 --- a/crates/bindings/tests/ui/environment_enum.stderr +++ b/crates/bindings/tests/ui/environment_enum.stderr @@ -46,13 +46,13 @@ error: expected string literal 38 | #[env(value = 1)] | ^ -error[E0277]: the trait bound `Option: RequiredEnvironmentValue` is not satisfied +error[E0277]: the trait bound `Option: spacetimedb::rt::RequiredEnvironmentValue` is not satisfied --> tests/ui/environment_enum.rs:51:17 | 51 | pub NESTED: Option>, - | ^^^^^^^^^^^^^^^^^^^^ the trait `RequiredEnvironmentValue` is not implemented for `Option` + | ^^^^^^^^^^^^^^^^^^^^ the trait `spacetimedb::rt::RequiredEnvironmentValue` is not implemented for `Option` | -help: the following other types implement trait `RequiredEnvironmentValue` +help: the following other types implement trait `spacetimedb::rt::RequiredEnvironmentValue` --> tests/ui/environment_enum.rs:42:10 | 42 | #[derive(spacetimedb::EnvironmentValue)] @@ -62,7 +62,7 @@ help: the following other types implement trait `RequiredEnvironmentValue` | | impl RequiredEnvironmentValue for String {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `std::string::String` - = note: required for `Option>` to implement `EnvironmentValue` + = note: required for `Option>` to implement `spacetimedb::rt::EnvironmentValue` = note: this error originates in the derive macro `spacetimedb::EnvironmentValue` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: `#[env(values(...))]` requires `String` or `Option`; map enum variants with `#[env(value = "...")]` instead @@ -71,12 +71,12 @@ error[E0277]: `#[env(values(...))]` requires `String` or `Option`; map e 50 | pub MODE: Mode, | ^^^^ unsatisfied trait bound | -help: the trait `StringEnvironmentValue` is not implemented for `Mode` +help: the trait `spacetimedb::rt::StringEnvironmentValue` is not implemented for `Mode` --> tests/ui/environment_enum.rs:43:1 | 43 | pub enum Mode { | ^^^^^^^^^^^^^ -help: the following other types implement trait `StringEnvironmentValue` +help: the following other types implement trait `spacetimedb::rt::StringEnvironmentValue` --> src/rt.rs | | impl StringEnvironmentValue for String {} diff --git a/crates/bindings/tests/ui/environment_types.stderr b/crates/bindings/tests/ui/environment_types.stderr index f884a635897..9341025b102 100644 --- a/crates/bindings/tests/ui/environment_types.stderr +++ b/crates/bindings/tests/ui/environment_types.stderr @@ -4,12 +4,12 @@ error[E0277]: environment fields must be `String`, an enum deriving `Environment 6 | pub VALUE: String, | ^^^^^^ unsatisfied trait bound | -help: the trait `EnvironmentValue` is not implemented for `shadowed_string::String` +help: the trait `spacetimedb::rt::EnvironmentValue` is not implemented for `shadowed_string::String` --> tests/ui/environment_types.rs:2:5 | 2 | pub struct String; | ^^^^^^^^^^^^^^^^^ -help: the following other types implement trait `EnvironmentValue` +help: the following other types implement trait `spacetimedb::rt::EnvironmentValue` --> src/rt.rs | | impl EnvironmentValue for String { @@ -24,12 +24,12 @@ error[E0277]: environment fields must be `String`, an enum deriving `Environment 15 | pub VALUE: Option, | ^^^^^^^^^^^^^^ unsatisfied trait bound | -help: the trait `EnvironmentValue` is not implemented for `shadowed_option::Option` +help: the trait `spacetimedb::rt::EnvironmentValue` is not implemented for `shadowed_option::Option` --> tests/ui/environment_types.rs:11:5 | 11 | pub struct Option(T); | ^^^^^^^^^^^^^^^^^^^^ -help: the following other types implement trait `EnvironmentValue` +help: the following other types implement trait `spacetimedb::rt::EnvironmentValue` --> src/rt.rs | | impl EnvironmentValue for String { @@ -42,9 +42,9 @@ error[E0277]: environment fields must be `String`, an enum deriving `Environment --> tests/ui/environment_types.rs:21:15 | 21 | pub BOOL: bool, - | ^^^^ the trait `EnvironmentValue` is not implemented for `bool` + | ^^^^ the trait `spacetimedb::rt::EnvironmentValue` is not implemented for `bool` | -help: the following other types implement trait `EnvironmentValue` +help: the following other types implement trait `spacetimedb::rt::EnvironmentValue` --> src/rt.rs | | impl EnvironmentValue for String { @@ -53,26 +53,26 @@ help: the following other types implement trait `EnvironmentValue` | impl EnvironmentValue for Option { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `std::option::Option` -error[E0277]: the trait bound `std::option::Option: RequiredEnvironmentValue` is not satisfied +error[E0277]: the trait bound `std::option::Option: spacetimedb::rt::RequiredEnvironmentValue` is not satisfied --> tests/ui/environment_types.rs:22:17 | 22 | pub NESTED: Option>, - | ^^^^^^^^^^^^^^^^^^^^^^ the trait `RequiredEnvironmentValue` is not implemented for `std::option::Option` + | ^^^^^^^^^^^^^^^^^^^^^^ the trait `spacetimedb::rt::RequiredEnvironmentValue` is not implemented for `std::option::Option` | -help: the trait `RequiredEnvironmentValue` is implemented for `std::string::String` +help: the trait `spacetimedb::rt::RequiredEnvironmentValue` is implemented for `std::string::String` --> src/rt.rs | | impl RequiredEnvironmentValue for String {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - = note: required for `std::option::Option>` to implement `EnvironmentValue` + = note: required for `std::option::Option>` to implement `spacetimedb::rt::EnvironmentValue` error[E0277]: environment fields must be `String`, an enum deriving `EnvironmentValue`, or an `Option` of either --> tests/ui/environment_types.rs:24:14 | 24 | pub get: u32, - | ^^^ the trait `EnvironmentValue` is not implemented for `u32` + | ^^^ the trait `spacetimedb::rt::EnvironmentValue` is not implemented for `u32` | -help: the following other types implement trait `EnvironmentValue` +help: the following other types implement trait `spacetimedb::rt::EnvironmentValue` --> src/rt.rs | | impl EnvironmentValue for String { diff --git a/crates/bindings/tests/ui/http_handlers.stderr b/crates/bindings/tests/ui/http_handlers.stderr index c28936ac12e..d22111bb64e 100644 --- a/crates/bindings/tests/ui/http_handlers.stderr +++ b/crates/bindings/tests/ui/http_handlers.stderr @@ -1,205 +1,48 @@ -error: HTTP handlers must take exactly two arguments - --> tests/ui/http_handlers.rs:5:1 - | -5 | fn handler_no_args() -> Response { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: HTTP handlers must take exactly two arguments - --> tests/ui/http_handlers.rs:20:1 - | -20 | fn handler_no_request_arg(_ctx: &mut HandlerContext) -> Response { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: HTTP handlers must return `spacetimedb::http::Response` - --> tests/ui/http_handlers.rs:30:1 - | -30 | fn handler_no_return_type(_ctx: &mut HandlerContext, _req: Request) { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: expected `fn` - --> tests/ui/http_handlers.rs:58:1 - | -58 | static ROUTER_NOT_A_FUNCTION: Router = Router::new(); - | ^^^^^^ - -error: HTTP router functions must take no arguments - --> tests/ui/http_handlers.rs:61:1 - | -61 | fn router_fn_with_args(ctx: &mut HandlerContext) -> Router { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -warning: unused import: `Router` - --> tests/ui/http_handlers.rs:1:77 +error[E0432]: unresolved imports `spacetimedb::http::handler`, `spacetimedb::http::router`, `spacetimedb::http::HandlerContext`, `spacetimedb::http::Router` + --> tests/ui/http_handlers.rs:1:25 | 1 | use spacetimedb::http::{handler, router, HandlerContext, Request, Response, Router}; - | ^^^^^^ + | ^^^^^^^ ^^^^^^ ^^^^^^^^^^^^^^ ^^^^^^ no `Router` in `http` + | | | | + | | | no `HandlerContext` in `http` + | | no `router` in `http` + | no `handler` in `http` + | +note: found an item that was configured out + --> src/http.rs + | + | #[cfg(feature = "unstable")] + | -------------------- the item is gated behind the `unstable` feature + | #[doc(inline)] + | pub use spacetimedb_bindings_macro::http_handler as handler; + | ^^^^^^^ +note: found an item that was configured out + --> src/http.rs | - = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default + | #[cfg(feature = "unstable")] + | -------------------- the item is gated behind the `unstable` feature + | #[doc(inline)] + | pub use spacetimedb_bindings_macro::http_router as router; + | ^^^^^^ +note: found an item that was configured out + --> src/http.rs + | + | #[cfg(feature = "unstable")] + | -------------------- the item is gated behind the `unstable` feature + | #[non_exhaustive] + | pub struct HandlerContext { + | ^^^^^^^^^^^^^^ +note: found an item that was configured out + --> src/http.rs + | + | #[cfg(feature = "unstable")] + | -------------------- the item is gated behind the `unstable` feature + | #[derive(Clone, Default)] + | pub struct Router { + | ^^^^^^ error[E0601]: `main` function not found in crate `$CRATE` --> tests/ui/http_handlers.rs:68:2 | 68 | } | ^ consider adding a `main` function to `$DIR/tests/ui/http_handlers.rs` - -error[E0308]: mismatched types - --> tests/ui/http_handlers.rs:10:4 - | - 9 | #[handler] - | ---------- arguments to this function are incorrect -10 | fn handler_immutable_ctx(_ctx: &HandlerContext, _req: Request) -> Response { - | ^^^^^^^^^^^^^^^^^^^^^ types differ in mutability - | - = note: expected fn pointer `for<'a> fn(&'a mut HandlerContext, http::request::Request) -> http::response::Response` - found fn item `for<'a> fn(&'a HandlerContext, http::request::Request) -> http::response::Response {__spacetimedb_http_handler_handler_immutable_ctx}` -note: function defined here - --> src/rt.rs - | - | pub fn register_http_handler(name: &'static str, handler: HttpHandlerFn) { - | ^^^^^^^^^^^^^^^^^^^^^ - -error[E0277]: the first argument of an HTTP handler must be `&mut HandlerContext` - --> tests/ui/http_handlers.rs:10:32 - | -10 | fn handler_immutable_ctx(_ctx: &HandlerContext, _req: Request) -> Response { - | ^^^^^^^^^^^^^^^ first argument must be `&mut HandlerContext` - | - = help: the trait `HttpHandlerContextArg` is not implemented for `&HandlerContext` -help: the trait `HttpHandlerContextArg` is implemented for `&mut HandlerContext` - --> src/rt.rs - | - | impl HttpHandlerContextArg for &mut HandlerContext {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - = note: `HttpHandlerContextArg` is implemented for `&mut HandlerContext`, but not for `&HandlerContext` - -error[E0308]: mismatched types - --> tests/ui/http_handlers.rs:15:4 - | -14 | #[handler] - | ---------- arguments to this function are incorrect -15 | fn handler_wrong_ctx(_ctx: &mut ProcedureContext, _req: Request) -> Response { - | ^^^^^^^^^^^^^^^^^ expected fn pointer, found fn item - | - = note: expected fn pointer `for<'a> fn(&'a mut HandlerContext, http::request::Request) -> http::response::Response` - found fn item `for<'a> fn(&'a mut ProcedureContext, http::request::Request) -> http::response::Response {__spacetimedb_http_handler_handler_wrong_ctx}` -note: function defined here - --> src/rt.rs - | - | pub fn register_http_handler(name: &'static str, handler: HttpHandlerFn) { - | ^^^^^^^^^^^^^^^^^^^^^ - -error[E0277]: the first argument of an HTTP handler must be `&mut HandlerContext` - --> tests/ui/http_handlers.rs:15:28 - | -15 | fn handler_wrong_ctx(_ctx: &mut ProcedureContext, _req: Request) -> Response { - | ^^^^^^^^^^^^^^^^^^^^^ first argument must be `&mut HandlerContext` - | - = help: the trait `HttpHandlerContextArg` is not implemented for `&mut ProcedureContext` -help: the trait `HttpHandlerContextArg` is implemented for `&mut HandlerContext` - --> src/rt.rs - | - | impl HttpHandlerContextArg for &mut HandlerContext {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error[E0308]: mismatched types - --> tests/ui/http_handlers.rs:25:4 - | -24 | #[handler] - | ---------- arguments to this function are incorrect -25 | fn handler_wrong_request_arg_type(_ctx: &mut HandlerContext, _req: u32) -> Response { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected fn pointer, found fn item - | - = note: expected fn pointer `for<'a> fn(&'a mut HandlerContext, http::request::Request) -> http::response::Response` - found fn item `for<'a> fn(&'a mut HandlerContext, u32) -> http::response::Response {__spacetimedb_http_handler_handler_wrong_request_arg_type}` -note: function defined here - --> src/rt.rs - | - | pub fn register_http_handler(name: &'static str, handler: HttpHandlerFn) { - | ^^^^^^^^^^^^^^^^^^^^^ - -error[E0277]: the second argument of an HTTP handler must be `spacetimedb::http::Request` - --> tests/ui/http_handlers.rs:25:68 - | -25 | fn handler_wrong_request_arg_type(_ctx: &mut HandlerContext, _req: u32) -> Response { - | ^^^ the trait `HttpHandlerRequestArg` is not implemented for `u32` - | -help: the trait `HttpHandlerRequestArg` is implemented for `http::request::Request` - --> src/rt.rs - | - | impl HttpHandlerRequestArg for crate::http::Request {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error[E0308]: mismatched types - --> tests/ui/http_handlers.rs:35:4 - | -34 | #[handler] - | ---------- arguments to this function are incorrect -35 | fn handler_wrong_return_type(_ctx: &mut HandlerContext, _req: Request) -> u32 { - | ^^^^^^^^^^^^^^^^^^^^^^^^^ expected fn pointer, found fn item - | - = note: expected fn pointer `for<'a> fn(&'a mut HandlerContext, http::request::Request) -> http::response::Response` - found fn item `for<'a> fn(&'a mut HandlerContext, http::request::Request) -> u32 {__spacetimedb_http_handler_handler_wrong_return_type}` -note: function defined here - --> src/rt.rs - | - | pub fn register_http_handler(name: &'static str, handler: HttpHandlerFn) { - | ^^^^^^^^^^^^^^^^^^^^^ - -error[E0277]: HTTP handlers must return `spacetimedb::http::Response` - --> tests/ui/http_handlers.rs:35:75 - | -35 | fn handler_wrong_return_type(_ctx: &mut HandlerContext, _req: Request) -> u32 { - | ^^^ the trait `HttpHandlerReturn` is not implemented for `u32` - | -help: the trait `HttpHandlerReturn` is implemented for `http::response::Response` - --> src/rt.rs - | - | impl HttpHandlerReturn for crate::http::Response {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error[E0599]: no method named `sender` found for mutable reference `&mut HandlerContext` in the current scope - --> tests/ui/http_handlers.rs:41:23 - | -41 | let _sender = ctx.sender(); - | ^^^^^^ method not found in `&mut HandlerContext` - -error[E0599]: no method named `connection_id` found for mutable reference `&mut HandlerContext` in the current scope - --> tests/ui/http_handlers.rs:42:24 - | -42 | let _conn_id = ctx.connection_id(); - | ^^^^^^^^^^^^^ method not found in `&mut HandlerContext` - -error[E0609]: no field `db` on type `&mut HandlerContext` - --> tests/ui/http_handlers.rs:53:21 - | -53 | let _rows = ctx.db.test_table().iter(); - | ^^ unknown field - | - = note: available fields are: `env`, `timestamp`, `http` - -error[E0308]: mismatched types - --> tests/ui/http_handlers.rs:66:4 - | -65 | #[router] - | --------- expected due to this -66 | fn router_fn_wrong_return_type() -> u32 { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected fn pointer, found fn item - | - = note: expected fn pointer `fn() -> Router` - found fn item `fn() -> u32 {router_fn_wrong_return_type}` - -error[E0308]: mismatched types - --> tests/ui/http_handlers.rs:66:4 - | -65 | #[router] - | --------- arguments to this function are incorrect -66 | fn router_fn_wrong_return_type() -> u32 { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected fn pointer, found fn item - | - = note: expected fn pointer `fn() -> Router` - found fn item `fn() -> u32 {router_fn_wrong_return_type}` -note: function defined here - --> src/rt.rs - | - | pub fn register_http_router(build: fn() -> crate::http::Router) { - | ^^^^^^^^^^^^^^^^^^^^ diff --git a/crates/bindings/tests/ui/reducers.stderr b/crates/bindings/tests/ui/reducers.stderr index acc25c83008..cbade14ff0a 100644 --- a/crates/bindings/tests/ui/reducers.stderr +++ b/crates/bindings/tests/ui/reducers.stderr @@ -28,13 +28,13 @@ error[E0277]: invalid reducer signature 6 | fn bad_type(_ctx: &ReducerContext, _a: Test) {} | ^^^^^^^^ this reducer signature is not valid | - = help: the trait `Reducer<'_, _>` is not implemented for fn item `for<'a> fn(&'a ReducerContext, Test) {bad_type}` + = help: the trait `spacetimedb::rt::Reducer<'_, _>` is not implemented for fn item `for<'a> fn(&'a ReducerContext, Test) {bad_type}` = note: = note: reducer signatures must match the following pattern: = note: `Fn(&ReducerContext, [T1, ...]) [-> Result<(), impl Display>]` = note: where each `Ti` type implements `SpacetimeType`. = note: -note: required by a bound in `register_reducer` +note: required by a bound in `spacetimedb::rt::register_reducer` --> src/rt.rs | | pub fn register_reducer<'a, A: Args<'a>, I: FnInfo>(_: impl Reducer<'a, A>) { @@ -62,7 +62,7 @@ help: the trait `SpacetimeType` is not implemented for `Test` Box ColumnAttribute and $N others - = note: required for `Test` to implement `ReducerArg` + = note: required for `Test` to implement `spacetimedb::rt::ReducerArg` error[E0277]: invalid reducer signature --> tests/ui/reducers.rs:6:4 @@ -72,13 +72,13 @@ error[E0277]: invalid reducer signature 6 | fn bad_type(_ctx: &ReducerContext, _a: Test) {} | ^^^^^^^^ this reducer signature is not valid | - = help: the trait `Reducer<'_, _>` is not implemented for fn item `for<'a> fn(&'a ReducerContext, Test) {bad_type}` + = help: the trait `spacetimedb::rt::Reducer<'_, _>` is not implemented for fn item `for<'a> fn(&'a ReducerContext, Test) {bad_type}` = note: = note: reducer signatures must match the following pattern: = note: `Fn(&ReducerContext, [T1, ...]) [-> Result<(), impl Display>]` = note: where each `Ti` type implements `SpacetimeType`. = note: -note: required by a bound in `invoke_reducer` +note: required by a bound in `spacetimedb::rt::invoke_reducer` --> src/rt.rs | | pub fn invoke_reducer<'a, A: Args<'a>>( @@ -94,13 +94,13 @@ error[E0277]: invalid reducer signature 9 | fn bad_return_type(_ctx: &ReducerContext) -> Test { | ^^^^^^^^^^^^^^^ this reducer signature is not valid | - = help: the trait `Reducer<'_, _>` is not implemented for fn item `for<'a> fn(&'a ReducerContext) -> Test {bad_return_type}` + = help: the trait `spacetimedb::rt::Reducer<'_, _>` is not implemented for fn item `for<'a> fn(&'a ReducerContext) -> Test {bad_return_type}` = note: = note: reducer signatures must match the following pattern: = note: `Fn(&ReducerContext, [T1, ...]) [-> Result<(), impl Display>]` = note: where each `Ti` type implements `SpacetimeType`. = note: -note: required by a bound in `register_reducer` +note: required by a bound in `spacetimedb::rt::register_reducer` --> src/rt.rs | | pub fn register_reducer<'a, A: Args<'a>, I: FnInfo>(_: impl Reducer<'a, A>) { @@ -112,13 +112,13 @@ error[E0277]: `Test` is not a valid reducer return type 9 | fn bad_return_type(_ctx: &ReducerContext) -> Test { | ^^^^ unsatisfied trait bound | -help: the trait `IntoReducerResult` is not implemented for `Test` +help: the trait `spacetimedb::rt::IntoReducerResult` is not implemented for `Test` --> tests/ui/reducers.rs:3:1 | 3 | struct Test; | ^^^^^^^^^^^ = note: reducers cannot return values -- you can only return `()` or `Result<(), impl Display>` -help: the following other types implement trait `IntoReducerResult` +help: the following other types implement trait `spacetimedb::rt::IntoReducerResult` --> src/rt.rs | | impl IntoReducerResult for () { @@ -135,13 +135,13 @@ error[E0277]: invalid reducer signature 9 | fn bad_return_type(_ctx: &ReducerContext) -> Test { | ^^^^^^^^^^^^^^^ this reducer signature is not valid | - = help: the trait `Reducer<'_, _>` is not implemented for fn item `for<'a> fn(&'a ReducerContext) -> Test {bad_return_type}` + = help: the trait `spacetimedb::rt::Reducer<'_, _>` is not implemented for fn item `for<'a> fn(&'a ReducerContext) -> Test {bad_return_type}` = note: = note: reducer signatures must match the following pattern: = note: `Fn(&ReducerContext, [T1, ...]) [-> Result<(), impl Display>]` = note: where each `Ti` type implements `SpacetimeType`. = note: -note: required by a bound in `invoke_reducer` +note: required by a bound in `spacetimedb::rt::invoke_reducer` --> src/rt.rs | | pub fn invoke_reducer<'a, A: Args<'a>>( @@ -157,13 +157,13 @@ error[E0277]: invalid reducer signature 23 | fn missing_ctx(_a: u8) {} | ^^^^^^^^^^^ this reducer signature is not valid | - = help: the trait `Reducer<'_, _>` is not implemented for fn item `fn(u8) {missing_ctx}` + = help: the trait `spacetimedb::rt::Reducer<'_, _>` is not implemented for fn item `fn(u8) {missing_ctx}` = note: = note: reducer signatures must match the following pattern: = note: `Fn(&ReducerContext, [T1, ...]) [-> Result<(), impl Display>]` = note: where each `Ti` type implements `SpacetimeType`. = note: -note: required by a bound in `register_reducer` +note: required by a bound in `spacetimedb::rt::register_reducer` --> src/rt.rs | | pub fn register_reducer<'a, A: Args<'a>, I: FnInfo>(_: impl Reducer<'a, A>) { @@ -175,8 +175,8 @@ error[E0277]: the first argument of a reducer must be `&ReducerContext` 23 | fn missing_ctx(_a: u8) {} | ^^ first argument must be `&ReducerContext` | - = help: the trait `ReducerContextArg` is not implemented for `u8` -help: the trait `ReducerContextArg` is implemented for `&ReducerContext` + = help: the trait `spacetimedb::rt::ReducerContextArg` is not implemented for `u8` +help: the trait `spacetimedb::rt::ReducerContextArg` is implemented for `&ReducerContext` --> src/rt.rs | | impl ReducerContextArg for &ReducerContext {} @@ -190,13 +190,13 @@ error[E0277]: invalid reducer signature 23 | fn missing_ctx(_a: u8) {} | ^^^^^^^^^^^ this reducer signature is not valid | - = help: the trait `Reducer<'_, _>` is not implemented for fn item `fn(u8) {missing_ctx}` + = help: the trait `spacetimedb::rt::Reducer<'_, _>` is not implemented for fn item `fn(u8) {missing_ctx}` = note: = note: reducer signatures must match the following pattern: = note: `Fn(&ReducerContext, [T1, ...]) [-> Result<(), impl Display>]` = note: where each `Ti` type implements `SpacetimeType`. = note: -note: required by a bound in `invoke_reducer` +note: required by a bound in `spacetimedb::rt::invoke_reducer` --> src/rt.rs | | pub fn invoke_reducer<'a, A: Args<'a>>( @@ -212,13 +212,13 @@ error[E0277]: invalid reducer signature 26 | fn ctx_by_val(_ctx: ReducerContext, _a: u8) {} | ^^^^^^^^^^ this reducer signature is not valid | - = help: the trait `Reducer<'_, _>` is not implemented for fn item `fn(ReducerContext, u8) {ctx_by_val}` + = help: the trait `spacetimedb::rt::Reducer<'_, _>` is not implemented for fn item `fn(ReducerContext, u8) {ctx_by_val}` = note: = note: reducer signatures must match the following pattern: = note: `Fn(&ReducerContext, [T1, ...]) [-> Result<(), impl Display>]` = note: where each `Ti` type implements `SpacetimeType`. = note: -note: required by a bound in `register_reducer` +note: required by a bound in `spacetimedb::rt::register_reducer` --> src/rt.rs | | pub fn register_reducer<'a, A: Args<'a>, I: FnInfo>(_: impl Reducer<'a, A>) { @@ -230,8 +230,8 @@ error[E0277]: the first argument of a reducer must be `&ReducerContext` 26 | fn ctx_by_val(_ctx: ReducerContext, _a: u8) {} | ^^^^^^^^^^^^^^ first argument must be `&ReducerContext` | - = help: the trait `ReducerContextArg` is not implemented for `ReducerContext` -help: the trait `ReducerContextArg` is implemented for `&ReducerContext` + = help: the trait `spacetimedb::rt::ReducerContextArg` is not implemented for `ReducerContext` +help: the trait `spacetimedb::rt::ReducerContextArg` is implemented for `&ReducerContext` --> src/rt.rs | | impl ReducerContextArg for &ReducerContext {} @@ -245,13 +245,13 @@ error[E0277]: invalid reducer signature 26 | fn ctx_by_val(_ctx: ReducerContext, _a: u8) {} | ^^^^^^^^^^ this reducer signature is not valid | - = help: the trait `Reducer<'_, _>` is not implemented for fn item `fn(ReducerContext, u8) {ctx_by_val}` + = help: the trait `spacetimedb::rt::Reducer<'_, _>` is not implemented for fn item `fn(ReducerContext, u8) {ctx_by_val}` = note: = note: reducer signatures must match the following pattern: = note: `Fn(&ReducerContext, [T1, ...]) [-> Result<(), impl Display>]` = note: where each `Ti` type implements `SpacetimeType`. = note: -note: required by a bound in `invoke_reducer` +note: required by a bound in `spacetimedb::rt::invoke_reducer` --> src/rt.rs | | pub fn invoke_reducer<'a, A: Args<'a>>( @@ -271,9 +271,9 @@ error[E0593]: function is expected to take 2 arguments, but it takes 3 arguments 48 | fn scheduled_table_reducer(_ctx: &ReducerContext, _x: u8, _y: u8) {} | ----------------------------------------------------------------- takes 3 arguments | - = note: required for `for<'a> fn(&'a ReducerContext, u8, u8) {scheduled_table_reducer}` to implement `Reducer<'_, (ScheduledTable,)>` - = note: required for `for<'a> fn(&'a ReducerContext, u8, u8) {scheduled_table_reducer}` to implement `ExportFunctionForScheduledTable<'_, ScheduledTable, FnKindReducer>` -note: required by a bound in `scheduled_typecheck` + = note: required for `for<'a> fn(&'a ReducerContext, u8, u8) {scheduled_table_reducer}` to implement `spacetimedb::rt::Reducer<'_, (ScheduledTable,)>` + = note: required for `for<'a> fn(&'a ReducerContext, u8, u8) {scheduled_table_reducer}` to implement `spacetimedb::rt::ExportFunctionForScheduledTable<'_, ScheduledTable, spacetimedb::rt::FnKindReducer>` +note: required by a bound in `spacetimedb::rt::scheduled_typecheck` --> src/rt.rs | | pub const fn scheduled_typecheck<'de, Row, FnKind>(_x: impl ExportFunctionForScheduledTable<'de, Row, FnKind>) diff --git a/crates/bindings/tests/ui/tables.stderr b/crates/bindings/tests/ui/tables.stderr index 7609d9ba378..9072d344284 100644 --- a/crates/bindings/tests/ui/tables.stderr +++ b/crates/bindings/tests/ui/tables.stderr @@ -197,7 +197,7 @@ help: the trait `SpacetimeType` is not implemented for `Test` ArrayType Box and $N others - = note: required for `Test` to implement `TableColumn` + = note: required for `Test` to implement `spacetimedb::rt::TableColumn` error[E0277]: `&'a Alpha` cannot appear as an argument to an index filtering operation --> tests/ui/tables.rs:32:33 @@ -226,7 +226,7 @@ note: required by a bound in `UniqueColumn:: &'a Col::ColType: FilterableValue, | ^^^^^^^^^^^^^^^ required by this bound in `UniqueColumn::::ColType, Col>::find` -error[E0277]: the trait bound `Alpha: IndexScanRangeBounds<(Alpha,), SingleBound>` is not satisfied +error[E0277]: the trait bound `Alpha: spacetimedb::table::IndexScanRangeBounds<(Alpha,), spacetimedb::table::SingleBound>` is not satisfied --> tests/ui/tables.rs:33:40 | 33 | ctx.db.delta().compound_b().filter(Alpha { beta: 1 }); @@ -249,7 +249,7 @@ help: the trait `FilterableValue` is not implemented for `Alpha` &bool ðnum::int::I256 and $N others - = note: required for `Alpha` to implement `IndexScanRangeBounds<(Alpha,), SingleBound>` + = note: required for `Alpha` to implement `spacetimedb::table::IndexScanRangeBounds<(Alpha,), spacetimedb::table::SingleBound>` note: required by a bound in `RangedIndex::::filter` --> src/table.rs | diff --git a/crates/bindings/tests/ui/views.stderr b/crates/bindings/tests/ui/views.stderr index 2b0c0dd6033..426b623b985 100644 --- a/crates/bindings/tests/ui/views.stderr +++ b/crates/bindings/tests/ui/views.stderr @@ -54,36 +54,13 @@ error: Views do not take parameters other than `&ViewContext` or `&AnonymousView 138 | fn sched_table_view(_: &ViewContext, _args: ScheduledTable) -> Vec { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error[E0425]: cannot find type `ScheduledTable` in this scope - --> tests/ui/views.rs:138:45 - | -138 | fn sched_table_view(_: &ViewContext, _args: ScheduledTable) -> Vec { - | ^^^^^^^^^^^^^^ not found in this scope - -error[E0425]: cannot find type `T` in this scope - --> tests/ui/views.rs:196:60 - | -196 | fn view_nonexistent_table(ctx: &ViewContext) -> impl Query { - | ^ not found in this scope - | -help: you might be missing a type parameter - | -196 | fn view_nonexistent_table(ctx: &ViewContext) -> impl Query { - | +++ - -error[E0425]: cannot find type `T` in this scope - --> tests/ui/views.rs:196:60 - | -196 | fn view_nonexistent_table(ctx: &ViewContext) -> impl Query { - | ^ not found in this scope - -error[E0277]: the trait bound `spacetimedb::rt::ViewKind: ViewKindTrait` is not satisfied +error[E0277]: the trait bound `spacetimedb::rt::ViewKind: spacetimedb::rt::ViewKindTrait` is not satisfied --> tests/ui/views.rs:101:1 | 101 | #[view(accessor = view_def_wrong_context, public)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `ViewKindTrait` is not implemented for `spacetimedb::rt::ViewKind` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `spacetimedb::rt::ViewKindTrait` is not implemented for `spacetimedb::rt::ViewKind` | -help: the following other types implement trait `ViewKindTrait` +help: the following other types implement trait `spacetimedb::rt::ViewKindTrait` --> src/rt.rs | | impl ViewKindTrait for ViewKind { @@ -97,7 +74,7 @@ error[E0276]: impl has stricter requirements than trait --> tests/ui/views.rs:101:1 | 101 | #[view(accessor = view_def_wrong_context, public)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ impl has extra requirement `spacetimedb::rt::ViewKind: ViewKindTrait` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ impl has extra requirement `spacetimedb::rt::ViewKind: spacetimedb::rt::ViewKindTrait` | = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) @@ -123,8 +100,7 @@ error[E0599]: no method named `insert` found for reference `&test__ViewHandle` i candidate #1: `bitflags::traits::Flags` candidate #2: `ppv_lite86::types::Vec2` candidate #3: `ppv_lite86::types::Vec4` - candidate #4: `similar::algorithms::DiffHook` - candidate #5: `spacetimedb::Table` + candidate #4: `spacetimedb::Table` error[E0599]: no method named `try_insert` found for reference `&test__ViewHandle` in the current scope --> tests/ui/views.rs:31:25 @@ -149,9 +125,8 @@ error[E0599]: no method named `delete` found for reference `&test__ViewHandle` i | ^^^^^^ method not found in `&test__ViewHandle` | = help: items from traits can only be used if the trait is implemented and in scope - = note: the following traits define an item `delete`, perhaps you need to implement one of them: - candidate #1: `similar::algorithms::DiffHook` - candidate #2: `spacetimedb::Table` + = note: the following trait defines an item `delete`, perhaps you need to implement it: + candidate #1: `spacetimedb::Table` error[E0599]: no method named `delete` found for struct `UniqueColumnReadOnly` in the current scope --> tests/ui/views.rs:45:30 @@ -171,24 +146,24 @@ error[E0599]: no method named `delete` found for struct `RangedIndexReadOnly` -error[E0599]: no function or associated item named `register` found for struct `ViewRegistrar` in the current scope +error[E0599]: no associated function or constant named `register` found for struct `spacetimedb::rt::ViewRegistrar` in the current scope --> tests/ui/views.rs:101:1 | 101 | #[view(accessor = view_def_wrong_context, public)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ function or associated item not found in `ViewRegistrar` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ associated function or constant not found in `spacetimedb::rt::ViewRegistrar` | - = note: the function or associated item was found for - - `ViewRegistrar` - - `ViewRegistrar` + = note: the associated function or constant was found for + - `spacetimedb::rt::ViewRegistrar` + - `spacetimedb::rt::ViewRegistrar` = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: The first parameter of a `#[view]` must be `&ViewContext` or `&AnonymousViewContext` --> tests/ui/views.rs:102:31 | 102 | fn view_def_wrong_context(_: &ReducerContext) -> Vec { - | ^^^^^^^^^^^^^^ the trait `ViewContextArg` is not implemented for `ReducerContext` + | ^^^^^^^^^^^^^^ the trait `spacetimedb::rt::ViewContextArg` is not implemented for `ReducerContext` | -help: the following other types implement trait `ViewContextArg` +help: the following other types implement trait `spacetimedb::rt::ViewContextArg` --> src/rt.rs | | impl ViewContextArg for ViewContext {} @@ -196,15 +171,15 @@ help: the following other types implement trait `ViewContextArg` | impl ViewContextArg for AnonymousViewContext {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `AnonymousViewContext` -error[E0599]: no function or associated item named `invoke` found for struct `ViewDispatcher` in the current scope +error[E0599]: no associated function or constant named `invoke` found for struct `spacetimedb::rt::ViewDispatcher` in the current scope --> tests/ui/views.rs:101:1 | 101 | #[view(accessor = view_def_wrong_context, public)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ function or associated item not found in `ViewDispatcher` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ associated function or constant not found in `spacetimedb::rt::ViewDispatcher` | - = note: the function or associated item was found for - - `ViewDispatcher` - - `ViewDispatcher` + = note: the associated function or constant was found for + - `spacetimedb::rt::ViewDispatcher` + - `spacetimedb::rt::ViewDispatcher` = note: this error originates in the attribute macro `view` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: invalid view signature @@ -219,7 +194,7 @@ error[E0277]: invalid view signature = note: `Fn(&ViewContext, [T1, ...]) -> Vec | Option` = note: where each `Ti` implements `SpacetimeType`. = note: -note: required by a bound in `ViewRegistrar::::register` +note: required by a bound in `spacetimedb::rt::ViewRegistrar::::register` --> src/rt.rs | | pub fn register<'a, A, I, T, V>(view: V) @@ -235,12 +210,12 @@ error[E0277]: Views must return `Vec` or `Option` where `T` is a `Spacetim 124 | fn view_def_wrong_return(_: &ViewContext) -> Player { | ^^^^^^ unsatisfied trait bound | -help: the trait `ViewReturn` is not implemented for `Player` +help: the trait `spacetimedb::rt::ViewReturn` is not implemented for `Player` --> tests/ui/views.rs:63:1 | 63 | struct Player { | ^^^^^^^^^^^^^ - = help: the following other types implement trait `ViewReturn`: + = help: the following other types implement trait `spacetimedb::rt::ViewReturn`: FromWhere LeftSemiJoin Option @@ -261,7 +236,7 @@ error[E0277]: invalid view signature = note: `Fn(&ViewContext, [T1, ...]) -> Vec | Option` = note: where each `Ti` implements `SpacetimeType`. = note: -note: required by a bound in `ViewDispatcher::::invoke` +note: required by a bound in `spacetimedb::rt::ViewDispatcher::::invoke` --> src/rt.rs | | pub fn invoke<'a, A, T, V>(view: V, ctx: ViewContext, args: &'a [u8]) -> Vec @@ -277,13 +252,13 @@ error[E0277]: invalid anonymous view signature 131 | #[view(accessor = view_def_returns_not_a_spacetime_type, public)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this view signature is not valid | - = help: the trait `AnonymousView<'_, _, _>` is not implemented for fn item `for<'a> fn(&'a AnonymousViewContext) -> Option {view_def_returns_not_a_spacetime_type}` + = help: the trait `spacetimedb::rt::AnonymousView<'_, _, _>` is not implemented for fn item `for<'a> fn(&'a AnonymousViewContext) -> Option {view_def_returns_not_a_spacetime_type}` = note: = note: anonymous view signatures must match: = note: `Fn(&AnonymousViewContext, [T1, ...]) -> Vec | Option` = note: where each `Ti` implements `SpacetimeType`. = note: -note: required by a bound in `ViewRegistrar::::register` +note: required by a bound in `spacetimedb::rt::ViewRegistrar::::register` --> src/rt.rs | | pub fn register<'a, A, I, T, V>(view: V) @@ -315,7 +290,7 @@ help: the trait `SpacetimeType` is not implemented for `NotSpacetimeType` Box ColumnAttribute and $N others - = note: required for `Option` to implement `ViewReturn` + = note: required for `Option` to implement `spacetimedb::rt::ViewReturn` error[E0277]: the trait bound `NotSpacetimeType: Serialize` is not satisfied --> tests/ui/views.rs:132:71 @@ -338,7 +313,7 @@ help: the trait `Serialize` is not implemented for `NotSpacetimeType` (T0, T1, T2, T3, T4, T5) (T0, T1, T2, T3, T4, T5, T6) and $N others - = note: required for `Option` to implement `ViewReturn` + = note: required for `Option` to implement `spacetimedb::rt::ViewReturn` error[E0277]: invalid anonymous view signature --> tests/ui/views.rs:131:1 @@ -346,13 +321,13 @@ error[E0277]: invalid anonymous view signature 131 | #[view(accessor = view_def_returns_not_a_spacetime_type, public)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this view signature is not valid | - = help: the trait `AnonymousView<'_, _, _>` is not implemented for fn item `for<'a> fn(&'a AnonymousViewContext) -> Option {view_def_returns_not_a_spacetime_type}` + = help: the trait `spacetimedb::rt::AnonymousView<'_, _, _>` is not implemented for fn item `for<'a> fn(&'a AnonymousViewContext) -> Option {view_def_returns_not_a_spacetime_type}` = note: = note: anonymous view signatures must match: = note: `Fn(&AnonymousViewContext, [T1, ...]) -> Vec | Option` = note: where each `Ti` implements `SpacetimeType`. = note: -note: required by a bound in `ViewDispatcher::::invoke` +note: required by a bound in `spacetimedb::rt::ViewDispatcher::::invoke` --> src/rt.rs | | pub fn invoke<'a, A, T, V>(view: V, ctx: AnonymousViewContext, args: &'a [u8]) -> Vec @@ -422,8 +397,11 @@ help: the trait `RHS` is not implemented for `u32` but trait `RHS` is implemented for it --> $WORKSPACE/crates/query-builder/src/expr.rs | + | impl RHS for $ty { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... | impl_rhs!(u32, |v: u32| v.to_string()); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | -------------------------------------- in this macro invocation = help: for that trait implementation, expected `u32`, found `u8` note: required by a bound in `Col::::eq` --> $WORKSPACE/crates/query-builder/src/table.rs @@ -496,7 +474,7 @@ error[E0277]: `&'a NonFilterableViewPrimaryKey` cannot appear as an argument to ðnum::int::I256 ðnum::uint::U256 and $N others - = note: required for `NonFilterableViewPrimaryKey` to implement `ViewPrimaryKeyColumn` + = note: required for `NonFilterableViewPrimaryKey` to implement `spacetimedb::ViewPrimaryKeyColumn` note: required by a bound in `_::_assert_view_primary_key_column::_assert_view_primary_key_column_type` --> tests/ui/views.rs:229:1 | diff --git a/crates/cli/src/subcommands/dev.rs b/crates/cli/src/subcommands/dev.rs index 95cc579182f..d24344b6490 100644 --- a/crates/cli/src/subcommands/dev.rs +++ b/crates/cli/src/subcommands/dev.rs @@ -1270,7 +1270,7 @@ async fn select_database(config: &Config, server: &str, token: &str) -> Result = stream::iter(result.identities.into_iter()) + let databases: Vec = stream::iter(result.identities) .map(|identity_str| { let config = config.clone(); async move { diff --git a/crates/cli/src/subcommands/repl.rs b/crates/cli/src/subcommands/repl.rs index 6561475ca92..9e4d7e5b50d 100644 --- a/crates/cli/src/subcommands/repl.rs +++ b/crates/cli/src/subcommands/repl.rs @@ -61,7 +61,7 @@ pub(crate) async fn exec(con: Connection, format: Format) -> Result<(), anyhow:: let api = ClientApi::new(con); loop { - let readline = rl.readline(&format!("🪐{}>", &database).green()); + let readline = rl.readline(&format!("🪐{}>", database).green()); match readline { Ok(line) => match line.as_str() { ".exit" => break, diff --git a/crates/client-api-messages/Cargo.toml b/crates/client-api-messages/Cargo.toml index f278ac59e02..7cfaad7d3e8 100644 --- a/crates/client-api-messages/Cargo.toml +++ b/crates/client-api-messages/Cargo.toml @@ -20,6 +20,9 @@ serde_with.workspace = true smallvec.workspace = true thiserror.workspace = true derive_more.workspace = true +http.workspace = true +headers.workspace = true +sfv.workspace = true [dev-dependencies] hex.workspace = true diff --git a/crates/client-api-messages/src/lib.rs b/crates/client-api-messages/src/lib.rs index 67b58de659f..c174c310170 100644 --- a/crates/client-api-messages/src/lib.rs +++ b/crates/client-api-messages/src/lib.rs @@ -3,6 +3,5 @@ pub mod energy; pub mod http; pub mod name; -pub mod websocket; - pub mod publish; +pub mod websocket; diff --git a/crates/client-api-messages/src/publish.rs b/crates/client-api-messages/src/publish.rs index c77a21d56a8..1dfc643f1da 100644 --- a/crates/client-api-messages/src/publish.rs +++ b/crates/client-api-messages/src/publish.rs @@ -1,7 +1,7 @@ //! Atomic publish input. Environment values travel only in the request body. use serde::{Deserialize, Deserializer, Serialize}; -use serde_with::{base64::Base64, serde_as}; use spacetimedb_lib::environment::{validate_key, validate_value, MAX_ENV_VARS}; +use spacetimedb_lib::Hash; use std::collections::BTreeMap; pub const CONTENT_TYPE: &str = "application/vnd.spacetimedb.publish+json"; @@ -11,13 +11,9 @@ pub const MAX_REQUEST_BYTES: usize = 192 * 1024 * 1024; /// Values deliberately have no Debug representation. Omission is an empty map, /// including for a publish of an unchanged module. -#[serde_as] #[derive(Clone, Default, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct PublishRequest { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[serde_as(as = "Option")] - pub module: Option>, +pub struct EnvironmentPublish { #[serde(default, deserialize_with = "deserialize_environment")] pub environment: BTreeMap, #[serde(default)] @@ -28,6 +24,99 @@ pub struct PublishRequest { pub expected_module_version: Option, } +pub struct SpacetimeEnvironment(pub BTreeMap); + +impl headers::Header for SpacetimeEnvironment { + fn name() -> &'static http::HeaderName { + static NAME: http::HeaderName = http::HeaderName::from_static("spacetime-environment"); + &NAME + } + + fn decode<'i, I>(values: &mut I) -> Result + where + Self: Sized, + I: Iterator, + { + let mut entries = BTreeMap::new(); + for value in values { + let dict = sfv::Parser::new(value) + .with_version(sfv::Version::Rfc9651) + .parse::() + .map_err(|_| headers::Error::invalid())?; + for (k, v) in dict { + validate_key(k.as_str()).map_err(|_| headers::Error::invalid())?; + let v = match v { + sfv::ListEntry::Item(sfv::Item { bare_item, params }) if params.is_empty() => bare_item, + _ => return Err(headers::Error::invalid()), + }; + let v = match v { + sfv::BareItem::String(s) => s.into(), + sfv::BareItem::DisplayString(s) => s, + _ => return Err(headers::Error::invalid()), + }; + validate_value(&v).map_err(|_| headers::Error::invalid())?; + entries.insert(k.into(), v); + } + } + Ok(Self(entries)) + } + + fn encode>(&self, values: &mut E) { + let mut ser = sfv::DictSerializer::new(); + for (k, v) in &self.0 { + let _ = ser.bare_item(k.as_str().try_into().unwrap(), sfv::RefBareItem::DisplayString(v)); + } + if let Some(header) = ser.finish() { + values.extend([header.try_into().unwrap()]); + } + } +} + +// pub struct SpacetimeEnvironmentRemove(Vec); + +// impl headers::Header for SpacetimeEnvironmentRemove { +// fn name() -> &'static http::HeaderName { +// static NAME: http::HeaderName = http::HeaderName::from_static("spacetime-environment-remove"); +// &NAME +// } + +// fn decode<'i, I>(values: &mut I) -> Result +// where +// Self: Sized, +// I: Iterator, +// { +// let mut entries = Vec::new(); +// for value in values { +// let list = sfv::Parser::new(value) +// .with_version(sfv::Version::Rfc9651) +// .parse::() +// .map_err(|_| headers::Error::invalid())?; +// entries.reserve(list.len()); +// for v in list { +// let tok = match v { +// sfv::ListEntry::Item(sfv::Item { +// bare_item: sfv::BareItem::Token(tok), +// params, +// }) if params.is_empty() => tok, +// _ => return Err(headers::Error::invalid()), +// }; +// entries.push(tok.into()); +// } +// } +// Ok(Self(entries)) +// } + +// fn encode>(&self, values: &mut E) { +// let mut ser = sfv::ListSerializer::new(); +// for v in &self.0 { +// let _ = ser.bare_item(sfv::TokenRef::from_str(v).unwrap()); +// } +// if let Some(header) = ser.finish() { +// values.extend([header.try_into().unwrap()]); +// } +// } +// } + /// Authorized environment metadata. Values never leave the database in this response. #[derive(Clone, Serialize, Deserialize)] pub struct EnvironmentMetadata { @@ -44,7 +133,7 @@ pub enum PublishRequestError { TooLarge, } -impl PublishRequest { +impl EnvironmentPublish { pub fn decode(body: &[u8]) -> Result { if body.len() > MAX_REQUEST_BYTES { return Err(PublishRequestError::TooLarge); @@ -61,12 +150,7 @@ impl PublishRequest { } fn validate(&self) -> Result<(), PublishRequestError> { - if self - .module - .as_ref() - .is_some_and(|module| module.len() > MAX_MODULE_BYTES) - || self.environment.len() > MAX_ENV_VARS - { + if self.environment.len() > MAX_ENV_VARS { return Err(PublishRequestError::TooLarge); } spacetimedb_lib::environment::EnvironmentUpdate { @@ -122,22 +206,17 @@ mod tests { use super::*; #[test] fn roundtrip_and_omission_preserve_complete_string_input() { - let request = PublishRequest { - module: Some(vec![0, 1, 255]), + let request = EnvironmentPublish { environment: BTreeMap::from([("EMPTY".into(), "".into()), ("TOKEN".into(), "雪\0false".into())]), ..Default::default() }; - let decoded = PublishRequest::decode(&request.encode().unwrap()).unwrap(); - assert_eq!(decoded.module, request.module); + let decoded = EnvironmentPublish::decode(&request.encode().unwrap()).unwrap(); assert_eq!(decoded.environment, request.environment); - assert!(PublishRequest::decode(br#"{"module":""}"#) - .unwrap() - .environment - .is_empty()); + assert!(EnvironmentPublish::decode(br#"{}"#).unwrap().environment.is_empty()); } #[test] fn environment_only_mutation_roundtrips_and_rejects_conflicting_operations() { - let request = PublishRequest { + let request = EnvironmentPublish { environment: BTreeMap::from([("FUTURE".into(), "secret-marker".into())]), environment_remove: vec!["OPTIONAL".into()], expected_module_version: Some("ab".repeat(32)), @@ -148,7 +227,7 @@ mod tests { .unwrap() .get("module") .is_none()); - let decoded = PublishRequest::decode(&bytes).unwrap(); + let decoded = EnvironmentPublish::decode(&bytes).unwrap(); assert_eq!(decoded.environment_remove, request.environment_remove); assert_eq!(decoded.expected_module_version, request.expected_module_version); for body in [ @@ -157,7 +236,7 @@ mod tests { r#"{"environment_remove":["KEY","KEY"]}"#, r#"{"expected_module_version":"secret-marker"}"#, ] { - let error = PublishRequest::decode(body.as_bytes()).err().expect("must reject"); + let error = EnvironmentPublish::decode(body.as_bytes()).err().expect("must reject"); assert!(!error.to_string().contains("secret-marker")); } } @@ -165,14 +244,13 @@ mod tests { #[test] fn malformed_inputs_and_duplicate_keys_are_rejected_without_values() { for body in [ - r#"{"module":"","environment":{"KEY":true}}"#, - r#"{"module":"","environment":{"KEY":null}}"#, - r#"{"module":"","environment":{"KEY":"first","KEY":"secret-marker"}}"#, - r#"{"module":"","environment":{"KEY":["secret-marker"]}}"#, - r#"{"module":"secret-marker"}"#, - r#"{"module":"","unknown":"secret-marker"}"#, + r#"{"environment":{"KEY":true}}"#, + r#"{"environment":{"KEY":null}}"#, + r#"{"environment":{"KEY":"first","KEY":"secret-marker"}}"#, + r#"{"environment":{"KEY":["secret-marker"]}}"#, + r#"{"unknown":"secret-marker"}"#, ] { - let error = PublishRequest::decode(body.as_bytes()).err().expect("must reject"); + let error = EnvironmentPublish::decode(body.as_bytes()).err().expect("must reject"); assert!(!format!("{error:?}: {error}").contains("secret-marker")); } } diff --git a/crates/client-api/src/lib.rs b/crates/client-api/src/lib.rs index eb383a5f8c9..1793aab590f 100644 --- a/crates/client-api/src/lib.rs +++ b/crates/client-api/src/lib.rs @@ -17,7 +17,8 @@ use spacetimedb::messages::control_db::{Database, HostType, Node, Replica}; use spacetimedb::sql; use spacetimedb_client_api_messages::http::{SqlStmtResult, SqlStmtStats}; use spacetimedb_client_api_messages::name::{DomainName, InsertDomainResult, RegisterTldResult, SetDomainsResult, Tld}; -use spacetimedb_lib::{ProductTypeElement, ProductValue}; +use spacetimedb_lib::environment::EnvironmentUpdate; +use spacetimedb_lib::{Hash, ProductTypeElement, ProductValue}; use spacetimedb_paths::server::ModuleLogsDir; use spacetimedb_schema::auto_migrate::{MigrationPolicy, PrettyPrintStyle}; use thiserror::Error; @@ -231,11 +232,6 @@ pub struct DatabaseDef { pub database_identity: Identity, /// The compiled program of the database module. pub program_bytes: Bytes, - /// Supplied overrides, never persisted in the public Database record. - pub environment: std::collections::BTreeMap, - pub environment_remove: Vec, - pub environment_replace: bool, - pub expected_module_version: Option, /// The desired number of replicas the database shall have. /// /// If `None`, the edition default is used. @@ -253,9 +249,6 @@ pub struct DatabaseDef { pub struct DatabaseResetDef { pub database_identity: Identity, pub program_bytes: Option, - pub environment: std::collections::BTreeMap, - pub environment_remove: Vec, - pub environment_replace: bool, pub num_replicas: Option, pub host_type: Option, } @@ -328,6 +321,7 @@ pub trait ControlStateWriteAccess: Send + Sync { publisher: &Identity, spec: DatabaseDef, policy: MigrationPolicy, + environment: EnvironmentUpdate, ) -> anyhow::Result>; async fn migrate_plan(&self, spec: DatabaseDef, style: PrettyPrintStyle) -> anyhow::Result; @@ -374,6 +368,13 @@ pub trait ControlStateWriteAccess: Send + Sync { database_identity: &Identity, locked: bool, ) -> anyhow::Result<()>; + + async fn update_environment( + &self, + publisher: &Identity, + environment: EnvironmentUpdate, + expected_module_version: Hash, + ) -> anyhow::Result<()>; } #[async_trait] @@ -442,8 +443,9 @@ impl ControlStateWriteAccess for Arc { identity: &Identity, spec: DatabaseDef, policy: MigrationPolicy, + environment: EnvironmentUpdate, ) -> anyhow::Result> { - (**self).publish_database(identity, spec, policy).await + (**self).publish_database(identity, spec, policy, environment).await } async fn migrate_plan(&self, spec: DatabaseDef, style: PrettyPrintStyle) -> anyhow::Result { @@ -499,6 +501,17 @@ impl ControlStateWriteAccess for Arc { .set_database_lock(caller_identity, database_identity, locked) .await } + + async fn update_environment( + &self, + publisher: &Identity, + environment: EnvironmentUpdate, + expected_module_version: Hash, + ) -> anyhow::Result<()> { + (**self) + .update_environment(publisher, environment, expected_module_version) + .await + } } #[async_trait] diff --git a/crates/client-api/src/routes/database.rs b/crates/client-api/src/routes/database.rs index 4e6bb1af152..8af2bf9c766 100644 --- a/crates/client-api/src/routes/database.rs +++ b/crates/client-api/src/routes/database.rs @@ -1,5 +1,8 @@ mod publish_environment; -use publish_environment::{ModuleBody, PublishBody}; +use axum_extra::typed_header::TypedHeaderRejection; +use publish_environment::ModuleBody; +use spacetimedb_client_api_messages::publish::SpacetimeEnvironment; +use spacetimedb_lib::environment::EnvironmentUpdate; use std::borrow::Cow; use std::future::Future; @@ -14,6 +17,7 @@ use crate::auth::{ }; use crate::routes::subscribe::generate_random_connection_id; use crate::util::serde::humantime_duration; +use crate::util::OptionalHeader; pub use crate::util::{ByteStringBody, NameOrIdentity}; use crate::{ log_and_500, Action, Authorization, ControlStateDelegate, DatabaseDef, DatabaseResetDef, Host, MaybeMisdirected, @@ -642,6 +646,68 @@ where Ok(([(http::header::CACHE_CONTROL, "no-store")], axum::Json(metadata))) } +#[derive(Deserialize)] +pub struct EnvironmentUpdateQueryParams { + remove: Vec, + expected_module_version: Hash, +} + +pub async fn environment_set( + State(ctx): State, + Extension(ResolvedDatabase(database)): Extension, + Extension(auth): Extension, + Query(EnvironmentUpdateQueryParams { + remove, + expected_module_version, + }): Query, + TypedHeader(SpacetimeEnvironment(env)): TypedHeader, +) -> axum::response::Result +where + S: ControlStateDelegate + NodeDelegate + Authorization, +{ + ctx.authorize_action(auth.claims.identity, database.database_identity, Action::UpdateDatabase) + .await?; + let update = EnvironmentUpdate { + values: env, + remove, + replace: true, + }; + update + .validate() + .map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?; + ctx.update_environment(&auth.claims.identity, update, expected_module_version) + .await + .map_err(publish_error) +} + +pub async fn environment_patch( + State(ctx): State, + Extension(ResolvedDatabase(database)): Extension, + Extension(auth): Extension, + Query(EnvironmentUpdateQueryParams { + remove, + expected_module_version, + }): Query, + TypedHeader(SpacetimeEnvironment(env)): TypedHeader, +) -> axum::response::Result +where + S: ControlStateDelegate + NodeDelegate + Authorization, +{ + ctx.authorize_action(auth.claims.identity, database.database_identity, Action::UpdateDatabase) + .await?; + let update = EnvironmentUpdate { + values: env, + remove, + replace: false, + }; + update + .validate() + .map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?; + ctx.update_environment(&auth.claims.identity, update, expected_module_version) + .await + .map_err(publish_error) +} + pub async fn db_info( Extension(ResolvedDatabase(database)): Extension, ) -> axum::response::Result { @@ -901,21 +967,9 @@ pub async fn reset( host_type, }): Query, Extension(auth): Extension, - PublishBody { - program_bytes, - environment, - environment_remove, - environment_replace, - expected_module_version, - environment_only, - }: PublishBody, + OptionalHeader(environment): OptionalHeader, + program_bytes: Bytes, ) -> axum::response::Result> { - if expected_module_version.is_some() { - return Err(bad_request( - "expected_module_version is not supported for database reset".into(), - )); - } - let _ = environment_only; let database_identity = database.database_identity; ctx.authorize_action(auth.claims.identity, database.database_identity, Action::ResetDatabase) @@ -934,10 +988,7 @@ pub async fn reset( &auth.claims.identity, DatabaseResetDef { database_identity, - program_bytes, - environment, - environment_remove, - environment_replace, + program_bytes: Some(program_bytes), num_replicas, host_type: Some(host_type), }, @@ -984,6 +1035,8 @@ pub struct PublishDatabaseQueryParams { /// The parameter has no effect when creating a new database. #[serde(with = "humantime_duration", default = "default_update_confirmation_timeout")] update_confirmation_timeout: Duration, + environment_remove: Vec, + environment_replace: bool, } /// Default timeout for a database update to become confirmed / durable. @@ -1013,32 +1066,13 @@ pub async fn publish( parent, organization, update_confirmation_timeout: confirmation_timeout, - }): Query, - Extension(auth): Extension, - PublishBody { - program_bytes, - environment, environment_remove, environment_replace, - expected_module_version, - environment_only, - }: PublishBody, + }): Query, + OptionalHeader(environment): OptionalHeader, + Extension(auth): Extension, + program_bytes: Bytes, ) -> axum::response::Result> { - if environment_only && (clear || parent.is_some() || organization.is_some() || num_replicas.is_some()) { - return Err(bad_request( - "environment-only publication cannot change database configuration or reset data".into(), - )); - } - if environment_only && expected_module_version.is_none() { - return Err(bad_request( - "environment-only publication requires expected_module_version".into(), - )); - } - if environment_only && name_or_identity.is_none() { - return Err(bad_request( - "environment-only publication requires an existing database".into(), - )); - } // If `clear`, check that the database exists and delegate to `reset`. // If it doesn't exist, ignore the `clear` parameter. // TODO: Replace with actual redirect at the next possible version bump. @@ -1067,27 +1101,15 @@ pub async fn publish( host_type, }), Extension(auth), - PublishBody { - program_bytes, - environment, - environment_remove, - environment_replace, - expected_module_version, - environment_only, - }, + OptionalHeader(environment), + program_bytes, ) .await; } } } - let program_bytes = program_bytes.unwrap_or_default(); - let (database_identity, db_name) = if environment_only { - let name = name_or_identity.as_ref().expect("validated existing database name"); - (name.resolve(&ctx).await?, name.name()) - } else { - get_or_create_identity_and_name(&ctx, &auth, name_or_identity.as_ref()).await? - }; + let (database_identity, db_name) = get_or_create_identity_and_name(&ctx, &auth, name_or_identity.as_ref()).await?; let maybe_parent_database_identity = match parent.as_ref() { None => None, Some(parent) => parent.resolve(&ctx).await.map(Some)?, @@ -1107,13 +1129,6 @@ pub async fn publish( .get_database_by_identity(&database_identity) .await .map_err(log_and_500)?; - if environment_only && existing.is_none() { - return Err(( - StatusCode::NOT_FOUND, - "environment-only publication requires an existing database", - ) - .into()); - } match existing.as_ref() { None => { allow_creation(&auth)?; @@ -1157,16 +1172,17 @@ pub async fn publish( DatabaseDef { database_identity, program_bytes, - environment, - environment_remove, - environment_replace, - expected_module_version, num_replicas, host_type, parent, organization: maybe_org_identity, }, schema_migration_policy, + EnvironmentUpdate { + values: environment.map(|x| x.0).unwrap_or_default(), + remove: environment_remove, + replace: environment_replace, + }, ) .await .map_err(publish_error)?; @@ -1362,10 +1378,6 @@ pub async fn pre_publish DatabaseDef { database_identity, program_bytes, - environment: Default::default(), - environment_remove: Default::default(), - environment_replace: false, - expected_module_version: None, num_replicas: None, host_type, parent: None, @@ -1605,7 +1617,12 @@ pub struct DatabaseRoutes { pub call_reducer_procedure_post: MethodRouter, /// GET: /database/:name_or_identity/schema pub schema_get: MethodRouter, + /// GET: /database/:name_or_identity/environment pub environment_get: MethodRouter, + /// PUT: /database/:name_or_identity/environment + pub environment_put: MethodRouter, + /// PATCH: /database/:name_or_identity/environment + pub environment_patch: MethodRouter, /// GET: /database/:name_or_identity/logs pub logs_get: MethodRouter, /// POST: /database/:name_or_identity/sql @@ -1635,7 +1652,7 @@ where S: NodeDelegate + ControlStateDelegate + HasWebSocketOptions + Authorization + Clone + 'static, { fn default() -> Self { - use axum::routing::{any, delete, get, post, put}; + use axum::routing::{any, delete, get, patch, post, put}; Self { root_post: post(publish::), db_put: put(publish::), @@ -1649,6 +1666,8 @@ where call_reducer_procedure_post: post(call::), schema_get: get(schema::), environment_get: get(environment_metadata::), + environment_put: put(environment_metadata::), + environment_patch: patch(environment_metadata::), logs_get: get(logs::), sql_post: post(sql::), mcp_post: post(crate::routes::mcp::mcp::), @@ -1682,6 +1701,8 @@ where .route("/call/:reducer", self.call_reducer_procedure_post) .route("/schema", self.schema_get) .route("/environment", self.environment_get) + .route("/environment", self.environment_put) + .route("/environment", self.environment_patch) .route("/logs", self.logs_get) .route("/sql", self.sql_post) .route("/mcp", self.mcp_post) @@ -2137,6 +2158,7 @@ mod tests { _publisher: &Identity, _spec: DatabaseDef, _policy: MigrationPolicy, + _environment: EnvironmentUpdate, ) -> anyhow::Result> { Err(anyhow::anyhow!("unused")) } @@ -2199,6 +2221,15 @@ mod tests { ) -> anyhow::Result { Err(anyhow::anyhow!("unused")) } + + async fn update_environment( + &self, + _publisher: &Identity, + _environment: EnvironmentUpdate, + _expected_module_version: Hash, + ) -> anyhow::Result<()> { + Err(anyhow::anyhow!("unused")) + } } impl Authorization for DummyState { diff --git a/crates/client-api/src/routes/database/publish_environment.rs b/crates/client-api/src/routes/database/publish_environment.rs index 86d8d30f9cb..e8a7c2375d9 100644 --- a/crates/client-api/src/routes/database/publish_environment.rs +++ b/crates/client-api/src/routes/database/publish_environment.rs @@ -1,15 +1,20 @@ //! Bounded publish extraction. Neither errors nor Debug output retain configuration values. use axum::body::{to_bytes, Bytes}; -use axum::extract::{FromRequest, Request}; +use axum::extract::{FromRequest, FromRequestParts, Request}; use axum::response::{IntoResponse, Response}; +use axum_extra::TypedHeader; use http::{header, StatusCode}; -use spacetimedb_client_api_messages::publish::{PublishRequest, CONTENT_TYPE, MAX_MODULE_BYTES, MAX_REQUEST_BYTES}; +use spacetimedb_client_api_messages::publish::{ + SpacetimeEnvironment, SpacetimeEnvironmentRemove, CONTENT_TYPE, MAX_MODULE_BYTES, MAX_REQUEST_BYTES, +}; use std::collections::BTreeMap; -pub struct PublishBody { - pub program_bytes: Option, - pub environment: BTreeMap, - pub environment_remove: Vec, +#[derive(FromRequestParts)] +pub struct PublishOptions { + #[from_request(via(TypedHeader))] + pub environment: SpacetimeEnvironment, + #[from_request(via(TypedHeader))] + pub environment_remove: SpacetimeEnvironmentRemove, pub environment_replace: bool, pub expected_module_version: Option, pub environment_only: bool, diff --git a/crates/client-api/src/util.rs b/crates/client-api/src/util.rs index 66e5fc91158..012d06d2670 100644 --- a/crates/client-api/src/util.rs +++ b/crates/client-api/src/util.rs @@ -9,8 +9,10 @@ use std::net::IpAddr; use std::ops::{Deref, DerefMut}; use axum::body::Bytes; -use axum::extract::{FromRequest, Request}; +use axum::extract::{FromRequest, FromRequestParts, Request}; use axum::response::IntoResponse; +use axum_extra::typed_header::TypedHeaderRejection; +use axum_extra::TypedHeader; use bytestring::ByteString; use futures::TryStreamExt; use http::{HeaderName, HeaderValue, StatusCode}; @@ -298,6 +300,21 @@ impl FromRequest for EmptyBody { } } +pub struct OptionalHeader(pub Option); + +#[async_trait::async_trait] +impl FromRequestParts for OptionalHeader { + type Rejection = TypedHeaderRejection; + + async fn from_request_parts(parts: &mut http::request::Parts, state: &S) -> Result { + match TypedHeader::::from_request_parts(parts, state).await { + Ok(TypedHeader(x)) => Ok(Self(Some(x))), + Err(e) if e.is_missing() => Ok(Self(None)), + Err(e) => Err(e), + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/codegen/src/unrealcpp.rs b/crates/codegen/src/unrealcpp.rs index a521e739180..f5d03e3f3de 100644 --- a/crates/codegen/src/unrealcpp.rs +++ b/crates/codegen/src/unrealcpp.rs @@ -416,7 +416,7 @@ impl Lang for UnrealCpp<'_> { let name = type_ref_name(self.module_prefix, module, typ.ty); let filename = format!( "Source/{}/Public/ModuleBindings/Types/{}Type.g.h", - self.module_name, &name + self.module_name, name ); let code: String = match &module.typespace_for_generate()[typ.ty] { AlgebraicTypeDef::PlainEnum(plain_enum) => autogen_cpp_enum(&name, plain_enum), diff --git a/crates/commitlog/src/segment.rs b/crates/commitlog/src/segment.rs index c810e2399ae..c67246f8cf3 100644 --- a/crates/commitlog/src/segment.rs +++ b/crates/commitlog/src/segment.rs @@ -16,7 +16,7 @@ use crate::{ Options, }; -pub const MAGIC: [u8; 6] = [b'(', b'd', b's', b')', b'^', b'2']; +pub const MAGIC: [u8; 6] = *b"(ds)^2"; pub const DEFAULT_LOG_FORMAT_VERSION: u8 = 1; pub const DEFAULT_CHECKSUM_ALGORITHM: u8 = CHECKSUM_ALGORITHM_CRC32C; diff --git a/crates/core/src/host/instance_env.rs b/crates/core/src/host/instance_env.rs index 97609d60bb3..76f605d2244 100644 --- a/crates/core/src/host/instance_env.rs +++ b/crates/core/src/host/instance_env.rs @@ -406,7 +406,7 @@ impl InstanceEnv { /// End a console timer by logging the span at INFO level. pub(crate) fn console_timer_end(&self, span: &TimingSpan, function: Option<&str>) { let elapsed = span.start.elapsed(); - let message = format!("Timing span {:?}: {:?}", &span.name, elapsed); + let message = format!("Timing span {:?}: {:?}", span.name, elapsed); self.console_log_simple_message(LogLevel::Info, function, &message); } diff --git a/crates/core/src/host/v8/error.rs b/crates/core/src/host/v8/error.rs index 54095f657cf..aa462506bef 100644 --- a/crates/core/src/host/v8/error.rs +++ b/crates/core/src/host/v8/error.rs @@ -520,7 +520,7 @@ impl fmt::Display for JsStackTraceFrame { // TODO(v8): make it more like chrome in the future. f.write_fmt(format_args!( "at {} ({}:{}:{})", - fn_name, script_name, &self.line, &self.column + fn_name, script_name, self.line, self.column ))?; if self.is_ctor { diff --git a/crates/datastore/src/locking_tx_datastore/datastore.rs b/crates/datastore/src/locking_tx_datastore/datastore.rs index 1082e0ae9c6..205de3567fe 100644 --- a/crates/datastore/src/locking_tx_datastore/datastore.rs +++ b/crates/datastore/src/locking_tx_datastore/datastore.rs @@ -229,9 +229,8 @@ impl Locking { /// error. pub fn take_snapshot(&self, repo: &DynSnapshotRepo) -> Result> { Self::take_snapshot_internal(&self.committed_state, repo)? - .map(|(_offset, snap)| snap.sync_all()) + .map(|(_offset, snap)| snap.sync_all().map_err(Into::into)) .transpose() - .map_err(Into::into) } pub fn assert_system_tables_match(&self) -> Result<()> { @@ -3021,7 +3020,7 @@ pub(crate) mod tests { fn assert_rows(datastore: &Locking, table_id: TableId, rows: Vec) -> ResultTest<()> { let tx = begin_tx(datastore); - for (actual, expected) in datastore.iter_tx(&tx, table_id)?.zip_eq(rows.into_iter()) { + for (actual, expected) in datastore.iter_tx(&tx, table_id)?.zip_eq(rows) { assert_eq!(actual.to_bsatn_vec()?, expected.to_bsatn_vec()?); } Ok(()) diff --git a/crates/engine/src/relational_db.rs b/crates/engine/src/relational_db.rs index 19cda5b6fbe..800148f9251 100644 --- a/crates/engine/src/relational_db.rs +++ b/crates/engine/src/relational_db.rs @@ -627,13 +627,10 @@ impl RelationalDB { // Try to restore from any snapshot that was taken within the // range `(min_commitlog_offset + 1)..=durable_tx_offset`. let mut upper_bound = durable_tx_offset; - loop { - let Some(snapshot_offset) = snapshot_repo - .latest_snapshot_older_than(upper_bound) - .map_err(Box::new)? - else { - break; - }; + while let Some(snapshot_offset) = snapshot_repo + .latest_snapshot_older_than(upper_bound) + .map_err(Box::new)? + { if min_commitlog_offset > 0 && min_commitlog_offset > snapshot_offset + 1 { log::debug!("snapshot_offset={snapshot_offset} min_commitlog_offset={min_commitlog_offset}"); break; diff --git a/crates/engine/src/update.rs b/crates/engine/src/update.rs index 5b9e6e0fde2..ab864688d1c 100644 --- a/crates/engine/src/update.rs +++ b/crates/engine/src/update.rs @@ -705,7 +705,7 @@ pub fn create_table_from_view_def( view_def: &ViewDef, ) -> anyhow::Result<()> { stdb.create_view(tx, module_def, view_def) - .with_context(|| format!("failed to create table for view {}", &view_def.name))?; + .with_context(|| format!("failed to create table for view {}", view_def.name))?; Ok(()) } @@ -719,7 +719,7 @@ pub fn create_table_from_view_def_with_prefix( name_prefix: &NamespacePath, ) -> anyhow::Result<()> { stdb.create_view_with_prefix(tx, owning_def, view_def, name_prefix) - .with_context(|| format!("failed to create table for view {}{}", name_prefix, &view_def.name))?; + .with_context(|| format!("failed to create table for view {}{}", name_prefix, view_def.name))?; Ok(()) } diff --git a/crates/lib/src/environment.rs b/crates/lib/src/environment.rs index ad86b19fb58..cbddd89e872 100644 --- a/crates/lib/src/environment.rs +++ b/crates/lib/src/environment.rs @@ -10,7 +10,7 @@ pub const MAX_ENV_UNION_ENTRIES: usize = 256; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum EnvironmentValidationError { InvalidKey, - ValueTooLarge, + InvalidValue, TooManyVariables, } @@ -18,7 +18,7 @@ impl std::fmt::Display for EnvironmentValidationError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(match self { Self::InvalidKey => "invalid POSIX environment variable name (maximum 256 bytes)", - Self::ValueTooLarge => "environment value exceeds 8192 UTF-8 bytes", + Self::InvalidValue => "invalid environment value (maximum 8192 bytes)", Self::TooManyVariables => "environment store exceeds 256 variables", }) } @@ -40,8 +40,8 @@ pub fn validate_key(key: &str) -> Result<(), EnvironmentValidationError> { /// NUL is representable in the database. Container launch separately rejects it. pub fn validate_value(value: &str) -> Result<(), EnvironmentValidationError> { - if value.len() > MAX_ENV_VALUE_BYTES { - return Err(EnvironmentValidationError::ValueTooLarge); + if value.len() > MAX_ENV_VALUE_BYTES || value.contains('\0') { + return Err(EnvironmentValidationError::InvalidValue); } Ok(()) } diff --git a/crates/lib/src/lib.rs b/crates/lib/src/lib.rs index 55d5621a16d..0ee6395186b 100644 --- a/crates/lib/src/lib.rs +++ b/crates/lib/src/lib.rs @@ -40,12 +40,12 @@ pub use filterable_value::Private; pub use filterable_value::{FilterableValue, IndexScanRangeBoundsTerminator, TermBound, ViewPrimaryKeyColumn}; pub use identity::Identity; pub use scheduler::ScheduleAt; +pub use spacetimedb_sats::__make_register_reftype; pub use spacetimedb_sats::hash::{self, hash_bytes, Hash}; pub use spacetimedb_sats::time_duration::TimeDuration; pub use spacetimedb_sats::timestamp::Timestamp; pub use spacetimedb_sats::uuid::Uuid; pub use spacetimedb_sats::SpacetimeType; -pub use spacetimedb_sats::__make_register_reftype; pub use spacetimedb_sats::{self as sats, bsatn, buffer, de, ser}; pub use spacetimedb_sats::{AlgebraicType, ProductType, ProductTypeElement, SumType}; pub use spacetimedb_sats::{AlgebraicValue, ProductValue}; diff --git a/crates/pg/src/pg_server.rs b/crates/pg/src/pg_server.rs index f1de39f4efd..361da919418 100644 --- a/crates/pg/src/pg_server.rs +++ b/crates/pg/src/pg_server.rs @@ -157,6 +157,7 @@ where let params = self.cached.lock().await.clone().unwrap(); let name_or_identity = database::NameOrIdentity::Name(DatabaseName(params.database.clone())); let database_identity = response(name_or_identity.resolve(&self.ctx).await, ¶ms.database).await?; + #[expect(clippy::result_large_err)] let database = response( self.ctx .get_database_by_identity(&database_identity) diff --git a/crates/schema/src/def/validate/v10.rs b/crates/schema/src/def/validate/v10.rs index 11fbcf4e3d4..a57d56a9212 100644 --- a/crates/schema/src/def/validate/v10.rs +++ b/crates/schema/src/def/validate/v10.rs @@ -398,7 +398,7 @@ fn validate_submodules(submodules: Vec) -> Result errors.extend(e.into_iter()), + Err(e) => errors.extend(e), } } } diff --git a/crates/smoketests/tests/standalone/detect_wasm_bindgen.rs b/crates/smoketests/tests/standalone/detect_wasm_bindgen.rs index baf95009eb4..af848bb5b1d 100644 --- a/crates/smoketests/tests/standalone/detect_wasm_bindgen.rs +++ b/crates/smoketests/tests/standalone/detect_wasm_bindgen.rs @@ -61,7 +61,7 @@ fn test_detect_getrandom() { let stderr = String::from_utf8_lossy(&output.stderr); assert!( - stderr.contains("getrandom usage detected"), + stderr.contains("getrandom usage detected") || stderr.contains("undefined symbol: __getrandom_custom"), "Expected 'getrandom usage detected' in stderr, got: {}", stderr ); diff --git a/crates/sqltest/src/main.rs b/crates/sqltest/src/main.rs index 7856008afd3..304042fc21c 100644 --- a/crates/sqltest/src/main.rs +++ b/crates/sqltest/src/main.rs @@ -449,15 +449,11 @@ async fn update_test_file { - if sql.contains("NOT_REWRITE") { - continue; - } + Record::Statement { sql, .. } if sql.contains("NOT_REWRITE") => { + continue; } - Record::Query { sql, .. } => { - if sql.contains("NOT_REWRITE") { - continue; - } + Record::Query { sql, .. } if sql.contains("NOT_REWRITE") => { + continue; } _ => (), } diff --git a/crates/standalone/Dockerfile b/crates/standalone/Dockerfile index fa74fa1f232..cd321e802d4 100644 --- a/crates/standalone/Dockerfile +++ b/crates/standalone/Dockerfile @@ -1,7 +1,8 @@ ARG CARGO_PROFILE=release -FROM rust:1.93.0 AS chef +# !rust-toolchain-sync +FROM rust:1.96.1 AS chef RUN rust_target=$(rustc -vV | awk '/^host:/{ print $2 }') && \ curl https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-$rust_target.tgz -fL | tar xz -C $CARGO_HOME/bin RUN cargo binstall -y cargo-chef@0.1.70 diff --git a/crates/standalone/src/lib.rs b/crates/standalone/src/lib.rs index 4a7479cd6a8..d35fea38e64 100644 --- a/crates/standalone/src/lib.rs +++ b/crates/standalone/src/lib.rs @@ -30,6 +30,7 @@ use spacetimedb_client_api_messages::name::{ use spacetimedb_datastore::db_metrics::data_size::DATA_SIZE_METRICS; use spacetimedb_datastore::db_metrics::DB_METRICS; use spacetimedb_datastore::traits::Program; +use spacetimedb_lib::environment::EnvironmentUpdate; use spacetimedb_paths::server::{ModuleLogsDir, PidFile, ServerDataDir}; use spacetimedb_paths::standalone::StandaloneDataDirExt; use spacetimedb_schema::auto_migrate::{MigrationPolicy, PrettyPrintStyle}; @@ -277,14 +278,11 @@ impl spacetimedb_client_api::ControlStateWriteAccess for StandaloneEnv { publisher: &Identity, spec: spacetimedb_client_api::DatabaseDef, policy: MigrationPolicy, + environment: EnvironmentUpdate, ) -> anyhow::Result> { let existing_db = self.control_db.get_database_by_identity(&spec.database_identity)?; - let update = spacetimedb_lib::environment::EnvironmentUpdate { - values: spec.environment, - remove: spec.environment_remove, - replace: spec.environment_replace, - }; + let update = spec.environment; update.validate()?; // standalone does not support replication. let num_replicas = 1; diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 28f0403f3d4..96fcd8bf21f 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,8 +1,7 @@ [toolchain] -# change crates/{standalone,bench}/Dockerfile, .github/Dockerfile, and the docker image tag in -# .github/workflows/benchmarks.yml:jobs/callgrind_benchmark/container/image -# maybe also the rust-version in Cargo.toml -channel = "1.93.0" +# run ./tools/rust-toolchain-sync.sh when you change the channel! +# also consider changing the rust-version in Cargo.toml +channel = "1.96.1" profile = "default" targets = ["wasm32-unknown-unknown"] components = ["rust-src"] diff --git a/sdks/rust/src/db_connection.rs b/sdks/rust/src/db_connection.rs index 137166ae614..c1810f7e5dd 100644 --- a/sdks/rust/src/db_connection.rs +++ b/sdks/rust/src/db_connection.rs @@ -19,7 +19,6 @@ //! This module is internal, and may incompatibly change without warning. use crate::{ - Event, ReducerEvent, Status, __codegen::{InternalError, Reducer}, callbacks::{ CallbackId, DbCallbacks, ProcedureCallback, ProcedureCallbacks, ReducerCallback, ReducerCallbacks, RowCallback, @@ -29,6 +28,7 @@ use crate::{ spacetime_module::{AbstractEventContext, AppliedDiff, DbConnection, DbUpdate, InModule, SpacetimeModule}, subscription::{PendingUnsubscribeResult, SubscriptionHandleImpl, SubscriptionManager}, websocket::{WsConnection, WsParams}, + Event, ReducerEvent, Status, }; use bytes::Bytes; use futures::StreamExt; diff --git a/sdks/rust/src/spacetime_module.rs b/sdks/rust/src/spacetime_module.rs index dfec381e56d..cba7c73122b 100644 --- a/sdks/rust/src/spacetime_module.rs +++ b/sdks/rust/src/spacetime_module.rs @@ -3,12 +3,12 @@ //! This module is internal, and may incompatibly change without warning. use crate::{ + __codegen::InternalError, callbacks::DbCallbacks, client_cache::ClientCache, db_connection::DbContextImpl, subscription::{OnEndedCallback, SubscriptionHandleImpl}, Event, ReducerEvent, - __codegen::InternalError, }; use bytes::Bytes; use spacetimedb_client_api_messages::websocket::{self as ws, common::RowListLen as _, v2::BsatnRowList}; diff --git a/tools/ci/commands/update-flow/src/main.rs b/tools/ci/commands/update-flow/src/main.rs index 273e5f26efc..be9d99b8460 100644 --- a/tools/ci/commands/update-flow/src/main.rs +++ b/tools/ci/commands/update-flow/src/main.rs @@ -56,7 +56,7 @@ fn main() -> Result<()> { ["run", "-p", "spacetimedb-update"] .into_iter() .chain(common_args.clone()) - .chain(["--", "self-install", &root_arg, "--yes"].into_iter()), + .chain(["--", "self-install", &root_arg, "--yes"]), ) .run()?; diff --git a/tools/release/src/crates_resolver.rs b/tools/release/src/crates_resolver.rs index 3412ecf93da..158cfcc441c 100644 --- a/tools/release/src/crates_resolver.rs +++ b/tools/release/src/crates_resolver.rs @@ -72,9 +72,9 @@ pub fn get_crate_deps(crate_name: &String, manifest_map: &HashMap&2 "$0: couldn't extract version from rust-toolchain.toml" + exit 1 +} + +rg -.F "$MAGIC_COMMENT" -l "$@" | xargs gawk -i inplace -v toolchain_ver="$toolchain" " +/$MAGIC_COMMENT/ { version = 1; print; next } +{ if (version) { gsub(/$VERSION_REGEX/, toolchain_ver); version = 0; } print } +" diff --git a/tools/xtask-llm-benchmark/src/bin/llm_benchmark.rs b/tools/xtask-llm-benchmark/src/bin/llm_benchmark.rs index be76423286d..2b6caaa1ada 100644 --- a/tools/xtask-llm-benchmark/src/bin/llm_benchmark.rs +++ b/tools/xtask-llm-benchmark/src/bin/llm_benchmark.rs @@ -564,7 +564,7 @@ fn model_filter_from_groups(groups: Option>) -> Option> = HashMap::new(); for g in groups { - out.entry(g.vendor).or_default().extend(g.models.into_iter()); + out.entry(g.vendor).or_default().extend(g.models); } Some(out) } diff --git a/tools/xtask-llm-benchmark/src/context/combine.rs b/tools/xtask-llm-benchmark/src/context/combine.rs index ac8eb278c1b..9c3b1a2f59f 100644 --- a/tools/xtask-llm-benchmark/src/context/combine.rs +++ b/tools/xtask-llm-benchmark/src/context/combine.rs @@ -163,7 +163,7 @@ fn build_context_from_rustdoc_json() -> Result { }); } - rows.sort_by(|a, b| (order_key(&a.kind), a.path.to_lowercase()).cmp(&(order_key(&b.kind), b.path.to_lowercase()))); + rows.sort_by_key(|a| (order_key(&a.kind), a.path.to_lowercase())); let mut out = String::with_capacity(1024 * 1024); out.push_str(&format!(