Back to flin
flin

Helpers de respuesta y códigos de estado

Cómo el sistema de respuestas de FLIN convierte los valores de retorno en respuestas HTTP automáticamente: serialización JSON, códigos de estado, helpers de redirección y formateo de errores sin código repetitivo.

Thales & Claude | March 30, 2026 8 min flin
EN/ FR/ ES
flinrust

En Express.js, enviar una respuesta JSON requiere res.status(201).json({ user }). En Django, construyes JsonResponse(data, status=201). En FastAPI, devuelves un dict y decoras la función con @app.post(status_code=201). Cada framework tiene su propia API para la misma operación fundamental: convertir datos de aplicación en una respuesta HTTP.

FLIN simplifica esto a su mínimo lógico. La última expresión en un bloque de ruta ES la respuesta. Si es un objeto, se convierte en JSON con estado 200. Si es un literal response{}, controlas el estado y las cabeceras. Si es una llamada error(), obtienes una respuesta de error formateada. Si llamas a redirect(), el cliente es enviado a otro lugar. Cuatro patrones cubren cada respuesta HTTP que necesitarás.

Patrón 1: respuesta JSON implícita

El patrón más simple y común. La última expresión en un bloque de ruta se serializa automáticamente a JSON:

flin// Devuelve 200 OK con cuerpo JSON
route GET {
    User.all
}
// Respuesta: [{"id": 1, "name": "Thales", ...}, ...]

route GET {
    user = User.find(params.id)
    user
}
// Respuesta: {"id": 42, "name": "Thales", "email": "[email protected]"}

route GET {
    {
        status: "healthy",
        uptime: server_uptime(),
        version: "1.0.0",
        database: "connected"
    }
}
// Respuesta: {"status": "healthy", "uptime": 3600, ...}

La serialización sigue reglas predecibles:

Tipo FLINSalida JSON
Instancia de entidadObjeto con todos los campos visibles
Lista de entidadesArreglo de objetos
Map/literal de objetoObjeto JSON
TextCadena JSON
Int/FloatNúmero JSON
BoolBooleano JSON
NoneJSON null
ListArreglo JSON

Los campos marcados con @hidden (como contraseñas) se excluyen automáticamente de la serialización. Nunca filtras accidentalmente un hash de contraseña en una respuesta de API.

Patrón 2: respuesta explícita

Cuando necesitas controlar el código de estado, las cabeceras o el formato de respuesta, usa el literal response{}:

flin// 201 Created con cabecera Location
route POST {
    user = User { name: body.name, email: body.email }
    save user

    response {
        status: 201
        headers: {
            "Location": "/api/users/" + to_text(user.id)
        }
        body: user
    }
}

// 204 No Content
route DELETE {
    user = User.find(params.id)
    delete user

    response {
        status: 204
    }
}

// Tipo de contenido personalizado
route GET {
    csv_data = generate_csv(User.all)

    response {
        status: 200
        headers: {
            "Content-Type": "text/csv",
            "Content-Disposition": "attachment; filename=\"users.csv\""
        }
        body: csv_data
    }
}

El literal response{} tiene tres campos opcionales:

  • status -- código de estado HTTP (por defecto: 200)
  • headers -- mapa de cabeceras de respuesta
  • body -- cuerpo de la respuesta (serializado a JSON si es un valor FLIN)

Patrón 3: respuestas de error

La función error() genera una respuesta de error estructurada:

flinroute GET {
    user = User.find(params.id)
    if user == none {
        return error(404, "User not found")
    }
    user
}
// Respuesta de error: {"error": "User not found", "status": 404}

route POST {
    existing = User.where(email == body.email).first
    if existing != none {
        return error(409, "Email already registered")
    }
    // ...
}

route PUT {
    if body.quantity > product.stock {
        return error(422, "Insufficient stock", {
            available: product.stock,
            requested: body.quantity
        })
    }
    // ...
}
// Respuesta de error: {"error": "Insufficient stock", "status": 422,
//                  "details": {"available": 5, "requested": 10}}

La función error() acepta dos o tres argumentos:

flinerror(status_code, message)
error(status_code, message, details)

La respuesta siempre incluye el campo error (el mensaje), el campo status (el código), y opcionalmente un campo details (contexto adicional). Este formato consistente significa que el código frontend siempre puede analizar errores de la misma manera.

Patrón 4: redirecciones

La función redirect() envía una respuesta 302 Found:

flinroute POST {
    // Procesar formulario...
    redirect("/dashboard")
}

// Con código de estado específico
route POST {
    redirect("/new-location", 301)   // Redirección permanente
}

Las redirecciones se usan comúnmente en el flujo de autenticación, donde las páginas de procesamiento redirigen al panel de control en caso de éxito o de vuelta a la página de inicio de sesión en caso de fallo:

flinroute POST {
    if login_successful {
        session.user = user.email
        redirect("/tasks")
    } else {
        session.loginError = "Invalid credentials"
        redirect("/login")
    }
}

Respuestas de error de validación

Cuando un bloque validate falla, FLIN genera automáticamente una respuesta 400 con detalles de error por campo:

flinroute POST {
    validate {
        name: text @required @minLength(2) @maxLength(100)
        email: text @required @email
        age: int @min(13) @max(120)
    }
    // ...
}

Si un cliente envía {"name": "A", "email": "not-an-email", "age": 5}, la respuesta es:

