Tapir supports validation for primitive types. Validation of composite values, whole data structures, business
rules enforcement etc. should be done as part of the server logic of the endpoint, using the
dedicated error output (the
Endpoint[A, I, E, O, S]) to report errors.
For some guidelines as to where to perform a specific type of validation, see the “Validation analysis paralysis” article. A good indicator as to where to place particular validation logic might be if the property that we are checking is a format error, or a business-level error? The validation capabilities described in this section are intended only for format errors.
Validation rules added using the built-in validators are translated to OpenAPI documentation.
Adding validators to schemas
A validator is always part of a
Schema, which is part of a
Codec. It can validate either the top-level object, or
some nested component (such as a field value).
If you are using automatic or semi-automatic schema derivation, validators for such schemas, and their nested components, including collections and options, can be added as described in schema customisation.
Adding validators to inputs/outputs
Validators can also be added to individual inputs/outputs. Behind the scenes, this modifies the schema, but it’s easier to add top-level validators this way, rather than modifying the implicit schemas, for example:
import sttp.tapir._ val e = endpoint.in( query[Int]("amount") .validate(Validator.min(0)) .validate(Validator.max(100)))
For optional/iterable inputs/outputs, to validate the contained value(s), use:
import sttp.tapir._ query[Option[Int]]("item").validateOption(Validator.min(0)) query[List[Int]]("item").validateIterable(Validator.min(0)) // validates each repeated parameter
Adding validators to codecs
Finally, if you are creating a reusable codec, a validator can be added to it as well:
import sttp.tapir._ import sttp.tapir.CodecFormat.TextPlain case class MyId(id: String) implicit val myIdCodec: Codec[String, MyId, TextPlain] = Codec.string .map(MyId(_))(_.id) .validate(Validator.pattern("^[A-Z].*").contramap(_.id))
The validators are run when a value is being decoded from its low-level representation. This is done using the
Codec.decode method, which returns a
DecodeResult. Such a result can be successful, or a decoding failure.
Keep in mind that the validator mechanism described here is meant for input/output values which are in an incorrect low-level format. Validation and more generally decoding failures should be reported only for format failures. Business validation errors, which are often contextual, should use the error output instead.
To customise error messages that are returned upon validation/decode failures by the server, see error handling.
Validators for enumerations can be created using:
for arbitrary types, using
Validator.enumeration, which takes the list of possible values
sealedhierarchies, where all implementations are objects, using
Validator.derivedEnumeration[T]. This method is a macro which determines the possible values.
enums, where all implementation don’t have parameters, using
Enumeration#Value, automatically derived
Schemas have the validator added (see
The enumeration schemas and codecs that are created by tapir-provided methods already have the enumeration validator added. See the section on enumerations for more information.
Enumeration values in documentation
To properly represent possible values in documentation, the enum validator additionally needs an
encode method, which
converts the enum value to a raw type (typically a string). This can be specified by:
explicitly providing it using the overloaded
enumerationmethod with an
by using one of the
.encodemethods on the
when the values possible values are of a basic type (numbers, strings), the encode function is inferred if not present
by adding the validator directly to a codec using
.validate(the encode function is then taken from the codec)
import sttp.tapir._ sealed trait Color case object Blue extends Color case object Red extends Color // providing the enum values by hand implicit def colorSchema: Schema[Color] = Schema.string.validate( Validator.enumeration(List(Blue, Red), (c: Color) => Some(c.toString.toLowerCase)))
Validation of unrepresentable values
Note that validation is run on a fully decoded values. That is, during decoding, first all the provided decoding
functions are run, followed by validations. If you’d like to validate before decoding, e.g. because the value
isn’t representable unless validator conditions are met due to preconditions, you can use
this will cause the validator function to be run twice if there are no validation error.
Read on about content types.