Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 51 additions & 4 deletions fontes/erros.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,57 @@
export const listaDeErros = [
/**
* Mapeamento de erros de execução para códigos de Liquido.
*
* `palavraChave` é comparada (em minúsculas) com a mensagem original do erro.
* `mensagem`, quando presente, é a explicação amigável em português exibida
* ao desenvolvedor no console e na resposta HTTP de erro; a mensagem original
* é preservada como detalhe técnico.
*
* `LIQ99999` continua sendo o código de contingência para erros ainda não
* mapeados. Ao encontrar um LIQ99999 recorrente, o caminho é adicionar aqui
* uma nova entrada com código próprio e mensagem em português.
*/
export interface ErroMapeadoInterface {
codigo: string;
palavraChave: string;
mensagem?: string;
}

export const listaDeErros: ErroMapeadoInterface[] = [
{ codigo: 'LIQ00001', palavraChave: 'não encontrado' },
{ codigo: 'LIQ00002', palavraChave: 'não foi definida' },
{ codigo: 'LIQ00003', palavraChave: 'inesperado' },
// Erros vindos do motor JavaScript, normalmente por valores nulos ou
// indefinidos chegando onde não deviam. A mensagem original (em inglês)
// é traduzida para orientar quem está começando.
{
codigo: 'LIQ20001',
palavraChave: 'cannot read propert',
mensagem: 'Tentativa de acessar uma propriedade de um valor nulo ou indefinido.'
},
{
codigo: 'LIQ20002',
palavraChave: 'is not a function',
mensagem: 'Tentativa de chamar como função algo que não é uma função.'
},
{
codigo: 'LIQ20003',
palavraChave: 'is not defined',
mensagem: 'Uso de um nome que não foi definido.'
},
// Erros do interpretador Delégua/Pituguês que já chegam em português,
// mas merecem código próprio para diagnóstico.
{ codigo: 'LIQ20004', palavraChave: 'só pode chamar função ou classe' },
// Erro genérico de LinConEs: o driver de banco de dados (SQLite, MySQL, PostgreSQL etc.)
// rejeitou o comando (violação de restrição, sintaxe SQL inválida, etc.) e a mensagem
// de erro nativa do driver chegou sem tratamento específico até aqui.
{ codigo: 'LIQ10000', palavraChave: 'sqlite_' },
{ codigo: 'LIQ10000', palavraChave: 'constraint failed' },
];
{
codigo: 'LIQ10000',
palavraChave: 'sqlite_',
mensagem: 'O banco de dados rejeitou o comando executado.'
},
{
codigo: 'LIQ10000',
palavraChave: 'constraint failed',
mensagem: 'O banco de dados rejeitou o comando por violação de restrição.'
}
];
2 changes: 1 addition & 1 deletion fontes/interfaces/interface-liquido.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ export interface LiquidoInterface {
prepararRequisicao(requisicao: any, nomeFuncao: string, funcaoConstruto: FuncaoConstruto): void;

chamarInterpretador(nomeFuncao: string): Promise<RetornoInterpretadorInterface>;
adicionarRota(metodoRoteador: string, caminhoRota: string, argumentos: ConstrutoInterface[]): void;
adicionarRota(metodoRoteador: string, caminhoRota: string, argumentos: ConstrutoInterface[], arquivoFonte?: string): void;
}
81 changes: 61 additions & 20 deletions fontes/liquido.ts
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,8 @@
arquivo,
linguagemSelecionada
),
argumentosResolvidos
argumentosResolvidos,
arquivo
);
}
}
Expand Down Expand Up @@ -636,48 +637,69 @@
}
}

