Skip to content

Commit 9429db7

Browse files
author
Steve Ayers
committed
Docs
1 parent 516a943 commit 9429db7

2 files changed

Lines changed: 87 additions & 131 deletions

File tree

protovalidate/internal/extra_func.py

Lines changed: 87 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -29,28 +29,17 @@
2929

3030

3131
def is_ip(val: celtypes.Value, ver: typing.Optional[celtypes.Value] = None) -> celpy.Result:
32-
"""Validate whether a given string is a valid IP address according to an optional IP version.
32+
"""Return True if the string is an IPv4 or IPv6 address, optionally limited to a specific version.
33+
34+
Version 0 or None means either 4 or 6. Passing a version other than 0, 4, or 6 always returns False.
3335
3436
IPv4 addresses are expected in the dotted decimal format, for example "192.168.5.21".
3537
IPv6 addresses are expected in their text representation, for example "::1" or "2001:0DB8:ABCD:0012::0".
3638
3739
Both formats are well-defined in the internet standard RFC 3986. Zone
3840
identifiers for IPv6 addresses (for example "fe80::a%en1") are supported.
3941
40-
Args:
41-
val (celTypes.Value): The string to validate.
42-
version (typing.Optional[celtypes.Value]): An optional version to use for validating the IP address.
43-
Passing None for a version of 0 means either 4 or 6.
44-
Passing a version other than 0, 4, or 6 always returns False.
45-
46-
Returns:
47-
True if the string is an IPv4 or IPv6 address, optionally limited to a specific version.
48-
49-
Raises:
50-
celpy.CELEvalError: If val is not an instance of celtypes.StringType or
51-
if version is not an instance of celtypes.IntType and is not None.
5242
"""
53-
5443
if not isinstance(val, celtypes.StringType):
5544
msg = "invalid argument, expected string"
5645
raise celpy.CELEvalError(msg)
@@ -80,6 +69,25 @@ def _is_ip(string: str, version: int) -> bool:
8069

8170

8271
def is_ip_prefix(val: celtypes.Value, *args) -> celpy.Result:
72+
"""Return True if the string is a valid IP with prefix length, optionally
73+
limited to a specific version (v4 or v6), and optionally requiring the host
74+
portion to be all zeros.
75+
76+
An address prefix divides an IP address into a network portion, and a host portion.
77+
The prefix length specifies how many bits the network portion has.
78+
For example, the IPv6 prefix "2001:db8:abcd:0012::0/64" designates the
79+
left-most 64 bits as the network prefix. The range of the network is 2**64
80+
addresses, from 2001:db8:abcd:0012::0 to 2001:db8:abcd:0012:ffff:ffff:ffff:ffff.
81+
82+
An address prefix may include a specific host address, for example
83+
"2001:db8:abcd:0012::1f/64". With strict = true, this is not permitted. The
84+
host portion must be all zeros, as in "2001:db8:abcd:0012::0/64".
85+
86+
The same principle applies to IPv4 addresses. "192.168.1.0/24" designates
87+
the first 24 bits of the 32-bit IPv4 as the network prefix.
88+
89+
"""
90+
8391
if not isinstance(val, celtypes.StringType):
8492
msg = "invalid argument, expected string or bytes"
8593
raise celpy.CELEvalError(msg)
@@ -118,7 +126,7 @@ def _is_ip_prefix(string: str, version: int, *, strict=False) -> bool:
118126

119127

120128
def is_email(string: celtypes.Value) -> celpy.Result:
121-
"""Return true if the string is an email address, for example "foo@example.com".
129+
"""Return True if the string is an email address, for example "foo@example.com".
122130
123131
Conforms to the definition for a valid email address from the HTML standard.
124132
Note that this standard willfully deviates from RFC 5322, which allows many
@@ -134,7 +142,7 @@ def is_email(string: celtypes.Value) -> celpy.Result:
134142

135143

136144
def is_uri(string: celtypes.Value) -> celpy.Result:
137-
"""Return true if the string is a URI, for example "https://example.com/foo/bar?baz=quux#frag".
145+
"""Return True if the string is a URI, for example "https://example.com/foo/bar?baz=quux#frag".
138146
139147
URI is defined in the internet standard RFC 3986.
140148
Zone Identifiers in IPv6 address literals are supported (RFC 6874).
@@ -148,7 +156,7 @@ def is_uri(string: celtypes.Value) -> celpy.Result:
148156

149157

150158
def is_uri_ref(string: celtypes.Value) -> celpy.Result:
151-
"""Return true if the string is a URI Reference - a URI such as "https://example.com/foo/bar?baz=quux#frag" or
159+
"""Return True if the string is a URI Reference - a URI such as "https://example.com/foo/bar?baz=quux#frag" or
152160
a Relative Reference such as "./foo/bar?query".
153161
154162
URI, URI Reference, and Relative Reference are defined in the internet standard RFC 3986.
@@ -163,13 +171,25 @@ def is_uri_ref(string: celtypes.Value) -> celpy.Result:
163171

164172

165173
def is_hostname(val: celtypes.Value) -> celpy.Result:
174+
"""Returns True if the string is a valid hostname, for example "foo.example.com".
175+
176+
A valid hostname follows the rules below:
177+
- The name consists of one or more labels, separated by a dot (".").
178+
- Each label can be 1 to 63 alphanumeric characters.
179+
- A label can contain hyphens ("-"), but must not start or end with a hyphen.
180+
- The right-most label must not be digits only.
181+
- The name can have a trailing dot, for example "foo.example.com.".
182+
- The name can be 253 characters at most, excluding the optional trailing dot.
183+
184+
"""
166185
if not isinstance(val, celtypes.StringType):
167186
msg = "invalid argument, expected string"
168187
raise celpy.CELEvalError(msg)
169188
return celtypes.BoolType(_is_hostname(val))
170189

171190

172191
def _is_hostname(val: str) -> bool:
192+
"""Internal implementation"""
173193
if len(val) > 253:
174194
return False
175195

@@ -219,6 +239,18 @@ def _is_port(val: str) -> bool:
219239

220240

221241
def is_host_and_port(string: celtypes.Value, port_required: celtypes.Value) -> celpy.Result:
242+
"""Return True if the string is a valid host/port pair, for example "example.com:8080".
243+
244+
If the argument `port_required` is True, the port is required. If the argument
245+
is False, the port is optional.
246+
247+
The host can be one of:
248+
- An IPv4 address in dotted decimal format, for example "192.168.0.1".
249+
- An IPv6 address enclosed in square brackets, for example "[::1]".
250+
- A hostname, for example "example.com".
251+
252+
The port is separated by a colon. It must be non-empty, with a decimal number in the range of 0-65535, inclusive.
253+
"""
222254
if not isinstance(string, celtypes.StringType):
223255
msg = "invalid argument, expected string"
224256
raise celpy.CELEvalError(msg)
@@ -287,18 +319,15 @@ def unique(val: celtypes.Value) -> celpy.Result:
287319

288320

289321
class Ipv4:
290-
"""a class"""
322+
"""Ipv4 is a class used to parse a given string to determine if it is a valid IPv4 address or address prefix."""
291323

292324
_string: str
293325
_index: int
294326
_octets: bytearray
295327
_prefix_len: int
296328

297329
def __init__(self, string: str):
298-
"""ipv4
299-
300-
Args:
301-
"""
330+
"""Initialize an Ipv4 validation class with a given string."""
302331

303332
super().__init__()
304333
self._string = string
@@ -317,23 +346,21 @@ def address_prefix(self) -> bool:
317346
)
318347

319348
def get_bits(self) -> int:
320-
"""Get the bits of an address parsed through address() or address_prefix()
349+
"""Return the 32-bit value of an address parsed through address() or address_prefix().
350+
351+
Return -1 if no address was parsed successfully.
321352
322-
Returns:
323-
The 32-bit value if address was parsed successfully. 0 if not successful.
324353
"""
325354
if len(self._octets) != 4:
326-
return 0
355+
return -1
327356

