Validol Quick start ===================== Let's start with an example of what `Validol `_ can do. Here I'll describe features that will be enough for you in most of the cases. Example -------- Consider the following JSON schema: .. code-block:: JSON { "guest":{ "phone":"+44123456789", "name":"Vasily Belov", "email":"vasya@belov.com" }, "bag":{ "items":[ { "id":888 }, { "id":777 } ], "discount":{ "promo_code":"VASYA1988" }, "served_for":3 }, "delivery":{ "type_id":20, "where":{ "restaurant_id":1 }, "when":{ "datetime":"2019-10-23T08:33:11.798400+00:00" }, "from_where":{ "restaurant_id":1 } }, "payment":{ "type_id":30 }, "source":20 } This is a request that clearly has something to do with food order delivery. There must be plenty of things to validate before an order will be registered. First, take a really deep breath and have a glance at the whole picture. The whole validation from a bird's view ------------------------------------------ .. code-block:: java :linenos: new FastFail<>( new WellFormedJson( new Unnamed<>(Either.right(new Present<>(this.jsonRequestString))) ), requestJsonObject -> new UnnamedBlocOfNameds<>( List.of( new FastFail<>( new IsJsonObject( new Required( new IndexedValue("guest", requestJsonObject) ) ), guestJsonObject -> new NamedBlocOfNameds<>( "guest", List.of( new AsString( new Required( new IndexedValue("email", guestJsonObject) ) ), new AsString( new Required( new IndexedValue("name", guestJsonObject) ) ) ), Guest.class ) ), new FastFail<>( new Required( new IndexedValue("items", requestJsonObject) ), itemsJsonElement -> new NamedBlocOfUnnameds<>( "items", itemsJsonElement, item -> new UnnamedBlocOfNameds<>( List.of( new AsInteger( new Required( new IndexedValue("id", item) ) ) ), Item.class ), Items.class ) ), new FastFail<>( new Required( new IndexedValue("delivery", requestJsonObject) ), deliveryJsonElement -> new SwitchTrue<>( "delivery", List.of( new Specific<>( // Here goes the condition whether this order should be delivered by courier or picked up. // It's omitted for brevity. () -> true, new UnnamedBlocOfNameds<>( List.of( new FastFail<>( new IndexedValue("where", deliveryJsonElement), whereJsonElement -> new NamedBlocOfNameds<>( "where", List.of( new AsString( new Required( new IndexedValue("street", whereJsonElement) ) ), new AsInteger( new Required( new IndexedValue("building", whereJsonElement) ) ) ), Where.class ) ), new FastFail<>( new IndexedValue("when", deliveryJsonElement), whenJsonElement -> new NamedBlocOfNameds<>( "when", List.of( new AsDate( new AsString( new Required( new IndexedValue("date", whenJsonElement) ) ), new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") ) ), DefaultWhen.class ) ) ), CourierDelivery.class ) ) ) ) ), new AsInteger( new Required( new IndexedValue("source", requestJsonObject) ) ) ), OrderRegistrationRequestData.class ) ) .result() Now breath out. It's not that scary. Line-by-line analysis ----------------------- Fancy some line-by-line analysis? Here it goes: | ``Lines 1-4``: check whether the input request data represents well-formed json. Otherwise, fail fast and return a corresponding error. | ``Line 5``: if json is well-formed , a closure is invoked, and json data is passed. | ``Line 6``: json structure is validated. Higher-level structure is an unnamed block of named entities. | It corresponds to higher level json request with the following keys ``guest``, ``bag``, ``delivery``, ``payment``, and ``source``. | ``Line 7``: A list of original elements, the ones corresponding to respective higher-level keys. | ``Line 11``: The first one is ``guest``; | ``Line 10``: and it's required. | ``Line 9``: It must be a json object -- neither a primitive, nor an array. | ``Line 8``: If any of previous checks fail (either there is no such key ``guest`` or it's not a json object), then do it fast, don't dive any deeper. Move on to the next element. | ``Line 14``: If all previous conditions are satisfied, closure is invoked. The single argument is a json object corresponding to ``guest`` element. | ``Lines 15-16``: A block named ``guest`` consists of other named entities. | ``Line 20``: The first one is an email; | ``Line 19``: it's required; | ``Line 18``: and should be represented as string. | ``Line 25``: The second one is an name; | ``Line 24``: it's required as well; | ``Line 23``: and should be represented as string either. | ``Line 29``: If all original elements in current named block are valid, then an object of class ``Guest`` will be returned. | The corresponding class has two arguments: `email`, which must be String, and `name`, which must be String as well. | ``Lines 32-35``: Already familiar fast-failing block named ``items``. Mind the absense of structure validation. | If ``items`` element will be a string, subsequent validation breaks not-so-gracefully. | ``Line 36``: If ``items`` block is present, closure is invoked. The single argument represents ``items`` json element. | ``Line 37``: Here the description of ``items`` element starts. For now we can see that it's a named block | which consists of an arbitrary number of unnamed elements. | ``Line 38``: The block mentioned above is named ``items``. | ``Line 39``: As the second argument, its corresponding json element is passed, which should be a json array. It's a good idea | to reinforce this knowledge | ``Line 40``: Since I don't know the exact structure, I need a mapping function, taking a json array and returning an array of concrete objects. | ``Line 41``: Each array element corresponds to an unnamed block. | ``Line 42``: The first argument is a list of array values. | ``Line 45``: In this particular case, there is a single ``id`` element. | ``Line 44``: It's required, | ``Line 43``: ... and should be represented as integer. | ``Line 49``: If everything's ok, an object of the ``Item`` class is created. It has a single argument: `id`, and it must be integer. | ``Line 51``: If validation for every block is correct, then the ``Items`` object is created, consisting of ``Item`` objects list passed as constructor argument. | ``Lines 54-57``: On to the next element, ``delivery``. First goes usual ``FastFail`` block. | ``Line 58``: Closure is invoked. | ``Line 59``: The structure of ``delivery`` block depends on what type of delivery it is. It's denoted by the ``type_id`` key. | Hence the block name is ``SwitchTrue``, switching between whether ``type_id`` is 10, 20, or something else. | `Check tests for more examples `_. | ``Line 60``: The name of the block is ``delivery``. | ``Line 61``: The list of original elements is passed. | ``Line 62``: The first original element is specific to a concrete condition. It could pretty much anything, | for example checking that ``type_id`` equals to 20. | ``Line 65``: Here it's just stub always returning true. | ``Line 66``: If the previous condition is satisfied, original passed in a second argument validates ``deliveryJsonElement``. | It's an unnamed block of named elements. | ``Line 67``: The list of original elements. | ``Line 69``: The first one is a ``where`` block. | ``Line 68``: It's wrapped into already familiar ``FastFail`` thing. | ``Line 70``: The second argument is a closure. It's first argument is a ``where`` json object. | I was a bit sloppy and haven't reinforced this knowledge with ``IsJsonObject`` object, but I should've done it. | ``Line 71``: Here goes the named block of named elements. | ``Line 72``: ``where`` is its name. | ``Line 73``: The second argument is a list of all elements. | ``Line 76``: The first element is ``street``. | ``Line 75``: It's required. | ``Line 74``: And should be represented as string. | ``Line 81``: The second one is ``building``. | ``Line 80``: It's required as well. | ``Line 79``: And should be represented as an integer. | ``Line 85``: If all previous checks are successful, an ``Where`` object is created. | It's first argument is `street`, which must be a String; the second one is `building`, which must be an integer. | ``Lines 88-106``: It's pretty much the same with ``where`` block. | ``Line 107``: If all original elements are correct, an object of ``CourierDelivery`` class is created. | It has two arguments: `where`, which should be of ``Where`` class, and `when`, which should be of ``When`` class. | ``Lines 113-117``: The last element is ``source``. All the basic standard checks are in place. | ``Line 119``: Finally, an object of class ``OrderRegistrationRequestData`` is created. | ``Line 122``: An object of class ``Result`` is returned. | If the result is successful, it contains an ``OrderRegistrationRequestData`` object. Otherwise, there is an ``Object`` of arbitrary structure. | Typically, you, backend engineer, shouldn't even have a clue of its structure -- your system's frontend should. Learn by doing! ----------------- The quickest way to get the taste of this declarative style is to try it out for yourself. If you use Maven, here's how you can install it: .. code-block:: java com.github.wrong-about-everything Validol 0.2.0 If you use any other dependency manager, you can find the instructions you need `here `_. Please make sure you're installing the latest version! Have fun!