Skip to content

[internal] Data Validation Design

Jensen Cochran edited this page Oct 26, 2015 · 3 revisions

Note: This page is intended for internal use by the API team to assist implementation and does not contain information about the usage of the API.

Data Validation Implementation Design

Usage of a validator for a given view function should take the form of a decorator that takes a single argument, which is a concrete implementation (child) of a base Validator class.

The decorator should create an instance of the concrete Validator with the request and call the validate() method of the Validator.

If the validation fails, the decorator should return the FailureResponse returned by the Validator appropriately as JSON (see the documentation on FailureResponse for how to do this). Exceptions thrown by the Validator should be caught and an appropriate FailureResponse should be returned (also as JSON). Otherwise, if validations are successful and no exceptions are thrown, the view function should be run as usual.

@app.route(...)
@requires_auth(...)
@requires_validation(SomeConcreteValidator)
def some_view_function():
    ...

Field Classes (fields.py)

The base Field class should take in two parameters in its __init__ method: name, which is the name of the attribute (key) in the JSON of the request, and required, which is a boolean of whether the field is required to exist in the JSON. It should set both of these parameters as attributes of itself.

The base Field class should set an attribute named default_success_msg to be returned if validation is successful. This will act as a constant as the message to return if validation is successful. This should not be set in __init__ but rather as an attribute directly under the class declaration.

The base Field class should implement a success() method that simply returns the tuple (True, self.default_success_msg) to act as a helper when validation is successful.

The base Field class should implement a list of validators and a validate(value) method that iterates through the validators and calls each on the value to validate. If all validators return success, validate(value) will return self.success() to signify that the field was validated successfully.

The base Field class' validate(value) should test the value based on required before iterating through the list of validators.

A subclass of Field should be implemented for each of the data types that may need to be validated (ex. StringField and ImageField).

Each concrete implementation should take in parameters in their __init__ that are specific to that type of field and set them as attributes. For instance, a StringField may take in a min_length and max_length, while a NumberField may take in a max_value and min_value.

All children of Field (and children of these and so on) should override _validate(value) to validate the specifics of that data type. It is important to note that the user only needs to implement the validation that is not covered in the parent class.

The body of these _validate(value) methods should validate the data as it relates to the concrete Field. For instance, StringField should validate that the data is of the correct type and its length is within the bounds of min_length and max_length. An EmailField could then inherit StringField and validate only that the email is of the correct format, knowing that StringField validated length and type already.

Upon validation failure, each validate(data) method should return a tuple of (success, message), where success is a boolean of whether validation was successful and message is a message describing the reason. If the validation is successful, self.success() should be returned (which ultimately returns a tuple of (True, self.default_success_msg)).

Validator Classes (validators.py)

The base Validator class must take in the current request as a parameter of its __init__ method and store it as an attribute.

The base Validator class must implement a method named validate(). If at any time validation fails for any reason, the method should return an instance of FailureResponse containing information about the failure.

The base Validator class should have an attribute named 'self.fields' that is set to an empty list.
Each concrete Validator only needs to do one thing: override the 'self.fields' attribute with a list of concrete Fields with correct data to be validated.

The method should first validate that the request data is valid JSON.

Once the JSON is loaded, you can call data.get(field.name) and pass this into the field.validate(value) method (i.e. field.validate(data.get(field.name))).

Finally, the method should iterate through the fields defined in the concrete Validator and call the validate(value) method on each of the fields.

If a field's validate(...) fails, a FailureResponse should be returned containing the failure message returned by the method.

Concrete Validators (validators.py)

We will need a validator class for each request that will be accepted by our server. At the moment, we have three different requests data sets: authentication, employee-info, and ticket-submission. If you will have a look at our request data definition, you will see the fields that need to be validated. Any information that is not known for a field should be left blank in the field's constructor and the default will be taken.

For example, our authentication URI will be validating companyID and password. These are both StringField with a max_length of 64 and 16 respectively. They are also both required fields. It is also important to notice for data fields such as employee info that we have an EmailField even though it is of string type. Please examine the request data definitions and implement the concrete validators for the following:

  • AuthInfoValidator
  • EmployeeInfoValidator
  • TicketInfoValidator

Note: re-defining the fields list with a list of StringField, EmailField, etc. in each concrete validator should be sufficient

Clone this wiki locally