Build API Validation

This doc provides quick reference/usage information about the Build API validate decorators. The validation tutorial provides a tutorial format, but starting at the beginning is recommended if you are new to the Build API.

Validation should be done using the validate decorators whenever possible. In particular, the decorators handle validation only calls for you if all validation can be done with the decorators. If you do need to do custom validation in the controller, you will also need to handle the validation only call by checking the config for the validation only call type and manually returning early with a success return code after the validation is complete.

Addressing Nested Fields

All validation decorators support addressing arbitrarily nested fields for any field or subfield argument. This is done by simply adding ‘.’ between field names as one would do if accessing the fields directly from the message object itself. As with accessing properties of the object itself, it works for any arbitrary nesting as long as there are no repeated fields in the specified path. See the protobuf message below commented with the string used to address each field.

message Example {
    message Foo {
        message Bar {
            // 'foo.bar.id'.
            int32 id = 1;
            message Baz {
                // 'foo.bar.bazzes.name' will NOT work.
                string name = 1;
            }
            // 'foo.bar.bazzes'.
            repeated Baz bazzes = 2;
        }
        // 'foo.bar'.
        Bar bar = 1;
    }
    // 'foo'.
    Foo foo = 1;
}

each_in

each_in(field: str, subfield: str, values: Iterable, optional=False)

each_in will check that each value in a repeated field, or each value in a subfield of a repeated message, is in the given values. Passing optional=True allows only validating the field when it is set to some value. This is the equivalent of is_in for repeated fields.

The following example illustrates the two use cases for each_in; a repeated scalar field, and a repeated message field.

message ExampleRequest {
    repeated string names = 1;

    message SubMessage {
        // Can be validated.
        int32 id = 1;
        // Can't be validated because it's a repeated field in a repeated
        // message.
        repeated int32 cant_be_validated = 2;
    }
    repeated SubMessage submessages = 2;
}
# Repeated scalar field, checks every `names` value is either 'foo' or 'bar'.
# Python equivalent:
#   request.names and [x in ['foo', 'bar'] for x in request.names]
@validate.each_in('names', None, ['foo', 'bar'])
# Repeated message field, checks the `id` field of every `submessage` is in
# 1-10, but `submessages` may be empty.
# Python equivalent:
#   all(x.id in range(10) for x in request.submessages)
@validate.each_in('submessages', 'id', range(10), optional=True)
def Example(request, response, config):
    pass

exists

exists(*fields: str)

exists verifies the file or directory pointed to by the given fields exist. exists can be given as many fields as need to be checked. This has the side effect of also requiring the field to be set, technically making require decorators for these fields redundant, but even so are not discouraged.

# Validate request.path1 and request.path2 exist.
# Python equivalent:
#   os.path.exists(x) for x in (request.path1, request.path2)
@validate.exists('path1', 'path2')
def Example(request, response, config):
    pass

is_in

is_in(field: str, values: Iterable)

is_in verifies the given field has one of the given values.

# Python equivalent:
#   request.id in range(1, 10)
@validate.is_in('id', range(1, 10))
def Example(request, response, config):
    pass

require

require(*fields: str)

require verifies each field has a value set. Since protobuf gives back falsey values when not set, this validator is not capable of distinguishing between an unset field and one that were set with a falsey value (e.g. 0, empty string, False).

# Require `foo` and `bar` are set.
# Python equivalent:
#   request.foo and request.bar
@validate.require('foo', 'bar')
def Example(request, response, config):
    pass

require_any

require_any(*fields: str)

require_any verifies that at least one of the given fields has been set. It has the same semantics as require for validating values. This is useful when there are multiple fields that can be used specify an argument that is required, but any are acceptable. For example, when transitioning between two fields, it may be easiest to support using either of them, but the endpoint only needs one of them to be set.

# Require either 'id' or 'identifier' is set.
# Python equivalent:
#   request.id or request.identifier
@validate.require_any('id', 'identifier')
def Example(request, response, config):
    pass

require_each

require_each(field: str, subfields: Iterable[str], allow_empty=True)

require_each verifies every entry in the repeated field has each of the given subfields set. By default, the validator allows field itself to be empty, only verifying that when it is populated the fields are set. Setting allow_empty to False also requires the field itself is not empty.

# Require any `foos` have `bar` and `baz` set.
# Python equivalent:
#   all(x.bar and x.baz for x in request.foos)
@validate.require('foos', ['bar', 'baz'])
# Require all `points` have `x` and `y` set.
# Python equivalent:
#   request.points and all(p.x and p.y for p in request.points)
@validate.require('points', ['x', 'y'], allow_empty=False)
def Example(request, response, config):
    pass

validation_complete

validation_complete()

When all validation can be done with validate decorators, validation_complete handles validation only calls. It should be used in every instance it can be used. It must be the last validate decorator when used.

@validate.require('foo')
@validate.validation_complete
def Example(request, response, config):
    pass