feat(shared): contrato Zod PingResponse em @sar/api-interface (Frente C)
Primeiro contrato compartilhado API↔Web. Lib stays framework-free:
nestjs-zod (createZodDto) fica fora — Web vai consumir esta mesma lib
e não pode arrastar dependência backend.
- PingResponseSchema + type PingResponse (z.infer) em ping.contract.ts
- 6 testes vitest (1 happy + 5 rejeições: status, uuid, uptime, datetime, workspaceId)
- ping.controller importa PingResponse via @sar/api-interface
- placeholders Nx api-interface.{ts,spec.ts} removidos
- design-log atualizado com decisão arquitetural e pegadinhas da sessão
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
"main": "./src/index.js",
|
||||
"types": "./src/index.d.ts",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
"tslib": "^2.3.0",
|
||||
"zod": "catalog:"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
export * from './lib/api-interface';
|
||||
export * from './lib/ping.contract';
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { apiInterface } from './api-interface';
|
||||
|
||||
describe('apiInterface', () => {
|
||||
it('should work', () => {
|
||||
expect(apiInterface()).toEqual('api-interface');
|
||||
});
|
||||
});
|
||||
@@ -1,3 +0,0 @@
|
||||
export function apiInterface(): string {
|
||||
return 'api-interface';
|
||||
}
|
||||
43
libs/shared/api-interface/src/lib/ping.contract.spec.ts
Normal file
43
libs/shared/api-interface/src/lib/ping.contract.spec.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { PingResponseSchema, type PingResponse } from './ping.contract';
|
||||
|
||||
describe('PingResponseSchema', () => {
|
||||
const validPayload: PingResponse = {
|
||||
status: 'ok',
|
||||
service: 'sar-api',
|
||||
version: '0.1.0',
|
||||
workspaceId: 'dev-workspace',
|
||||
requestId: '550e8400-e29b-41d4-a716-446655440000',
|
||||
uptimeSeconds: 42,
|
||||
now: '2026-05-27T12:34:56.000Z',
|
||||
};
|
||||
|
||||
it('aceita payload válido', () => {
|
||||
const parsed = PingResponseSchema.parse(validPayload);
|
||||
expect(parsed).toEqual(validPayload);
|
||||
});
|
||||
|
||||
it('rejeita status diferente de "ok"', () => {
|
||||
const bad = { ...validPayload, status: 'degraded' };
|
||||
expect(() => PingResponseSchema.parse(bad)).toThrow();
|
||||
});
|
||||
|
||||
it('rejeita requestId que não é UUID', () => {
|
||||
const bad = { ...validPayload, requestId: 'not-a-uuid' };
|
||||
expect(() => PingResponseSchema.parse(bad)).toThrow();
|
||||
});
|
||||
|
||||
it('rejeita uptimeSeconds negativo', () => {
|
||||
const bad = { ...validPayload, uptimeSeconds: -1 };
|
||||
expect(() => PingResponseSchema.parse(bad)).toThrow();
|
||||
});
|
||||
|
||||
it('rejeita "now" que não é ISO datetime', () => {
|
||||
const bad = { ...validPayload, now: '2026-05-27' };
|
||||
expect(() => PingResponseSchema.parse(bad)).toThrow();
|
||||
});
|
||||
|
||||
it('rejeita workspaceId vazio', () => {
|
||||
const bad = { ...validPayload, workspaceId: '' };
|
||||
expect(() => PingResponseSchema.parse(bad)).toThrow();
|
||||
});
|
||||
});
|
||||
24
libs/shared/api-interface/src/lib/ping.contract.ts
Normal file
24
libs/shared/api-interface/src/lib/ping.contract.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
// Contrato canônico do endpoint GET /api/v1/ping.
|
||||
//
|
||||
// Consumido por:
|
||||
// - apps/api: PingController retorna PingResponse (saída tipada).
|
||||
// - apps/web: TanStack Query parseia a resposta via PingResponseSchema.parse(...)
|
||||
// para validar o contrato em runtime — qualquer drift no servidor falha alto.
|
||||
//
|
||||
// CODING-RULES §06: o schema Zod É o contrato. DTO/classes (createZodDto) ficam
|
||||
// na camada API quando precisar do pipe de validação; aqui carregamos só o que
|
||||
// é compartilhável entre Node e Browser.
|
||||
|
||||
export const PingResponseSchema = z.object({
|
||||
status: z.literal('ok'),
|
||||
service: z.string().min(1),
|
||||
version: z.string().min(1),
|
||||
workspaceId: z.string().min(1),
|
||||
requestId: z.uuid(),
|
||||
uptimeSeconds: z.number().int().nonnegative(),
|
||||
now: z.iso.datetime(),
|
||||
});
|
||||
|
||||
export type PingResponse = z.infer<typeof PingResponseSchema>;
|
||||
Reference in New Issue
Block a user