Skip to content

Commit 0558632

Browse files
committed
feat(model): support v2 schema containers and validate model versions
- Add supported version check (v1, v2) with explicit error for unsupported versions - Parse `interactions` as an alias for relations/communication - Extend container parsing for v2: require `kind`, validate `ContainerKind`, and support nested `contains` children
1 parent 33a28b7 commit 0558632

1 file changed

Lines changed: 41 additions & 4 deletions

File tree

pacta/model/loader.py

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
from pathlib import Path
55
from typing import Any
66

7-
from pacta.model.types import ArchitectureModel, CodeMapping, Container, Context, Layer, Relation
7+
from pacta.model.types import ArchitectureModel, CodeMapping, Container, ContainerKind, Context, Layer, Relation
8+
9+
_SUPPORTED_VERSIONS = {1, 2}
810

911

1012
@dataclass(frozen=True, slots=True)
@@ -37,6 +39,11 @@ def load(self, path: Path) -> ArchitectureModel:
3739
version = data.get("version", 1)
3840
if not isinstance(version, int):
3941
raise ModelLoadError(code="invalid_version", message="'version' must be an integer.")
42+
if version not in _SUPPORTED_VERSIONS:
43+
raise ModelLoadError(
44+
code="unsupported_version",
45+
message=f"Unsupported model version: {version}. Supported: {sorted(_SUPPORTED_VERSIONS)}.",
46+
)
4047

4148
metadata: dict[str, Any] = {}
4249
# carry system into metadata (README has system section)
@@ -51,9 +58,13 @@ def load(self, path: Path) -> ArchitectureModel:
5158
metadata[k] = v
5259

5360
contexts = self._parse_contexts(data.get("contexts"))
54-
containers = self._parse_containers(data.get("containers"))
61+
containers = self._parse_containers(data.get("containers"), version=version)
5562

56-
raw_relations = data.get("relations") if data.get("relations") is not None else data.get("communication") or ()
63+
raw_relations = (
64+
data.get("relations")
65+
if data.get("relations") is not None
66+
else data.get("interactions") or data.get("communication") or ()
67+
)
5768

5869
relations = self._parse_relations(raw_relations)
5970

@@ -123,7 +134,7 @@ def _parse_contexts(self, raw: Any) -> dict[str, Context]:
123134
)
124135
return out
125136

126-
def _parse_containers(self, raw: Any) -> dict[str, Container]:
137+
def _parse_containers(self, raw: Any, *, version: int = 1) -> dict[str, Container]:
127138
if raw is None:
128139
return {}
129140

@@ -147,13 +158,39 @@ def _parse_containers(self, raw: Any) -> dict[str, Container]:
147158
else:
148159
tags_tuple = ()
149160

161+
# v2 fields
162+
kind: ContainerKind | None = None
163+
children: dict[str, Container] = {}
164+
if version >= 2:
165+
raw_kind = spec.get("kind")
166+
if raw_kind is None:
167+
raise ModelLoadError(
168+
code="missing_container_kind",
169+
message=f"Container '{cid}' must have a 'kind' field in v2 schema.",
170+
)
171+
try:
172+
kind = ContainerKind(str(raw_kind))
173+
except ValueError:
174+
raise ModelLoadError(
175+
code="invalid_container_kind",
176+
message=(
177+
f"Container '{cid}' has invalid kind '{raw_kind}'. "
178+
f"Must be one of: {', '.join(k.value for k in ContainerKind)}."
179+
),
180+
)
181+
raw_contains = spec.get("contains")
182+
if raw_contains is not None:
183+
children = self._parse_containers(raw_contains, version=version)
184+
150185
out[cid] = Container(
151186
id=cid,
152187
name=spec.get("name"),
153188
context=spec.get("context"),
154189
description=spec.get("description"),
155190
code=code,
156191
tags=tags_tuple,
192+
kind=kind,
193+
children=children,
157194
)
158195

159196
return out

0 commit comments

Comments
 (0)