Skip to content

Commit f953b8f

Browse files
Descobre rotas em todo o diretório rotas/ e popula paths corretamente (#130)
* fix(openapi): descobre rotas em todo o diretório `rotas/` e popula paths corretamente * chore: troca por
1 parent 51503a9 commit f953b8f

2 files changed

Lines changed: 98 additions & 40 deletions

File tree

fontes/infraestrutura/auto-documentacao/auto-documentador.ts

Lines changed: 95 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,26 @@ import {
77
Declaracao,
88
Expressao,
99
Chamada,
10+
AcessoMetodo,
1011
AcessoMetodoOuPropriedade,
1112
Decorador,
1213
Literal,
1314
Vetor,
1415
ConstrutoInterface
1516
} from '@designliquido/delegua';
17+
import { LexadorPitugues } from '@designliquido/delegua/lexador';
1618
import { Importador } from '@designliquido/delegua-node/importador';
17-
import { AvaliadorSintaticoComImportacao } from '@designliquido/delegua-node/avaliador-sintatico/avaliador-sintatico-com-importacao';
19+
import {
20+
AvaliadorSintaticoComImportacao
21+
} from '@designliquido/delegua-node/avaliador-sintatico/avaliador-sintatico-com-importacao';
1822

1923
import { RotaOpenApi } from './rota-open-api';
2024
import { MetodoHttpOpenApi } from './metodo-http-open-api';
2125
import { RespostaOpenApi } from './resposta-open-api';
2226
import { DocumentoOpenApi } from './documento-open-api';
23-
import { AutoDocumentadorInterface } from '../../interfaces/auto-documentador-interface';
27+
import {
28+
AutoDocumentadorInterface
29+
} from '../../interfaces/auto-documentador-interface';
2430

2531
/**
2632
* O auto documentador lê o projeto e gera uma especificação OpenAPI
@@ -38,7 +44,9 @@ export class AutoDocumentador implements AutoDocumentadorInterface {
3844

3945
constructor() {
4046
this.erros = [];
41-
this.diretorioRotas = caminho.join(process.cwd(), 'rotas/rest').replace(/\\/gi, '/');
47+
this.diretorioRotas = caminho
48+
.join(process.cwd(), 'rotas')
49+
.replace(/\\/gi, '/');
4250

4351
this.decoradoresValidos = {
4452
'@rest.documentacao': {
@@ -61,11 +69,17 @@ export class AutoDocumentador implements AutoDocumentadorInterface {
6169
}
6270

6371
// TODO: Pensar em como fazer isso considerando importações de outros arquivos.
64-
protected async obterEstruturasDeAltoNivelDeControlador(caminhoControlador: string): Promise<Declaracao[]> {
72+
protected async obterEstruturasDeAltoNivelDeControlador(
73+
caminhoControlador: string
74+
): Promise<Declaracao[]> {
6575
const arquivosAbertos = {};
6676
const conteudoArquivosAbertos = {};
6777

68-
const importador = new Importador(new Lexador(), arquivosAbertos, conteudoArquivosAbertos, false);
78+
const lexador = caminhoControlador.endsWith('.pitu')
79+
? new LexadorPitugues()
80+
: new Lexador();
81+
82+
const importador = new Importador(lexador, arquivosAbertos, conteudoArquivosAbertos, false);
6983

7084
const avaliadorSintatico = new AvaliadorSintaticoComImportacao(importador);
7185

@@ -84,45 +98,67 @@ export class AutoDocumentador implements AutoDocumentadorInterface {
8498
}
8599

86100
protected async encontrarControladores() {
87-
const formatoGlob = (this.diretorioRotas + '/**/*.delegua').replace(/\\/gi, '/');
88-
const arquivos = await glob([formatoGlob], { dot: true, absolute: false, stats: false });
101+
const formatoGlob = (
102+
this.diretorioRotas + '/**/*.{delegua,pitu}'
103+
).replace(/\\/gi, '/');
104+
const arquivos = await glob(
105+
[formatoGlob],
106+
{ dot: true, absolute: false, stats: false }
107+
);
89108

90109
const controladores = [];
110+
91111
for (const caminhoArquivo of arquivos) {
92-
const estruturas = await this.obterEstruturasDeAltoNivelDeControlador(caminhoArquivo);
93-
const rotaEControlador = this.lerControlador(caminhoArquivo, estruturas);
112+
const estruturas = await this
113+
.obterEstruturasDeAltoNivelDeControlador(caminhoArquivo);
114+
const rotaEControlador = this.lerControlador(
115+
caminhoArquivo,
116+
estruturas
117+
);
118+
94119
controladores.push(rotaEControlador);
95120
}
96121

97122
return controladores;
98123
}
99124

