codify "platform" into resolution logic, server-fn/fullstack overhaul (#4596)
overhaul dioxus fullstack using a new implementation of server_fn This PR is a complete rewrite of dioxus fullstack designed to improve compatibility with axum and to fix a number of compilation errors based on feature flag unification plumbing through macros.
This commit is contained in:
1 parent
8421c3aef0
commit
f2b1c6d97f
424 files changed
+17284
-6624
No files matched your search
@@ -0,0 +1,173 @@
|
||||
//! An example of handling errors from server functions.
|
||||
//!
|
||||
//! This example showcases a few important error handling patterns when using Dioxus Fullstack.
|
||||
//!
|
||||
//! Run with:
|
||||
//!
|
||||
//! ```sh
|
||||
//! dx serve --web
|
||||
//! ```
|
||||
//!
|
||||
//! What this example shows:
|
||||
//! - You can return `anyhow::Result<T>` (i.e. `Result<T>` without an `E`) for
|
||||
//! untyped errors with rich context (converted to HTTP 500 responses by default).
|
||||
//! - You can return `Result<T, E>` where `E` is one of:
|
||||
//! - `HttpError` (convenience for returning HTTP status codes)
|
||||
//! - `StatusCode` (return raw status codes)
|
||||
//! - a custom error type that implements `From<ServerFnError>` or
|
||||
//! is `Serialize`/`Deserialize` so it can be sent to the client.
|
||||
//! - This file demonstrates external API errors, custom typed errors, explicit
|
||||
//! HTTP errors, and basic success cases. The UI uses `use_action` to call
|
||||
//! server functions and shows loading/result states simply.
|
||||
//!
|
||||
//! Try running requests against the endpoints directly with `curl` or `postman` to see the actual HTTP responses!
|
||||
|
||||
use dioxus::fullstack::{Json, StatusCode};
|
||||
use dioxus::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(|| {
|
||||
let mut dog_data = use_action(get_dog_data);
|
||||
let mut dog_data_err = use_action(get_dog_data_err);
|
||||
let mut ip_data = use_action(get_ip_data);
|
||||
let mut custom_data = use_action(move || {
|
||||
get_custom_encoding(Json(serde_json::json!({
|
||||
"example": "data",
|
||||
"number": 123,
|
||||
"array": [1, 2, 3],
|
||||
})))
|
||||
});
|
||||
let mut error_data = use_action(get_throws_error);
|
||||
let mut typed_error_data = use_action(get_throws_typed_error);
|
||||
let mut throws_ok_data = use_action(get_throws_ok);
|
||||
let mut http_error_data = use_action(throws_http_error);
|
||||
let mut http_error_context_data = use_action(throws_http_error_context);
|
||||
|
||||
rsx! {
|
||||
button { onclick: move |_| { dog_data.call(); }, "Fetch dog data" }
|
||||
button { onclick: move |_| { ip_data.call(); }, "Fetch IP data" }
|
||||
button { onclick: move |_| { custom_data.call(); }, "Fetch custom encoded data" }
|
||||
button { onclick: move |_| { error_data.call(); }, "Fetch error data" }
|
||||
button { onclick: move |_| { typed_error_data.call(); }, "Fetch typed error data" }
|
||||
button { onclick: move |_| { dog_data_err.call(); }, "Fetch dog error data" }
|
||||
button { onclick: move |_| { throws_ok_data.call(); }, "Fetch throws ok data" }
|
||||
button { onclick: move |_| { http_error_data.call(); }, "Fetch HTTP 400" }
|
||||
button { onclick: move |_| { http_error_context_data.call(); }, "Fetch HTTP 400 (context)" }
|
||||
button {
|
||||
onclick: move |_| {
|
||||
ip_data.reset();
|
||||
dog_data.reset();
|
||||
custom_data.reset();
|
||||
error_data.reset();
|
||||
typed_error_data.reset();
|
||||
dog_data_err.reset();
|
||||
throws_ok_data.reset();
|
||||
http_error_data.reset();
|
||||
http_error_context_data.reset();
|
||||
},
|
||||
"Clear data"
|
||||
}
|
||||
div { display: "flex", flex_direction: "column", gap: "8px",
|
||||
pre { "Dog data: {dog_data.value():#?}" }
|
||||
pre { "IP data: {ip_data.value():#?}" }
|
||||
pre { "Custom encoded data: {custom_data.value():#?}" }
|
||||
pre { "Error data: {error_data.result():#?}" }
|
||||
pre { "Typed error data: {typed_error_data.result():#?}" }
|
||||
pre { "HTTP 400 data: {http_error_data.result():#?}" }
|
||||
pre { "HTTP 400 (context) data: {http_error_context_data.result():#?}" }
|
||||
pre { "Dog error data: {dog_data_err.result():#?}" }
|
||||
pre { "Throws ok data: {throws_ok_data.result():#?}" }
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Simple POST endpoint used to show a successful server function that returns `StatusCode`.
|
||||
#[post("/api/data")]
|
||||
async fn post_server_data(data: String) -> Result<(), StatusCode> {
|
||||
println!("Server received: {}", data);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Fetches IP info from an external service. Demonstrates propagation of external errors.
|
||||
#[get("/api/ip-data")]
|
||||
async fn get_ip_data() -> Result<serde_json::Value> {
|
||||
Ok(reqwest::get("https://httpbin.org/ip").await?.json().await?)
|
||||
}
|
||||
|
||||
/// Fetches a random dog image (successful external API example).
|
||||
#[get("/api/dog-data")]
|
||||
async fn get_dog_data() -> Result<serde_json::Value> {
|
||||
Ok(reqwest::get("https://dog.ceo/api/breeds/image/random")
|
||||
.await?
|
||||
.json()
|
||||
.await?)
|
||||
}
|
||||
|
||||
/// Calls the Dog API with an invalid breed to trigger an external API error (e.g. 404).
|
||||
#[get("/api/dog-data-err")]
|
||||
async fn get_dog_data_err() -> Result<serde_json::Value> {
|
||||
Ok(
|
||||
reqwest::get("https://dog.ceo/api/breed/NOT_A_REAL_DOG/images")
|
||||
.await?
|
||||
.json()
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
|
||||
/// Accepts JSON and returns a custom-encoded JSON response.
|
||||
#[post("/api/custom-encoding")]
|
||||
async fn get_custom_encoding(takes: Json<serde_json::Value>) -> Result<serde_json::Value> {
|
||||
Ok(serde_json::json!({
|
||||
"message": "This response was encoded with a custom encoder!",
|
||||
"success": true,
|
||||
"you sent": takes.0,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Returns an untyped `anyhow` error with context (results in HTTP 500).
|
||||
#[get("/api/untyped-error")]
|
||||
async fn get_throws_error() -> Result<()> {
|
||||
Err(None.context("This is an example error using anyhow::Error")?)
|
||||
}
|
||||
|
||||
/// Demonstrates returning an explicit HTTP error (400 Bad Request) using `HttpError`.
|
||||
#[get("/api/throws-http-error")]
|
||||
async fn throws_http_error() -> Result<()> {
|
||||
HttpError::bad_request("Bad request example")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Convenience example: handles an Option and returns HTTP 400 with a message if None.
|
||||
#[get("/api/throws-http-error-context")]
|
||||
async fn throws_http_error_context() -> Result<String> {
|
||||
let res = None.or_bad_request("Value was None")?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// A simple server function that always succeeds.
|
||||
#[get("/api/throws-ok")]
|
||||
async fn get_throws_ok() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug, Serialize, Deserialize)]
|
||||
enum MyCustomError {
|
||||
#[error("bad request")]
|
||||
BadRequest { custom_name: String },
|
||||
|
||||
#[error("not found")]
|
||||
NotFound,
|
||||
|
||||
#[error("internal server error: {0}")]
|
||||
ServerFnError(#[from] ServerFnError),
|
||||
}
|
||||
|
||||
/// Returns a custom typed error (serializable) so clients can handle specific cases.
|
||||
#[get("/api/typed-error")]
|
||||
async fn get_throws_typed_error() -> Result<(), MyCustomError> {
|
||||
Err(MyCustomError::BadRequest {
|
||||
custom_name: "Invalid input".into(),
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user