Skip to content

Commit dc88632

Browse files
authored
IFC-1883: Validate inherited relationships fields match (#7343)
* IFC-1883: Validate inherited relationships fields * refactor tests, rename variables * check for equal delete actions * refactor for readability * update matching properties
1 parent 0deed2d commit dc88632

File tree

2 files changed

+388
-0
lines changed

2 files changed

+388
-0
lines changed

backend/infrahub/core/schema/schema_branch.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,7 @@ def process_validate(self) -> None:
547547
self.validate_parent_component()
548548
self.validate_human_friendly_id()
549549
self.validate_required_relationships()
550+
self.validate_inherited_relationships_fields()
550551

551552
def process_post_validation(self) -> None:
552553
self.cleanup_inherited_elements()
@@ -1327,6 +1328,81 @@ def validate_count_against_cardinality(self) -> None:
13271328
f"{node.kind}: Relationship {rel.name!r} max_count must be 0 or greater than 1 when cardinality is MANY"
13281329
)
13291330

1331+
def validate_inherited_relationships_fields(self) -> None:
1332+
for name in self.node_names:
1333+
node_schema = self.get(name=name, duplicate=False)
1334+
if not node_schema.inherit_from:
1335+
continue
1336+
1337+
self.validate_node_inherited_relationship_fields(node_schema)
1338+
1339+
def validate_node_inherited_relationship_fields(self, node_schema: NodeSchema) -> None:
1340+
generics = [self.get(name=node_name, duplicate=False) for node_name in node_schema.inherit_from]
1341+
relationship_names = [node.relationship_names for node in generics]
1342+
related_relationship_names = set().union(
1343+
*[
1344+
set(relationship_name_a) & set(relationship_name_b)
1345+
for index, relationship_name_a in enumerate(relationship_names)
1346+
for relationship_name_b in relationship_names[index + 1 :]
1347+
]
1348+
)
1349+
# Check that the relationship properties match
1350+
# for every generic node in generics list having related relationship names
1351+
for index, generic_a in enumerate(generics):
1352+
for generic_b in generics[index + 1 :]:
1353+
for relationship_name in related_relationship_names:
1354+
try:
1355+
relationship_a = generic_a.get_relationship(name=relationship_name)
1356+
relationship_b = generic_b.get_relationship(name=relationship_name)
1357+
except ValueError:
1358+
continue
1359+
1360+
matched, _property = self._check_relationship_properties_match(
1361+
relationship_a=relationship_a, relationship_b=relationship_b
1362+
)
1363+
if not matched:
1364+
raise ValueError(
1365+
f"{node_schema.kind} inherits from '{generic_a.kind}' & '{generic_b.kind}'"
1366+
f" with different '{_property}' on the '{relationship_name}' relationship"
1367+
)
1368+
1369+
def _check_relationship_properties_match(
1370+
self, relationship_a: RelationshipSchema, relationship_b: RelationshipSchema
1371+
) -> tuple[bool, str | None]:
1372+
compulsorily_matching_properties = (
1373+
"name",
1374+
"peer",
1375+
"kind",
1376+
"identifier",
1377+
"cardinality",
1378+
"min_count",
1379+
"max_count",
1380+
"common_parent",
1381+
"common_relatives",
1382+
"optional",
1383+
"branch",
1384+
"direction",
1385+
"on_delete",
1386+
"read_only",
1387+
"hierarchical",
1388+
"allow_override",
1389+
)
1390+
for _property in compulsorily_matching_properties:
1391+
if not hasattr(relationship_a, _property) or not hasattr(relationship_b, _property):
1392+
continue
1393+
1394+
equal_delete_actions = (None, RelationshipDeleteBehavior.NO_ACTION)
1395+
if (
1396+
_property == "on_delete"
1397+
and getattr(relationship_a, _property) in equal_delete_actions
1398+
and getattr(relationship_b, _property) in equal_delete_actions
1399+
):
1400+
continue
1401+
1402+
if getattr(relationship_a, _property) != getattr(relationship_b, _property):
1403+
return False, _property
1404+
return True, None
1405+
13301406
def process_dropdowns(self) -> None:
13311407
for name in self.all_names:
13321408
node = self.get(name=name, duplicate=False)

0 commit comments

Comments
 (0)