| Crates.io | impulse-utils |
| lib.rs | impulse-utils |
| version | 1.0.0-alpha.6 |
| created_at | 2025-07-21 21:06:52.556266+00 |
| updated_at | 2025-11-14 20:04:56.27653+00 |
| description | Bunch of fullstack utils |
| homepage | |
| repository | |
| max_upload_size | |
| id | 1762680 |
| size | 123,176 |
A bunch of fullstack utils.
With impulse-utils, you can:
impulse_utils::requests::MsgPackParser extension trait (salvo::Request)msgpack!(data) macroimpulse_utils::responses::MsgPackResponse extension trait (reqwest::Response)impulse_utils::requests::MsgPackRequest extension trait (reqwest::RequestBuilder)Example:
/// server-side
#[handler]
async fn change_text(req: &mut Request) -> MResult<MsgPack<FooData>> {
let mut foo = req.parse_msgpack::<FooData>().await?; // parse
foo.text = String::from("Gotcha!");
msgpack!(foo) // send
}
/// client-side
async fn change_text_at_backend(old_data: &FooData) -> CResult<FooData> {
let new_data = reqwest::Client::new()
.post("127.0.0.1:8080/change-text")
.msgpack(old_data)? // send
.send()
.await?
.msgpack::<FooData>() // parse
.await?;
Ok(new_data)
}
For MsgPack ser/de, impulse-utils uses rmp_serde::to_vec and rmp_serde::from_slice methods.
sonic-rsimpulse-utils provide:
impulse_utils::requests::SimdJsonParser extension trait (salvo::Request)json!(data) macro[!NOTE] To enable SIMD ser/de for JSON, compile your project with compiler option
-C target-cpu=native.
In Salvo and Server Kit, you're writing backend routes by one of three ways:
/// like this...
#[handler]
async fn route(req: &mut Request, res: &mut Response) { .. }
/// or like this
#[handler]
async fn route(req: &mut Request) -> impl salvo::Writer + use<> { .. }
/// or like this
#[handler]
async fn route(req: &mut Request) -> Result<MyType, MyError> { .. }
Only the last one provides you a ? way to expose server errors.
impulse-utils provide unified type MResult<T> for specifying response type while using ServerError for errors.
impulse_utils::results::MResultMResult allows you to specify these response types:
MResult<OK> and ok!() at the end of a routeMResult<Plain> and plain!(text)MResult<Html> and html!(text)MResult<File> and file_upload!(pathbuf, filename) (supports file caching with ETag and Cache-Control)MResult<Json<T>> and json!(object) (implemented by sonic-rs with SIMD support)MResult<MsgPack<T>> and msgpack!(object)Resulting macros are used in TRACE log level to show you what you're sending as a response and from what function.
Examples:
#[handler]
async fn frontend(req: &Request) -> MResult<Html> {
let filepath = get_filepath_from_dist("index.html").await?;
let site = tokio::fs::read_to_string(&filepath)
.await
.map_err(|e| ServerError::from_private(e).with_500())?;
html!(site)
}
#[handler]
async fn json_to_msgpack(req: &mut Request, depot: &mut Depot) -> MResult<MsgPack<HelloData>> {
let hello = req.parse_json_simd::<HelloData>().await?;
let app_name = depot.obtain::<Setup>()?.generic_values().app_name.as_str();
msgpack!(HelloData { text: format!("From `{}` application: {}", app_name, hello.text) })
}
impulse_utils::errors::ServerErrorServerError also implements salvo::Writer, but it does more. Internally it contains HTTP status code to return with (500 by default), public error message and the list of private error messages. ServerError is designed to hide implementation details exposed with errors by separation into two types: public errors and private errors.
ServerError on response converts into simple JSON:
{"err":"Public error text"}
This is how your clients will get error messages.
If public_msg field is set, your server will respond with value of this field. You can set public_msg several times, and client will get only one:
ServerError::from_public("Bad data inside SQL")
.with_public("Database error")
.with_public("Internal server error, call 911")
.with_500()
.bail()?;
/// Client will see:
///
/// ```json
/// {"err":"Internal server error, call 911"}
/// ```
///
/// Backend logs:
///
/// ```
/// 2025-05-11T00:24:49.474405Z ERROR Error: `500` status code
/// Error message: "Internal server error, call 911"
/// Caused by: Database error
/// Caused by: Bad data inside SQL
/// ```
Example with any error type that implements std::error::Error trait:
verify_sign(&challenge, sign, &public_key)
.map_err(|e| ServerError::from_private(e).with_public("Invalid sign!").with_401())?;
Or Option:
let user_data = kv
.get::<UserData>(&key)
.await?
.ok_or(ServerError::from_public("This user isn't exist!").with_404())?;
ServerError is used by MResult<T> type and supports both Salvo and Server Kit frameworks.
ExplicitServerWrite traitThis trait is designed to use only &mut Response on write instead of a full signature of salvo::Writer trait:
async fn write(self, req: &mut Request, depot: &mut Depot, res: &mut Response) { .. }
You can use it, for example, when you're using any reference to the depot data and responding with this data:
// Usually you responds by `HTTP 200 OK`, but sometimes need to also include some data that referring to `depot`.
// Of course, you can just `.clone()` your value, but `ExplicitServerWrite` needs no allocations compared to cloning.
#[handler]
async fn maybe_token(depot: &mut Depot, res: &mut Response) -> MResult<OK> {
let state_ref = depot.obtain::<State>()?;
if state_ref.allow_send_token && let Some(token) = &state_ref.token {
msgpack!(token).unwrap().explicit_write(res).await;
}
ok!()
}
This trait is implemented to all exposed by impulse-utils response types above except file_upload! (internally it needs req: &mut Request to compare ETag inside headers to allow file caching).
impulse_utils::results::CResult and impulse_utils::errors::ClientErrorCResult<T> is a Result<T, ClientError>. ClientError was designed to be simple client error which you can just console.log at client-side.
Example:
let resp = reqwest::Client::new()
.post("127.0.0.1:8080/post-data")
.json(&data)
.send()
.await
.map_err(|e| ClientError::from(e).context("Can't send request!"))?
.error_for_status()
.map_err(|e| ClientError::from(e).context("Server error!"))?;
If you have several backends and communicate between them by reqwest, you may be considering of usage redirect_server_error method on reqwest::Response to directly redirect any error, especially ErrorResponse (public part of ServerError):
let resp = reqwest::Client::new()
.post("127.0.0.1:8080/post-data")
.json(&data)
.send()
.await
.redirect_server_error() // if status >= 400, it will throw `Err::<ServerError>`; if no, returns `Ok::<reqwest::Response>`
.await?
.json::<MyResponse>()
.await?;
But, if you're using reqwest on the client-side, you may want to use collect_server_error to collect CResult instead of MResult:
let resp = reqwest::Client::new()
.post("127.0.0.1:8080/post-data")
.json(&data)
.send()
.await
.collect_server_error() // if status >= 400, it will throw `Err::<ClientError>`; if no, returns `Ok::<reqwest::Response>`
.await?
.json::<MyResponse>()
.await?;
[!NOTE]
redirect_server_errormethod is available withreqwestandmresultfeatures.
collect_server_errormethod is available withreqwestandcresultfeatures.