mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-27 08:44:28 -05:00
feat: support aliased Result return types for server_fn
- Enhanced error messages for better clarity when server functions do not meet the expected return type requirements. - Added `trybuild` tests to ensure behaviour is not broken in future releases.
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -3279,6 +3279,7 @@ dependencies = [
|
||||
"tokio-tungstenite",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"trybuild",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
|
||||
@@ -73,6 +73,7 @@ server_fn_macro = { path = "./server_fn_macro", version = "0.8.0-beta" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.8.0-beta" }
|
||||
tachys = { path = "./tachys", version = "0.2.0-beta" }
|
||||
wasm-bindgen = { version = "0.2.100" }
|
||||
trybuild = "1"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
|
||||
@@ -32,7 +32,7 @@ tracing = { version = "0.1.41", optional = true }
|
||||
[dev-dependencies]
|
||||
log = "0.4.22"
|
||||
typed-builder = "0.20.0"
|
||||
trybuild = "1.0"
|
||||
trybuild = { workspace = true }
|
||||
leptos = { path = "../leptos" }
|
||||
leptos_router = { path = "../router", features = ["ssr"] }
|
||||
server_fn = { path = "../server_fn", features = ["cbor"] }
|
||||
@@ -58,7 +58,7 @@ generic = ["server_fn_macro/generic"]
|
||||
# https://github.com/rust-lang/cargo/issues/4423
|
||||
# TLDR proc macros will ignore RUSTFLAGS when --target is specified on the cargo command.
|
||||
# This works around the issue by the non proc-macro crate which does see RUSTFLAGS enabling the replacement feature on the proc-macro crate, which wouldn't.
|
||||
# This is automatic as long as the leptos crate is depended upon,
|
||||
# This is automatic as long as the leptos crate is depended upon,
|
||||
# downstream usage should never manually enable this feature.
|
||||
__internal_erase_components = []
|
||||
|
||||
|
||||
@@ -81,6 +81,9 @@ url = "2"
|
||||
pin-project-lite = "0.2.15"
|
||||
tokio = { version = "1.43.0", features = ["rt"], optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
trybuild = { workspace = true }
|
||||
|
||||
[features]
|
||||
axum-no-default = [
|
||||
"ssr",
|
||||
|
||||
@@ -559,6 +559,28 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[diagnostic::on_unimplemented(
|
||||
message = "{Self} is not a `Result` or aliased `Result`. Server functions \
|
||||
must return a `Result` or aliased `Result`.",
|
||||
label = "Must return a `Result` or aliased `Result`.",
|
||||
note = "If you are trying to return an alias of `Result`, you must also \
|
||||
implement `FromServerFnError` for the error type."
|
||||
)]
|
||||
/// A trait for extracting the error and ok types from a [`Result`]. This is used to allow alias types to be returned from server functions.
|
||||
pub trait ServerFnMustReturnResult {
|
||||
/// The error type of the [`Result`].
|
||||
type Err;
|
||||
/// The ok type of the [`Result`].
|
||||
type Ok;
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl<T, E> ServerFnMustReturnResult for Result<T, E> {
|
||||
type Err = E;
|
||||
type Ok = T;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_from_server_fn_error_impl() {
|
||||
fn assert_impl<T: FromServerFnError>() {}
|
||||
|
||||
16
server_fn/tests/invalid/aliased_return_full.rs
Normal file
16
server_fn/tests/invalid/aliased_return_full.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
use server_fn_macro_default::server;
|
||||
|
||||
#[derive(Debug, thiserror::Error, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub enum InvalidError {
|
||||
#[error("error a")]
|
||||
A,
|
||||
}
|
||||
|
||||
type FullAlias = Result<String, InvalidError>;
|
||||
|
||||
#[server]
|
||||
pub async fn full_alias_result() -> FullAlias {
|
||||
Ok("hello".to_string())
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
51
server_fn/tests/invalid/aliased_return_full.stderr
Normal file
51
server_fn/tests/invalid/aliased_return_full.stderr
Normal file
@@ -0,0 +1,51 @@
|
||||
error[E0277]: the trait bound `InvalidError: FromServerFnError` is not satisfied
|
||||
--> tests/invalid/aliased_return_full.rs:11:1
|
||||
|
|
||||
11 | #[server]
|
||||
| ^^^^^^^^^ the trait `FromServerFnError` is not implemented for `InvalidError`
|
||||
|
|
||||
= help: the trait `FromServerFnError` is implemented for `ServerFnError<CustErr>`
|
||||
= note: required for `BrowserClient` to implement `Client<InvalidError>`
|
||||
note: required by a bound in `server_fn::ServerFn::Client`
|
||||
--> src/lib.rs
|
||||
|
|
||||
| type Client: Client<Self::Error>;
|
||||
| ^^^^^^^^^^^^^^^^^^^ required by this bound in `ServerFn::Client`
|
||||
= note: this error originates in the attribute macro `server` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `InvalidError: FromServerFnError` is not satisfied
|
||||
--> tests/invalid/aliased_return_full.rs:11:1
|
||||
|
|
||||
11 | #[server]
|
||||
| ^^^^^^^^^ the trait `FromServerFnError` is not implemented for `InvalidError`
|
||||
|
|
||||
= help: the trait `FromServerFnError` is implemented for `ServerFnError<CustErr>`
|
||||
= note: required for `BrowserClient` to implement `Client<InvalidError>`
|
||||
= note: required for `Http<PostUrl, Post<JsonEncoding>>` to implement `Protocol<FullAliasResult, std::string::String, BrowserClient, BrowserMockServer, InvalidError>`
|
||||
note: required by a bound in `server_fn::ServerFn::Protocol`
|
||||
--> src/lib.rs
|
||||
|
|
||||
| type Protocol: Protocol<
|
||||
| ____________________^
|
||||
| | Self,
|
||||
| | Self::Output,
|
||||
| | Self::Client,
|
||||
| | Self::Server,
|
||||
| | Self::Error,
|
||||
| | >;
|
||||
| |_____^ required by this bound in `ServerFn::Protocol`
|
||||
= note: this error originates in the attribute macro `server` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `InvalidError: FromServerFnError` is not satisfied
|
||||
--> tests/invalid/aliased_return_full.rs:11:1
|
||||
|
|
||||
11 | #[server]
|
||||
| ^^^^^^^^^ the trait `FromServerFnError` is not implemented for `InvalidError`
|
||||
|
|
||||
= help: the trait `FromServerFnError` is implemented for `ServerFnError<CustErr>`
|
||||
note: required by a bound in `server_fn::ServerFn::Error`
|
||||
--> src/lib.rs
|
||||
|
|
||||
| type Error: FromServerFnError + Send + Sync;
|
||||
| ^^^^^^^^^^^^^^^^^ required by this bound in `ServerFn::Error`
|
||||
= note: this error originates in the attribute macro `server` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
14
server_fn/tests/invalid/aliased_return_none.rs
Normal file
14
server_fn/tests/invalid/aliased_return_none.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use server_fn_macro_default::server;
|
||||
|
||||
#[derive(Debug, thiserror::Error, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub enum InvalidError {
|
||||
#[error("error a")]
|
||||
A,
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub async fn no_alias_result() -> Result<String, InvalidError> {
|
||||
Ok("hello".to_string())
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
50
server_fn/tests/invalid/aliased_return_none.stderr
Normal file
50
server_fn/tests/invalid/aliased_return_none.stderr
Normal file
@@ -0,0 +1,50 @@
|
||||
error[E0277]: the trait bound `InvalidError: FromServerFnError` is not satisfied
|
||||
--> tests/invalid/aliased_return_none.rs:9:1
|
||||
|
|
||||
9 | #[server]
|
||||
| ^^^^^^^^^ the trait `FromServerFnError` is not implemented for `InvalidError`
|
||||
|
|
||||
= help: the trait `FromServerFnError` is implemented for `ServerFnError<CustErr>`
|
||||
= note: required for `BrowserClient` to implement `Client<InvalidError>`
|
||||
note: required by a bound in `server_fn::ServerFn::Client`
|
||||
--> src/lib.rs
|
||||
|
|
||||
| type Client: Client<Self::Error>;
|
||||
| ^^^^^^^^^^^^^^^^^^^ required by this bound in `ServerFn::Client`
|
||||
= note: this error originates in the attribute macro `server` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `InvalidError: FromServerFnError` is not satisfied
|
||||
--> tests/invalid/aliased_return_none.rs:9:1
|
||||
|
|
||||
9 | #[server]
|
||||
| ^^^^^^^^^ the trait `FromServerFnError` is not implemented for `InvalidError`
|
||||
|
|
||||
= help: the trait `FromServerFnError` is implemented for `ServerFnError<CustErr>`
|
||||
= note: required for `BrowserClient` to implement `Client<InvalidError>`
|
||||
= note: required for `Http<PostUrl, Post<JsonEncoding>>` to implement `Protocol<NoAliasResult, std::string::String, BrowserClient, BrowserMockServer, InvalidError>`
|
||||
note: required by a bound in `server_fn::ServerFn::Protocol`
|
||||
--> src/lib.rs
|
||||
|
|
||||
| type Protocol: Protocol<
|
||||
| ____________________^
|
||||
| | Self,
|
||||
| | Self::Output,
|
||||
| | Self::Client,
|
||||
| | Self::Server,
|
||||
| | Self::Error,
|
||||
| | >;
|
||||
| |_____^ required by this bound in `ServerFn::Protocol`
|
||||
= note: this error originates in the attribute macro `server` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `InvalidError: FromServerFnError` is not satisfied
|
||||
--> tests/invalid/aliased_return_none.rs:10:50
|
||||
|
|
||||
10 | pub async fn no_alias_result() -> Result<String, InvalidError> {
|
||||
| ^^^^^^^^^^^^ the trait `FromServerFnError` is not implemented for `InvalidError`
|
||||
|
|
||||
= help: the trait `FromServerFnError` is implemented for `ServerFnError<CustErr>`
|
||||
note: required by a bound in `server_fn::ServerFn::Error`
|
||||
--> src/lib.rs
|
||||
|
|
||||
| type Error: FromServerFnError + Send + Sync;
|
||||
| ^^^^^^^^^^^^^^^^^ required by this bound in `ServerFn::Error`
|
||||
16
server_fn/tests/invalid/aliased_return_part.rs
Normal file
16
server_fn/tests/invalid/aliased_return_part.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
use server_fn_macro_default::server;
|
||||
|
||||
#[derive(Debug, thiserror::Error, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub enum InvalidError {
|
||||
#[error("error a")]
|
||||
A,
|
||||
}
|
||||
|
||||
type PartAlias<T> = Result<T, InvalidError>;
|
||||
|
||||
#[server]
|
||||
pub async fn part_alias_result() -> PartAlias<String> {
|
||||
Ok("hello".to_string())
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
51
server_fn/tests/invalid/aliased_return_part.stderr
Normal file
51
server_fn/tests/invalid/aliased_return_part.stderr
Normal file
@@ -0,0 +1,51 @@
|
||||
error[E0277]: the trait bound `InvalidError: FromServerFnError` is not satisfied
|
||||
--> tests/invalid/aliased_return_part.rs:11:1
|
||||
|
|
||||
11 | #[server]
|
||||
| ^^^^^^^^^ the trait `FromServerFnError` is not implemented for `InvalidError`
|
||||
|
|
||||
= help: the trait `FromServerFnError` is implemented for `ServerFnError<CustErr>`
|
||||
= note: required for `BrowserClient` to implement `Client<InvalidError>`
|
||||
note: required by a bound in `server_fn::ServerFn::Client`
|
||||
--> src/lib.rs
|
||||
|
|
||||
| type Client: Client<Self::Error>;
|
||||
| ^^^^^^^^^^^^^^^^^^^ required by this bound in `ServerFn::Client`
|
||||
= note: this error originates in the attribute macro `server` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `InvalidError: FromServerFnError` is not satisfied
|
||||
--> tests/invalid/aliased_return_part.rs:11:1
|
||||
|
|
||||
11 | #[server]
|
||||
| ^^^^^^^^^ the trait `FromServerFnError` is not implemented for `InvalidError`
|
||||
|
|
||||
= help: the trait `FromServerFnError` is implemented for `ServerFnError<CustErr>`
|
||||
= note: required for `BrowserClient` to implement `Client<InvalidError>`
|
||||
= note: required for `Http<PostUrl, Post<JsonEncoding>>` to implement `Protocol<PartAliasResult, std::string::String, BrowserClient, BrowserMockServer, InvalidError>`
|
||||
note: required by a bound in `server_fn::ServerFn::Protocol`
|
||||
--> src/lib.rs
|
||||
|
|
||||
| type Protocol: Protocol<
|
||||
| ____________________^
|
||||
| | Self,
|
||||
| | Self::Output,
|
||||
| | Self::Client,
|
||||
| | Self::Server,
|
||||
| | Self::Error,
|
||||
| | >;
|
||||
| |_____^ required by this bound in `ServerFn::Protocol`
|
||||
= note: this error originates in the attribute macro `server` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `InvalidError: FromServerFnError` is not satisfied
|
||||
--> tests/invalid/aliased_return_part.rs:11:1
|
||||
|
|
||||
11 | #[server]
|
||||
| ^^^^^^^^^ the trait `FromServerFnError` is not implemented for `InvalidError`
|
||||
|
|
||||
= help: the trait `FromServerFnError` is implemented for `ServerFnError<CustErr>`
|
||||
note: required by a bound in `server_fn::ServerFn::Error`
|
||||
--> src/lib.rs
|
||||
|
|
||||
| type Error: FromServerFnError + Send + Sync;
|
||||
| ^^^^^^^^^^^^^^^^^ required by this bound in `ServerFn::Error`
|
||||
= note: this error originates in the attribute macro `server` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
8
server_fn/tests/invalid/empty_return.rs
Normal file
8
server_fn/tests/invalid/empty_return.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
use server_fn_macro_default::server;
|
||||
|
||||
#[server]
|
||||
pub async fn empty_return() -> () {
|
||||
()
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
57
server_fn/tests/invalid/empty_return.stderr
Normal file
57
server_fn/tests/invalid/empty_return.stderr
Normal file
@@ -0,0 +1,57 @@
|
||||
error[E0277]: () is not a `Result` or aliased `Result`. Server functions must return a `Result` or aliased `Result`.
|
||||
--> tests/invalid/empty_return.rs:3:1
|
||||
|
|
||||
3 | #[server]
|
||||
| ^^^^^^^^^ Must return a `Result` or aliased `Result`.
|
||||
|
|
||||
= help: the trait `ServerFnMustReturnResult` is not implemented for `()`
|
||||
= note: If you are trying to return an alias of `Result`, you must also implement `FromServerFnError` for the error type.
|
||||
= help: the trait `ServerFnMustReturnResult` is implemented for `Result<T, E>`
|
||||
|
||||
error[E0271]: expected `impl Future<Output = ()>` to be a future that resolves to `Result<_, _>`, but it resolves to `()`
|
||||
--> tests/invalid/empty_return.rs:3:1
|
||||
|
|
||||
3 | #[server]
|
||||
| ^^^^^^^^^ expected `Result<_, _>`, found `()`
|
||||
|
|
||||
= note: expected enum `Result<_, _>`
|
||||
found unit type `()`
|
||||
note: required by a bound in `ServerFn::{synthetic#0}`
|
||||
--> src/lib.rs
|
||||
|
|
||||
| ) -> impl Future<Output = Result<Self::Output, Self::Error>> + Send;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `ServerFn::{synthetic#0}`
|
||||
|
||||
error[E0277]: () is not a `Result` or aliased `Result`. Server functions must return a `Result` or aliased `Result`.
|
||||
--> tests/invalid/empty_return.rs:3:1
|
||||
|
|
||||
3 | #[server]
|
||||
| ^^^^^^^^^ Must return a `Result` or aliased `Result`.
|
||||
|
|
||||
= help: the trait `ServerFnMustReturnResult` is not implemented for `()`
|
||||
= note: If you are trying to return an alias of `Result`, you must also implement `FromServerFnError` for the error type.
|
||||
= help: the trait `ServerFnMustReturnResult` is implemented for `Result<T, E>`
|
||||
= note: this error originates in the attribute macro `server` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: () is not a `Result` or aliased `Result`. Server functions must return a `Result` or aliased `Result`.
|
||||
--> tests/invalid/empty_return.rs:3:1
|
||||
|
|
||||
3 | #[server]
|
||||
| ^^^^^^^^^ Must return a `Result` or aliased `Result`.
|
||||
|
|
||||
= help: the trait `ServerFnMustReturnResult` is not implemented for `()`
|
||||
= note: If you are trying to return an alias of `Result`, you must also implement `FromServerFnError` for the error type.
|
||||
= help: the trait `ServerFnMustReturnResult` is implemented for `Result<T, E>`
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> tests/invalid/empty_return.rs:3:1
|
||||
|
|
||||
3 | #[server]
|
||||
| ^^^^^^^^^ expected `()`, found `Result<_, _>`
|
||||
|
|
||||
= note: expected unit type `()`
|
||||
found enum `Result<_, _>`
|
||||
help: consider using `Result::expect` to unwrap the `Result<_, _>` value, panicking if the value is a `Result::Err`
|
||||
|
|
||||
3 | #[server].expect("REASON")
|
||||
| +++++++++++++++++
|
||||
6
server_fn/tests/invalid/no_return.rs
Normal file
6
server_fn/tests/invalid/no_return.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
use server_fn_macro_default::server;
|
||||
|
||||
#[server]
|
||||
pub async fn no_return() {}
|
||||
|
||||
fn main() {}
|
||||
5
server_fn/tests/invalid/no_return.stderr
Normal file
5
server_fn/tests/invalid/no_return.stderr
Normal file
@@ -0,0 +1,5 @@
|
||||
error: expected `->`
|
||||
--> tests/invalid/no_return.rs:4:26
|
||||
|
|
||||
4 | pub async fn no_return() {}
|
||||
| ^
|
||||
9
server_fn/tests/invalid/not_async.rs
Normal file
9
server_fn/tests/invalid/not_async.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use server_fn_macro_default::server;
|
||||
use server_fn::error::ServerFnError;
|
||||
|
||||
#[server]
|
||||
pub fn not_async() -> Result<String, ServerFnError> {
|
||||
Ok("hello".to_string())
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
13
server_fn/tests/invalid/not_async.stderr
Normal file
13
server_fn/tests/invalid/not_async.stderr
Normal file
@@ -0,0 +1,13 @@
|
||||
error: expected `async`
|
||||
--> tests/invalid/not_async.rs:5:5
|
||||
|
|
||||
5 | pub fn not_async() -> Result<String, ServerFnError> {
|
||||
| ^^
|
||||
|
||||
warning: unused import: `server_fn::error::ServerFnError`
|
||||
--> tests/invalid/not_async.rs:2:5
|
||||
|
|
||||
2 | use server_fn::error::ServerFnError;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `#[warn(unused_imports)]` on by default
|
||||
23
server_fn/tests/invalid/not_result.rs
Normal file
23
server_fn/tests/invalid/not_result.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use server_fn_macro_default::server;
|
||||
use server_fn::error::{FromServerFnError, ServerFnErrorErr};
|
||||
|
||||
#[derive(Debug, thiserror::Error, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub enum CustomError {
|
||||
#[error("error a")]
|
||||
A,
|
||||
#[error("error b")]
|
||||
B,
|
||||
}
|
||||
|
||||
impl FromServerFnError for CustomError {
|
||||
fn from_server_fn_error(_: ServerFnErrorErr) -> Self {
|
||||
Self::A
|
||||
}
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub async fn full_alias_result() -> CustomError {
|
||||
CustomError::A
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
57
server_fn/tests/invalid/not_result.stderr
Normal file
57
server_fn/tests/invalid/not_result.stderr
Normal file
@@ -0,0 +1,57 @@
|
||||
error[E0277]: CustomError is not a `Result` or aliased `Result`. Server functions must return a `Result` or aliased `Result`.
|
||||
--> tests/invalid/not_result.rs:18:1
|
||||
|
|
||||
18 | #[server]
|
||||
| ^^^^^^^^^ Must return a `Result` or aliased `Result`.
|
||||
|
|
||||
= help: the trait `ServerFnMustReturnResult` is not implemented for `CustomError`
|
||||
= note: If you are trying to return an alias of `Result`, you must also implement `FromServerFnError` for the error type.
|
||||
= help: the trait `ServerFnMustReturnResult` is implemented for `Result<T, E>`
|
||||
|
||||
error[E0271]: expected `impl Future<Output = CustomError>` to be a future that resolves to `Result<_, _>`, but it resolves to `CustomError`
|
||||
--> tests/invalid/not_result.rs:18:1
|
||||
|
|
||||
18 | #[server]
|
||||
| ^^^^^^^^^ expected `Result<_, _>`, found `CustomError`
|
||||
|
|
||||
= note: expected enum `Result<_, _>`
|
||||
found enum `CustomError`
|
||||
note: required by a bound in `ServerFn::{synthetic#0}`
|
||||
--> src/lib.rs
|
||||
|
|
||||
| ) -> impl Future<Output = Result<Self::Output, Self::Error>> + Send;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `ServerFn::{synthetic#0}`
|
||||
|
||||
error[E0277]: CustomError is not a `Result` or aliased `Result`. Server functions must return a `Result` or aliased `Result`.
|
||||
--> tests/invalid/not_result.rs:18:1
|
||||
|
|
||||
18 | #[server]
|
||||
| ^^^^^^^^^ Must return a `Result` or aliased `Result`.
|
||||
|
|
||||
= help: the trait `ServerFnMustReturnResult` is not implemented for `CustomError`
|
||||
= note: If you are trying to return an alias of `Result`, you must also implement `FromServerFnError` for the error type.
|
||||
= help: the trait `ServerFnMustReturnResult` is implemented for `Result<T, E>`
|
||||
= note: this error originates in the attribute macro `server` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: CustomError is not a `Result` or aliased `Result`. Server functions must return a `Result` or aliased `Result`.
|
||||
--> tests/invalid/not_result.rs:18:1
|
||||
|
|
||||
18 | #[server]
|
||||
| ^^^^^^^^^ Must return a `Result` or aliased `Result`.
|
||||
|
|
||||
= help: the trait `ServerFnMustReturnResult` is not implemented for `CustomError`
|
||||
= note: If you are trying to return an alias of `Result`, you must also implement `FromServerFnError` for the error type.
|
||||
= help: the trait `ServerFnMustReturnResult` is implemented for `Result<T, E>`
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> tests/invalid/not_result.rs:18:1
|
||||
|
|
||||
18 | #[server]
|
||||
| ^^^^^^^^^ expected `CustomError`, found `Result<_, _>`
|
||||
|
|
||||
= note: expected enum `CustomError`
|
||||
found enum `Result<_, _>`
|
||||
help: consider using `Result::expect` to unwrap the `Result<_, _>` value, panicking if the value is a `Result::Err`
|
||||
|
|
||||
18 | #[server].expect("REASON")
|
||||
| +++++++++++++++++
|
||||
8
server_fn/tests/server_macro.rs
Normal file
8
server_fn/tests/server_macro.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
#![cfg(feature = "browser")]
|
||||
|
||||
#[test]
|
||||
fn aliased_results() {
|
||||
let t = trybuild::TestCases::new();
|
||||
t.pass("tests/valid/*.rs");
|
||||
t.compile_fail("tests/invalid/*.rs")
|
||||
}
|
||||
11
server_fn/tests/valid/aliased_return_full.rs
Normal file
11
server_fn/tests/valid/aliased_return_full.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use server_fn_macro_default::server;
|
||||
use server_fn::error::ServerFnError;
|
||||
|
||||
type FullAlias = Result<String, ServerFnError>;
|
||||
|
||||
#[server]
|
||||
pub async fn full_alias_result() -> FullAlias {
|
||||
Ok("hello".to_string())
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
10
server_fn/tests/valid/aliased_return_none.rs
Normal file
10
server_fn/tests/valid/aliased_return_none.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
use server_fn_macro_default::server;
|
||||
use server_fn::error::ServerFnError;
|
||||
|
||||
#[server]
|
||||
pub async fn no_alias_result() -> Result<String, ServerFnError> {
|
||||
Ok("hello".to_string())
|
||||
}
|
||||
|
||||
|
||||
fn main() {}
|
||||
11
server_fn/tests/valid/aliased_return_part.rs
Normal file
11
server_fn/tests/valid/aliased_return_part.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use server_fn_macro_default::server;
|
||||
use server_fn::error::ServerFnError;
|
||||
|
||||
type PartAlias<T> = Result<T, ServerFnError>;
|
||||
|
||||
#[server]
|
||||
pub async fn part_alias_result() -> PartAlias<String> {
|
||||
Ok("hello".to_string())
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
25
server_fn/tests/valid/custom_error_aliased_return_full.rs
Normal file
25
server_fn/tests/valid/custom_error_aliased_return_full.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use server_fn_macro_default::server;
|
||||
use server_fn::error::{FromServerFnError, ServerFnErrorErr};
|
||||
|
||||
#[derive(Debug, thiserror::Error, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub enum CustomError {
|
||||
#[error("error a")]
|
||||
ErrorA,
|
||||
#[error("error b")]
|
||||
ErrorB,
|
||||
}
|
||||
|
||||
impl FromServerFnError for CustomError {
|
||||
fn from_server_fn_error(_: ServerFnErrorErr) -> Self {
|
||||
Self::ErrorA
|
||||
}
|
||||
}
|
||||
|
||||
type FullAlias = Result<String, CustomError>;
|
||||
|
||||
#[server]
|
||||
pub async fn full_alias_result() -> FullAlias {
|
||||
Ok("hello".to_string())
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
23
server_fn/tests/valid/custom_error_aliased_return_none.rs
Normal file
23
server_fn/tests/valid/custom_error_aliased_return_none.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use server_fn_macro_default::server;
|
||||
use server_fn::error::{FromServerFnError, ServerFnErrorErr};
|
||||
|
||||
#[derive(Debug, thiserror::Error, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub enum CustomError {
|
||||
#[error("error a")]
|
||||
ErrorA,
|
||||
#[error("error b")]
|
||||
ErrorB,
|
||||
}
|
||||
|
||||
impl FromServerFnError for CustomError {
|
||||
fn from_server_fn_error(_: ServerFnErrorErr) -> Self {
|
||||
Self::ErrorA
|
||||
}
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub async fn no_alias_result() -> Result<String, CustomError> {
|
||||
Ok("hello".to_string())
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
25
server_fn/tests/valid/custom_error_aliased_return_part.rs
Normal file
25
server_fn/tests/valid/custom_error_aliased_return_part.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use server_fn_macro_default::server;
|
||||
use server_fn::error::{FromServerFnError, ServerFnErrorErr};
|
||||
|
||||
#[derive(Debug, thiserror::Error, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub enum CustomError {
|
||||
#[error("error a")]
|
||||
ErrorA,
|
||||
#[error("error b")]
|
||||
ErrorB,
|
||||
}
|
||||
|
||||
impl FromServerFnError for CustomError {
|
||||
fn from_server_fn_error(_: ServerFnErrorErr) -> Self {
|
||||
Self::ErrorA
|
||||
}
|
||||
}
|
||||
|
||||
type PartAlias<T> = Result<T, CustomError>;
|
||||
|
||||
#[server]
|
||||
pub async fn part_alias_result() -> PartAlias<String> {
|
||||
Ok("hello".to_string())
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -551,16 +551,25 @@ impl ServerFnCall {
|
||||
let protocol = self.protocol();
|
||||
let middlewares = &self.body.middlewares;
|
||||
let return_ty = &self.body.return_ty;
|
||||
let output_ty = &self.body.output_ty;
|
||||
let output_ty = self.body.output_ty
|
||||
.as_ref()
|
||||
.map_or_else(|| {
|
||||
quote! {
|
||||
<#return_ty as #server_fn_path::error::ServerFnMustReturnResult>::Ok
|
||||
}
|
||||
},
|
||||
ToTokens::to_token_stream,
|
||||
);
|
||||
let error_ty = &self.body.error_ty;
|
||||
let error_ty = error_ty
|
||||
.as_ref()
|
||||
.map(ToTokens::to_token_stream)
|
||||
.unwrap_or_else(|| {
|
||||
.map_or_else(|| {
|
||||
quote! {
|
||||
#server_fn_path::error::NoCustomError
|
||||
<#return_ty as #server_fn_path::error::ServerFnMustReturnResult>::Err
|
||||
}
|
||||
});
|
||||
},
|
||||
ToTokens::to_token_stream,
|
||||
);
|
||||
let field_names = self.field_names();
|
||||
|
||||
// run_body in the trait implementation
|
||||
@@ -877,7 +886,7 @@ impl Parse for Middleware {
|
||||
}
|
||||
}
|
||||
|
||||
fn output_type(return_ty: &Type) -> Result<&GenericArgument> {
|
||||
fn output_type(return_ty: &Type) -> Option<&Type> {
|
||||
if let syn::Type::Path(pat) = &return_ty {
|
||||
if pat.path.segments[0].ident == "Result" {
|
||||
if pat.path.segments.is_empty() {
|
||||
@@ -885,19 +894,17 @@ fn output_type(return_ty: &Type) -> Result<&GenericArgument> {
|
||||
} else if let PathArguments::AngleBracketed(args) =
|
||||
&pat.path.segments[0].arguments
|
||||
{
|
||||
return Ok(&args.args[0]);
|
||||
if let GenericArgument::Type(ty) = &args.args[0] {
|
||||
return Some(ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Err(syn::Error::new(
|
||||
return_ty.span(),
|
||||
"server functions should return Result<T, E> where E: \
|
||||
FromServerFnError",
|
||||
))
|
||||
None
|
||||
}
|
||||
|
||||
fn err_type(return_ty: &Type) -> Result<Option<&Type>> {
|
||||
fn err_type(return_ty: &Type) -> Option<&Type> {
|
||||
if let syn::Type::Path(pat) = &return_ty {
|
||||
if pat.path.segments[0].ident == "Result" {
|
||||
if let PathArguments::AngleBracketed(args) =
|
||||
@@ -905,21 +912,17 @@ fn err_type(return_ty: &Type) -> Result<Option<&Type>> {
|
||||
{
|
||||
// Result<T>
|
||||
if args.args.len() == 1 {
|
||||
return Ok(None);
|
||||
return None;
|
||||
}
|
||||
// Result<T, _>
|
||||
else if let GenericArgument::Type(ty) = &args.args[1] {
|
||||
return Ok(Some(ty));
|
||||
return Some(ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Err(syn::Error::new(
|
||||
return_ty.span(),
|
||||
"server functions should return Result<T, E> where E: \
|
||||
FromServerFnError",
|
||||
))
|
||||
None
|
||||
}
|
||||
|
||||
/// The arguments to the `server` macro.
|
||||
@@ -1369,7 +1372,7 @@ pub struct ServerFnBody {
|
||||
/// The return type of the server function.
|
||||
pub return_ty: syn::Type,
|
||||
/// The Ok output type of the server function.
|
||||
pub output_ty: syn::GenericArgument,
|
||||
pub output_ty: Option<syn::Type>,
|
||||
/// The error output type of the server function.
|
||||
pub error_ty: Option<syn::Type>,
|
||||
/// The body of the server function.
|
||||
@@ -1399,8 +1402,8 @@ impl Parse for ServerFnBody {
|
||||
|
||||
let output_arrow = input.parse()?;
|
||||
let return_ty = input.parse()?;
|
||||
let output_ty = output_type(&return_ty)?.clone();
|
||||
let error_ty = err_type(&return_ty)?.cloned();
|
||||
let output_ty = output_type(&return_ty).cloned();
|
||||
let error_ty = err_type(&return_ty).cloned();
|
||||
|
||||
let block = input.parse()?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user