328357
return (self._octets[0] << 24) | (self._octets[1] << 16) | (self._octets[2] << 8) | self._octets[3]
329358

330359
def is_prefix_only(self) -> bool:
331-
"""Determines TODO
360+
"""Return True if all bits to the right of the prefix-length are all zeros.
332361
333-
Behavior is undefined if address_prefix() has not been called before or has returned false.
362+
Behavior is undefined if address_prefix() has not been called before, or has returned False.
334363
335-
Returns:
336-
True if all bits to the right of the prefix-length are all zeros. False otherwise.
337364
"""
338365
bits = self.get_bits()
339366

@@ -348,6 +375,8 @@ def is_prefix_only(self) -> bool:
348375
return bits == masked
349376

350377
def __prefix_length(self) -> bool:
378+
"""Store value in `prefix_len`"""
379+
351380
start = self._index
352381

353382
while True:
@@ -436,11 +465,12 @@ def __dec_octet(self) -> bool:
436465
return False
437466

438467
def __digit(self) -> bool:
439-
"""Reports whether the current position is a digit.
468+
"""Report whether the current position is a digit.
440469
441470
Method parses the rule:
442471
443-
DIGIT = %x30-39 ; 0-9
472+
DIGIT = %x30-39 ; 0-9
473+
444474
"""
445475

