|
9 | 9 | from pydantic import GetCoreSchemaHandler |
10 | 10 | from pydantic_core import PydanticCustomError, core_schema |
11 | 11 |
|
| 12 | +MINIMUM_LENGTH: int = 14 |
| 13 | +ALLOWED_CHUNK_COUNTS: tuple[int, int, int] = (6, 8, 20) |
| 14 | + |
12 | 15 |
|
13 | 16 | class MacAddress(str): |
14 | 17 | """Represents a MAC address and provides methods for conversion, validation, and serialization. |
@@ -63,36 +66,41 @@ def _validate(cls, __input_value: str, _: Any) -> str: |
63 | 66 | @staticmethod |
64 | 67 | def validate_mac_address(value: bytes) -> str: |
65 | 68 | """Validate a MAC Address from the provided byte value.""" |
66 | | - string = value.decode() |
67 | | - if len(string) < 14: |
| 69 | + raw = value.decode() |
| 70 | + if len(raw) < MINIMUM_LENGTH: |
68 | 71 | raise PydanticCustomError( |
69 | 72 | 'mac_address_len', |
70 | 73 | 'Length for a {mac_address} MAC address must be {required_length}', |
71 | | - {'mac_address': string, 'required_length': 14}, |
| 74 | + {'mac_address': raw, 'required_length': MINIMUM_LENGTH}, |
72 | 75 | ) |
73 | | - for sep, partbytes in ((':', 2), ('-', 2), ('.', 4)): |
74 | | - if sep in string: |
75 | | - parts = string.split(sep) |
76 | | - if any(len(part) != partbytes for part in parts): |
77 | | - raise PydanticCustomError( |
78 | | - 'mac_address_format', |
79 | | - f'Must have the format xx{sep}xx{sep}xx{sep}xx{sep}xx{sep}xx', |
80 | | - ) |
81 | | - if len(parts) * partbytes // 2 not in (6, 8, 20): |
82 | | - raise PydanticCustomError( |
83 | | - 'mac_address_format', |
84 | | - 'Length for a {mac_address} MAC address must be {required_length}', |
85 | | - {'mac_address': string, 'required_length': (6, 8, 20)}, |
86 | | - ) |
87 | | - mac_address = [] |
| 76 | + |
| 77 | + for seperator, chunk_len in ((':', 2), ('-', 2), ('.', 4)): |
| 78 | + if seperator not in raw: |
| 79 | + continue |
| 80 | + |
| 81 | + parts = raw.split(seperator) |
| 82 | + if any(len(p) != chunk_len for p in parts): |
| 83 | + raise PydanticCustomError( |
| 84 | + 'mac_address_format', |
| 85 | + f'Must have the format xx{seperator}xx{seperator}xx{seperator}xx{seperator}xx{seperator}xx', |
| 86 | + ) |
| 87 | + |
| 88 | + total_bytes = (len(parts) * chunk_len) // 2 |
| 89 | + if total_bytes not in ALLOWED_CHUNK_COUNTS: |
| 90 | + raise PydanticCustomError( |
| 91 | + 'mac_address_format', |
| 92 | + 'Length for a {mac_address} MAC address must be {required_length}', |
| 93 | + {'mac_address': raw, 'required_length': ALLOWED_CHUNK_COUNTS}, |
| 94 | + ) |
| 95 | + |
| 96 | + try: |
| 97 | + mac_bytes: list[int] = [] |
88 | 98 | for part in parts: |
89 | | - for idx in range(0, partbytes, 2): |
90 | | - try: |
91 | | - byte_value = int(part[idx : idx + 2], 16) |
92 | | - except ValueError as exc: |
93 | | - raise PydanticCustomError('mac_address_format', 'Unrecognized format') from exc |
94 | | - else: |
95 | | - mac_address.append(byte_value) |
96 | | - return ':'.join(f'{b:02x}' for b in mac_address) |
97 | | - else: |
98 | | - raise PydanticCustomError('mac_address_format', 'Unrecognized format') |
| 99 | + for i in range(0, chunk_len, 2): |
| 100 | + mac_bytes.append(int(part[i : i + 2], base=16)) |
| 101 | + except ValueError as exc: |
| 102 | + raise PydanticCustomError('mac_address_format', 'Unrecognized format') from exc |
| 103 | + |
| 104 | + return ':'.join(f'{b:02x}' for b in mac_bytes) |
| 105 | + |
| 106 | + raise PydanticCustomError('mac_address_format', 'Unrecognized format') |
0 commit comments