Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,76 +30,146 @@ public JsonNode resolveReferences(JsonNode schemaNode, List<String> roles) {
}

private JsonNode resolveReferences(JsonNode schemaNode, JsonNode rootNode, List<String> roles) {
// Prevent mutability by using a deep copy
return doResolveReferences(schemaNode.deepCopy(), rootNode, roles);
return resolveReferences(schemaNode, rootNode, roles, null);
}

private JsonNode doResolveReferences(
JsonNode schemaNode, JsonNode rootNode, List<String> roles) {
// Returns a fresh JsonNode by resolving all $ref, overwriteSchemaWith and patchSchemaWith
// in schemaNode and then merging the result with patchNode (if not null)
private JsonNode resolveReferences(
JsonNode schemaNode, JsonNode rootNode, List<String> roles, JsonNode patchNode) {
if (schemaNode.isObject()) {
ObjectNode objectNode = (ObjectNode) schemaNode;
Iterator<Map.Entry<String, JsonNode>> fields = objectNode.fields();
Map<String, JsonNode> updates = new HashMap<>();

while (fields.hasNext()) {
Map.Entry<String, JsonNode> field = fields.next();
JsonNode fieldValue = field.getValue();

if (field.getKey().equals("$ref") && fieldValue.isTextual()) {
String ref = fieldValue.asText();
JsonNode refNode = null;
if (ref.startsWith("#/definitions/")) {
refNode = rootNode.at(ref.substring(1));
} else {
refNode = registryService.getSchema(roles, ref);
}

if (refNode != null && !refNode.isMissingNode()) {
JsonNode resolvedNode = resolveReferences(refNode, rootNode, roles);
updates.putAll(convertToMap((ObjectNode) resolvedNode));
updates.put("$ref", null);
}
} else if (fieldValue.isObject()
&& fieldValue.has("x-onyxia")
&& fieldValue.get("x-onyxia").has("overwriteSchemaWith")) {
String overrideSchemaName =
fieldValue.get("x-onyxia").get("overwriteSchemaWith").asText();
JsonNode overrideSchemaNode =
registryService.getSchema(roles, overrideSchemaName);

if (overrideSchemaNode != null && !overrideSchemaNode.isMissingNode()) {
JsonNode resolvedNode =
resolveReferences(overrideSchemaNode, rootNode, roles);
updates.put(field.getKey(), resolvedNode);
}
} else if (fieldValue.isObject() || fieldValue.isArray()) {
updates.put(field.getKey(), resolveReferences(fieldValue, rootNode, roles));
}
return resolveReferences((ObjectNode) schemaNode, rootNode, roles, patchNode);
} else if (patchNode != null) {
// If provided, the patch replaces any non-object
return patchNode;
} else if (schemaNode.isArray()) {
ArrayNode arrayNode = this.objectMapper.createArrayNode();
for (int i = 0; i < schemaNode.size(); i++) {
arrayNode.set(i, resolveReferences(schemaNode.get(i), rootNode, roles));
}
return arrayNode;
} else {
return schemaNode;
}
}

private JsonNode resolveReferences(
ObjectNode schemaNode, JsonNode rootNode, List<String> roles, JsonNode patchNode) {
if (patchNode != null && !patchNode.isObject()) {
// If the patch is a value (not an object), then it replaces the schema
return resolveReferences(patchNode, rootNode, roles, null);
}

// Special case 1: resolve $ref
if (schemaNode.has("$ref")) {
String ref = schemaNode.get("$ref").asText();
JsonNode refNode;
if (ref.startsWith("#/definitions/")) {
refNode = rootNode.at(ref.substring(1));
} else {
refNode = registryService.getSchema(roles, ref);
}
if (refNode != null && !refNode.isMissingNode()) {
// Proceed with the $ref node but still apply current patch (if any)
return resolveReferences(refNode, rootNode, roles, patchNode);
}
}

// Special case 2: resolve x-onyxia.overwriteSchemaWith
if (schemaNode.has("x-onyxia") && schemaNode.get("x-onyxia").has("overwriteSchemaWith")) {
String overwriteSchemaName =
schemaNode.get("x-onyxia").get("overwriteSchemaWith").asText();
JsonNode overwriteSchemaNode = registryService.getSchema(roles, overwriteSchemaName);
if (overwriteSchemaNode != null && !overwriteSchemaNode.isMissingNode()) {
// Proceed with the overwriteSchemaWith node but still apply current patch (if any)
return resolveReferences(overwriteSchemaNode, rootNode, roles, patchNode);
}
}

for (Map.Entry<String, JsonNode> update : updates.entrySet()) {
if (update.getValue() == null) {
objectNode.remove(update.getKey());
// Special case 3: resolve x-onyxia.patchSchemaWith
if (schemaNode.has("x-onyxia") && schemaNode.get("x-onyxia").has("patchSchemaWith")) {
String patchSchemaName = schemaNode.get("x-onyxia").get("patchSchemaWith").asText();
JsonNode newPatchNode = registryService.getSchema(roles, patchSchemaName);
if (newPatchNode != null && !newPatchNode.isMissingNode()) {
if (!newPatchNode.isObject()) {
// If the new patch is not an object, it replaces the schema
return resolveReferences(newPatchNode, rootNode, roles, patchNode);
} else if (patchNode == null) {
// Otherwise it's an object. If no patch is currently applied, then it becomes
// the patch.
patchNode = newPatchNode;
} else {
objectNode.set(update.getKey(), update.getValue());
// Otherwise we have two object patches.
// Apply first the new patch to the schema object
schemaNode =
resolveReferencesInObject(
schemaNode, rootNode, roles, (ObjectNode) newPatchNode);
// then proceed with the main processing and apply the original patch
}
}
} else if (schemaNode.isArray()) {
ArrayNode arrayNode = (ArrayNode) schemaNode;
for (int i = 0; i < arrayNode.size(); i++) {
arrayNode.set(i, resolveReferences(arrayNode.get(i), rootNode, roles));
}
return cleanOnyxiaTags(
resolveReferencesInObject(schemaNode, rootNode, roles, (ObjectNode) patchNode));
}

private ObjectNode resolveReferencesInObject(
ObjectNode schemaNode, JsonNode rootNode, List<String> roles, ObjectNode patchNode) {
ObjectNode objectNode = this.objectMapper.createObjectNode();
// Iterate over the schema keys
Iterator<Map.Entry<String, JsonNode>> it = schemaNode.fields();

if (patchNode == null) {
// If no patch is supplied, simply map all keys from schemaNode to their resolved values
while (it.hasNext()) {
Map.Entry<String, JsonNode> entry = it.next();
objectNode.put(
entry.getKey(), resolveReferences(entry.getValue(), rootNode, roles));
}

} else {
//
while (it.hasNext()) {
Map.Entry<String, JsonNode> entry = it.next();
String key = entry.getKey();
JsonNode patchVal = patchNode.get(key);
if (patchVal == null) {
// The key is not patched: resolve the value without any patch
objectNode.put(key, resolveReferences(entry.getValue(), rootNode, roles));
} else if (!patchVal.isNull()) {
// The key is patched to non-null: resolve the value using that patch
objectNode.put(
key, resolveReferences(entry.getValue(), rootNode, roles, patchVal));
}
// Otherwise the key is patched to an explicit null: to not add it to the output
// schema (removed key)
}

// For all keys in the patch
Iterator<Map.Entry<String, JsonNode>> patchIt = patchNode.fields();
while (patchIt.hasNext()) {
Map.Entry<String, JsonNode> patchEntry = patchIt.next();
String patchKey = patchEntry.getKey();
JsonNode patchVal = patchEntry.getValue();
// If the key is patched with a non-null value, not already set in the previous
// loop: add the patch value
if (!patchVal.isNull() && !objectNode.has(patchKey)) {
objectNode.put(patchKey, patchVal);
}
}
}
return schemaNode;
return objectNode;
}

private Map<String, JsonNode> convertToMap(ObjectNode objectNode) {
Map<String, JsonNode> map = new HashMap<>();
Iterator<Map.Entry<String, JsonNode>> fields = objectNode.fields();
while (fields.hasNext()) {
Map.Entry<String, JsonNode> field = fields.next();
map.put(field.getKey(), field.getValue());
private ObjectNode cleanOnyxiaTags(ObjectNode node) {
JsonNode onyxiaNode = node.get("x-onyxia");
if (onyxiaNode != null && onyxiaNode.isObject()) {
ObjectNode onyxiaObject = (ObjectNode) onyxiaNode;
onyxiaObject.remove("patchSchemaWith");
onyxiaObject.remove("overwriteSchemaWith");
if (onyxiaObject.isEmpty()) {
node.remove("x-onyxia");
}
}
return map;
return node;
}
}
Loading