api/validate: Add require_each validator.
The require_each validator allows specifying fields of a repeated
message that must be specified for each instance.
BUG=chromium:1130818
TEST=run_pytest
Cq-Depend: chromium:2426691
Change-Id: I42da9fa19fd13b700715734cf9c92d7c03b49556
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/2424785
Commit-Queue: Alex Klein <saklein@chromium.org>
Tested-by: Alex Klein <saklein@chromium.org>
Reviewed-by: Michael Mortensen <mmortensen@google.com>
diff --git a/api/validate.py b/api/validate.py
index c27a562..d8a075d 100644
--- a/api/validate.py
+++ b/api/validate.py
@@ -132,6 +132,44 @@
return decorator
+def require_each(field, subfields, allow_empty=True):
+ """Verify |field| each have all of the |subfields| set.
+
+ When |allow_empty| is True, |field| may be empty, and |subfields| are only
+ validated when it is not empty. When |allow_empty| is False, |field| must
+ also have at least one entry.
+
+ Args:
+ field (str): The repeated field being checked. May be . separated nested
+ fields.
+ subfields (list[str]): The fields of the repeated message to validate.
+ allow_empty (bool): Also require at least one entry in the repeated field.
+ """
+ assert field
+ assert subfields
+ assert not isinstance(subfields, str)
+
+ def decorator(func):
+ @functools.wraps(func)
+ def _require_each(input_proto, output_proto, config, *args, **kwargs):
+ if config.do_validation:
+ members = _value(field, input_proto) or []
+ if not allow_empty and not members:
+ cros_build_lib.Die('The %s field is empty.', field)
+ for member in members:
+ for subfield in subfields:
+ logging.debug('Validating %s.[each].%s is set.', field, subfield)
+ value = _value(subfield, member)
+ if not value:
+ cros_build_lib.Die('%s is required.', field)
+
+ return func(input_proto, output_proto, config, *args, **kwargs)
+
+ return _require_each
+
+ return decorator
+
+
def validation_complete(func):
"""Automatically skip the endpoint when called after all other validators.