liquido
Version:
Conjunto de ferramentas para desenvolvimento de aplicações para a internet 100% em português
191 lines • 9.15 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AutoDocumentador = void 0;
const path_1 = __importDefault(require("path"));
const fast_glob_1 = require("fast-glob");
const delegua_1 = require("@designliquido/delegua");
const importador_1 = require("@designliquido/delegua-node/importador");
const avaliador_sintatico_com_importacao_1 = require("@designliquido/delegua-node/avaliador-sintatico/avaliador-sintatico-com-importacao");
/**
* O auto documentador lê o projeto e gera uma especificação OpenAPI
* baseada no que foi implementado a nível de rotas.
*/
class AutoDocumentador {
constructor() {
this.nomeAplicacao = 'Teste Liquido';
this.versao = '0.0.1';
this.descricao = 'Este é um teste em Liquido';
this.nomeLicenca = 'MIT';
this.urlLicensa = 'https://github.com/DesignLiquido/liquido/LICENSE';
this.erros = [];
this.diretorioRotas = path_1.default.join(process.cwd(), 'rotas/rest').replace(/\\/gi, '/');
this.decoradoresValidos = {
'@rest.documentacao': {
sumario: 'summary',
sumário: 'summary',
descricao: 'description',
descrição: 'description',
idOperacao: 'operationId',
idOperação: 'operationId',
etiquetas: 'tags'
},
'@rest.resposta': {
codigo: 'statusCode',
código: 'statusCode',
descricao: 'description',
descrição: 'description',
formatos: 'content'
}
};
}
// TODO: Pensar em como fazer isso considerando importações de outros arquivos.
obterEstruturasDeAltoNivelDeControlador(caminhoControlador) {
const arquivosAbertos = {};
const conteudoArquivosAbertos = {};
const importador = new importador_1.Importador(new delegua_1.Lexador(), arquivosAbertos, conteudoArquivosAbertos, false);
const avaliadorSintatico = new avaliador_sintatico_com_importacao_1.AvaliadorSintaticoComImportacao(importador);
const retornoImportador = importador.importar(caminhoControlador, -1);
const retornoAvaliadorSintatico = avaliadorSintatico.analisar(retornoImportador.retornoLexador, retornoImportador.hashArquivo);
if (retornoAvaliadorSintatico.erros.length > 0) {
this.erros.push(new Error(`O controlador em ${caminhoControlador} possui erros: ${retornoAvaliadorSintatico.erros.map((erro) => ' - ' + erro.message + '\n')}`));
return [];
}
return retornoAvaliadorSintatico.declaracoes;
}
async encontrarControladores() {
const formatoGlob = (this.diretorioRotas + '/**/*.delegua').replace(/\\/gi, '/');
const arquivos = await (0, fast_glob_1.async)([formatoGlob], { dot: true, absolute: false, stats: false });
const controladores = [];
for (const caminhoArquivo of arquivos) {
const estruturas = this.obterEstruturasDeAltoNivelDeControlador(caminhoArquivo);
const rotaEControlador = this.lerControlador(caminhoArquivo, estruturas);
controladores.push(rotaEControlador);
}
return controladores;
}
resolverConstrutoValorDecorador(construtoValor) {
switch (construtoValor.constructor.name) {
case 'Literal':
return construtoValor.valor;
case 'Vetor':
const valoresResolvidos = [];
for (const valor of construtoValor.valores) {
valoresResolvidos.push(this.resolverConstrutoValorDecorador(valor));
}
return valoresResolvidos;
}
}
resolverAtributosDecorador(decorador) {
const decoradorResolvido = {};
for (const [nomeAtributo, valorAtributo] of Object.entries(decorador.atributos)) {
decoradorResolvido[nomeAtributo] = this.resolverConstrutoValorDecorador(valorAtributo);
}
return decoradorResolvido;
}
resolverDecoradorDocumentacao(atributos) {
const retorno = {};
const decoradoresValidosDocumentacao = this.decoradoresValidos['@rest.documentacao'];
for (const [nomeAtributo, valorAtributo] of Object.entries(atributos)) {
retorno[decoradoresValidosDocumentacao[nomeAtributo]] = valorAtributo;
}
return retorno;
}
resolverDecoradorResposta(atributos) {
const decoradoresValidosResposta = this.decoradoresValidos['@rest.resposta'];
if (!('codigo' in atributos) && !('código' in atributos)) {
this.erros.push(new Error(`Decorador .resposta não possui atributo obrigado 'código' ou 'codigo'.`));
return null;
}
const codigo = atributos['codigo'] || atributos['código'];
const retorno = {};
for (const [nomeAtributo, valorAtributo] of Object.entries(atributos)) {
retorno[decoradoresValidosResposta[nomeAtributo]] = valorAtributo;
}
delete retorno['statusCode'];
return [codigo, retorno];
}
resolverDecorador(decorador) {
if (!(decorador.nome in this.decoradoresValidos)) {
this.erros.push(new Error(`Decorador ${decorador.nome} não é válido para um método de um controlador.`));
return null;
}
const atributos = this.resolverAtributosDecorador(decorador);
switch (decorador.nome) {
case '@rest.documentacao':
return this.resolverDecoradorDocumentacao(atributos);
case '@rest.resposta':
return this.resolverDecoradorResposta(atributos);
}
}
/**
* Para cada declaração vinda de um controlador, o que se espera são um
* vetor de expressões, sendo cada expressão contendo pelo menos uma chamada
* a um dos métodos de Liquido. Normalmente, esses métodos são `rotaGet`, `rotaPost`,
* etc.
* @param caminhoControlador O caminho original do arquivo controlador.
* @param declaracoes As declarações implementadas no arquivo controlador.
* @returns O descritivo do controlador, ou seja, as rotas e seus respectivos métodos.
*/
lerControlador(caminhoControlador, declaracoes) {
this.erros = [];
const descritivoControlador = {};
const rotaRelativa = caminhoControlador
.replace(this.diretorioRotas, '')
.replace('inicial.delegua', '')
.replace('.delegua', '');
// console.log('rotaRelativa', rotaRelativa);
for (const declaracao of declaracoes) {
// Os decoradores contêm a documentação adicional para uma rota.
const decoradores = declaracao.decoradores;
// Aqui normalmente teremos uma expressão com uma chamada dentro.
const chamada = declaracao.expressao;
// Tipicamente, a entidade chamada é uma variável com o nome reservado `liquido`.
// o método é um Símbolo.
// A execução e middlewares ficam em argumentos.
const entidadeChamada = chamada.entidadeChamada;
// const argumentos = chamada.argumentos;
// console.log(decoradores, argumentos, entidadeChamada.objeto, entidadeChamada.simbolo);
let descritivoMetodoRota = {};
for (const decorador of decoradores) {
const decoradorResolvido = this.resolverDecorador(decorador);
if (Array.isArray(decoradorResolvido)) {
// Resposta
if (!descritivoMetodoRota.responses) {
descritivoMetodoRota.responses = {};
}
descritivoMetodoRota.responses[decoradorResolvido[0]] = decoradorResolvido[1];
}
else {
descritivoMetodoRota = Object.assign(descritivoMetodoRota, decoradorResolvido);
}
}
const metodoResolvido = entidadeChamada.simbolo.lexema.replace('rota', '');
descritivoControlador[metodoResolvido.toLowerCase()] = descritivoMetodoRota;
}
return [rotaRelativa, descritivoControlador];
}
async documentar() {
const rotasEControladores = await this.encontrarControladores();
const documento = {
openapi: '3.0.0',
servers: [],
info: {
description: this.descricao,
version: this.versao,
title: this.nomeAplicacao,
license: { name: this.nomeLicenca, url: this.urlLicensa }
},
paths: {}
};
for (const rotaEControlador of rotasEControladores) {
documento.paths[rotaEControlador[0]] = rotaEControlador[1];
}
// console.log(documento);
return documento;
}
}
exports.AutoDocumentador = AutoDocumentador;
//# sourceMappingURL=auto-documentador.js.map