446476
if self._index >= len(self._string):
@@ -458,11 +488,7 @@ def __take(self, char: str) -> bool:
458488
459489
If char is at the current index, increment the index.
460490
461-
Returns:
462-
True if char is at the current index. False if char is not at the
463-
current index or the end of string has been reached.
464491
"""
465-
466492
if self._index >= len(self._string):
467493
return False
468494

@@ -474,34 +500,20 @@ def __take(self, char: str) -> bool:
474500

475501

476502
class Ipv6:
477-
"""a class"""
503+
"""Ipv6 is a class used to parse a given string to determine if it is a valid IPv6 address or address prefix."""
478504

479505
_string: str
480506
_index: int
481-
_pieces: list[int]
482-
_double_colon_at: int
507+
_pieces: list[int] # 16-bit pieces found
508+
_double_colon_at: int # Number of 16-bit pieces found when double colon was found.
483509
_double_colon_seen: bool
484-
_dotted_raw: str
485-
_dotted_addr: typing.Optional[Ipv4]
510+
_dotted_raw: str # Dotted notation for right-most 32 bits.
511+
_dotted_addr: typing.Optional[Ipv4] # Dotted notation successfully parsed as Ipv4.
486512
_zone_id_found: bool
487-
_prefix_len: int
513+
_prefix_len: int # 0 -128
488514

489515
def __init__(self, string: str):
490-
"""ipv6
491-
492-
Args:
493-
494-
Attributes:
495-
_string (str): The string to parse.
496-
_index (int): The index.
497-
_pieces (list[int]): 16-bit pieces found.
498-
_double_colon_at (bool): Number of 16-bit pieces found when double colon was found.
499-
_double_colon_seen (bool): Whether a double colon has been seen in string.
500-
_dotted_raw (str): Dotted notation for right-most 32 bits.
501-
_dotted_addr (typing.Optional[Ipv4]): Dotted notation successfully parsed as Ipv4.
502-
_zone_id_found (bool): Whether a zone ID has been found in string.
503-
_prefix_len (int): 0 - 128
504-
"""
516+
"""Initialize a URI validation class with a given string."""
505517

506518
super().__init__()
507519
self._string = string
@@ -514,12 +526,11 @@ def __init__(self, string: str):
514526
self._zone_id_found = False
515527

516528
def get_bits(self) -> int:
517-
"""Get the bits of an address parsed through address() or address_prefix() as a 128-bit integer.
529+
"""Return the 128-bit value of an address parsed through address() or address_prefix().
518530
519-
Returns:
520-
The 128-bit value if address was parsed successfully. 0 if no address was parsed successfully.
521-
"""
531+
Return 0 if no address was parsed successfully.
522532
533+
"""
523534
p16 = self._pieces
524535

525536
# Handle dotted decimal, add to p16
@@ -555,12 +566,10 @@ def get_bits(self) -> int:
555566
)
556567

557568
def is_prefix_only(self) -> bool:
558-
"""Determine whether string is an ipv6 prefix only.
569+
"""Return True if all bits to the right of the prefix-length are all zeros.
559570
560-
Behavior is undefined if address_prefix() has not been called before.
571+
Behavior is undefined if address_prefix() has not been called before, or has returned False.
561572
562-
Returns:
563-
True if all bits to the right of the prefix-length are all zeros. False otherwise.
564573
"""
565574
bits = self.get_bits()
566575
mask: int
@@ -594,6 +603,7 @@ def address_prefix(self) -> bool:
594603
)
595604

