05. Validações e tratamento de erros

Verificação dos dados de Entrada

As verificações são feitas no escopo do REST Framework, obedecendo as definições de modelo utilizadas quando Entity ou EntitySet são usados definidos na ação de um controlador. As definições das classe são feitas em arquivos do tipo x-class e x-model e são compartilhadas com o Web Framework, garantindo que ambas tecnologias possam utilizar as mesmas validações.

As verificações a nível de entidades são classificadas em dois tipos:
  • Estruturais
    • As propriedades do JSON de entrada precisam obedecer o esquema das tabelas do banco de dados a partir das definições de modelo. Isso quer dizer que o REST Framework retornará um erro se for identificada uma propriedade no JSON que não está no modelo de dados.
    • A partir das entidades, o tipo de dado deve estar correto e as permissões do usuário devem ser validadas para alterar/inserir/consultar/excluir o registro/campo.
    • Os valores dos campos precisam atender as regras definidas nos campos. 
  • Regras de négocio
    • A entrada deve respeitar as regras especificadas no contexto de campos e de tabela durante a emissão dos listeners para os tipos de evento registrados na classe do dado.

Validações de estrutura dos dados

A sequência de verificações estruturais são realizadas na ordem descrita abaixo:
  1. Uso de Entity ou EntitySet na ação do controlador, o que garante que as validações de modelo serão executadas.
  2. Conversão e tratamento de valores e tipos de acordo com as definições em x-model. 
  3. Validação de permissão de classe do dado com o uso de Entity ou EntitySet na ação do controlador,.
  4. Validação de permissão do campo da classe com o uso de Entity ou EntitySet na ação do controlador,.

Para fazer o tratamento do item 2 (Conversão e tratamento de valores e tipos de acordo com as definições em x-model), são utilizadas algumas propriedades dos campos nas definições de modeloNa tabela abaixo estão descritas as verificações estruturais que são validadas:

Verificação Propriedade do Field  Validações 
 Nome  name
  1. Verifica se as propriedades no JSON de entrada possuem paridade com a interface REST e o modelo de dados.
    1. O nível da declaração tem a ver com a forma de como o modelo do recurso REST foi declarado no controlador com base nas collections/entities.
      1. Exemplo: 
        1. Entrada REST
          POST /bematech/v1/requisicao/
          {
              "requisicao": {
                  "classe": -1899999777
                  "recurso": 1234112,

                  "quantidade": 1,
                  "requisitan": 234543,
                  "observacao": "Para consumo diário"
                  
              }
          }

        2. Definição da entity requisição no controlador
          var requisicoes = new bdo.orm.EntitySet.
              fromClass(-2008877000 /* Requisições */);
          var body = request.body.asJson();
          var requisicao = requisicoes.newEntity();
          requisicoes.persist();
          return requisicao;

        3. Definição da classe -2008877000 /* Requisições */
          var fld = this.field( "RECURSO", "integer" );
          fld.classKey = -2007800000 /* Recursos */;
          fld.dataDictionary = "Campo que contém o recurso que está sendo requisitado. Corresponde ao campo CHAVE da tabela RECURSO.";
          fld.required = true;

          var fld = this.field( "QUANTIDADE", "number" );
          fld.dataDictionary = "Campo que contém a Quantidade que está sendo requisitada.";
          fld.required = true;

          var fld = this.field( "REQUISITAN", "integer" );
          fld.classKey = -2007890000 /* Pessoas */;
          fld.dataDictionary = "Campo que contém o Requisitante do recurso. Corresponde ao campo CHAVE da tabela ENTIDADE.";

          var fld = this.field( "OBSERVACAO", "memo" );
          fld.dataDictionary = "Campo que contém qualquer observação sobre a requisição.";

        4. As propriedades do JSON de entrada devem corresponder as definições do modelo dados da classe utilizada pelo EntitySet ou Entity.
    2. O nome dos campos devem ser em minúsculo na propriedade do JSON. 
 Tipo e Tamanho type

