Las especificaciones OpenAPI te dicen cómo se ve una petición válida y cómo se ve una respuesta válida. No te dicen si puedes llamar a POST /orders antes de POST /auth, o qué ocurre si llamas a GET /invoice/{id} después de DELETE /invoice/{id}. Esa información vive en una protocol spec, y OpenAPI no es una protocol spec.
Este es el vacío. Puedes generar todos los message types de tu sistema a partir de un documento OpenAPI. No puedes generar las reglas sobre cuándo se permiten enviar esos mensajes. Esas reglas son el session type, y viven en una capa de abstracción diferente.
Qué captura OpenAPI y qué ignora
OpenAPI es un contract para la superficie HTTP. Define paths, métodos, query parameters, request bodies, response codes y JSON schemas. Esto es valioso. También es fundamentalmente estático. Cada endpoint se describe de forma aislada. Las relaciones entre endpoints, las transiciones de estado que desencadenan y las secuencias que son legales se dejan como ejercicio para el lector.
Los session types son lo opuesto. Un session type es una descripción formal de un protocol de comunicación. Especifica el orden en el que los mensajes deben enviarse y recibirse, quién envía qué, y cómo el protocol se ramifica según el contenido del mensaje. Un session type binario podría decir: el cliente envía un mensaje Login, luego el servidor responde con Success y el protocol continúa, o responde con Failure y el protocol termina.
No puedes derivar esa secuencia de una spec de OpenAPI porque la secuencia nunca fue escrita. El documento OpenAPI lista los endpoints. No especifica que /auth debe preceder a /orders. Esa restricción vive en la documentación, el código o la cabeza del ingeniero que diseñó la API.
Qué puedes extraer: la capa de mensajes
Lo que sí te da OpenAPI son definiciones de mensajes precisas y legibles por máquina. Cada request body schema, cada response schema, cada enum y discriminator está especificado en JSON Schema. Esta es la materia prima para los message types en una especificación de session type.
Aquí tienes un ejemplo concreto. Considera este fragmento de OpenAPI:
paths:
/auth:
post:
requestBody:
content:
application/json:
schema:
type: object
properties:
username: { type: string }
password: { type: string }
responses:
'200':
description: Success
content:
application/json:
schema:
type: object
properties:
token: { type: string }
'401':
description: Unauthorized
A partir de esto, puedes extraer dos message types: un AuthRequest que contiene un username y un password, y un AuthResponse que es o bien un Success con un token o un Failure. El session type necesita estos types. También necesita saber que el cliente envía la petición y el servidor envía la respuesta, lo cual OpenAPI implica a través de la semántica de HTTP.
Puedes automatizar esta checkout. Aquí tienes un script de Python que parsea una spec de OpenAPI en JSON y genera definiciones de mensajes parecidas a Rust:
import json
from dataclasses import dataclass
@dataclass
class Field:
name: str
type: str
@dataclass
class Message:
name: str
fields: list[Field]
TYPE_MAP = {"string": "String", "integer": "i64", "boolean": "bool"}
def sanitize_name(path: str, method: str, suffix: str) -> str:
cleaned = path.replace("/", "").replace("{", "").replace("}", "")
return f"{method.upper()}{cleaned}{suffix}"
def extract_messages(openapi_path: str) -> list[Message]:
with open(openapi_path) as f:
spec = json.load(f)
messages = []
for path, methods in spec.get("paths", {}).items():
for method, operation in methods.items():
if not isinstance(operation, dict):
continue
req = operation.get("requestBody", {})
schema = req.get("content", {}).get("application/json", {}).get("schema", {})
if schema:
fields = [
Field(name=k, type=TYPE_MAP.get(v.get("type"), "serde_json::Value"))
for k, v in schema.get("properties", {}).items()
]
messages.append(Message(name=sanitize_name(path, method, "Request"), fields=fields))
for code, resp in operation.get("responses", {}).items():
schema = resp.get("content", {}).get("application/json", {}).get("schema", {})
if schema:
fields = [
Field(name=k, type=TYPE_MAP.get(v.get("type"), "serde_json::Value"))
for k, v in schema.get("properties", {}).items()
]
messages.append(Message(name=sanitize_name(path, method, f"Response{code}"), fields=fields))
return messages
if __name__ == "__main__":
for m in extract_messages("api.json"):
print(f"struct {m.name} {{")
for f in m.fields:
print(f" {f.name}: {f.type},")
print("}")
Esto es mecánico, pero funciona. Convierte tu documento OpenAPI en structs que una implementación de session types puede referenciar. Los types son precisos. Los nombres son literales. Las relaciones faltan.
Dónde vive la información de la secuencia
Para obtener el protocol, necesitas conocer la state machine. Algunas APIs la codifican de forma implícita. Un POST /orders devuelve un ID de orden, y las llamadas posteriores a GET /orders/{id} lo referencian. El estado del protocol incluye “se ha creado una orden”. Una spec de OpenAPI no modela esta dependencia.
Hay estándares emergentes que intentan salvar este vacío. AsyncAPI maneja protocols dirigidos por events con semántica de canales, pero aún no te da el global session type. Smithy define operaciones y traits, y puede modelar state machines finitos a través de custom traits, pero requiere anotación explícita. JSON Hyper-Schema intentó vincular recursos, pero nunca tuvo una adopción generalizada.
Por ahora, el enfoque práctico es tratar el protocol como un artefacto separado. Generas los message types desde OpenAPI, y luego escribes el session type a mano sobre ellos.
Un híbrido práctico: types generados más un protocol escrito a mano
Así es como se ve esto en la práctica usando Rust y el crate session_types. Primero, genera los message types desde OpenAPI usando el script de arriba o una herramienta como typify. Luego define el session type de forma explícita:
use session_types::*;
// Generated from OpenAPI
struct AuthRequest { username: String, password: String }
struct AuthSuccess { token: String }
struct AuthFailure { reason: String }
struct OrderRequest { item_id: u64, quantity: u64 }
struct OrderConfirmation { order_id: u64 }
// The protocol: authenticate, then optionally place an order
type ServerProto = Receive<AuthRequest, Offer<
Send<AuthSuccess, Receive<OrderRequest, Send<OrderConfirmation, Eps>>>,
Send<AuthFailure, Eps>
>>;
fn server(c: Chan<(), ServerProto>) {
let (c, req) = c.recv();
if authenticate(&req) {
let c = c.sel1().send(AuthSuccess { token: "abc".into() });
let (c, order) = c.recv();
let c = c.send(OrderConfirmation { order_id: 42 });
c.close();
} else {
let c = c.sel2().send(AuthFailure { reason: "bad creds".into() });
c.close();
}
}
El session type ServerProto especifica exactamente lo que hace el servidor. Recibe un AuthRequest. Luego ofrece una elección: o envía AuthSuccess y continúa recibiendo un OrderRequest, o envía AuthFailure y termina. La spec de OpenAPI nos dio los structs. El session type nos dio la gramática.
Esta es la división del trabajo. OpenAPI maneja las formas de los mensajes. Los session types manejan el orden de los mensajes. Uno se genera, el otro se diseña.
Las compensaciones que deberías conocer
La checkout automatizada desde OpenAPI tiene aristas afiladas. La primera es el polimorfismo. OpenAPI usa oneOf y anyOf para unions, pero la información del discriminator no se mapea limpiamente a la ramificación de session types. Un oneOf en un response schema podría representar dos formas de éxito diferentes, o podría representar una branch del protocol. Tienes que leer la spec para saber cuál.
La segunda es la semántica específica de HTTP. Los session types son agnósticos al transporte. OpenAPI está profundamente ligado a los métodos HTTP, los status codes y los headers. Cuando extraes mensajes, pierdes la información del método y el path a menos que la codifiques en el nombre del mensaje. Por eso el script de Python de arriba genera nombres como POSTAuthResponse200. Es feo, pero preserva la procedencia.
La tercera son las specs parciales. Muchos documentos OpenAPI se generan a partir de código y no incluyen todas las respuestas de error. Si tu session type necesita manejar cada posible branch, una spec de OpenAPI incompleta producirá un session type incompleto. El type checker no detectará branches faltantes si esas branches nunca estuvieron en el documento fuente.
Cómo implementar esto en tu codebase
Empieza con los mensajes. Usa openapi-typescript, typify o un script personalizado para generar types específicos del lenguaje a partir de tus esquemas OpenAPI. No intentes generar el protocol en esta etapa. Solo obtén los structs.
A continuación, escribe el session type para el flujo más crítico de tu sistema. Elige el flujo donde un bug de secuenciación sería más costoso. La autenticación y los pagos son buenos candidatos. Define el session type en el lenguaje de tu elección. Rust tiene session_types. Hay bibliotecas experimentales para TypeScript, OCaml y Scala. Si tu lenguaje carece de una biblioteca de session types, escribe una prueba de state machine que afirme secuencias válidas. No es la misma garantía, pero detecta la misma clase de bugs.
Por último, mantén los dos artefactos sincronizados con CI. Cuando la spec de OpenAPI cambie, regenera los message types. Si se añade un nuevo campo requerido, el código del session type fallará al compilar. Esa es la seguridad que estás comprando.
Preguntas frecuentes
¿Esto funciona para APIs de WebSocket o gRPC?
No directamente. OpenAPI describe HTTP. Para gRPC, tienes esquemas de protobuf, que te dan message types pero aún no secuencias. Para WebSockets, AsyncAPI encaja mejor, pero se aplica la misma limitación: describe canales, no protocols de sesión globales.
¿Puedo generar session types desde AsyncAPI en su lugar?
AsyncAPI añade semántica de canales y enrutamiento de mensajes, lo cual te acerca más. Aún no especifica la state machine del protocol global. Extraerías los message types desde AsyncAPI y escribirías el session type a mano, igual que con OpenAPI.
¿Qué pasa si mi API tiene cincuenta endpoints?
Cincuenta endpoints significan cincuenta message types, no un session type con cincuenta pasos. Los protocols reales se descomponen en sesiones más pequeñas. Una API de comercio electrónico podría tener sesiones separadas para autenticación, checkout e inventario. Compónlas.
¿Existe una herramienta que haga todo esto automáticamente?
Aún no. La investigación existe. Hay papers sobre extraer session types desde REST APIs y desde choreographies. El tooling listo para producción no existe. Por ahora, el enfoque híbrido es el camino pragmático.
Tu spec de OpenAPI es un diccionario. Te dice qué significan las palabras. Un session type es una gramática. Te dice qué oraciones son legales. Genera el vocabulario desde OpenAPI. Escribe la gramática tú mismo.