596605
def __prefix_length(self) -> bool:
606+
"""Store value in `prefix_len`."""
597607
start = self._index
598608

599609
while True:
@@ -629,7 +639,7 @@ def __prefix_length(self) -> bool:
629639
return False
630640

631641
def __address_part(self) -> bool:
632-
"""Store the dotted notation for right-most 32 bits in dottedRaw / dottedAddr if found."""
642+
"""Store dotted notation for right-most 32 bits in dotted_raw / dotted_addr if found."""
633643

634644
while True:
635645
if self._index >= len(self._string):
@@ -671,14 +681,12 @@ def __address_part(self) -> bool:
671681
def __zone_id(self) -> bool:
672682
"""Determine whether string contains a zoneID.
673683
674-
Method parses the rule from RFC 6874:
684+
There is no definition for the character set allowed in the zone
685+
identifier. RFC 4007 permits basically any non-null string.
675686
676-
ZoneID = 1*( unreserved / pct-encoded )
687+
RFC 6874: ZoneID = 1*( unreserved / pct-encoded )
677688
678-
There is no definition for the character set allowed in the zone identifier.
679-
RFC 4007 permits basically any non-null string.
680689
"""
681-
682690
start = self._index
683691

684692
if self.__take("%"):
@@ -699,7 +707,7 @@ def __dotted(self) -> bool:
699707
700708
Method parses the rule:
701709
702-
1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT
710+
1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT
703711
704712
Stores match in _dotted_raw.
705713
"""
@@ -726,7 +734,7 @@ def __h16(self) -> bool:
726734
727735
Method parses the rule:
728736
729-
h16 = 1*4HEXDIG
737+
h16 = 1*4HEXDIG
730738
731739
Stores 16-bit value in _pieces.
732740
"""
@@ -765,9 +773,9 @@ def __hex_dig(self) -> bool:
765773
766774
Method parses the rule:
767775
768-
HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
769-
"""
776+
HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
770777
778+
"""
771779
if self._index >= len(self._string):
772780
return False
773781

@@ -785,9 +793,9 @@ def __digit(self) -> bool:
785793
786794
Method parses the rule:
787795
788-
DIGIT = %x30-39 ; 0-9
789-
"""
796+
DIGIT = %x30-39 ; 0-9
790797
798+
"""
791799
if self._index >= len(self._string):
792800
return False
793801

@@ -803,11 +811,7 @@ def __take(self, char: str) -> bool:
803811
804812
If char is at the current index, increment the index.
805813
806-
Returns:
807-
True if char is at the current index. False if char is not at the
808-
current index or the end of string has been reached.
809814
"""
810-
811815
if self._index >= len(self._string):
812816
return False
813817

@@ -1441,7 +1445,7 @@ def __pct_encoded(self) -> bool:
14411445
14421446
pct-encoded = "%" HEXDIG HEXDIG
14431447
1444-
Sets `_pct_encoded_found` to true if a valid triplet was found
1448+
Sets `_pct_encoded_found` to True if a valid triplet was found
14451449
14461450
"""
14471451
start = self._index
@@ -1554,6 +1558,7 @@ def __take(self, char: str) -> bool:
15541558
"""Take the given char at the current index.
15551559
15561560
If char is at the current index, increment the index.
1561+
15571562
"""
15581563
if self._index >= len(self._string):
15591564
return False

0 commit comments

Comments
 (0)