Pode utilizar: size, optionsstringIfTrue
  1. Verificar se o valor declarado no JSON é do mesmo tipo especificado na definição do campo no arquivo de modelo, da seguinte forma:
    1. date: deve estar no padrão ISO-8601
    2. combo: são validados pela propriedade options.
    3. boolean: são validados pela propriedade stringIfTrue.
      1. Se for enviado o valor true (boolean) o conteúdo deve ser transformado para a string definida na propriedade.
      2. Se for enviado como valor uma string, essa só poderá ser a que está definida na propriedade. Se for diferente e não null (que é considerado como false), deve disparar um erro.
      3. Na resposta de uma requisição REST que realiza uma consulta a uma entidade do recurso, os campos desse tipo estarão com o valor true ou false e não com o conteúdo da propriedade.
    4. string: respeita o tamanho máximo (caso exceda, deve disparar um erro).
    5. masterDetail: ao excluir um registro da classe, a propriedade masterDeleteAction  determina o comportamento das detalhes, de forma similar ao que ocorre ao excluir um registro de uma grade mestre. Os registros detalhes poderão ser excluídos, desvinculados ou o registro não poderá ser excluído enquanto houver detalhes. Nesta versão inicial da API RESTful, não é permitido informar os dados de uma detalhe de forma embarcada (campo detalhe preenchido com um array de outros objetos). É retornado um erro caso o desenvolvedor tente consumir a API RESTful dessa forma. O consumidor do serviço deve realizar uma outra requisição para cadastrar os registros detalhes. Exemplo: uma entidade com endereços deve ser gravada em duas requisições, uma para a entidade em si e outra para os endereços. Esse comportamento deve ser alterado nas versões futuras para permitir a gravação em uma única requisição.
    6. grid: é ignorado pelos serviços RESTful.
    7. tree: é ignorado pelos serviços RESTful.
    8. file: é um tipo de dados deprecated utilizado para indicar que um campo contém uma lista de chaves da VFS e é suportado em definições de modelo de dados.
    9. Por uma questão de compatibilidade, todos os tipos que definem máscaras de validação são respeitados pela Framework REST.
 Valor Padrão   defaultValue
  1.  Caso o campo não tenha sido informado no JSON de entrada na requisição POST, o valor será assumido o que está nessa propriedade.