json{
    "error": "Validation failed",
    "status": 400,
    "fields": {
        "name": "Must be at least 2 characters",
        "email": "Must be a valid email address",
        "age": "Must be at least 13"
    }
}

Este formato de respuesta es generado enteramente por el runtime. El desarrollador escribe el bloque validate y FLIN se encarga del formateo de errores. Los nombres de campo coinciden exactamente con lo que el cliente envió, haciendo sencillo para el código frontend mostrar errores junto a los campos de formulario correctos.

Cómo se construyen las respuestas internamente

El runtime de FLIN convierte los valores de retorno de bloques de ruta a respuestas HTTP a través de un despacho directo:

rustfn value_to_response(value: Value) -> Response {
    match value {
        Value::Response(r) => {
            // Literal response{} explícito
            Response::new(r.status)
                .headers(r.headers)
                .json_body_if_value(r.body)
        }

        Value::Error(status, message, details) => {
            // Resultado de función error()
            let mut body = json!({
                "error": message,
                "status": status,
            });
            if let Some(d) = details {
                body["details"] = value_to_json(d);
            }
            Response::new(status)
                .header("Content-Type", "application/json")
                .body(body.to_string())
        }

        Value::Redirect(url, status) => {
            Response::new(status.unwrap_or(302))
                .header("Location", url)
        }

        Value::None => {
            // Sin valor de retorno -> 204 No Content
            Response::new(204)
        }

        other => {
            // Cualquier otro valor -> 200 OK con cuerpo JSON
            let json = value_to_json(other);
            Response::new(200)
                .header("Content-Type", "application/json")
                .body(json.to_string())
        }
    }
}

La conversión es determinista y exhaustiva. Cada posible valor FLIN se mapea a exactamente una respuesta HTTP. No hay ambigüedad, no hay comportamiento por defecto que pueda sorprenderte, y no hay modo de fallo silencioso.

Cabeceras de seguridad en cada respuesta

Independientemente del patrón de respuesta que uses, FLIN agrega cabeceras de seguridad a cada respuesta HTTP en modo producción:

httpX-Frame-Options: DENY
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'
Permissions-Policy: camera=(), microphone=(), geolocation=()

Estas cabeceras no son opcionales. No son middleware que podrías olvidar agregar. Son parte de cada respuesta que sale del servidor. Una aplicación FLIN está protegida contra clickjacking, sniffing de MIME, XSS y fuga de información por defecto.

Combinando patrones en la práctica

Un endpoint de API realista usa múltiples patrones de respuesta dependiendo del resultado:

flin// app/api/orders.flin

guard auth

route POST {
    validate {
        product_id: int @required
        quantity: int @required @min(1) @max(100)
        shipping_address: text @required
    }

    product = Product.find(body.product_id)
    if product == none {
        return error(404, "Product not found")
    }

    if product.stock < body.quantity {
        return error(422, "Insufficient stock", {
            available: product.stock,
            requested: body.quantity
        })
    }

    order = Order {
        user_id: to_int(session.userId),
        product_id: product.id,
        quantity: body.quantity,
        total: product.price * body.quantity,
        shipping_address: body.shipping_address,
        status: "pending"
    }
    save order

    product.stock = product.stock - body.quantity
    save product

    response {
        status: 201
        headers: {
            "Location": "/api/orders/" + to_text(order.id)
        }
        body: order
    }
}

Este único manejador usa errores de validación (400), errores de no encontrado (404), errores de lógica de negocio (422) y una respuesta de creación exitosa (201). Cada tipo de respuesta se expresa claramente y se maneja consistentemente.

La filosofía del diseño de respuestas

El sistema de respuestas de FLIN refleja un principio de diseño central: el caso común debería ser sin esfuerzo, y el caso poco común debería ser directo.

Devolver JSON (el caso común) no requiere ceremonia -- simplemente devuelve un valor. Establecer un código de estado personalizado (menos común) requiere un literal response{}. Devolver un error (poco común pero importante) requiere una llamada error(). Redirigir (raro en rutas de API, común en flujos de autenticación) requiere una llamada redirect().

Sin clases de respuesta específicas del framework para instanciar. Sin cadenas de llamadas a métodos para construir. Sin decoradores o anotaciones importadas. Simplemente devuelve lo que quieres que el cliente reciba, y FLIN se encarga de la fontanería HTTP.

Esto concluye el Arco 9 -- el servidor web y el manejo HTTP de FLIN. Diez artículos cubriendo el servidor embebido, enrutamiento basado en archivos, rutas de API, análisis de cuerpo, inyección de contexto, middleware, guards, WebSockets, carga de archivos y manejo de respuestas. En el Arco 10, nos enfocamos en la seguridad: cómo FLIN aborda el OWASP Top 10, implementa hash de contraseñas Argon2, autenticación JWT, limitación de velocidad, 2FA, OAuth2 y WhatsApp OTP -- todo como características integradas del lenguaje.


Esta es la Parte 105 de la serie "Cómo construimos FLIN", que documenta cómo un CEO en Abiyán y un CTO de IA diseñaron y construyeron un lenguaje de programación desde cero.

Navegación de la serie: - [103] Soporte WebSocket integrado en el lenguaje - [104] Soporte de carga de archivos - [105] Helpers de respuesta y códigos de estado (estás aquí) - [106] Seguridad por diseño: OWASP Top 10 en el lenguaje

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles