#[resource_post]Expand description
Attribute macro for POST handlers mutating a single tenant-scoped resource.
Folds the same prelude as #[resource_get] plus the validation-failure
redirect envelope (via handle_action_result). Requires redirect_to.
§Required arguments
- First positional arg: the resource type implementing
TenantScoped. redirect_to = "/url"— default 303 redirect on success (and error fallback).
§Optional arguments
form_url = "/url/{id}/edit"— the edit form URL, synthesized from extracted path params; injected as__form_url: &strin the inner fn body.on_miss = "/url"— 303 redirect on lookup miss; omitted → 404HttpResponse.tenant = "expr"— escape-hatch expression for tenant resolution.find = "path::fn"— override the lookup function.
§Example
ⓘ
use ferro::{resource_post, ActionResult, Request, TenantContext};
#[resource_post(Customer,
redirect_to = "/dashboard/clienti",
form_url = "/dashboard/clienti/{id}/modifica")]
pub async fn save(req: &mut Request, tenant: &TenantContext, customer: &Customer) -> ActionResult {
let data = serde_json::json!({ "name": "test" });
ferro::Validator::new(&data)
.rules("name", ferro::rules![ferro::required()])
.validate_or_redirect(__form_url)?;
Ok(())
}§Expands to (abridged)
The attribute is equivalent to the following expansion (shown via cargo expand):
ⓘ
// Generated outer fn — same prelude as resource_get, plus form_url synthesis and
// validation-redirect envelope via handle_action_result.
pub async fn save(__ferro_req: ::ferro::Request) -> ::ferro::Response {
let mut __ferro_req = __ferro_req;
let __resource_id: <Customer as ::ferro::TenantScoped>::Id =
__ferro_req.param_as("id").map_err(|_| ::ferro::HttpResponse::new().status(400))?;
let __tenant: ::ferro::TenantContext = ::ferro::current_tenant()
.ok_or_else(|| ::ferro::HttpResponse::new().status(400))?;
let __resource_opt = <Customer as ::ferro::TenantScoped>::find_for_tenant(
__resource_id, __tenant.id,
).await.map_err(|_| ::ferro::HttpResponse::new().status(500))?;
let __resource = match __resource_opt {
Some(r) => r,
None => return Err(::ferro::HttpResponse::new().status(404)),
};
let __form_url_owned = format!("/dashboard/clienti/{}/modifica", __resource_id);
let __form_url: &str = &__form_url_owned;
// Inner fn borrow ends before handle_action_result borrows __ferro_req again (Pitfall 3).
let __action_result: ::ferro::ActionResult =
__save_inner(&mut __ferro_req, &__tenant, &__resource, __form_url).await;
::ferro::http::action::handle_action_result(
__action_result, "/dashboard/clienti", "module::save", &mut __ferro_req,
)
}
// Named inner fn — tenant, resource, and __form_url are real typed parameters.
async fn __save_inner(
req: &mut ::ferro::Request,
tenant: &::ferro::TenantContext,
customer: &Customer,
__form_url: &str,
) -> ::ferro::ActionResult {
// user body here
Ok(())
}§Security
Same tenant-scoping guarantee as #[resource_get]: lookup always passes
tenant.id. T-212-01.