diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/EngineConfigAnnotator.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/EngineConfigAnnotator.java new file mode 100644 index 0000000000..670b8e29de --- /dev/null +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/EngineConfigAnnotator.java @@ -0,0 +1,160 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.engine.config; + +import java.util.LinkedList; + +import jakarta.json.Json; +import jakarta.json.JsonArray; +import jakarta.json.JsonArrayBuilder; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import jakarta.json.JsonValue; + +import org.agrona.collections.MutableInteger; + +public final class EngineConfigAnnotator +{ + private final LinkedList schemaKeys; + private final LinkedList schemaIndexes; + + public EngineConfigAnnotator() + { + this.schemaKeys = new LinkedList<>(); + this.schemaIndexes = new LinkedList<>(); + } + + public JsonObject annotate( + JsonObject jsonObject) + { + schemaKeys.clear(); + schemaIndexes.clear(); + + return (JsonObject) annotateJsonObject(jsonObject); + } + + private JsonValue annotateJsonObject( + JsonObject jsonObject) + { + JsonObjectBuilder builder = Json.createObjectBuilder(); + + jsonObject.forEach((key, value) -> + { + schemaKeys.push(key); + + parse: + if ("expression".equals(key)) + { + builder.add(key, value); + } + else if (value.getValueType() == JsonValue.ValueType.OBJECT) + { + builder.add(key, annotateJsonObject(value.asJsonObject())); + } + else if (value.getValueType() == JsonValue.ValueType.ARRAY) + { + if (jsonObject.containsKey("type") && key.equals("enum")) + { + break parse; + } + builder.add(key, annotateJsonArray(value.asJsonArray())); + } + else if (key.equals("type") && isPrimitiveType(value)) + { + builder.add("anyOf", createAnyOfTypes(jsonObject)); + } + else if (jsonObject.containsKey("type") && + isPrimitiveType(jsonObject.get("type"))) + { + if (key.equals("title") || key.equals("description")) + { + builder.add(key, value); + } + } + else + { + builder.add(key, value); + } + + schemaKeys.pop(); + }); + + return builder.build(); + } + + private JsonValue annotateJsonArray( + JsonArray jsonArray) + { + JsonArrayBuilder arrayBuilder = Json.createArrayBuilder(); + + MutableInteger index = new MutableInteger(); + + jsonArray.forEach(item -> + { + schemaIndexes.push(index.value++); + if (item.getValueType() == JsonValue.ValueType.OBJECT) + { + arrayBuilder.add(annotateJsonObject(item.asJsonObject())); + } + else + { + arrayBuilder.add(item); + } + schemaIndexes.pop(); + }); + + return arrayBuilder.build(); + } + + private boolean isPrimitiveType( + JsonValue type) + { + String typeText = type.toString().replaceAll("\"", ""); + return "string".equals(typeText) || + "integer".equals(typeText) || + "boolean".equals(typeText) || + "number".equals(typeText); + } + + private JsonArray createAnyOfTypes( + JsonObject properties) + { + JsonArrayBuilder anyOfArrayBuilder = Json.createArrayBuilder(); + JsonObjectBuilder objectBuilder = Json.createObjectBuilder(); + + properties.forEach((key, value) -> + { + if (!"title".equals(key) && !"description".equals(key)) + { + objectBuilder.add(key, value); + } + }); + + anyOfArrayBuilder.add(objectBuilder); + + if (!(!schemaKeys.isEmpty() && + schemaKeys.size() > 1 && + "oneOf".equals(schemaKeys.get(1))) || + schemaIndexes.peek() == 0) + { + anyOfArrayBuilder.add(Json.createObjectBuilder() + .add("$ref", "#/$defs/expression") + ); + } + + return anyOfArrayBuilder.build(); + } +} diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/EngineConfigReader.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/EngineConfigReader.java index 3c047b6435..a70c9c7bbe 100644 --- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/EngineConfigReader.java +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/EngineConfigReader.java @@ -30,14 +30,10 @@ import java.util.List; import java.util.function.Consumer; -import jakarta.json.Json; import jakarta.json.JsonArray; -import jakarta.json.JsonArrayBuilder; import jakarta.json.JsonObject; -import jakarta.json.JsonObjectBuilder; import jakarta.json.JsonPatch; import jakarta.json.JsonReader; -import jakarta.json.JsonValue; import jakarta.json.bind.Jsonb; import jakarta.json.bind.JsonbBuilder; import jakarta.json.bind.JsonbConfig; @@ -62,6 +58,7 @@ public final class EngineConfigReader private final Collection schemaTypes; private final Consumer logger; + public EngineConfigReader( ConfigAdapterContext context, Resolver expressions, @@ -190,7 +187,8 @@ private boolean validateAnnotatedSchema( validate: try { - final JsonObject annotatedSchemaObject = (JsonObject) annotateJsonObject(schemaObject); + final EngineConfigAnnotator annotator = new EngineConfigAnnotator(); + final JsonObject annotatedSchemaObject = annotator.annotate(schemaObject); if (logger != null) { @@ -249,87 +247,5 @@ private boolean validateAnnotatedSchema( } - private JsonValue annotateJsonObject( - JsonObject jsonObject) - { - JsonObjectBuilder builder = Json.createObjectBuilder(); - - jsonObject.forEach((key, value) -> - { - if ("expression".equals(key)) - { - builder.add(key, value); - } - else if (value.getValueType() == JsonValue.ValueType.OBJECT) - { - builder.add(key, annotateJsonObject(value.asJsonObject())); - } - else if (value.getValueType() == JsonValue.ValueType.ARRAY) - { - builder.add(key, annotateJsonArray(value.asJsonArray())); - } - else if (key.equals("type") && - isPrimitiveType(value.toString().replaceAll("\"", ""))) - { - JsonValue pattern = jsonObject.get("pattern"); - builder.add(key, value); - builder.add("anyOf", createOneOfTypes(value.toString().replaceAll("\"", ""), pattern)); - } - else if (!"pattern".equals(key)) - { - builder.add(key, value); - } - }); - - return builder.build(); - } - - private JsonValue annotateJsonArray( - JsonArray jsonArray) - { - JsonArrayBuilder arrayBuilder = Json.createArrayBuilder(); - jsonArray.forEach(item -> - { - if (item.getValueType() == JsonValue.ValueType.OBJECT) - { - arrayBuilder.add(annotateJsonObject(item.asJsonObject())); - } - else - { - arrayBuilder.add(item); - } - }); - - return arrayBuilder.build(); - } - - private boolean isPrimitiveType( - String type) - { - return "string".equals(type) || - "integer".equals(type) || - "boolean".equals(type) || - "number".equals(type); - } - - private JsonArray createOneOfTypes( - String originalType, - JsonValue pattern) - { - JsonArrayBuilder oneOfArrayBuilder = Json.createArrayBuilder(); - JsonObjectBuilder objectBuilder = Json.createObjectBuilder(); - objectBuilder.add("type", originalType); - if (pattern != null) - { - objectBuilder.add("pattern", pattern); - } - oneOfArrayBuilder.add(objectBuilder); - - oneOfArrayBuilder.add(Json.createObjectBuilder() - .add("$ref", "#/$defs/expression") - ); - - return oneOfArrayBuilder.build(); - } } diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/config/EngineConfigAnnotatorTest.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/config/EngineConfigAnnotatorTest.java new file mode 100644 index 0000000000..2b3d2fa42b --- /dev/null +++ b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/config/EngineConfigAnnotatorTest.java @@ -0,0 +1,60 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.engine.config; + +import static org.junit.Assert.assertTrue; + +import java.io.StringReader; + +import jakarta.json.Json; +import jakarta.json.JsonArray; +import jakarta.json.JsonObject; +import jakarta.json.JsonReader; + +import org.junit.Test; + +public class EngineConfigAnnotatorTest +{ + @Test + public void shouldAnnotateJsonSchema() + { + final String json = new String("{" + + " \"title\": \"Port\"," + + " \"oneOf\":" + + " [" + + " {" + + " \"type\": \"integer\"" + + " }," + + " {" + + " \"type\": \"string\"," + + " \"pattern\": \"^\\\\d+(-\\\\d+)?$\"" + + " }" + + " ]" + + "}"); + try (JsonReader jsonReader = Json.createReader(new StringReader(json))) + { + JsonObject jsonObject = jsonReader.readObject(); + EngineConfigAnnotator annotator = new EngineConfigAnnotator(); + final JsonObject annotated = annotator.annotate(jsonObject); + + JsonArray jsonArray = annotator.annotate(annotated).getJsonArray("oneOf"); + + assertTrue(jsonArray.get(0).asJsonObject().asJsonObject().getJsonArray("anyOf").size() == 2); + assertTrue(jsonArray.get(1).asJsonObject().asJsonObject().getJsonArray("anyOf").size() == 1); + + } + } +} diff --git a/specs/exporter-prometheus.spec/src/main/scripts/io/aklivity/zilla/specs/exporter/prometheus/schema/prometheus.schema.patch.json b/specs/exporter-prometheus.spec/src/main/scripts/io/aklivity/zilla/specs/exporter/prometheus/schema/prometheus.schema.patch.json index 4eee03c61a..336ed01046 100644 --- a/specs/exporter-prometheus.spec/src/main/scripts/io/aklivity/zilla/specs/exporter/prometheus/schema/prometheus.schema.patch.json +++ b/specs/exporter-prometheus.spec/src/main/scripts/io/aklivity/zilla/specs/exporter/prometheus/schema/prometheus.schema.patch.json @@ -41,6 +41,7 @@ "scheme": { "title": "Scheme", + "type": "string", "enum": [ "http"