As especificações OpenAPI dizem como uma requisição válida se parece e como uma resposta válida se parece. Elas não dizem se você pode chamar POST /orders antes de POST /auth, ou o que acontece se você chamar GET /invoice/{id} depois de DELETE /invoice/{id}. Essa informação vive em uma especificação de protocol, e OpenAPI não é uma especificação de protocol.
Essa é a lacuna. Você pode gerar todos os tipos de mensagem do seu sistema a partir de um documento OpenAPI. Você não pode gerar as regras sobre quando essas mensagens podem trafegar. Essas regras são o session type, e elas vivem em uma camada de abstração diferente.
O que OpenAPI captura e o que ignora
OpenAPI é um contract para a superfície HTTP. Ele define paths, methods, query parameters, request bodies, response codes e schemas JSON. Isso é valioso. Também é fundamentalmente estático. Cada endpoint é descrito de forma isolada. Os relacionamentos entre endpoints, as transições de estado que eles disparam e as sequências que são legais são deixados como exercício para o leitor.
Session types são o oposto. Um session type é uma descrição formal de um protocol de comunicação. Ele especifica a ordem na qual as mensagens devem ser enviadas e recebidas, quem envia o quê e como o protocol ramifica com base no conteúdo da mensagem. Um session type binário pode dizer: o cliente envia uma mensagem de Login, então o servidor responde com Success e o protocol continua, ou responde com Failure e o protocol termina.
Você não pode derivar essa sequência de uma especificação OpenAPI porque a sequência nunca foi escrita. O documento OpenAPI lista os endpoints. Ele não especifica que /auth deve preceder /orders. Essa restrição vive na documentação, no código ou na cabeça do engenheiro que projetou a API.
O que você pode extrair: a camada de mensagem
O que OpenAPI realmente lhe dá são definições de mensagem precisas e legíveis por máquina. Cada schema de request body, cada schema de resposta, cada enum e discriminator é especificado em JSON Schema. Esse é o material bruto para os tipos de mensagem em uma especificação de session type.
Aqui está um exemplo concreto. Considere esse fragmento 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 disso, você pode extrair dois tipos de mensagem: um AuthRequest contendo um username e password, e um AuthResponse que é ou um Success com um token ou um Failure. O session type precisa desses tipos. Ele também precisa saber que o cliente envia a requisição e o servidor envia a resposta, o que OpenAPI implica através da semântica HTTP.
Você pode automatizar essa extração. Aqui está um script Python que analisa uma especificação OpenAPI JSON e gera definições de mensagem semelhantes às de 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("}")
Isso é mecânico, mas funciona. Ele transforma seu documento OpenAPI em structs que uma implementação de session type pode referenciar. Os tipos são precisos. Os nomes são literais. Os relacionamentos estão faltando.
Onde a informação de sequência vive
Para obter o protocol, você precisa conhecer a state machine. Algumas APIs codificam isso implicitamente. Um POST /orders retorna um ID de pedido, e chamadas subsequentes de GET /orders/{id} o referenciam. O estado do protocol inclui “um pedido foi criado”. Uma especificação OpenAPI não modela essa dependency.
Existem padrões emergentes que tentam preencher essa lacuna. O AsyncAPI lida com protocols orientados a events com semântica de canais, mas ainda não oferece o session type global. O Smithy define operações e traits, e pode modelar state machines finitas através de traits personalizados, mas exige anotação explícita. O JSON Hyper-Schema tentou vincular recursos, mas nunca teve adoção ampla.
Por enquanto, a abordagem prática é tratar o protocol como um artefato separado. Você gera os tipos de mensagem a partir do OpenAPI, depois escreve o session type manualmente sobre eles.
Um híbrido prático: tipos gerados mais um protocol escrito manualmente
É assim que isso se parece na prática usando Rust e a crate session_types. Primeiro, gere os tipos de mensagem a partir do OpenAPI usando o script acima ou uma ferramenta como typify. Depois defina o session type explicitamente:
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();
}
}
O session type ServerProto especifica exatamente o que o servidor faz. Ele recebe um AuthRequest. Depois oferece uma escolha: ou enviar AuthSuccess e continuar a receber um OrderRequest, ou enviar AuthFailure e terminar. A especificação OpenAPI nos deu as structs. O session type nos deu a gramática.
Essa é a divisão de trabalho. OpenAPI lida com as formas das mensagens. Session types lidam com a ordem das mensagens. Um é gerado, o outro é projetado.
As compensações que você deve conhecer
A extração automatizada a partir do OpenAPI apresenta desafios. A primeira é o polimorfismo. O OpenAPI usa oneOf e anyOf para uniões, mas a informação do discriminator não mapeia de forma limpa para a ramificação de session type. Um oneOf em um schema de resposta pode representar duas formas de sucesso diferentes, ou pode representar um branch de protocol. Você precisa ler a spec para saber qual.
A segunda é a semântica específica de HTTP. Os session types são agnósticos de transporte. O OpenAPI está profundamente ligado a methods, status codes e headers de HTTP. Quando você extrai mensagens, perde a informação de method e path a menos que a codifique no nome da mensagem. É por isso que o script Python acima gera nomes como POSTAuthResponse200. É feio, mas preserva a proveniência.
A terceira são as specs parciais. Muitos documentos OpenAPI são gerados a partir de código e não incluem todas as respostas de erro. Se o seu session type precisa lidar com cada branch possível, uma especificação OpenAPI incompleta produzirá um session type incompleto. O type checker não capturará branches ausentes se os branches nunca estiveram no documento fonte.
Como implementar isso no seu codebase
Comece com as mensagens. Use openapi-typescript, typify ou um script personalizado para gerar tipos específicos de linguagem a partir dos seus schemas OpenAPI. Não tente gerar o protocol nessa etapa. Apenas obtenha as structs.
Em seguida, escreva o session type para o fluxo mais crítico do seu sistema. Escolha o fluxo onde um bug de sequenciamento seria mais caro. Autenticação e pagamento são bons candidatos. Defina o session type na linguagem de sua escolha. Rust tem session_types. Existem bibliotecas experimentais para TypeScript, OCaml e Scala. Se a sua linguagem não tiver uma biblioteca de session type, escreva um state machine test que afirme sequências válidas. Não é a mesma garantia, mas captura a mesma classe de bugs.
Por fim, mantenha os dois artefatos em sincronia com CI. Quando a especificação OpenAPI mudar, regenere os tipos de mensagem. Se um novo campo obrigatório for adicionado, o código do session type falhará na compilação. Essa é a segurança que você está comprando.
FAQ
Isso funciona para APIs WebSocket ou gRPC?
Não diretamente. O OpenAPI descreve HTTP. Para gRPC, você tem schemas protobuf, que lhe dão tipos de mensagem, mas ainda não sequências. Para WebSockets, o AsyncAPI é mais adequado, mas a mesma limitação se aplica: ele descreve canais, não protocols de session globais.
Posso gerar session types a partir do AsyncAPI?
O AsyncAPI adiciona semântica de canais e roteamento de mensagens, o que aproxima mais. Ele ainda não especifica a state machine do protocol global. Você extrairia tipos de mensagem do AsyncAPI e escreveria o session type manualmente, assim como com o OpenAPI.
E se minha API tiver cinquenta endpoints?
Cinquenta endpoints significam cinquenta tipos de mensagem, não um session type com cinquenta passos. protocols reais se decompõem em sessions menores. Uma API de e-commerce pode ter sessions separadas para autenticação, checkout e inventário. Componha-os.
Existe uma ferramenta que faz tudo isso automaticamente?
Ainda não. A pesquisa existe. Há artigos sobre extração de session types a partir de APIs REST e de coreografias. Ferramentas prontas para produção não existem. Por enquanto, a abordagem híbrida é o caminho pragmático.
Sua especificação OpenAPI é um dicionário. Ela diz o que as palavras significam. Um session type é uma gramática. Ele diz quais frases são legais. Gere o vocabulário a partir do OpenAPI. Escreva a gramática você mesmo.