Skip to content

Commit 74d9cf2

Browse files
authored
Implement min/max list length validation (#3722)
1 parent adabd95 commit 74d9cf2

6 files changed

Lines changed: 100 additions & 20 deletions

File tree

docs/dev/validation-types.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ When an attribute has a data type defined with the **type** attribute, you can u
6666
| **list** | **valid_values** -- list of valid values, checked for every element in the list |
6767
| | **create_empty** (bool) -- replace None value with an empty list |
6868
| | **empty** (bool, default: True) -- can the list be empty? |
69-
| | **make_list** (bool) -- force non-scalar values to become single-element lists |
69+
| | **make_list** (bool) -- force non-scalar values to become single-element lists[^SLE] |
70+
| | **min_length** (int) -- minimum list length |
71+
| | **max_length** (int) -- maximum list length |
7072
| | **split_lines** (bool) -- split multi-line string value into multiple list elements |
7173
| | **_subtype** -- validate values as belonging to the specified subtype |
7274
| **dict** | **create_empty** (bool) -- replace None value with an empty dictionary |
@@ -82,6 +84,8 @@ When an attribute has a data type defined with the **type** attribute, you can u
8284
| **ipv4** | **use** -- [the use of IPv4 address/prefix](validation-ip-address) |
8385
| **ipv6** | **use** -- [the use of IPv6 address/prefix](validation-ip-address) |
8486

87+
[^SLE]: Scalar values are automatically converted to single-element lists
88+
8589
**Notes**
8690
* **_keys** attribute is rarely used in dictionary definitions. Using a [shortcut definition](validation-shortcut-type) is much better. See [examples](validation-definition-examples) for a counterexample.
8791

netsim/data/types.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -447,15 +447,22 @@ def must_be_list(
447447
value: typing.Any,
448448
make_list: bool = False, # Make anything (not just scalars) into a list
449449
split_lines: bool = False, # Split lines in a string into list items
450-
empty: bool = True) -> dict: # Is it OK to have an empty list?
451-
450+
empty: bool = True, # Is it OK to have an empty list?
451+
min_length: typing.Optional[int] = None, # Minimum...
452+
max_length: typing.Optional[int] = None # ... and maximum list length
453+
) -> dict:
452454
def transform_to_list(value: typing.Any) -> list:
453455
if isinstance(value,str) and split_lines:
454456
return value.rstrip().split("\n")
455457

456458
return [ value ]
457459

458460
if isinstance(value,list): # A list is what we want to have ;)
461+
if min_length is not None and len(value) < min_length:
462+
return { '_value': f'a list with at least {min_length} elements'}
463+
if max_length is not None and len(value) > max_length:
464+
return { '_value': f'a list with at most {max_length} elements'}
465+
459466
if not empty and not value: # Do we have an unacceptable empty list?
460467
return { '_value': 'a scalar or a non-empty list'}
461468

@@ -466,6 +473,9 @@ def transform_to_list(value: typing.Any) -> list:
466473
# is tied to the argument value passed to this function (it's a magic
467474
# transfer of hidden variables)
468475
#
476+
if min_length is not None and min_length >= 2:
477+
return { '_type': f'a list with at least {min_length} elements' }
478+
469479
if isinstance(value,(str,int,float,bool)):
470480
return { '_valid': True, '_transform': transform_to_list }
471481

tests/coverage/errors/attr-empty-list.log

Lines changed: 0 additions & 4 deletions
This file was deleted.

tests/coverage/errors/attr-empty-list.yml

Lines changed: 0 additions & 13 deletions
This file was deleted.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
IncorrectType in nodes: attribute 'nodes.n1.no_empty[1]' must be a scalar or a list, found NoneType
2+
... use 'netlab show attributes node' to display valid attributes
3+
IncorrectType in nodes: attribute 'nodes.n1.not_empty[1]' must be a scalar or a list, found NoneType
4+
IncorrectValue in nodes: attribute 'nodes.n1.not_empty[4]' must be a scalar or a non-empty list
5+
IncorrectType in nodes: attribute 'nodes.n1.empty[1]' must be a scalar or a list, found NoneType
6+
IncorrectType in nodes: attribute 'nodes.n1.regular[3]' must be a scalar or a list, found dictionary
7+
IncorrectType in nodes: attribute 'nodes.n1.min2[1]' must be a list with at least 2 elements, found str
8+
IncorrectValue in nodes: attribute 'nodes.n1.min2[2]' must be a list with at least 2 elements
9+
IncorrectValue in nodes: attribute 'nodes.n1.max3[3]' must be a list with at most 3 elements
10+
Fatal error in netlab: Cannot proceed beyond this point due to errors, exiting
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
module: [ isis, sr ]
2+
defaults.device: none
3+
4+
defaults.attributes.node:
5+
no_empty:
6+
type: list
7+
_subtype:
8+
type: list
9+
create_empty: False
10+
not_empty:
11+
type: list
12+
_subtype:
13+
type: list
14+
empty: False
15+
empty:
16+
type: list
17+
_subtype:
18+
type: list
19+
empty: True
20+
create_empty: False
21+
regular:
22+
type: list
23+
_subtype: list
24+
regular_force:
25+
type: list
26+
_subtype:
27+
type: list
28+
make_list: True
29+
min2:
30+
type: list
31+
_subtype:
32+
type: list
33+
min_length: 2
34+
max3:
35+
type: list
36+
_subtype:
37+
type: list
38+
max_length: 3
39+
nodes:
40+
n1:
41+
no_empty:
42+
- Null # Invalid, will not create an empty list
43+
- a
44+
- [ a ]
45+
not_empty:
46+
- Null # Invalid, the list cannot be empty
47+
- False
48+
- a
49+
- [] # Invalid, the list cannot be empty
50+
- [ b ]
51+
empty:
52+
- Null # Invalid, empty list is not created
53+
- False # ... but it can have a single False element
54+
- []
55+
- a
56+
- [ b ]
57+
regular:
58+
- a
59+
- [ b ]
60+
- { c: d } # Dict cannot be turned into a list
61+
regular_force: # Everything OK, dict is forced into a list
62+
- Null
63+
- a
64+
- [ b ]
65+
- { c: d }
66+
min2:
67+
- a # Cannot convert scalar into list with at least 2 elements
68+
- [ a ] # Too short
69+
- [ a, b ]
70+
max3:
71+
- a
72+
- [ a ]
73+
- [ a, b, c, d ] # Too long

0 commit comments

Comments
 (0)