100-
protected resolverConstrutoValorDecorador(construtoValor: ConstrutoInterface): any {
101-
switch (construtoValor.constructor.name) {
102-
case 'Literal':
103-
return (construtoValor as Literal).valor;
104-
case 'Vetor':
125+
protected resolverConstrutoValorDecorador(
126+
construtoValor: ConstrutoInterface
127+
): any {
128+
switch (construtoValor.constructor) {
129+
case Literal:
130+
return construtoValor.valor;
131+
case Vetor:
105132
const valoresResolvidos = [];
133+
106134
for (const valor of (construtoValor as Vetor).valores) {
107-
valoresResolvidos.push(this.resolverConstrutoValorDecorador(valor));
135+
valoresResolvidos.push(
136+
this.resolverConstrutoValorDecorador(valor)
137+
);
108138
}
109139

110140
return valoresResolvidos;
111141
}
112142
}
113143

114-
protected resolverAtributosDecorador(decorador: Decorador): Record<string, any> {
144+
protected resolverAtributosDecorador(
145+
decorador: Decorador
146+
): Record<string, any> {
115147
const decoradorResolvido: Record<string, any> = {};
148+
116149
for (const [nomeAtributo, valorAtributo] of Object.entries(decorador.atributos)) {
117150
decoradorResolvido[nomeAtributo] = this.resolverConstrutoValorDecorador(valorAtributo);
118151
}
119152

120153
return decoradorResolvido;
121154
}
122155

123-
protected resolverDecoradorDocumentacao(atributos: Record<string, any>): RotaOpenApi {
156+
protected resolverDecoradorDocumentacao(
157+
atributos: Record<string, any>
158+
): RotaOpenApi {
124159
const retorno: RotaOpenApi = {};
125160
const decoradoresValidosDocumentacao = this.decoradoresValidos['@rest.documentacao'];
161+
126162
for (const [nomeAtributo, valorAtributo] of Object.entries(atributos)) {
127163
retorno[decoradoresValidosDocumentacao[nomeAtributo] as keyof RotaOpenApi] = valorAtributo;
128164
}
@@ -132,18 +168,21 @@ export class AutoDocumentador implements AutoDocumentadorInterface {
132168

133169
protected resolverDecoradorResposta(atributos: Record<string, any>): any {
134170
const decoradoresValidosResposta = this.decoradoresValidos['@rest.resposta'];
171+
135172
if (!('codigo' in atributos) && !('código' in atributos)) {
136-
this.erros.push(new Error(`Decorador @rest.resposta não possui atributo obrigado 'código' ou 'codigo'.`));
173+
this.erros.push(new Error(
174+
`Decorador @rest.resposta não possui atributo obrigado 'código' ou 'codigo'.`
175+
));
176+
137177
return null;
138178
}
139179

140180
const codigo = atributos['codigo'] || atributos['código'];
141181
const retorno: RespostaOpenApi = {};
182+
142183
for (const [nomeAtributo, valorAtributo] of Object.entries(atributos)) {
143184
const nomeOpenApi = decoradoresValidosResposta[nomeAtributo];
144-
if (nomeOpenApi === 'statusCode') {
145-
continue;
146-
}
185+
if (nomeOpenApi === 'statusCode') continue;
147186

148187
if (nomeOpenApi === 'content') {
149188
retorno.content = valorAtributo;
@@ -158,7 +197,10 @@ export class AutoDocumentador implements AutoDocumentadorInterface {
158197

159198
protected resolverDecorador(decorador: Decorador): any {
160199
if (!(decorador.nome in this.decoradoresValidos)) {
161-
this.erros.push(new Error(`Decorador ${decorador.nome} não é válido para um método de um controlador.`));
200+
this.erros.push(new Error(
201+
`Decorador ${decorador.nome} não é válido para um método de um controlador.`
202+
));
203+
162204
return null;
163205
}
164206

@@ -185,24 +227,39 @@ export class AutoDocumentador implements AutoDocumentadorInterface {
185227
declaracoes: Declaracao[]
186228
): [string, { [key in MetodoHttpOpenApi]?: RotaOpenApi }] {
187229
const descritivoControlador: { [key in MetodoHttpOpenApi]?: RotaOpenApi } = {};
188-
const rotaRelativa = caminhoControlador
189-
.replace(this.diretorioRotas, '')
190-
.replace('inicial.delegua', '')
191-
.replace('.delegua', '');
192230

193-
// console.log('rotaRelativa', rotaRelativa);
231+
// Remove o prefixo do diretório de rotas e a extensão do arquivo
232+
// para obter o caminho relativo da rota
233+
let rotaRelativa = caminhoControlador.substring(
234+
this.diretorioRotas.length
235+
);
236+
237+
rotaRelativa = rotaRelativa.replace(/inicial\.(delegua|pitu)$/, '');
238+
rotaRelativa = rotaRelativa.replace(/\.(delegua|pitu)$/, '');
239+
rotaRelativa = rotaRelativa.replace(/\[(.+?)\]/g, '{$1}');
194240

195241
for (const declaracao of declaracoes) {
196-
// Os decoradores contêm a documentação adicional para uma rota.
242+
if (!(declaracao instanceof Expressao)) continue;
243+
244+
const expressao = declaracao.expressao;
245+
246+
if (!(expressao instanceof Chamada)) continue;
247+
248+
const chamada = expressao;
197249
const decoradores = declaracao.decoradores;
198-
// Aqui normalmente teremos uma expressão com uma chamada dentro.
199-
const chamada = (declaracao as Expressao).expressao as Chamada;
200-
// Tipicamente, a entidade chamada é uma variável com o nome reservado `liquido`.
201-
// o método é um Símbolo.
202-
// A execução e middlewares ficam em argumentos.
203-
const entidadeChamada = chamada.entidadeChamada as AcessoMetodoOuPropriedade;
204-
// const argumentos = chamada.argumentos;
205-
// console.log(decoradores, argumentos, entidadeChamada.objeto, entidadeChamada.simbolo);
250+
251+
// Extrai o nome do método (rotaGet, rotaPost, ...) suportando
252+
// AcessoMetodo (Delégua) e AcessoMetodoOuPropriedade (Pituguês)
253+
let nomeMetodo: string | null = null;
254+
const entidadeChamada = chamada.entidadeChamada;
255+
256+
if (entidadeChamada instanceof AcessoMetodo) {
257+
nomeMetodo = entidadeChamada.nomeMetodo;
258+
} else if (entidadeChamada instanceof AcessoMetodoOuPropriedade) {
259+
nomeMetodo = entidadeChamada.simbolo.lexema;
260+
}
261+
262+
if (!nomeMetodo) continue;
206263

207264
let descritivoMetodoRota: RotaOpenApi = {};
208265
for (const decorador of decoradores) {
@@ -219,7 +276,8 @@ export class AutoDocumentador implements AutoDocumentadorInterface {
219276
}
220277
}
221278

222-
const metodoResolvido = entidadeChamada.simbolo.lexema.replace('rota', '');
279+
const metodoResolvido = nomeMetodo.replace('rota', '');
280+
223281
descritivoControlador[metodoResolvido.toLowerCase() as MetodoHttpOpenApi] = descritivoMetodoRota;
224282
}
225283

testes/interface-linha-comando/documentar.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ describe('Comando documentar', () => {
3737

3838
const saida = saidaConsole();
3939
expect(saida).toContain('Nenhum arquivo de rota');
40-
expect(saida).toContain(caminho.join(diretorioTemporario, 'rotas/rest').replace(/\\/gi, '/'));
40+
expect(saida).toContain(caminho.join(diretorioTemporario, 'rotas').replace(/\\/gi, '/'));
4141
expect(saida).toContain('Nenhum arquivo de documentação foi gerado.');
4242

4343
expect(
@@ -46,7 +46,7 @@ describe('Comando documentar', () => {
4646
});
4747

4848
it('deve gerar openapi.json e reportar caminho e número de rotas', async () => {
49-
const diretorioRota = caminho.join(diretorioTemporario, 'rotas', 'rest', 'artigos');
49+
const diretorioRota = caminho.join(diretorioTemporario, 'rotas', 'artigos');
5050
sistemaArquivos.mkdirSync(diretorioRota, { recursive: true });
5151
sistemaArquivos.writeFileSync(
5252
caminho.join(diretorioRota, 'inicial.delegua'),
@@ -75,7 +75,7 @@ describe('Comando documentar', () => {
7575
});
7676

7777
it('deve gravar no caminho de saída informado quando fornecido', async () => {
78-
const diretorioRota = caminho.join(diretorioTemporario, 'rotas', 'rest');
78+
const diretorioRota = caminho.join(diretorioTemporario, 'rotas');
7979
sistemaArquivos.mkdirSync(diretorioRota, { recursive: true });
8080
sistemaArquivos.writeFileSync(
8181
caminho.join(diretorioRota, 'inicial.delegua'),

0 commit comments

Comments
 (0)