api/validate: add each_in validator
Add new validator to allow validating repeated fields, and their
subfieds, have specific values.
BUG=None
TEST=./run_pytest
Change-Id: I2c1645c64c2e69253d9a5b11c3dd50809bbae3b9
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/2595815
Tested-by: Alex Klein <saklein@chromium.org>
Commit-Queue: Alex Klein <saklein@chromium.org>
Reviewed-by: Michael Mortensen <mmortensen@google.com>
diff --git a/api/validate.py b/api/validate.py
index 0f61a39..7bb7b23 100644
--- a/api/validate.py
+++ b/api/validate.py
@@ -36,6 +36,9 @@
Returns:
str|None|int|list|Message|bool - The value of the field.
"""
+ if not field:
+ return message
+
value = message
for part in field.split('.'):
if not isinstance(value, protobuf_message.Message):
@@ -79,7 +82,7 @@
def is_in(field, values):
- """Validate |field| contains |value|.
+ """Validate |field| is an element of |values|.
Args:
field (str): The field being checked. May be . separated nested fields.
@@ -105,6 +108,41 @@
return decorator
+def each_in(field, subfield, values, optional=False):
+ """Validate each |subfield| of the repeated |field| is in |values|.
+
+ Args:
+ field (str): The field being checked. May be . separated nested fields.
+ subfield (str|None): The field in the repeated |field| to validate, or None
+ when |field| is not a repeated message, e.g. enum, scalars.
+ values (list): The possible values field may take.
+ optional (bool): Also allow the field to be empty when True.
+ """
+ assert field
+ assert values
+
+ def decorator(func):
+ @functools.wraps(func)
+ def _is_in(input_proto, output_proto, config, *args, **kwargs):
+ if config.do_validation:
+ members = _value(field, input_proto) or []
+ if not optional and not members:
+ cros_build_lib.Die('The %s field is empty.', field)
+ for member in members:
+ logging.debug('Validating %s.[each].%s is in %r.', field, subfield,
+ values)
+ value = _value(subfield, member)
+ if value not in values:
+ cros_build_lib.Die('%s.[each].%s (%r) must be in %r is required.',
+ field, subfield, value, values)
+
+ return func(input_proto, output_proto, config, *args, **kwargs)
+
+ return _is_in
+
+ return decorator
+
+
# pylint: disable=docstring-misnamed-args
def require(*fields):
"""Verify |fields| have all been set.