blob: e4dfdefa0df7208487bf18343d17bc53095f80f4 [file] [log] [blame]
Alex Klein2b236722019-06-19 15:44:26 -06001# -*- coding: utf-8 -*-
2# Copyright 2019 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
Alex Klein2008aee2019-08-20 16:25:27 -06006"""Validation helpers for simple input validation in the API.
7
8Note: Every validator MUST respect config.do_validation. This is an internally
9set config option that allows the mock call decorators to be placed before or
10after the validation decorators, rather than forcing an ordering that could then
11produce incorrect outputs if missed.
12"""
Alex Klein2b236722019-06-19 15:44:26 -060013
14from __future__ import print_function
15
Alex Klein4de25e82019-08-05 15:58:39 -060016import functools
Alex Klein2b236722019-06-19 15:44:26 -060017import os
18
19from chromite.lib import cros_build_lib
20from chromite.lib import cros_logging as logging
21
22from google.protobuf import message as protobuf_message
23
24
25def _value(field, message):
26 """Helper function to fetch the value of the field.
27
28 Args:
29 field (str): The field name. Can be nested via . separation.
30 message (Message): The protobuf message it is being fetched from.
31
32 Returns:
33 str|None|int|list|Message|bool - The value of the field.
34 """
35 value = message
36 for part in field.split('.'):
37 if not isinstance(value, protobuf_message.Message):
38 value = None
39 break
40
41 try:
42 value = getattr(value, part)
43 except AttributeError as e:
44 cros_build_lib.Die('Invalid field: %s', e.message)
45
46 return value
47
Alex Klein69339cc2019-07-22 14:08:35 -060048
Alex Klein2b236722019-06-19 15:44:26 -060049#pylint: disable=docstring-misnamed-args
50def exists(*fields):
51 """Validate that the paths in |fields| exist.
52
53 Args:
54 fields (str): The fields being checked. Can be . separated nested
55 fields.
56 """
57 assert fields
58
59 def decorator(func):
Alex Klein4de25e82019-08-05 15:58:39 -060060 @functools.wraps(func)
Alex Klein2008aee2019-08-20 16:25:27 -060061 def _exists(input_proto, output_proto, config, *args, **kwargs):
62 if config.do_validation:
63 for field in fields:
64 logging.debug('Validating %s exists.', field)
Alex Klein2b236722019-06-19 15:44:26 -060065
Alex Klein2008aee2019-08-20 16:25:27 -060066 value = _value(field, input_proto)
67 if not value or not os.path.exists(value):
68 cros_build_lib.Die('%s path does not exist: %s' % (field, value))
Alex Klein2b236722019-06-19 15:44:26 -060069
Alex Klein2008aee2019-08-20 16:25:27 -060070 return func(input_proto, output_proto, config, *args, **kwargs)
Alex Klein2b236722019-06-19 15:44:26 -060071
72 return _exists
73
74 return decorator
75
76
Alex Klein231d2da2019-07-22 16:44:45 -060077def is_in(field, values):
78 """Validate |field| does not contain |value|.
79
80 Args:
81 field (str): The field being checked. May be . separated nested fields.
82 values (list): The possible values field may take.
83 """
84 assert field
85 assert values
86
87 def decorator(func):
Alex Klein4de25e82019-08-05 15:58:39 -060088 @functools.wraps(func)
Alex Klein2008aee2019-08-20 16:25:27 -060089 def _is_in(input_proto, output_proto, config, *args, **kwargs):
90 if config.do_validation:
91 logging.debug('Validating %s is in %r', field, values)
92 value = _value(field, input_proto)
Alex Klein231d2da2019-07-22 16:44:45 -060093
Alex Klein2008aee2019-08-20 16:25:27 -060094 if value not in values:
95 cros_build_lib.Die('%s (%r) must be in %r', field, value, values)
Alex Klein231d2da2019-07-22 16:44:45 -060096
Alex Klein2008aee2019-08-20 16:25:27 -060097 return func(input_proto, output_proto, config, *args, **kwargs)
Alex Klein231d2da2019-07-22 16:44:45 -060098
99 return _is_in
100
101 return decorator
102
103
Alex Klein2b236722019-06-19 15:44:26 -0600104#pylint: disable=docstring-misnamed-args
105def require(*fields):
106 """Verify |fields| have all been set.
107
108 Args:
Alex Klein231d2da2019-07-22 16:44:45 -0600109 fields (str): The fields being checked. May be . separated nested fields.
Alex Klein2b236722019-06-19 15:44:26 -0600110 """
111 assert fields
112
113 def decorator(func):
Alex Klein4de25e82019-08-05 15:58:39 -0600114 @functools.wraps(func)
Alex Klein2008aee2019-08-20 16:25:27 -0600115 def _require(input_proto, output_proto, config, *args, **kwargs):
116 if config.do_validation:
117 for field in fields:
118 logging.debug('Validating %s is set.', field)
Alex Klein2b236722019-06-19 15:44:26 -0600119
Alex Klein2008aee2019-08-20 16:25:27 -0600120 value = _value(field, input_proto)
121 if not value:
122 cros_build_lib.Die('%s is required.', field)
Alex Klein2b236722019-06-19 15:44:26 -0600123
Alex Klein2008aee2019-08-20 16:25:27 -0600124 return func(input_proto, output_proto, config, *args, **kwargs)
Alex Klein2b236722019-06-19 15:44:26 -0600125
126 return _require
127
128 return decorator
Alex Klein69339cc2019-07-22 14:08:35 -0600129
130
131def validation_complete(func):
132 """Automatically skip the endpoint when called after all other validators.
133
134 This decorator MUST be applied after all other validate decorators.
135 The config can be checked manually if there is non-decorator validation, but
136 this is much cleaner if it is all done in decorators.
137 """
Alex Klein4de25e82019-08-05 15:58:39 -0600138
139 @functools.wraps(func)
Alex Klein69339cc2019-07-22 14:08:35 -0600140 def _validate_only(request, response, configs, *args, **kwargs):
141 if configs.validate_only:
142 # Avoid calling the endpoint.
143 return 0
144 else:
145 return func(request, response, configs, *args, **kwargs)
146
147 return _validate_only