Om-yum-yum e validação de dados

Olá pessoal! Vamos falar um pouco sobre validação de dados. O que é complicado e por que deveria ser necessário, digamos, em um projeto escrito em texto datilografado? O texto datilografado controla muito bem tudo, resta verificar a máxima entrada do usuário. Ou seja, lançar uma dúzia de regulares no projeto e tudo, ao que parece, pode fechar o tópico, mas ... Nem sempre, mas no caso da Web, quase nunca, todo o projeto está em uma única base de código e usa os mesmos tipos. Na junção de tais bases de código, surgem situações em que a espera não corresponde à realidade e aqui o texto datilografado não é mais um assistente. Alguns exemplos:


  • A parte do cliente do aplicativo recebe dados da API e os valida. Em primeiro lugar, a API pode repentinamente e algumas vezes sem aviso prévio, e em segundo lugar, os "funcionários do servidor" às vezes não sabem o que sua API pode fazer, por exemplo, em algum campo, em vez de uma matriz garantida, mesmo que vazia, a lua cheia pode ser null . Ao descrever os dados no cliente, os programadores parecem determinar com o que o cliente sabe trabalhar e se algo der errado, é muito mais agradável ver imediatamente uma mensagem no console sobre a origem do problema, em vez de detectar um bug incompreensível já na página de visualização. (e é bom se for notado imediatamente). Também agora já existem soluções ( 1 , 2 ) que permitem transferir tipos de servidor para cliente. Ainda não tentei fazê-lo, mas é bem possível que este seja o futuro.
  • A situação inversa é quando o servidor verifica os parâmetros enviados para interromper imediatamente o processamento da solicitação, se eles não atenderem aos esperados. Eu acho que não há necessidade de detalhes sobre por que isso é importante.
  • A validação dos dados antes de armazená-los no banco de dados também não será supérflua. Por exemplo, você pode ver como está organizado em uma das minhas bicicletas: MaraquiaORM # Validation .

Eu acho que os exemplos são bastante convincentes e agora não há sensação de que você possa fazer com regulares simples, porque não se trata apenas da entrada do usuário, mas da validação de complexos, geralmente aninhados em vários níveis de dados. Uma biblioteca especial já é necessária aqui. E é claro que existem! Acontece que nos últimos 10 anos, cada vez que inicio um novo projeto, tento usar outra biblioteca desse tipo, ajustando-a às minhas necessidades. E sempre que algo dá errado, o que às vezes leva à substituição do sujeito do teste no meio do desenvolvimento ativo. Não falarei sobre todas as opções que estudei, apenas direi sobre as testadas no projeto atual.


verificação de tipo


Github


Biblioteca pequena e bastante conveniente. O circuito é descrito como uma string. Usando cadeias de linhas múltiplas, você pode descrever estruturas bastante complexas:


 `{ ID: String, creator: { fname: String | Null, mname: String | Null, lname: String | Null, email: [String] } | Undefined, sender: Maybe { name: String, email: String }, type: Number, subject: String, ... }` 

Existem desvantagens bastante sérias:


  • O IDE não ajuda em nada com um conjunto de esquemas, o que foi especialmente irritante ao mudar para o texto datilografado.
  • Mensagens de erro quase inúteis. Não uso essa biblioteca há mais de um ano e talvez algo tenha mudado (a julgar pelo código, não). As mensagens estavam no estilo de "Sequência esperada, recebida nula". Agora imagine que você tenha uma matriz de peças para 200 objetos, cada um com campos com strings e, em apenas um objeto, um dos campos foi quebrado. Como encontrar este campo? Exibir todos os 200 itens? Sofri tantas vezes e isso realmente quebrou minha vida, arruinou a impressão da biblioteca. Normalmente, não quero saber o que era esperado e recebido lá, mas quero abrir o esquema de dados e encontrar o campo necessário nele e o mesmo nos próprios dados. Em outras palavras, na mensagem de erro, é essencial ter o caminho da chave no lugar certo nos dados / esquema, e o que era esperado e chegado lá pode ser omitido.
  • Um pouco, é claro, mas o recuo no exemplo acima não desaparecerá quando o código for compactado.

Joi


