Om-yum-yum and data validation

Hello! Let's talk a little about data validation. What is complicated and why should it be needed, say, in a project written in typescript? Typescript controls pretty well everything, it remains to check the user input as much as possible. That is, to throw a dozen regulars into the project and it would seem that you can close the topic, but ... Far from always, and in the case of the web, almost never, the whole project is in a single code base and uses the same types. At the junction of such code bases, situations arise when the wait does not correspond to reality and here typescript is no longer an assistant. A few examples:









I think the examples are quite convincing and now there is no feeling that you can do with simple regulars, because it's not just about user input, but about the validation of complex, usually nested on several levels of data. A special library is already needed here. And of course there are! It just so happens that over the past 10 years, each time starting a new project, I try to use another such library in it, adjusting it to my needs. And every time something goes wrong, which sometimes leads to the replacement of the test subject in the middle of active development. I will not talk about all the options I studied, I’ll only say about those tested in the current project.







type-check



Github







Small and quite convenient library. The circuit is described as a string. Using multi-line strings, you can describe fairly complex structures:







 `{ 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, ... }`
      
      





There are quite serious drawbacks:









Joi



Github

Browser Version: joi-browser







Probably the most famous library on this subject with a bunch of features and an endless API. At first I used it on the server and it showed itself perfectly. At some point, I decided to replace it with type-check



on the client. At that time, I almost did not control the size of the bundle, there were simply no problems with this. But over the year it has grown rapidly and on the mobile Internet the first application download became completely uncomfortable. It was decided to organize a lazy component loading. The webpack-bundle-analyzer report showed a bunch of giants in the bundle and they all went easily to the chunks created by the webpack. Everyone except Joi



. Many components communicate with the server and all server responses are validated, that is, putting Joi



into some kind of chunk does not make sense, it will simply always load right after the main one. At some point, the main bundle looked like this: tyts . Of course, a lasting desire arose to do something about it. I wanted the same convenient library, but much less.







Yup



Github







In the readme they promise about the same Joi



, but in size suitable for the frontend. In fact, it is only about two times smaller, that is, Yup



was still the largest library in the main bundle. In addition, additional disadvantages appeared:









Ow



Github







The next applicant finally turned out to be really small, plus he didn’t make him constantly write ()



where you can do without it. For example, you can write a validator that allows a string or undefined



as follows:







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





Great! What about null



? Turning all the documentation over, I found the following method:







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





Oh God! When I tried to rewrite part of the validation in the project, I almost broke my fingers while typing this. I ow.nullable



issue on adding ow.nullable



, which was sent here . In short, they say that null



is not needed at all. The arguments given there are also quite adequate given the first line in their readme:







Function argument validation for humans

That is, this library is for validating the values ​​that come as arguments to the function. Apparently, they didn’t really count on huge nested structures coming from the server.

Further study and attempts to use revealed several more features that, again, were well explained by the same line in the readme, but did not suit me very well. This is actually a pretty good library, it is just for a few other purposes.










Around here, I was completely tired of giving up and decided to write my own library with blackjack and virgins. Yes, yes, I'm back to you with the next bike :). Meet:







Omyumyum



A few examples:







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





.or



can be used as many times as you want by increasing the feasible options:







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





In this case, an almost ordinary function is constantly generated that takes any argument and returns a boolean



.

If you want the function to throw an error if the test fails:







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





Or with currying:







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





The resulting function is not quite ordinary, as it has additional methods. .or



already shown, part of the methods will depend on the selected type (see API ), for example, a string can be enhanced with a regular expression:







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





And for the object, you can specify its shape:







 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
      
      





The promised keypath to the problem spot:







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





If the built-in features are not enough, you can always use .custom(validator: (value: any) => boolean)



:







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





In stock is also the expected .and



used to combine and improve types:







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





.and



takes precedence over .or



, but since .custom()



accepts the validator of exactly the same form as created by the library, this can be circumvented:







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





You can continue to improve previously created validators. The old ones do not spoil at all. Let's try to improve the isUserData



created earlier:







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





Well, stayed .not



:







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





Other available methods can be found in the library API .







Advantages of the library:





Cons of the library:





Everything! If you liked the article, like it, subscribe to the channel and good luck)).








All Articles