blob: 285b5b20d4717f20269a330cc82caef5641e17b6 [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
6"""Validation helpers for simple input validation in the API."""
7
8from __future__ import print_function
9
Alex Klein4de25e82019-08-05 15:58:39 -060010import functools
Alex Klein2b236722019-06-19 15:44:26 -060011import os
12
13from chromite.lib import cros_build_lib
14from chromite.lib import cros_logging as logging
15
16from google.protobuf import message as protobuf_message
17
18
19def _value(field, message):
20 """Helper function to fetch the value of the field.
21
22 Args:
23 field (str): The field name. Can be nested via . separation.
24 message (Message): The protobuf message it is being fetched from.
25
26 Returns:
27 str|None|int|list|Message|bool - The value of the field.
28 """
29 value = message
30 for part in field.split('.'):
31 if not isinstance(value, protobuf_message.Message):
32 value = None
33 break
34
35 try:
36 value = getattr(value, part)
37 except AttributeError as e:
38 cros_build_lib.Die('Invalid field: %s', e.message)
39
40 return value
41
Alex Klein69339cc2019-07-22 14:08:35 -060042
Alex Klein2b236722019-06-19 15:44:26 -060043#pylint: disable=docstring-misnamed-args
44def exists(*fields):
45 """Validate that the paths in |fields| exist.
46
47 Args:
48 fields (str): The fields being checked. Can be . separated nested
49 fields.
50 """
51 assert fields
52
53 def decorator(func):
Alex Klein4de25e82019-08-05 15:58:39 -060054 @functools.wraps(func)
Alex Klein2b236722019-06-19 15:44:26 -060055 def _exists(input_proto, *args, **kwargs):
56 for field in fields:
57 logging.debug('Validating %s exists.', field)
58
59 value = _value(field, input_proto)
60 if not value or not os.path.exists(value):
61 cros_build_lib.Die('%s path does not exist: %s' % (field, value))
62
63 return func(input_proto, *args, **kwargs)
64
65 return _exists
66
67 return decorator
68
69
Alex Klein231d2da2019-07-22 16:44:45 -060070def is_in(field, values):
71 """Validate |field| does not contain |value|.
72
73 Args:
74 field (str): The field being checked. May be . separated nested fields.
75 values (list): The possible values field may take.
76 """
77 assert field
78 assert values
79
80 def decorator(func):
Alex Klein4de25e82019-08-05 15:58:39 -060081 @functools.wraps(func)
Alex Klein231d2da2019-07-22 16:44:45 -060082 def _is_in(input_proto, *args, **kwargs):
83 logging.debug('Validating %s is in %r', field, values)
84 value = _value(field, input_proto)
85
86 if value not in values:
87 cros_build_lib.Die('%s (%r) must be in %r', field, value, values)
88
89 return func(input_proto, *args, **kwargs)
90
91 return _is_in
92
93 return decorator
94
95
Alex Klein2b236722019-06-19 15:44:26 -060096#pylint: disable=docstring-misnamed-args
97def require(*fields):
98 """Verify |fields| have all been set.
99
100 Args:
Alex Klein231d2da2019-07-22 16:44:45 -0600101 fields (str): The fields being checked. May be . separated nested fields.
Alex Klein2b236722019-06-19 15:44:26 -0600102 """
103 assert fields
104
105 def decorator(func):
Alex Klein4de25e82019-08-05 15:58:39 -0600106 @functools.wraps(func)
Alex Klein2b236722019-06-19 15:44:26 -0600107 def _require(input_proto, *args, **kwargs):
108 for field in fields:
109 logging.debug('Validating %s is set.', field)
110
111 value = _value(field, input_proto)
112 if not value:
113 cros_build_lib.Die('%s is required.', field)
114
115 return func(input_proto, *args, **kwargs)
116
117 return _require
118
119 return decorator
Alex Klein69339cc2019-07-22 14:08:35 -0600120
121
122def validation_complete(func):
123 """Automatically skip the endpoint when called after all other validators.
124
125 This decorator MUST be applied after all other validate decorators.
126 The config can be checked manually if there is non-decorator validation, but
127 this is much cleaner if it is all done in decorators.
128 """
Alex Klein4de25e82019-08-05 15:58:39 -0600129
130 @functools.wraps(func)
Alex Klein69339cc2019-07-22 14:08:35 -0600131 def _validate_only(request, response, configs, *args, **kwargs):
132 if configs.validate_only:
133 # Avoid calling the endpoint.
134 return 0
135 else:
136 return func(request, response, configs, *args, **kwargs)
137
138 return _validate_only