7. Extended validations

In some situations we could need additional validations which don’t fit the JSON schema very well, such as a combination of parameters leading to a faulty result or options which are dynamically determined.

The goal for this chapter is to create a new service, which performs the following calculation result=c/(a-b). Our service will be part of the sample package, like the previous example and will be called div_min_numbers.

Create the service

Like the previous example with parameters, we’re going to need a json schema to validate our input. To keep things a simple as possible, we’re going to use a similar setup as the previous example only adding the parameter c.

services/valuea/samples/div_min_numbers.json
 1{
 2	"$schema": "http://json-schema.org/draft-04/schema#",
 3	"description": "div_min_numbers",
 4	"properties": {
 5		"a": {
 6			"type": "integer"
 7		},
 8		"b": {
 9			"type": "integer"
10		},
11		"c": {
12			"type": "integer"
13		}
14	},
15	"required": [
16		"a", "b", "c"
17	]
18}

If a equals b then our calculation would result in a division by zero.

Note

To inspect the type of exception returned, you can easily perform the calculation in a python script or shell, which would throw the following error for something like 2/(1-1) : ZeroDivisionError: integer division or modulo by zero

Now we’re going to implement our service and hook in a custom validation, which handles the “is equal” situation.

services/valuea/samples/div_min_numbers.py
 1import valuea_framework.broker.Service
 2
 3
 4class Service(valuea_framework.broker.Service.BaseService):
 5    def __init__(self, *args, **kwargs):
 6        super(Service, self).__init__(*args, **kwargs)
 7
 8    def validate(self):
 9        """ Validate our input data before executing.
10        :return: list
11        """
12        result = super(Service, self).validate()
13        if not result:
14            msg = self.get_message()
15            if msg['a'] == msg['b']:
16                for ref in ['a', 'b']:
17                    result.append({
18                        'message': 'Division by zero',
19                        'ref': ref,
20                        'user_message': 'Cannot divide by zero'
21                    })
22        return result
23
24    def execute(self):
25        msg = self.get_message()
26        return {'result': msg['c']/(msg['a'] - msg['b'])}

The emphasized lines contain the steps needed to hook in our custom validations on top of the default schemes.

First of all, we’re going to make sure our schema validation is executed like it normally would in line [12], because our type validation is encapsulated in the schema, we will only extend our validation if we know for sure our types match (in 13).

Next steps are relatively simple, we validate if a equal b in line 15 and if it does we’re extending the result list with the validation output for the service.

Note

The field ref should contain the actual input field which isn’t valid, in our example both inputs are affected and should therefore be returned. message contains the technical fault, a user readable version should go into user_message.

Test our service

Now we’re ready to test our service, using input that should return validation errors:

run_div_min_numbers_local.py
 1#!/usr/bin/env python
 2
 3import services.valuea.samples.div_min_numbers
 4
 5srv = services.valuea.samples.div_min_numbers.Service()
 6srv.set_message({'a': 1, 'b': 1, 'c':5})
 7# execute validations, normally processed within the listener
 8validation_output = srv.validate()
 9if validation_output:
10    # Oo, our validation returned issues, print all
11    for message in validation_output:
12        print (message)
13else:
14    # all well, print result
15    result = srv.execute()
16    print (result)

This should return the following output:

{'message': 'Division by zero', 'ref': 'a', 'user_message': 'Cannot divide by zero'}
{'message': 'Division by zero', 'ref': 'b', 'user_message': 'Cannot divide by zero'}

Tip

Restart (or start) the listener and try to execute some calls via the queue yourself using the examples provided earlier.

Extending the UI

In the previous chapter we’ve created a user interface, which accepts two variables. Now we’re going to change our previous user interface to accept three values and trigger our new service.

First let’s make sure we can call our service, by adding the binding to our controller.

/usr/local/opnsense/mvc/app/controllers/ValueA/Samples/Api/MathController.xml
 1<route>
 2    <add>
 3        <bind>valuea.samples.add_numbers</bind>
 4        <type>POST</type>
 5        <guess_types>1</guess_types>
 6    </add>
 7    <div_min>
 8        <bind>valuea.samples.div_min_numbers</bind>
 9        <type>POST</type>
10        <guess_types>1</guess_types>
11    </div_min>
12</route>

The emphasized lines are the ones we added to register our service, next step is to change our input form.

/usr/local/opnsense/mvc/app/views/ValueA/Samples/math.volt
 1<script type="text/javascript">
 2    $( document ).ready(function() {
 3        $("#add_numbers").click(function(){
 4            var params = {'a': $("#value_a").val(), 'b': $("#value_b").val(), 'c': $("#value_c").val()};
 5            ajaxCall(url="/api/samples/math/div_min", sendData=params, callback=function(data,status) {
 6                if (data.error != undefined) {
 7                    var all_errors = "";
 8                    data.error.validations.map(function(validation){
 9                        all_errors += "<br/>["+validation.ref+"] " + validation.message;
10                    });
11                    BootstrapDialog.alert('Please input valid parameters' + all_errors);
12                } else {
13                    $("#result").val(data.result);
14                }
15            });
16        });
17    });
18</script>
19
20<!-- Bootstrap grid layout -->
21<div class="row">
22    <div class=" col-md-4 col-xs-12">
23        <div class="input-group">
24            <label for="value_a"><strong>Input A</strong></label>
25            <input type="text" id="value_a"  class="form-control">
26        </div>
27    </div>
28    <div class=" col-md-4 col-xs-12">
29        <div class="input-group">
30            <label for="value_b"><strong>Input B</strong></label>
31            <input type="text" id="value_b" class="form-control">
32        </div>
33    </div>
34    <div class=" col-md-4 col-xs-12">
35        <div class="input-group">
36            <label for="value_c"><strong>Input C</strong></label>
37            <input type="text" id="value_c" class="form-control">
38        </div>
39    </div>
40    <div class=" col-md-4 col-xs-12">
41        <div class="input-group">
42            <label for="result"><strong>Result</strong></label>
43            <input type="text" id="result" class="form-control">
44        </div>
45    </div>
46</div>
47<div class="row">
48    <div class=" col-md-12 col-xs-12">
49         <hr/>
50         <button type="button" class="btn btn-default" id="add_numbers">
51            Go!
52         </button>
53         <br/><br/>
54    </div>
55</div>

The emphasized lines are again the ones we’ve changed to call our other function and yield the results. In lines 4,5 we map the new function with it’s parameters, the additional parameter is assigned in lines 40-45. The block from 7-11 handles the validation output and adds it to the dialog.

Tip

Use console.log() in java script to output object to the developer console for easier analysis.

Tip

When the above sample functions, try to combine both examples (add and div_minus) into two different forms for practice.