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:
{
"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¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | 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.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.Lines 32-35
: Already familiar fast-failing block named items
. Mind the absense of structure validation.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 blockLine 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 ideaLine 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.SwitchTrue
, switching between whether type_id
is 10, 20, or something else.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,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
.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.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.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.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<OrderRegistrationRequestData>
is returned.OrderRegistrationRequestData
object. Otherwise, there is an Object
of arbitrary structure.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:
<dependency>
<groupId>com.github.wrong-about-everything</groupId>
<artifactId>Validol</artifactId>
<version>0.2.0</version>
</dependency>
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!