Valor


  1. Se required for true
    1. Não aceita valor null ou undefined
    2. Não aceita '' (string vazia) quando o type for 'string' ou 'memo'
       Precisão  decimalPrecision, minDecimalPrecision, maxDecimalPrecision
      1. Ajusta o valor do dado para a precisão requerida.
      Chave de Classe   classKeylookupType
      1. Se o lookupType for LookupType.RECORD: A chave referenciada no JSON deve ser filha da chave declarada nesta propriedade
      2. Se o lookupType for LookupType.CLASS: A chave referenciada no JSON tem que ser uma classe filha da chave declarada nesta propriedade
      3. Se o lookupType for LookupType.FILE: A chave referenciada no JSON deve ser um registro na VFS, cujo iParent seja filha da chave declarada nesta propriedade
       Conteúdo do Valor Lookup

      Pode utilizar: classKey, lookupType, value
      1. Se o valor recebido não foi um número inteiro, o REST Framework deve executar a estrutura de pesquisa de registro para substituir pela chave.
        1. Se não localizar retorna erro.
        2. Se localizar mais de uma ocorrência retorna erro.
        3. Se localizar apenas uma ocorrência verifica se pertence a classe do classKey.
      2. Quando o campo tiver o classKey preenchido o valor informado passa pelos critérios de validações de possíveis conteúdos válidos para o campo.
      3. A ordem de execução da validação é:
        1. beforeLookupAddResult
        2. lookupAddResult
        3. afterLookupAddResult
      Tipo da Caixa  caseType
      1.  Ajusta o conteúdo de acordo com o valor informado nesta propriedade (texto somente maiúsculo ou minúsculo)
       Formato  format

      Pode utilizar: type
      1.  Realiza o mesmo tratamento para entrada de dados que a grade do WebFramework faz.
      Validação de Faixa  maxmin

      1. Se somente max estiver preenchido, o valor não pode ser maior que ou igual max, o mesmo para min.
      2. Se ambos estiverem preenchidos, o valor terá de ser maior que ou igual a min e menor que ou igual a max.
      Alteração de Chave Negativa  userCanChangeNegativeKey
      1. Se for definida como false e a chave deste registro for negativa, é retornado um erro de validação caso o valor do JSON seja diferente do valor atual.
      Campo de Banco de Dados  isDatabaseField
      1. Apenas campos que possuem o valor true nesta propriedade serão manipulados pelo REST Framework, um erro é disparado caso essa propriedade esteja definida como false. A razão disso é que campos com essa propriedade marcada com false não existe no esquema da tabela do banco.
       Somente Leitura  readOnly
      1.  Se readOnly for true e o valor do campo no JSON é diferente do valor do campo no banco, um erro é lançado.
      Evento de Alteração  beforeChange, afterChange
      1. Executa o evento beforeChange antes da atribuição do valor na estrutura de dados
      2. Executa o evento afterChange depois da atribuição do valor na estrutura de dados
       Ordem  order
      1. Utilizado apenas para organizar os campos em processos que necessitem exibir os campos do modelo de dados para o usuário, como o gerador de relatórios e ferramentas de geração de documentação. Também serve como sugestão para a configuração de visão, portanto a ordem deve ser a de relevância das informações, sendo os campos de menor ordem, os de maior relevância. 
      2. A ordem de atribuição dos campos pelo Framework REST é indeterminada, portanto as regras de negócio não devem depender da ordem dos campos.

      Eventos que podem disparar regras de negócio do modelo

      Abaixo estão os eventos que são disparados a nível de registro durante o fluxo de uma requisição REST que utilize a API de entidades para acesso a dados.
      • Eventos disparados durante uma inserção no banco de dados, utilizando a API de acesso aos dados bdo.orm.EntitySet.insert() bdo.orm.EntitySet.newEntity()
        • beforeInsert 
        • afterInsert 
      • Eventos disparados durante uma remoção no banco de dados, utilizando a API de acesso aos dados bdo.orm.EntitySet.remove() e bdo.orm.Entity.delete()
        • beforeDelete
        • afterDelete
      • Eventos disparado durante uma efetivação de edição da entidade, utilizando a API de acesso aos dados bdo.orm.EntitySet.merge() e bdo.orm.Entity.post()
        • beforePost
        • afterPost
      • Eventos disparados durante uma edição da entidade, utilizando a API de acesso aos dados bdo.orm.Entity.edit() que é utilizado em qualquer manipulação do bdo.orm.EntitySet, como update()insert()newEntity()remove() e merge()
        • beforeEdit
        • afterEdit
      • Eventos disparados durante um cancelamento de edição, utilizando a API de acesso aos dados bdo.orm.Entity.cancel()esse método foi criado por uma questão de compatibilidade com WebFramework e outras APIs do Bematech ERP, mas ele não tem função para o REST Framework.
        • beforeCancel
        • afterCancel
      • Ainda não é disparado pelo REST Framework, mais será disparado quando for necessário obter um valor semântico para um campo lookup, esse evento é obtido a partir da instanciação do modelo da classe em que a definição do registro referenciado está declarado.
        • lookupDisplay
      Eventos que podem disparar regras de negócio dos campos do modelo

      Abaixo estão os eventos que são disparados a nível de campo durante o fluxo de uma requisição REST que utilize a API de entidades para acesso a dados.
      • Eventos disparados durante qualquer manipulação de entidade, utilizando APIs como update(), insert(), newEntity(), remove() e merge() do bdo.orm.EntitySet ou set() do bdo.orm.Entity.
        • beforeLookupAddResult
        • lookupAddResult
        • afterLookupAddResult
        • beforeChange
        • afterChange
      • Disparado durante o insert() do bdo.orm.EntitySet 
        • getOptions
      • Ainda não é disparado pelo REST Framework, mais será disparado quando for necessário obter um valor semântico de um campo lookup, esse evento é obtido a partir do campo que realiza a referencia para a classe que define a tabela do registro referenciado.
        • lookupDisplay
      Compatibilização das classes de dados

      As definições de classes de dados sofreram segregação de responsabilidades para permitir que o Framework REST pudesse consumir as regras de negócio definidas em eventos definidos nas classes e nos campos. Contudo, todas as definições precisam ser revistas para que seja possível manipular os dados. A API de entidades, que realiza acesso aos dados, não pode conter definições de visão durante sua execução pois a requisição retorna mídias estruturais que são compartilhadas com o cliente, que é quem detém o sistema que constrói a interface com o usuário.

      Para que essa segregação fosse possível, foi necessário criar um modo que garante que as definições estejam separadas, então o modo estrito foi criado.

      O modo estrito é habilitado por classe e também garante que a hierarquia de classes antecedentes precisa estar com este modo habilitado. O arquivo *.config dentro da classe é responsável por habilitar o modo estrito apenas com a flag this.strictMode = true;.

      Utilizando definições de modelo personalizadas

      Devida a eventual necessidade de utilizar recursos das classes que não foram compatibilizadas com o modo estrito, foi disponibilizada a opção de informar um modelo de dados customizado para uma classe de dados na API de entidades de dados, por meio da opção modelDef

      No exemplo abaixo é utilizada a classe de dados Engines sem os eventos configurados no modelo dados. Desativar os eventos permite utilizar as regras estruturais definidas em uma classe de dados em modo não estrito, sem os eventos que normalmente estão associados à API da grade de dados do Web Framework.

      var modelDef = classDefManager.getModelDef(ngin.keys.Classes.ENGINES);                              
      modelDef.offAll(); // retira todos os listeners de todos os eventos.     
      var ds = classes.getCachedDataSet(ngin.keys.Classes.ENGINES);            
      var entitySet = new bdo.orm.EntitySet(ngin.keys.Classes.ENGINES, ds, {   
        modelDef: modelDef                                                     
      });  

      É importante observar que desligar os eventos possibilita que manipulações de modelo possam ser realizadas sem que as regras de negócio sejam executadas, portanto essa opção não é recomendada e deve ser utilizada apenas quando a controladora assume totalmente a responsabilidade de realizar todas as validações das regras de negócio. Essa funcionalidade deve ser vista como uma opção de transição enquanto não é concluída a revisão de todo o modelo de dados para o modo estrito e que no futuro poderá ser desativada.

      Por segurança, o sistema impede por padrão o uso das classes JavaScript bdo.orm.EntitySet e do bdo.orm.Entity em classes de dados que não estejam em modo estrito. Para utilizar classes de dados que não estejam em modo estrito, o desenvolvedor deverá criar um arquivo JavaScript (extensão .ijs) no diretório  /Configurações/Inicialização do Roteador HTTP, com o conteúdo abaixo.

      __includeOnce('ufs:/bdo/config/config.js');

      Para maiores detalhes sobre a segregação de responsabilidades consulte o manual de classes.

      Tratamento de Erros

      Durante o atendimento de uma requisição HTTP há dois tipos de exceções no que se refere ao tratamento realizado pelo Framework: 

      • exceções gerenciadas, ou seja, capturadas dentro do contexto de um controlador: como os erros são tratados dentro do controlador, é responsabilidade dele especificar o status HTTP de retorno, o código de erro e uma mensagem compreensiva a fim de ser exibida para o requisitante.
      • exceções lançadas na camada de negócio que não forem tratadas pelos controladores: serão automaticamente encapsuladas em um objeto de erro pelo Framework e por padrão são retornadas com o código de erro 500 (Internal Server Error). Esse comportamento pode ser alterado, conforme observado mais a frente. Para não expor informações potencialmente privilegiadas para os usuários da API, detalhes desse tipo de exceção, como a pilha de chamadas, serão gravadas apenas no log do Engine e identificados no log por um número único chamado de ticket. Esse ticket será enviado na resposta, permitindo que o requisitante da API possa consultar mais detalhes do erro caso tenha acesso ao log do Engine.

      O requisitante identificará que ocorreu um erro durante a execução da API ao receber uma resposta com um código 4xx ou 5xx. O corpo dessa resposta conterá um JSON com mais detalhes do erro, tendo no mínimo as propriedades name e message. Classes de erros mais especializadas, como o DetailedError, poderão adicionar outras propriedades como details, errorCode e solution. Mensagens capturadas pelo Framework que não foram tratadas pelo controlador também terão a propriedade ticket, contendo o identificador a ser pesquisado no log do Engine para obter mais detalhes técnicos sobre o erro.

      Por padrão, respostas HTTP de erro terão o código 500. Esse comportamento pode ser alterado explicitamente pelo controlador, ao criar uma resposta com outro código, ou implicitamente pelos meios abaixo:

      1. Se a propriedade name do objeto que estiver no conteúdo do resultado for um nome de um estado HTTP acrescido do sufixo "Error", esse estado será o retornado. Por exemplo, um erro com nome "NotFoundError" irá produzir uma resposta com código de estado 404. 
      Exemplo do corpo de uma resposta com erro:








      Códigos de estado HTTP

      Os códigos de erro de uma API HTTP dividem-se em dois grupos: os erros 4xx, usados para indicar que foi uma falha na requisição do cliente, e os erros 5xx, que indicam uma falha no servidor. Quando um cliente recebe como retorno um erro 5xx, ele poderá tentar enviar novamente a requisição sem alterações, pois a causa do problema é um erro no servidor que, a princípio, não tem relação com a requisição enviada. Já um erro 4xx indica para o cliente que a requisição possui um erro que deve ser corrigido antes de uma nova tentativa.

      Os códigos de estados que representam erros mais utilizados são:
      • 400 - Bad Request
      • 401 - Unauthorized
      • 404 - Not Found
      • 405 - Method Not Allowed - Usado para indicar que o método HTTP não é suportado para este recurso
      • 500 - Internal Server Error - Erro do servidor não tratado pelo objeto controlador

      Rastreamento de erros através de Logs

      O objeto controlador deverá utilizar a API Logger, fazendo uso das prioridades de forma adequada, conforme tabela abaixo:

      PrioridadeUtilização 
       info Informações que identifiquem operações importantes do sistema, como aprovar pedido, baixar pedido, adicionar usuário, entre outras.
       warn Um aviso que identifique que algo não esperado ocorreu, mas será possível continuar. Uma outra aplicação é quando o comportamento identificado poderá significar uma falha futura. 
       error  Um erro que fez com que a requisição tivesse que ser abortada. Não deve haver processamento após um log de erro.


      Conclusão

      Por fim, com os conceitos mostrados neste manual constata-se que o Bematech ERP é capaz de implementar Recursos REST provendo acesso de outros sistemas que utilizem o consumo de APIs HTTP ou similares, bem como de frameworks client-side que obedecem as especificações do conceito REST.

      Foram visitados conceitos de Recursos, Rotas, APIs de acesso ao modelo de dados, Transformações, Validações do Bematech ERP e Tratamentos de erros .

      Sugerimos fortemente que sempre que houver dúvidas a cerca dos comportamentos da API REST no Bematech ERP, que consulte este Manual, para que depois utilize ou consulte manuais de domínio publico a respeito do conceito da API  REST.

      Mesmo assim, se mesmo após a leitura deste manual, houverem dúvidas de desenvolvimento, esperamos que o desenvolvedor que fizer uso da API REST nos contacte a fim de aprimorar o conteúdo deste Manual.