Validating Protobufs
scalapb-validate
is a code generator that generates validation methods for your messages based on rules and constraints defined in the proto. It uses the same validation rules provided by protoc-gen-validate.
#
IntroductionIn many situations, you may want to add validation rules for your messages. For example, you might want to enforce that a certain string field is an email address, or that a repeated field has at least one element.
Such rules and constraints can be defined using custom options defined in validate/validate.proto
. Here is an example taken from protoc-gen-validate's documentation.
scalapb-validate supports all the rules available in protoc-gen-validate and is tested with the same test harness containing over 900 test cases.
#
InstallationAdd the following to your project/plugins.sbt
:
Change your PB.targets
to generate the validation code. The output directory must be the same as the one used for scalapb.gen
:
Note that we are adding scalapb-validate-core
as a protobuf
dependency. This makes it possible to import validate/validate.proto
from your own protos.
If ScalaPB generator parameters are passed via scalapb.gen(options: GeneratorOption*)
, the same parameters must be passed to scalapb.validate.gen(options: GeneratorOption*)
.
#
Using the generated codeGenerated code for both ScalaPB and scalapb-validate is generated at compilation time. In sbt
, just type compile
.
In addition to the standard ScalaPB generated files, scalapb-validate will generate a validator object for each message based on its protoc-gen-validate rules. For a message named Msg
the validator object will be named MsgValidator
and will extend scalapb.validate.Validator[Msg]
. An implicit instance of the validator is added to the companion object of each message, which makes it possible to write Validator[Msg]
to obtain an instance of the validator.
#
ValidatorsThe validator object is an object with a validate
method that takes an instance of a message and returns the validation result: The Validator[T]
is defined as follows:
where Result
is a data structure that can be either a Success
or a Failure
:
Therefore, the validation for the test person casn be run like this:
#
Package-scoped extension optionsScalaPB-validate further extends ScalaPB's package-scoped options to achieve additional customization:
validate_at_construction
when true, a check for validity is added to the message class body, so construction of invalid messages results in a validation exception. Default:false
.insert_validator_instance
when true, implicit instance of aValidator
is added to the companion object of the message. This enables writingValidator[MyMsg].validate(instance)
. Default:true
.skip
when true, skips gnerating validators for messages defined in this file. This can be set for a third-party package to work around the problem that there are no validators for it. See this testcase for a usage example.
#
Rule-based type customizationStarting from version 0.10.10, ScalaPB provides a way to customize its own options by writing rules that are matched against
arbitrary protobuf options. When these rules are matched, additional ScalaPB options are added to the matched entity. For example, you can create a transformation that whenever a field has a PGV-rule like int32: { gt: 0 }}
, then it will be typemapped to a custom class PositiveInt
.
#
InstallationThe features described in this section have the following version requirements:
- sbt-protoc >=
1.0.0
- ScalaPB >=
0.10.10
- scalapb-validate >=
0.2.0
While field transformation is a generic ScalaPB mechanism, it is also recommended that you add
scalapb-validate's preprocessor to PB.targets
. The preprocessor does two things:
- Provides field transformations for
Set
and cats data types. - Expand your PGV-based rules such that they match repeated items, map keys and map values.
There is an example project available on github.
#
Field transformationsIf you want all positive integers to be typemapped to a Scala class called PositiveInt
you can
create a proto file with the following content:
The scope of this definition is the entire protobuf file it is found in. Field tranformations can also be used in package-scoped options so they are passed to all files within the package.
You can learn more about field transformations in this page.
More examples of field transformations usage:
The following rule with match whenever there is a gt
field set, no matter to which value:
Since the when
clause is FieldDescriptorProto
, it is possible to match on type
and label
. For example,
the following will match only when the field is in a repeated int32
:
#
What does the preprocessor do?The preprocessor scans for field_transformations
with when
fields that contain [validate.rules]
extensions. Whenever a [validate.rules]
does contain a repeated
or map
validation rules it is assumed to be applied to a singleton type, so we add a copy of the rule for repeated, map-key and map-value context. For example, for this PositiveInt
rule:
the following field transformations will be automatically added by the preprocessor:
This saves you from writing those rules manually so the type transformation is applied in repeated fields or maps. Note that the rewrite mechanism rewrites the type
in the original set
field, into key_type
or value_type
.
#
Cats non-empty collectionsUsing rules like the ones defined above, it is possible to detect when a list or a map are non-empty (via. {repeated: { min_items: 1}}
or {map: {min_pairs: 1}}
, and map them to corresponding non-empty collections. Cats collections require some additional adaptation to ScalaPB since their API is different enough from standard Scala collections. ScalaPB-validate comes with support to automatically map non-empty collections to NonEmptyMap
, NonEmptySet
and NonEmptyList
. To enable, add the following to a proto file. The scope of the settings will be for the entire file. You can turn the setting on for the
entire package by adding scope: PACKAGE
.
As stated above, you will need to have scalapb-validate-cats
listed in
libraryDependencies
. The setting unique_to_set
can be used independently
of cats to transform a repeated with unique: true
rule to a set.
#
Unboxing required fieldsIf you use validate.message.required
you can apply a transformation that
would set the scalapb.field.required
option. As a result, the field will
not be boxed in an Option
and parsing will throw an exception if the field
is missing. To set this transformation add the following to ScalaPB-validate's options:
#
Using with refinedAs explained in "referencing rule values", it is possible
to reference field descriptor values in the set
part of field transformation.
A use case for this is with refined types. For example, you can
define the following field transformations:
For this to work, a typemapper for refined types need to be either put in a package object in the same package where the code is generated, or be manually imported through import
options.
The typemapper used in scalapb-validate tests is here.
Additional resources:
- test proto files (refined.proto): note it uses
shapeless.Witness
since ScalaPB-validate is cross-tested for Scala 2.12 and Scala 2.13. - end-to-end tests demonstrating compile-time validation.