Skip to content

Manejo de respuestas HTTP ​

La forma recomendada de responder en un endpoint web es usando WebResult. Esto asegura que todas las respuestas tengan un formato consistente y que los errores se manejen de manera uniforme.

WebResult ​

Este es un alias de:

rust
Result<JsonResponse, JsonResponse>

Esto permite devolver respuestas exitosas y de error con el mismo formato JSON.

JsonResponse ​

La estructura principal para construir respuestas JSON en Sword. Esta estructura provee una serie de métodos estáticos equivalentes a los códigos de estado HTTP más comunes, y permite encadenar métodos para agregar mensajes, datos o errores.

Constructores habituales ​

  • JsonResponse::Ok()
  • JsonResponse::Created()
  • JsonResponse::BadRequest()
  • JsonResponse::Unauthorized()
  • JsonResponse::NotFound()
  • JsonResponse::InternalServerError()

Mensaje por defecto ​

Si no especificas un mensaje con message(...), Sword añade automáticamente el texto canónico del código HTTP. Esto ocurre también con códigos menos habituales, como los que se construyen con JsonResponse::TooManyRequests():

rust
JsonResponse::TooManyRequests()
json
{
	"code": 429,
	"success": false,
	"message": "Too Many Requests",
	"timestamp": "2024-06-01T12:00:00Z"
}

Construcción de payload ​

Método message() ​

rust
pub fn message(mut self, message: impl Into<String>) -> Self

Añade un mensaje descriptivo a la respuesta.

rust
JsonResponse::Ok().message("Successful operation");
json
{
	"code": 200,
	"success": true,
	"message": "Successful operation",
	"timestamp": "2024-06-01T12:00:00Z"
}

Método data() ​

rust
pub fn data<T: Serialize>(mut self, data: T) -> Self

Añadir información serializable a la respuesta.

rust
use serde::Serialize;

#[derive(Serialize)]
struct MyData {
    field1: String,
    field2: u32,
}

let response = JsonResponse::Ok().data(MyData {
    field1: "value".to_string(),
    field2: 42,
});
json
{
	"code": 200,
	"message": "OK",
	"success": true,
	"timestamp": "2024-06-01T12:00:00Z",
	"data": {
		"field1": "value",
		"field2": 42
	}
}

Método error() y errors() ​

rust
pub fn error<T: Serialize>(mut self, error: T) -> Self
pub fn errors<T: Serialize>(mut self, errors: T) -> Self

Permiten adjuntar errores a la respuesta. error() es para un único error, mientras que errors() es para una colección de errores o una estructura de validación. Ambos tienen en si el mismo propósito, sin embargo se incluyen ambas para semántica y claridad en la intención de la respuesta.

rust
JsonResponse::BadRequest()
    .error("Invalid input data")
    .errors(vec!["Error 1", "Error 2"]);
json
{
	"code": 400,
	"message": "Bad Request",
	"success": false,
	"timestamp": "2024-06-01T12:00:00Z",
	"error": "Invalid input data",
	"errors": ["Error 1", "Error 2"]
}

Errores automáticos desde Request ​

Muchos métodos de Request retornan RequestError, si usas ? en el método del controlador, el error se convertirá automáticamente en un JsonResponse con el código de estado correspondiente.

Ejemplo ​

rust
#[derive(Deserialize)]
struct UserData {
    name: String,
    email: String,
}

#[post("/")]
async fn create_user(&self, req: Request) -> WebResult {
    let _ = req.body::<UserData>()?;
    Ok(JsonResponse::Created())
}
json
{
	"name": "Alice"
}
json
{
	"code": 400,
	"error": "Failed to deserialize request body to the required type.",
	"message": "Invalid request body",
	"success": false,
	"timestamp": "2024-06-01T12:00:00Z"
}

Auto conversión de respuesta con WebResult<T> ​

Las macros de ruta (#[get], #[post], etc.) detectan el tipo genérico T de WebResult<T> y lo envuelven automáticamente en un JsonResponse.

El código de estado se elige según el método HTTP de la ruta: POST responde con 201 Created, mientras que el resto de métodos responden con 200 OK.

Ejemplo GET (200) ​

rust
#[derive(Serialize)]
struct User {
    id: u32,
    name: String,
}

// Asumiendo una estructura `UserController`

#[get("/users/{id}")]
async fn get_user(&self, req: Request) -> WebResult<User> {
    let user = User { id: 1, name: "Alice".to_string() };
    Ok(user)
}
json
{
	"code": 200,
	"message": "OK",
	"success": true,
	"timestamp": "2024-06-01T12:00:00Z",
	"data": {
		"id": 1,
		"name": "Alice"
	}
}

En este caso el valor de Ok(...) se coloca automáticamente en data. Si no quieres devolver datos, puedes usar () y la respuesta llevará solo el código de estado y su mensaje.

Ejemplo POST (201) ​

rust
#[derive(Serialize)]
struct CreateUserResponse {
    id: u32,
}

#[post("/users")]
async fn create_user(&self) -> WebResult<CreateUserResponse> {
    Ok(CreateUserResponse { id: 1 })
}
json
{
	"code": 201,
	"message": "Created",
	"success": true,
	"timestamp": "2024-06-01T12:00:00Z",
	"data": {
		"id": 1
	}
}

Descargas y contenido inline con File ​

JsonResponse cubre las respuestas JSON, pero a veces necesitas devolver archivos. Para eso Sword expone File, un builder que construye respuestas de descarga o de visualización inline según la ContentDisposition que uses. Por defecto, File se comporta como una descarga (ContentDisposition::Attachment) con Content-Type application/octet-stream.

Un endpoint de descarga típico se ve así:

rust
use sword::prelude::*;

#[get("/reports/{name}")]
async fn download_report(&self) -> File {
    let data = std::fs::read("report.pdf").unwrap();

    File::new()
        .bytes(&data)
        .content_type("application/pdf")
        .filename("report.pdf")
        .attachment()
}

El método bytes() recibe el contenido del archivo, content_type() define su Content-Type, y filename() el nombre que llevará el header Content-Disposition.

Si en lugar de descargar quieres que el navegador muestre el archivo directamente (por ejemplo, un PDF o una imagen), usa inline():

rust
File::new()
    .bytes(&data)
    .content_type("image/png")
    .filename("logo.png")
    .inline()

También puedes agregar headers personalizados con header(...). Y como File implementa IntoResponse, puedes devolverlo directamente o dentro de un WebResult<File>.

Redirecciones HTTP con Redirect ​

Cuando un endpoint necesita redirigir a otra URL, Sword expone Redirect. Cada constructor cubre uno de los códigos 3xx más comunes:

  • Redirect::permanent(url) — 301 Moved Permanently
  • Redirect::found(url) — 302 Found
  • Redirect::see_other(url) — 303 See Other
  • Redirect::temporary(url) — 307 Temporary Redirect
  • Redirect::permanent_redirect(url) — 308 Permanent Redirect
  • Redirect::status(code, url) — un código personalizado

Por ejemplo, después de un login exitoso puedes redirigir con see_other, que fuerza una solicitud GET a la nueva ubicación:

rust
use sword::prelude::*;

#[post("/login")]
async fn login(&self) -> Redirect {
    Redirect::see_other("/dashboard")
}

La redirección agrega el header Location con la URL de destino y el código de estado correspondiente. Puedes añadir headers extra con header(...):

rust
Redirect::temporary("/maintenance")
    .header("X-Redirect-Reason", "maintenance")

Al igual que File, Redirect implementa IntoResponse, por lo que también puede devolverse dentro de un WebResult<Redirect>.