Skip to content

Commit 5079204

Browse files
committed
feat(calm-models): add extension parsing to CalmRelationship
1 parent 463fd69 commit 5079204

4 files changed

Lines changed: 114 additions & 12 deletions

File tree

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,36 @@
11
package org.finos.calm.model;
22

3+
import com.fasterxml.jackson.core.JsonProcessingException;
4+
import com.fasterxml.jackson.databind.JsonNode;
35
import com.fasterxml.jackson.databind.ObjectMapper;
46
import org.finos.calm.model.canonical.CalmMetadataHelper;
57
import org.finos.calm.model.canonical.CalmRelationshipSchema;
68

79
import java.util.Map;
810
import java.util.Optional;
911

10-
public record CalmRelationship(
11-
String uniqueId,
12-
CalmRelationshipType relationshipType,
13-
Optional<String> description,
14-
Optional<CalmProtocol> protocol,
15-
Optional<CalmControls> controls,
16-
Map<String, Object> metadata
17-
) {
18-
public CalmRelationship {
19-
metadata = metadata == null ? Map.of() : Map.copyOf(metadata);
12+
public final class CalmRelationship {
13+
private final String uniqueId;
14+
private final CalmRelationshipType relationshipType;
15+
private final Optional<String> description;
16+
private final Optional<CalmProtocol> protocol;
17+
private final Optional<CalmControls> controls;
18+
private final Map<String, Object> metadata;
19+
private final Map<String, JsonNode> extensions;
20+
private final ObjectMapper mapper;
21+
22+
CalmRelationship(String uniqueId, CalmRelationshipType relationshipType,
23+
Optional<String> description, Optional<CalmProtocol> protocol,
24+
Optional<CalmControls> controls, Map<String, Object> metadata,
25+
Map<String, JsonNode> extensions, ObjectMapper mapper) {
26+
this.uniqueId = uniqueId;
27+
this.relationshipType = relationshipType;
28+
this.description = description;
29+
this.protocol = protocol;
30+
this.controls = controls;
31+
this.metadata = Map.copyOf(metadata);
32+
this.extensions = Map.copyOf(extensions);
33+
this.mapper = mapper;
2034
}
2135

2236
static CalmRelationship from(CalmRelationshipSchema schema, ObjectMapper mapper) {
@@ -29,11 +43,40 @@ static CalmRelationship from(CalmRelationshipSchema schema, ObjectMapper mapper)
2943
Optional.ofNullable(schema.getDescription()),
3044
Optional.ofNullable(schema.getProtocol()),
3145
controls,
32-
CalmMetadataHelper.flatten(schema.getMetadataRaw(), mapper)
46+
CalmMetadataHelper.flatten(schema.getMetadataRaw(), mapper),
47+
schema.getExtensions(),
48+
mapper
3349
);
3450
}
3551

52+
public String uniqueId() { return uniqueId; }
53+
public CalmRelationshipType relationshipType() { return relationshipType; }
54+
public Optional<String> description() { return description; }
55+
public Optional<CalmProtocol> protocol() { return protocol; }
56+
public Optional<CalmControls> controls() { return controls; }
57+
3658
public Optional<Object> getMetadata(String key) {
3759
return Optional.ofNullable(metadata.get(key));
3860
}
61+
62+
public <T> Optional<T> parseMetadata(Class<T> type) {
63+
if (metadata.isEmpty()) return Optional.empty();
64+
try {
65+
return Optional.of(mapper.convertValue(metadata, type));
66+
} catch (Exception e) {
67+
throw new CalmExtensionParseException(
68+
"Failed to parse relationship metadata as " + type.getSimpleName(), e);
69+
}
70+
}
71+
72+
public <T> Optional<T> parseExtension(String name, Class<T> type) {
73+
JsonNode node = extensions.get(name);
74+
if (node == null) return Optional.empty();
75+
try {
76+
return Optional.of(mapper.treeToValue(node, type));
77+
} catch (JsonProcessingException e) {
78+
throw new CalmExtensionParseException(
79+
"Failed to parse extension '" + name + "' as " + type.getSimpleName(), e);
80+
}
81+
}
3982
}

calm-models/src/main/java/org/finos/calm/model/canonical/CalmRelationshipSchema.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package org.finos.calm.model.canonical;
22

3+
import com.fasterxml.jackson.annotation.JsonAnySetter;
34
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
45
import com.fasterxml.jackson.annotation.JsonProperty;
56
import com.fasterxml.jackson.databind.JsonNode;
67
import org.finos.calm.model.CalmProtocol;
78

9+
import java.util.HashMap;
10+
import java.util.Map;
11+
812
@JsonIgnoreProperties(ignoreUnknown = true)
913
public class CalmRelationshipSchema {
1014
@JsonProperty("unique-id") private String uniqueId;
@@ -13,11 +17,18 @@ public class CalmRelationshipSchema {
1317
private CalmProtocol protocol;
1418
private CalmControlsSchema controls;
1519
@JsonProperty("metadata") private JsonNode metadataRaw;
20+
private final Map<String, JsonNode> extensions = new HashMap<>();
21+
22+
@JsonAnySetter
23+
void setExtension(String key, JsonNode value) {
24+
extensions.put(key, value);
25+
}
1626

1727
public String getUniqueId() { return uniqueId; }
1828
public String getDescription() { return description; }
1929
public CalmRelationshipTypeSchema getRelationshipType() { return relationshipType; }
2030
public CalmProtocol getProtocol() { return protocol; }
2131
public CalmControlsSchema getControls() { return controls; }
2232
public JsonNode getMetadataRaw() { return metadataRaw; }
33+
public Map<String, JsonNode> getExtensions() { return extensions; }
2334
}

calm-models/src/test/java/org/finos/calm/model/CalmRelationshipTest.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.finos.calm.model;
22

3+
import com.fasterxml.jackson.annotation.JsonProperty;
34
import com.fasterxml.jackson.databind.ObjectMapper;
45
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
56
import org.junit.jupiter.api.BeforeAll;
@@ -8,6 +9,7 @@
89
import java.io.InputStream;
910

1011
import static org.assertj.core.api.Assertions.assertThat;
12+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
1113

1214
class CalmRelationshipTest {
1315

@@ -44,4 +46,46 @@ void relationship_withoutProtocol_returnsEmpty() {
4446
.findFirst().orElseThrow();
4547
assertThat(rel.protocol()).isEmpty();
4648
}
49+
50+
@Test
51+
void parseExtension_returnsTypedObject_whenPresent() {
52+
record Sla(@JsonProperty("max-latency-ms") int maxLatencyMs,
53+
@JsonProperty("availability") String availability) {}
54+
55+
CalmRelationship rel = arch.getRelationships().stream()
56+
.filter(r -> r.uniqueId().equals("rel-connects"))
57+
.findFirst().orElseThrow();
58+
59+
Sla sla = rel.parseExtension("sla", Sla.class).orElseThrow();
60+
assertThat(sla.maxLatencyMs()).isEqualTo(50);
61+
assertThat(sla.availability()).isEqualTo("99.9%");
62+
}
63+
64+
@Test
65+
void parseExtension_returnsEmpty_whenAbsent() {
66+
CalmRelationship rel = arch.getRelationships().stream()
67+
.filter(r -> r.uniqueId().equals("rel-connects"))
68+
.findFirst().orElseThrow();
69+
assertThat(rel.parseExtension("no-such-extension", String.class)).isEmpty();
70+
}
71+
72+
@Test
73+
void parseExtension_throwsCalmExtensionParseException_whenMalformed() {
74+
record Bad(@JsonProperty("max-latency-ms") java.time.LocalDate maxLatencyMs) {}
75+
76+
CalmRelationship rel = arch.getRelationships().stream()
77+
.filter(r -> r.uniqueId().equals("rel-connects"))
78+
.findFirst().orElseThrow();
79+
80+
assertThatThrownBy(() -> rel.parseExtension("sla", Bad.class))
81+
.isInstanceOf(CalmExtensionParseException.class);
82+
}
83+
84+
@Test
85+
void relationship_withNoExtensions_returnsEmptyForAnyKey() {
86+
CalmRelationship rel = arch.getRelationships().stream()
87+
.filter(r -> r.uniqueId().equals("rel-deployed-in"))
88+
.findFirst().orElseThrow();
89+
assertThat(rel.parseExtension("sla", Object.class)).isEmpty();
90+
}
4791
}

calm-models/src/test/resources/test-architecture.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,11 @@
6868
"destination": { "node": "payment-db" }
6969
}
7070
},
71-
"protocol": "JDBC"
71+
"protocol": "JDBC",
72+
"sla": {
73+
"max-latency-ms": 50,
74+
"availability": "99.9%"
75+
}
7276
},
7377
{
7478
"unique-id": "rel-interacts",

0 commit comments

Comments
 (0)