diff --git a/axum/src/response/mod.rs b/axum/src/response/mod.rs index 70be745200..a3fe054bf7 100644 --- a/axum/src/response/mod.rs +++ b/axum/src/response/mod.rs @@ -82,6 +82,71 @@ impl IntoResponse for NoContent { } } +/// A response with a 201 CREATED status +/// +/// As 201 is recommended to come with a `Location` header referring to the location of the created +/// object according to the RFC7231, using this struct can remind a user to do so. +/// Remember that any IntoResponse struct used as an `inner` here may override the headers and status +/// code set by this struct. +#[derive(Clone, Debug)] +pub struct Created { + /// The value set for the `Location` header. + /// Existing location headers are not overwritten + location: Option, + inner: T, +} +impl Created { + #[must_use] + /// Creates a CREATED response with an empty body and no `Location` header + pub fn empty() -> Self { + Self { + location: None, + inner: (), + } + } + /// Creates a new `Created` with an empty body + pub fn empty_with_location(location: HeaderValue) -> Self { + Self { + location: Some(location), + inner: (), + } + } + /// Attaches a body to an empty `Created` response + pub fn with_body(self, body: T) -> Created { + let Self { + location, + inner: (), + } = self; + Created { + location, + inner: body, + } + } +} +impl Created { + /// Sets status and `Location` header along with the inner type's `IntoResponse` implementation. + /// Remember that the inner type may override headers and the status code. + pub fn new_with_location_header(body: T, location: HeaderValue) -> Self { + Self { + location: Some(location), + inner: body, + } + } +} +impl IntoResponse for Created { + fn into_response(self) -> Response { + match self.location { + Some(l) => ( + StatusCode::CREATED, + AppendHeaders([(http::header::LOCATION, l)]), + self.inner, + ) + .into_response(), + None => (StatusCode::CREATED, self.inner).into_response(), + } + } +} + #[cfg(test)] mod tests { use crate::extract::Extension;