forked from ChelseaKR/ctdl-validate
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathctid.py
More file actions
69 lines (55 loc) · 2.36 KB
/
Copy pathctid.py
File metadata and controls
69 lines (55 loc) · 2.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
"""CTID grammar, from the published definition.
Source ("About the CTID", https://credreg.net/ctdl/ctid, retrieved
2026-08-06): "Each CTID is made up of a standard UUID v4 prefixed with ce-",
"in the form 8-4-4-4-12", "a total of 39 characters (34 hexadecimal
characters and 5 hyphens)". Example: ce-e8a41a52-6ff6-48f0-9872-889c87b093b7.
"""
from __future__ import annotations
import re
from dataclasses import dataclass
_HEX_GROUPS = r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
#: The published grammar, strictly: lower case, ce- prefix, 8-4-4-4-12.
CTID_RE = re.compile(rf"^ce-{_HEX_GROUPS}$")
#: Same shape, any case (used to separate case problems from shape problems).
CTID_ANY_CASE_RE = re.compile(rf"^ce-{_HEX_GROUPS}$", re.IGNORECASE)
#: A UUID with no ce- prefix: the "bare UUID where a CTID belongs" bug class.
BARE_UUID_ANY_CASE_RE = re.compile(rf"^{_HEX_GROUPS}$", re.IGNORECASE)
EXPECTED_GRAMMAR = (
"ce- followed by a UUID v4 in 8-4-4-4-12 form, 39 characters, lower case "
"hexadecimal, e.g. ce-e8a41a52-6ff6-48f0-9872-889c87b093b7"
)
REGISTRY_RESOURCE_PREFIX = "https://credentialengineregistry.org/resources/"
REGISTRY_GRAPH_PREFIX = "https://credentialengineregistry.org/graph/"
#: Character offsets within a shape-valid CTID ("ce-" + UUID): the UUID
#: version nibble and variant nibble per RFC 4122 section 4.1.1/4.1.3.
_VERSION_OFFSET = 17
_VARIANT_OFFSET = 22
@dataclass(frozen=True)
class CtidShape:
matches_shape: bool
lowercase: bool
uuid_v4: bool
bare_uuid: bool
def classify_ctid(value: str) -> CtidShape:
if CTID_ANY_CASE_RE.match(value):
lowered = value.lower()
version_ok = lowered[_VERSION_OFFSET] == "4"
variant_ok = lowered[_VARIANT_OFFSET] in "89ab"
return CtidShape(
matches_shape=True,
lowercase=value == lowered,
uuid_v4=version_ok and variant_ok,
bare_uuid=False,
)
return CtidShape(
matches_shape=False,
lowercase=value == value.lower(),
uuid_v4=False,
bare_uuid=bool(BARE_UUID_ANY_CASE_RE.match(value)),
)
def registry_uri_tail(value: str) -> str | None:
"""The CTID portion of a Registry resource/graph URI, or None."""
for prefix in (REGISTRY_RESOURCE_PREFIX, REGISTRY_GRAPH_PREFIX):
if value.startswith(prefix):
return value[len(prefix) :]
return None