Github
Versão do navegador: joi-browser


Provavelmente a biblioteca mais famosa sobre esse assunto com vários recursos e uma API sem fim. No começo, eu o usei no servidor e ele se mostrou perfeitamente. Em algum momento, decidi substituí-lo pela type-check no cliente. Naquela época, eu quase não controlava o tamanho do pacote, simplesmente não havia problemas com isso. Mas, ao longo do ano, cresceu rapidamente e, na Internet móvel, o primeiro download de aplicativos não foi nada confortável. Foi decidido organizar um carregamento lento de componentes. O relatório do analisador de pacotes da Webpack mostrou um monte de gigantes no pacote e todos foram facilmente para os pedaços criados pelo pacote da Web. Todos, exceto Joi . Muitos componentes se comunicam com o servidor e todas as respostas do servidor são validadas, ou seja, colocar o Joi em algum tipo de pedaço não faz sentido, ele simplesmente sempre carrega logo após o principal. Em algum momento, o pacote principal ficou assim: tyts . É claro que surgiu um desejo duradouro de fazer algo a respeito. Eu queria a mesma biblioteca conveniente, mas muito menos.


Sim


Github


No leia-me, eles prometem o mesmo Joi , mas em tamanho adequado para o frontend. De fato, é apenas duas vezes menor, ou seja, Yup ainda era a maior biblioteca do pacote principal. Além disso, surgiram desvantagens adicionais:


  • A biblioteca padrão ignora todos os undefined . Escrever constantemente .required() não .required() muito agradável, e eu gosto mais quando inicialmente tudo é impossível e onde é permitido. Joi tem uma opção presence: 'required' para configurar esse comportamento. Criei uma solicitação com o número infernal 666 , mas até agora os autores estão em silêncio.
  • Não há como verificar os valores do objeto, permitindo todas as chaves. Joi usa object.pattern para isso, com o primeiro argumento aceitando qualquer string. É provável que ainda seja possível sair de algum modo, e os autores podem corrigir o primeiro sinal de menos, mas, considerando o tamanho, eu não queria esperar ou editar algo sozinho.

Ow


Github


O próximo candidato finalmente se mostrou muito pequeno, além de não fazê-lo escrever constantemente () onde você pode ficar sem ele. Por exemplo, você pode escrever um validador que permita uma sequência ou undefined seguinte maneira:


 let optionalStringValidator = ow.optional.string; ow(optionalStringValidator, '1'); // Ok ow(optionalStringValidator, undefined); // Ok 

Ótimo! E quanto a null ? Entregando toda a documentação, encontrei o seguinte método:


 ow.any(ow.optional.string, ow.null); 

Oh horror! Quando tentei reescrever parte da validação no projeto, quase quebrei os dedos enquanto digitava isso. ow.nullable problema ao adicionar ow.nullable , que foi enviado aqui . Em resumo, eles dizem que o null não é necessário. Os argumentos apresentados também são bastante adequados, dada a primeira linha em seu leia-me:


Validação de argumento de função para humanos

Ou seja, esta biblioteca é para validar os valores que vêm como argumentos para a função. Aparentemente, eles realmente não contavam com grandes estruturas aninhadas vindas do servidor.
Estudos adicionais e tentativas de uso revelaram vários outros recursos que, novamente, foram bem explicados pela mesma linha no leia-me, mas não me agradaram muito. Esta é realmente uma biblioteca muito boa, apenas para alguns outros propósitos.




Por aqui, eu já estava completamente cansada de desistir e decidi escrever minha biblioteca com blackjack e virgens. Sim, sim, volto para você com a próxima bicicleta :). Conheça:


Omyumyum


Alguns exemplos:


 import om from 'omyumyum'; const isOptionalNumber = om.number.or.undefined; isOptionalNumber('1'); // => false isOptionalNumber(1); // => true isOptionalNumber(undefined); // => true 

.or pode ser usado quantas vezes quiser, aumentando as opções possíveis:


 om.number.or.string.or.null.or.undefined; 

Nesse caso, é constantemente gerada uma função quase comum que aceita qualquer argumento e retorna um boolean .
Se você deseja que a função gere um erro se o teste falhar:


 om(om.number, '1'); //  TypeError 

