UNPKG

liquido

Version:

Conjunto de ferramentas para desenvolvimento de aplicações para a internet 100% em português

191 lines 9.15 kB
"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 @rest.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