Validations¶
Introduction¶
Validations in an API are processes that verify that the data sent to the server meets certain rules or criteria before being processed. This ensures that the information is correct, complete, and secure.
Importance¶
- Error Prevention: Prevents incorrect data from causing system failures.
- Security: Protects against code injections and other attacks.
- Data Integrity: Ensures that stored and processed data is consistent and reliable.
- User Experience: Provides immediate feedback on errors in the submitted data.
Essential for robust and secure API operation!
Usage¶
In Cafeto, DTOs will execute the validations that Pydantic has available by default.
Python | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 |
|
In the example above, the fields username
, password
, confirm_password
, and birth_date
are required, while the name
field is optional, and the username
field has a minimum length of 3 characters. These validations will be executed automatically when the action is consumed, and errors will be returned with a statusCode
of 400
.
For custom validations, there is the @validate
decorator. This decorator takes the name of the field to be validated as a parameter; if this field is not provided, the entire DTO will be validated.
Python | |
---|---|
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 |
|
-
Validates the
password
field. -
Validates the
birth_date
field. -
Validates the entire model.
The @validate
decorator is similar to Pydantic's @field_validator
and was developed to create custom and asynchronous validations. It also supports dependency injection in the method to which it is applied.
Bash | |
---|---|
1 2 3 4 5 6 7 8 |
|
If we send the above request, we will get the following result:
JSON | |
---|---|
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 |
|
This is more or less the same format in which Pydantic returns data validation.
FieldError
is used to throw an exception when a DTO field fails and takes an Error
object as a parameter, which in turn takes the parameters type: str
and msg: str
. There is a third parameter called loc: List[str | int] | str | int
; if this is not used, Pydantic will do it automatically.
In the loc
array, the word __model__
refers to global errors, i.e., those not necessarily associated with any DTO field.
Important Information¶
It is important to note that these methods are static, so they do not receive the self
parameter.
The parameters of the method where the @validate
decorator is applied are: value
and data
.
-
value: It is the current value of the field being validated, and the data type will be the one configured from the model.
Note: If the validator applies to the entire model, the first parameter will be a dictionary with the data of the entire model.
-
data: It is a dictionary with the other fields of the model.
Note: If the validator applies to the entire model, the second parameter does not exist.
As a general rule, if the second parameter is not needed, it is usually called "_" (underscore).
Python | |
---|---|
1 2 |
|
Error Format¶
There is an additional way to return errors; for this, the application must be configured to return them in this format.
Python | |
---|---|
1 2 3 4 |
|
In that case, errors will be thrown in both the previously seen format and the new one.
JSON | |
---|---|
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 |
|
In this case, two types of errors are thrown: errorList
and errorObject
. In the latter, the loc
field no longer exists and becomes the nested keys of the object with the errors.
Modifying Values¶
The @validate
decorator also serves to modify the DTO values when returned, i.e., it can be used not only to validate data but also to alter their values.
Note: It is important to note that validators always must return a value; this value will be the one finally used in the model.
Python | |
---|---|
1 2 3 4 5 6 |
|
The value of the name
field will be the assigned value + " - Hello".
Nested Models¶
If you need to validate a model where one of its fields is another model, you must pay attention to how these are validated. If we use the @validate
decorator on the field that contains the nested model, the custom validations of this model will not be executed. This is because the system must determine which validation to execute and will prioritize the one that is less nested in the model.
Python | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
In this last example, the validate_name_a
validation and any other in the MyModelA
model will not be executed because the validate_model_a_from_b
validation in the MyModelB
model is overwriting it.
Validators¶
As seen in the previous examples, validations are associated with DTOs. However, it may not always be desirable to have this behavior. For these cases, there are validators. These are classes to which the @validate
decorator is also applied, either to validate a field or the entire model, just like DTOs. To use these validators, you only need to inherit from the BaseValidator
class.
Python | |
---|---|
1 2 3 4 5 6 7 8 |
|
Dependencies can also be injected into validators, just like DTOs.
Python | |
---|---|
1 2 3 4 5 6 7 8 9 |
|
To use the validator, it must be configured in the controller action. This is done in the body
parameter of the @app.get
, @app.post
, @app.put
, @app.patch
, and @app.delete
decorators.
Python | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
In this example, the DTO's own validations, such as the required fields (name
, age
), will be applied. Additionally, custom validations will be executed using the MyValidator
validator. This is particularly useful if you want to keep a cleaner DTO and reuse these validations across different DTOs.
It is possible to have nested validators, just like DTOs.
Python | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
It doesn't matter if the field to be validated is a list or a dictionary; the configuration remains the same.
Python | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
There is also the possibility of not validating the DTO at all and delegating this task to a manual process. To do this, None
should be sent in the body
property instead of the validator.
Python | |
---|---|
1 2 3 4 5 |
|
In this case, the request
parameter will contain the values sent via the POST
method, but without validations.
The DTO can be validated later using the following code:
Python | |
---|---|
1 2 3 4 5 6 7 8 9 10 |
|
If you want to use a different validator than the DTO, it should be passed as a parameter to the check
method.
Python | |
---|---|
1 2 3 4 |
|
It is possible to return these errors as they are generated by the exception when validating the model, or format them to match the format normally used when validations are performed automatically.
Python | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|