Ou com currying:


 const isNumberOrThrow = om(om.number); isNumberOrThrow('1') //  TypeError 

A função resultante não é muito comum, pois possui métodos adicionais. .or.or mostrado, parte dos métodos dependerá do tipo selecionado (consulte API ); por exemplo, uma string pode ser aprimorada com uma expressão regular:


 const isNonEmptyString = om.string.pattern(/\S/); // == `om.string.nonEmpty` isNonEmptyString(' '); // => false isNonEmptyString('1'); // => true 

E para o objeto, você pode especificar sua forma:


 const isUserData = om.object.shape({ name: om.string, age: om.number.or.vacuum // `.or.vacuum` == `.or.null.or.undefined` }); isUserData({}); // => false isUserData({ age: 20 }) // => false isUserData({ name: '' }); // => true isUserData({ name: '', age: null }); // => true isUserData({ name: '', age: 20 }); // => true 

O caminho-chave prometido para o local do problema:


 om(om.array.of(om.object.shape({ name: om.string })), [{ name: '' }, { name: null }]); //  TypeError('Type mismatch at "[1].name"') 

Se os recursos .custom(validator: (value: any) => boolean) não forem suficientes, você sempre poderá usar .custom(validator: (value: any) => boolean) :


 const isEmailOrPhone = om.custom(require('is-email')).or.custom(require('is-phone')); isEmailOrPhone('test@test.test'); // => true 

Em estoque também é o esperado .and usado para combinar e melhorar os tipos:


 const isNonZeroString = om.string.and.custom(str => str.length > 0); // == `om.string.nonZero` isNonZeroString(''); // => false isNonZeroString('1'); // => true 

.and tem precedência sobre .or , mas como .custom() aceita o validador exatamente da mesma forma que a criada pela biblioteca, isso pode ser contornado:


 //      `age`,   `birthday` om.object.shape({ name: om.string }).and.custom( om.object.shape({ age: om.number }) .or.object.shape({ birthday: om.date })] ); 

Você pode continuar aprimorando os validadores criados anteriormente. Os antigos não estragam nada. Vamos tentar melhorar o isUserData criado anteriormente:


 const isImprovedUserData = isUserData.and.object.shape({ friends: om.array.of(isUserData).or.vacuum }); isImprovedUserData({ name: '', age: 20, friends: [{ name: '', age: 18 }] }); // => true 

Bem, ficou .not :


 const isNotVacuum = om.not.null.and.not.undefined; // == `om.not.vacuum` isNotVacuum(1); // => true isNotVacuum(null); // => false isNotVacuum(undefined); // => false 

Outros métodos disponíveis podem ser encontrados na API da biblioteca.


Vantagens da biblioteca:


  • Sintaxe lacônica com .or , .and , .not e um mínimo de colchetes. Em combinação com o texto datilografado de preenchimento automático, o conjunto se transforma em um prazer.
  • Peso minúsculo, mesmo comparado a Ow (quase 10 vezes menos (minify + gzip)), e comparado a Joi biblioteca é como uma pena ao lado de uma montanha.
  • Nice name :)

Contras da biblioteca:


  • Menos tipos e seus modificadores. E é improvável que haja muito mais. Todos os três cenários de uso fornecidos no início do artigo (algo sobre junções e bases de código) assumem a transmissão de dados de texto sem formatação; na maioria dos casos, isso é JSON. Ou seja, na minha opinião, essa biblioteca deve suportar os tipos possíveis no JSON, além de tipos undefined e alguns usados ​​com frequência. O mesmo Ow por algum motivo, está repleto de suporte para todos os tipos de matrizes digitadas e outras bobagens. Eu acho que isso é supérfluo.
  • Não é possível converter dados como Joi . Eu acho que Joi é muito ruim nisso. Pelo menos, não tenho recursos suficientes e, se necessário, faço transformações com ferramentas completamente diferentes. Talvez essa seja uma outra direção de desenvolvimento para omyumyum .

Isso é tudo! Se você gostou do artigo, inscreva-se no canal e boa sorte)).

Source: https://habr.com/ru/post/pt473026/


All Articles