blob: 285b5b20d4717f20269a330cc82caef5641e17b6 [file] [log] [blame]
# -*- coding: utf-8 -*-
# Copyright 2019 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Validation helpers for simple input validation in the API."""
from __future__ import print_function
import functools
import os
from chromite.lib import cros_build_lib
from chromite.lib import cros_logging as logging
from google.protobuf import message as protobuf_message
def _value(field, message):
"""Helper function to fetch the value of the field.
Args:
field (str): The field name. Can be nested via . separation.
message (Message): The protobuf message it is being fetched from.
Returns:
str|None|int|list|Message|bool - The value of the field.
"""
value = message
for part in field.split('.'):
if not isinstance(value, protobuf_message.Message):
value = None
break
try:
value = getattr(value, part)
except AttributeError as e:
cros_build_lib.Die('Invalid field: %s', e.message)
return value
#pylint: disable=docstring-misnamed-args
def exists(*fields):
"""Validate that the paths in |fields| exist.
Args:
fields (str): The fields being checked. Can be . separated nested
fields.
"""
assert fields
def decorator(func):
@functools.wraps(func)
def _exists(input_proto, *args, **kwargs):
for field in fields:
logging.debug('Validating %s exists.', field)
value = _value(field, input_proto)
if not value or not os.path.exists(value):
cros_build_lib.Die('%s path does not exist: %s' % (field, value))
return func(input_proto, *args, **kwargs)
return _exists
return decorator
def is_in(field, values):
"""Validate |field| does not contain |value|.
Args:
field (str): The field being checked. May be . separated nested fields.
values (list): The possible values field may take.
"""
assert field
assert values
def decorator(func):
@functools.wraps(func)
def _is_in(input_proto, *args, **kwargs):
logging.debug('Validating %s is in %r', field, values)
value = _value(field, input_proto)
if value not in values:
cros_build_lib.Die('%s (%r) must be in %r', field, value, values)
return func(input_proto, *args, **kwargs)
return _is_in
return decorator
#pylint: disable=docstring-misnamed-args
def require(*fields):
"""Verify |fields| have all been set.
Args:
fields (str): The fields being checked. May be . separated nested fields.
"""
assert fields
def decorator(func):
@functools.wraps(func)
def _require(input_proto, *args, **kwargs):
for field in fields:
logging.debug('Validating %s is set.', field)
value = _value(field, input_proto)
if not value:
cros_build_lib.Die('%s is required.', field)
return func(input_proto, *args, **kwargs)
return _require
return decorator
def validation_complete(func):
"""Automatically skip the endpoint when called after all other validators.
This decorator MUST be applied after all other validate decorators.
The config can be checked manually if there is non-decorator validation, but
this is much cleaner if it is all done in decorators.
"""
@functools.wraps(func)
def _validate_only(request, response, configs, *args, **kwargs):
if configs.validate_only:
# Avoid calling the endpoint.
return 0
else:
return func(request, response, configs, *args, **kwargs)
return _validate_only