private classificarErro(erro: ErroInterpretadorInterface): string {
private classificarErro(erro: ErroInterpretadorInterface): { codigo: string; mensagemAmigavel?: string } {
const textoErro = (erro.mensagem || erro.erroInterno?.message || '').toLowerCase();

for (const item of listaDeErros) {
if (textoErro.includes(item.palavraChave)) {
return item.codigo;
return { codigo: item.codigo, mensagemAmigavel: item.mensagem };
}
}

return 'LIQ99999';
return { codigo: 'LIQ99999' };
}

private logicaComumErrosInterpretacao(
retornoInterpretador: RetornoInterpretadorInterface
retornoInterpretador: RetornoInterpretadorInterface,
arquivoFonte?: string
): {
corpoRetorno?: any;
statusHttp?: number;
redirecionamento?: string;
tipoConteudo?: string;
} {
const listaErros: string[] = [];

for (const erro of retornoInterpretador.erros) {
const tipoErro = this.classificarErro(erro);
const { codigo, mensagemAmigavel } = this.classificarErro(erro);
const localizacao = [
arquivoFonte ? `Arquivo: ${arquivoFonte}` : undefined,
erro.linha !== undefined && erro.linha > 0 ? `Linha: ${erro.linha}` : undefined
]
.filter(Boolean)
.join(', ');

if (erro.erroInterno) {
const erroInternoTipado: {
message: string;
pilha: string;
} = erro.erroInterno;

console.error(`[Liquido] ${tipoErro}: ${erroInternoTipado.message}`);
const mensagemPrincipal = mensagemAmigavel || erroInternoTipado.message;
const detalheTecnico = mensagemAmigavel
? ` (detalhe técnico: ${erroInternoTipado.message})`
: '';

console.error(
`[Liquido] ${codigo}${localizacao ? ` [${localizacao}]` : ''}: ${mensagemPrincipal}${detalheTecnico}`
);
listaErros.push(
`
Código: ${tipoErro}\n
Mensagem: ${erroInternoTipado.message}\n
Código: ${codigo}\n
${localizacao ? `Onde: ${localizacao}\n` : ''}
Mensagem: ${mensagemPrincipal}\n
${mensagemAmigavel ? `Detalhe técnico: ${erroInternoTipado.message}\n` : ''}
Pilha: ${erroInternoTipado.pilha}
`
);
} else {
console.error(`[Liquido] ${tipoErro} - [Linha ${erro.linha}]: ${erro.mensagem}`);
const mensagemPrincipal = mensagemAmigavel || erro.mensagem;
const detalheTecnico = mensagemAmigavel ? ` (detalhe técnico: ${erro.mensagem})` : '';

Check warning on line 697 in fontes/liquido.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
const prefixoLocalizacao = localizacao ? ` [${localizacao}]` : ` - [Linha ${erro.linha}]`;

Check warning on line 698 in fontes/liquido.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

console.error(`[Liquido] ${codigo}${prefixoLocalizacao}: ${mensagemPrincipal}${detalheTecnico}`);
listaErros.push(
`${tipoErro} - [Linha ${erro.linha}]: ${erro.mensagem}`
`${codigo}${prefixoLocalizacao}: ${mensagemPrincipal}${detalheTecnico}`
);
}
}
Expand Down Expand Up @@ -793,14 +815,21 @@
`;
}

if (this.centroConfiguracoes.liquido.arquetipo === 'rest') {
if (this.centroConfiguracoes.liquido.arquetipo === 'mvc') {
return { corpoRetorno: corpoFinal, statusHttp: 500, tipoConteudo: 'HTML' };
}

// Arquétipo 'rest' e qualquer outro valor: resposta estruturada em JSON.
// Sem `tipoConteudo`, o corpo (um objeto) era coagido para texto e o
// cliente recebia o literal "[object Object]" com text/plain.
if (corpoFinal === undefined) {
corpoFinal = {
mensagem: 'Ocorreu um erro interno na aplicação.',
detalhes: listaErros
};
}

return { corpoRetorno: corpoFinal, statusHttp: 500 };
return { corpoRetorno: corpoFinal, statusHttp: 500, tipoConteudo: 'JSON' };
}

/**
Expand Down Expand Up @@ -900,10 +929,11 @@

private async logicaComumResultadoInterpretador(
caminhoRota: string,
retornoInterpretador: RetornoInterpretadorInterface
retornoInterpretador: RetornoInterpretadorInterface,
arquivoFonte?: string
): Promise<CorpoResposta> {
if (retornoInterpretador.erros.length > 0) {
return this.logicaComumErrosInterpretacao(retornoInterpretador);
return this.logicaComumErrosInterpretacao(retornoInterpretador, arquivoFonte);

Check warning on line 936 in fontes/liquido.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
}

Check warning on line 937 in fontes/liquido.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

let objetoResposta: ObjetoDeleguaClasse | null = null;
Expand Down Expand Up @@ -959,17 +989,20 @@
* @param caminhoRota O caminho da rota.
* @param funcao A função a ser executada.
* @param nomeFuncao O nome único para identificar a função no interpretador.
* @param arquivoFonte O caminho do arquivo fonte que define a rota, usado
* para apontar a origem em mensagens de erro.
* @returns O corpo e status da resposta, se houver.
*/
private async executarFuncaoRota(
req: any,
caminhoRota: string,
funcao: FuncaoConstruto,
nomeFuncao: string
nomeFuncao: string,
arquivoFonte?: string
): Promise<CorpoResposta> {
await this.prepararRequisicao(req, nomeFuncao, funcao);
const retornoInterpretador = await this.chamarInterpretador(nomeFuncao);
return await this.logicaComumResultadoInterpretador(caminhoRota, retornoInterpretador);
return await this.logicaComumResultadoInterpretador(caminhoRota, retornoInterpretador, arquivoFonte);
}

/**
Expand All @@ -979,17 +1012,25 @@
* @param argumentos Todas as funções em Delégua que devem ser executadas
* para a resolução da rota. O último argumento é o handler final,
* todos os anteriores são middlewares executados em sequência.
* @param arquivoFonte O caminho do arquivo fonte que define a rota, usado
* para apontar a origem em mensagens de erro.
*/
async adicionarRota(
metodoRoteador: string,
caminhoRota: string,
argumentos: FuncaoConstruto[]
argumentos: FuncaoConstruto[],
arquivoFonte?: string
): Promise<void> {
if (argumentos.length === 0) {
console.error(`Rota ${caminhoRota} não possui nenhuma função definida.`);
return;
}

// Caminho relativo à raiz do projeto, mais legível em mensagens de erro.
const arquivoFonteRelativo = arquivoFonte
? caminho.relative(process.cwd(), arquivoFonte)
: undefined;

// Separa middlewares (todos menos o último) e handler (último argumento)
const middlewares = argumentos.slice(0, -1);
const handler = argumentos[argumentos.length - 1];
Expand Down Expand Up @@ -1022,7 +1063,7 @@
const middleware = middlewares[i];
const nomeMiddleware = `middleware${i}_${metodoRoteador}`;

corpoEStatus = await this.executarFuncaoRota(req, caminhoRota, middleware, nomeMiddleware);
corpoEStatus = await this.executarFuncaoRota(req, caminhoRota, middleware, nomeMiddleware, arquivoFonteRelativo);

// Se o middleware enviou uma resposta, para a execução
if (this.respostaFoiDefinida(corpoEStatus)) {
Expand All @@ -1032,7 +1073,7 @@

// Se nenhum middleware enviou resposta, executa o handler final
if (!this.respostaFoiDefinida(corpoEStatus)) {
corpoEStatus = await this.executarFuncaoRota(req, caminhoRota, handler, `handler_${metodoRoteador}`);
corpoEStatus = await this.executarFuncaoRota(req, caminhoRota, handler, `handler_${metodoRoteador}`, arquivoFonteRelativo);
}

// Envia a resposta
Expand Down
2 changes: 1 addition & 1 deletion testes/cadeia-middlewares.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ describe('Cadeia de middlewares — adicionarRota', () => {

expect(executarFuncaoRotaSpy).toHaveBeenCalledTimes(1);
expect(executarFuncaoRotaSpy).toHaveBeenCalledWith(
mockReq, '/teste', handler, 'handler_rotaGet'
mockReq, '/teste', handler, 'handler_rotaGet', undefined
);
});

Expand Down
Loading
Loading