diff --git a/elide-core/src/main/resources/META-INF/native-image/com.yahoo.elide/elide-core/reflect-config.json b/elide-core/src/main/resources/META-INF/native-image/com.yahoo.elide/elide-core/reflect-config.json index ccdde4b809..aa3fe3ba7a 100644 --- a/elide-core/src/main/resources/META-INF/native-image/com.yahoo.elide/elide-core/reflect-config.json +++ b/elide-core/src/main/resources/META-INF/native-image/com.yahoo.elide/elide-core/reflect-config.json @@ -3,130 +3,6 @@ "name": "com.github.benmanes.caffeine.cache.SSSMSW", "allDeclaredConstructors": true }, - { - "name": "com.github.fge.jackson.jsonpointer.JsonPointerMessages", - "allDeclaredConstructors": true - }, - { - "name": "com.github.fge.jsonschema.core.messages.JsonSchemaCoreMessageBundle", - "allDeclaredConstructors": true - }, - { - "name": "com.github.fge.jsonschema.core.messages.JsonSchemaSyntaxMessageBundle", - "allDeclaredConstructors": true - }, - { - "name": "com.github.fge.jsonschema.keyword.validator.common.AdditionalItemsValidator", - "allDeclaredConstructors": true - }, - { - "name": "com.github.fge.jsonschema.keyword.validator.common.AdditionalPropertiesValidator", - "allDeclaredConstructors": true - }, - { - "name": "com.github.fge.jsonschema.keyword.validator.common.DependenciesValidator", - "allDeclaredConstructors": true - }, - { - "name": "com.github.fge.jsonschema.keyword.validator.common.EnumValidator", - "allDeclaredConstructors": true - }, - { - "name": "com.github.fge.jsonschema.keyword.validator.common.MaxItemsValidator", - "allDeclaredConstructors": true - }, - { - "name": "com.github.fge.jsonschema.keyword.validator.common.MaxLengthValidator", - "allDeclaredConstructors": true - }, - { - "name": "com.github.fge.jsonschema.keyword.validator.common.MaximumValidator", - "allDeclaredConstructors": true - }, - { - "name": "com.github.fge.jsonschema.keyword.validator.common.MinItemsValidator", - "allDeclaredConstructors": true - }, - { - "name": "com.github.fge.jsonschema.keyword.validator.common.MinLengthValidator", - "allDeclaredConstructors": true - }, - { - "name": "com.github.fge.jsonschema.keyword.validator.common.MinimumValidator", - "allDeclaredConstructors": true - }, - { - "name": "com.github.fge.jsonschema.keyword.validator.common.PatternValidator", - "allDeclaredConstructors": true - }, - { - "name": "com.github.fge.jsonschema.keyword.validator.common.UniqueItemsValidator", - "allDeclaredConstructors": true - }, - { - "name": "com.github.fge.jsonschema.keyword.validator.draftv3.DisallowKeywordValidator", - "allDeclaredConstructors": true - }, - { - "name": "com.github.fge.jsonschema.keyword.validator.draftv3.DivisibleByValidator", - "allDeclaredConstructors": true - }, - { - "name": "com.github.fge.jsonschema.keyword.validator.draftv3.DraftV3TypeValidator", - "allDeclaredConstructors": true - }, - { - "name": "com.github.fge.jsonschema.keyword.validator.draftv3.ExtendsValidator", - "allDeclaredConstructors": true - }, - { - "name": "com.github.fge.jsonschema.keyword.validator.draftv3.PropertiesValidator", - "allDeclaredConstructors": true - }, - { - "name": "com.github.fge.jsonschema.keyword.validator.draftv4.AllOfValidator", - "allDeclaredConstructors": true - }, - { - "name": "com.github.fge.jsonschema.keyword.validator.draftv4.AnyOfValidator", - "allDeclaredConstructors": true - }, - { - "name": "com.github.fge.jsonschema.keyword.validator.draftv4.DraftV4TypeValidator", - "allDeclaredConstructors": true - }, - { - "name": "com.github.fge.jsonschema.keyword.validator.draftv4.MaxPropertiesValidator", - "allDeclaredConstructors": true - }, - { - "name": "com.github.fge.jsonschema.keyword.validator.draftv4.MinPropertiesValidator", - "allDeclaredConstructors": true - }, - { - "name": "com.github.fge.jsonschema.keyword.validator.draftv4.MultipleOfValidator", - "allDeclaredConstructors": true - }, - { - "name": "com.github.fge.jsonschema.keyword.validator.draftv4.NotValidator", - "allDeclaredConstructors": true - }, - { - "name": "com.github.fge.jsonschema.keyword.validator.draftv4.OneOfValidator", - "allDeclaredConstructors": true - }, - { - "name": "com.github.fge.jsonschema.keyword.validator.draftv4.RequiredKeywordValidator", - "allDeclaredConstructors": true - }, - { - "name": "com.github.fge.jsonschema.messages.JsonSchemaConfigurationBundle", - "allDeclaredConstructors": true - }, - { - "name": "com.github.fge.jsonschema.messages.JsonSchemaValidationBundle", - "allDeclaredConstructors": true - }, { "name": "com.yahoo.elide.core.security.checks.prefab.Collections$AppendOnly", "allDeclaredMethods": true, diff --git a/elide-model-config/pom.xml b/elide-model-config/pom.xml index b69c405ae3..97d4f72156 100644 --- a/elide-model-config/pom.xml +++ b/elide-model-config/pom.xml @@ -94,7 +94,7 @@ - com.github.java-json-tools + com.networknt json-schema-validator diff --git a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/DraftV4LibraryWithElideFormatAttr.java b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/DraftV4LibraryWithElideFormatAttr.java deleted file mode 100644 index 425b76c387..0000000000 --- a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/DraftV4LibraryWithElideFormatAttr.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2020, Yahoo Inc. - * Licensed under the Apache License, Version 2.0 - * See LICENSE file in project root for terms. - */ -package com.yahoo.elide.modelconfig; - -import com.yahoo.elide.modelconfig.jsonformats.ElideArgumentNameFormatAttr; -import com.yahoo.elide.modelconfig.jsonformats.ElideCardinalityFormatAttr; -import com.yahoo.elide.modelconfig.jsonformats.ElideFieldNameFormatAttr; -import com.yahoo.elide.modelconfig.jsonformats.ElideFieldTypeFormatAttr; -import com.yahoo.elide.modelconfig.jsonformats.ElideGrainTypeFormatAttr; -import com.yahoo.elide.modelconfig.jsonformats.ElideJDBCUrlFormatAttr; -import com.yahoo.elide.modelconfig.jsonformats.ElideJoinKindFormatAttr; -import com.yahoo.elide.modelconfig.jsonformats.ElideJoinTypeFormatAttr; -import com.yahoo.elide.modelconfig.jsonformats.ElideNameFormatAttr; -import com.yahoo.elide.modelconfig.jsonformats.ElideNamespaceNameFormatAttr; -import com.yahoo.elide.modelconfig.jsonformats.ElideRSQLFilterFormatAttr; -import com.yahoo.elide.modelconfig.jsonformats.ElideRoleFormatAttr; -import com.yahoo.elide.modelconfig.jsonformats.ElideTimeFieldTypeFormatAttr; -import com.yahoo.elide.modelconfig.jsonformats.JavaClassNameFormatAttr; -import com.yahoo.elide.modelconfig.jsonformats.JavaClassNameWithExtFormatAttr; -import com.yahoo.elide.modelconfig.jsonformats.ValidateArgsPropertiesKeyword; -import com.yahoo.elide.modelconfig.jsonformats.ValidateDimPropertiesKeyword; -import com.yahoo.elide.modelconfig.jsonformats.ValidateTimeDimPropertiesKeyword; -import com.github.fge.jsonschema.library.DraftV4Library; -import com.github.fge.jsonschema.library.Library; - -import lombok.Getter; - -/** - * Augment the {@link DraftV4Library} with custom format attributes and keywords. - */ -public class DraftV4LibraryWithElideFormatAttr { - @Getter - private Library library; - - public DraftV4LibraryWithElideFormatAttr() { - library = DraftV4Library.get().thaw() - .addFormatAttribute(ElideFieldNameFormatAttr.FORMAT_NAME, new ElideFieldNameFormatAttr()) - .addFormatAttribute(ElideArgumentNameFormatAttr.FORMAT_NAME, new ElideArgumentNameFormatAttr()) - .addFormatAttribute(ElideCardinalityFormatAttr.FORMAT_NAME, new ElideCardinalityFormatAttr()) - .addFormatAttribute(ElideFieldTypeFormatAttr.FORMAT_NAME, new ElideFieldTypeFormatAttr()) - .addFormatAttribute(ElideGrainTypeFormatAttr.FORMAT_NAME, new ElideGrainTypeFormatAttr()) - .addFormatAttribute(ElideJoinTypeFormatAttr.FORMAT_NAME, new ElideJoinTypeFormatAttr()) - .addFormatAttribute(ElideJoinKindFormatAttr.FORMAT_NAME, new ElideJoinKindFormatAttr()) - .addFormatAttribute(ElideTimeFieldTypeFormatAttr.FORMAT_NAME, - new ElideTimeFieldTypeFormatAttr()) - .addFormatAttribute(ElideNameFormatAttr.FORMAT_NAME, new ElideNameFormatAttr()) - .addFormatAttribute(ElideNamespaceNameFormatAttr.FORMAT_NAME, - new ElideNamespaceNameFormatAttr()) - .addFormatAttribute(ElideRSQLFilterFormatAttr.FORMAT_NAME, new ElideRSQLFilterFormatAttr()) - .addFormatAttribute(JavaClassNameWithExtFormatAttr.FORMAT_NAME, - new JavaClassNameWithExtFormatAttr()) - .addFormatAttribute(ElideJDBCUrlFormatAttr.FORMAT_NAME, new ElideJDBCUrlFormatAttr()) - .addFormatAttribute(JavaClassNameFormatAttr.FORMAT_NAME, new JavaClassNameFormatAttr()) - .addFormatAttribute(ElideRoleFormatAttr.FORMAT_NAME, new ElideRoleFormatAttr()) - .addKeyword(new ValidateDimPropertiesKeyword().getKeyword()) - .addKeyword(new ValidateTimeDimPropertiesKeyword().getKeyword()) - .addKeyword(new ValidateArgsPropertiesKeyword().getKeyword()) - .freeze(); - } -} diff --git a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/DynamicConfigHelpers.java b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/DynamicConfigHelpers.java index 4e64ccf281..5910f2b516 100644 --- a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/DynamicConfigHelpers.java +++ b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/DynamicConfigHelpers.java @@ -13,7 +13,8 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.fge.jsonschema.core.exceptions.ProcessingException; +import com.fasterxml.jackson.databind.json.JsonMapper; + import org.apache.commons.lang3.StringUtils; import org.hjson.JsonValue; import org.hjson.ParseException; @@ -58,9 +59,9 @@ public static Map stringToVariablesPojo(String fileName, String if (schemaValidator.verifySchema(Config.MODELVARIABLE, jsonConfig, fileName)) { variables = getModelPojo(jsonConfig, Map.class); } - } catch (ProcessingException e) { + } catch (IOException e) { log.error("Error Validating Variable config : " + e.getMessage()); - throw new IOException(e); + throw e; } return variables; } @@ -81,9 +82,9 @@ public static ElideTableConfig stringToElideTablePojo(String fileName, String co if (schemaValidator.verifySchema(Config.TABLE, jsonConfig, fileName)) { table = getModelPojo(jsonConfig, ElideTableConfig.class); } - } catch (ProcessingException e) { + } catch (IOException e) { log.error("Error Validating Table config : " + e.getMessage()); - throw new IOException(e); + throw e; } return table; } @@ -104,9 +105,9 @@ public static ElideDBConfig stringToElideDBConfigPojo(String fileName, String co if (schemaValidator.verifySchema(Config.SQLDBConfig, jsonConfig, fileName)) { dbconfig = getModelPojo(jsonConfig, ElideDBConfig.class); } - } catch (ProcessingException e) { + } catch (IOException e) { log.error("Error Validating DB config : " + e.getMessage()); - throw new IOException(e); + throw e; } return dbconfig; } @@ -127,9 +128,9 @@ public static ElideNamespaceConfig stringToElideNamespaceConfigPojo(String fileN if (schemaValidator.verifySchema(Config.NAMESPACEConfig, jsonConfig, fileName)) { namespaceconfig = getModelPojo(jsonConfig, ElideNamespaceConfig.class); } - } catch (ProcessingException e) { + } catch (IOException e) { log.error("Error Validating DB config : " + e.getMessage()); - throw new IOException(e); + throw e; } return namespaceconfig; } @@ -149,9 +150,9 @@ public static ElideSecurityConfig stringToElideSecurityPojo(String fileName, Str if (schemaValidator.verifySchema(Config.SECURITY, jsonConfig, fileName)) { return getModelPojo(jsonConfig, ElideSecurityConfig.class); } - } catch (ProcessingException e) { + } catch (IOException e) { log.error("Error Validating Security config : " + e.getMessage()); - throw new IOException(e); + throw e; } return null; } @@ -176,9 +177,19 @@ private static String hjsonToJson(String hjson) { } } + private static class DynamicConfigObjectMapper { + private static final ObjectMapper INSTANCE; + + static { + INSTANCE = JsonMapper.builder().enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS).build(); + } + + private static ObjectMapper getInstance() { + return INSTANCE; + } + } + private static T getModelPojo(String jsonConfig, final Class configPojo) throws JsonProcessingException { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS); - return objectMapper.readValue(jsonConfig, configPojo); + return DynamicConfigObjectMapper.getInstance().readValue(jsonConfig, configPojo); } } diff --git a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/DynamicConfigSchemaValidator.java b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/DynamicConfigSchemaValidator.java index 9d8a42f78a..596860aadb 100644 --- a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/DynamicConfigSchemaValidator.java +++ b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/DynamicConfigSchemaValidator.java @@ -5,27 +5,19 @@ */ package com.yahoo.elide.modelconfig; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.github.fge.jsonschema.cfg.ValidationConfiguration; -import com.github.fge.jsonschema.core.exceptions.ProcessingException; -import com.github.fge.jsonschema.core.report.LogLevel; -import com.github.fge.jsonschema.core.report.ProcessingReport; -import com.github.fge.jsonschema.library.Library; -import com.github.fge.jsonschema.main.JsonSchema; -import com.github.fge.jsonschema.main.JsonSchemaFactory; -import com.github.fge.msgsimple.bundle.MessageBundle; -import org.apache.commons.lang3.StringUtils; +import com.networknt.schema.JsonMetaSchema; +import com.networknt.schema.JsonSchema; +import com.networknt.schema.JsonSchemaFactory; +import com.networknt.schema.SchemaValidatorsConfig; +import com.networknt.schema.ValidationMessage; import lombok.extern.slf4j.Slf4j; import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map.Entry; +import java.io.UncheckedIOException; +import java.util.Set; /** * Dynamic Model Schema validation. @@ -33,33 +25,30 @@ @Slf4j public class DynamicConfigSchemaValidator { - private JsonSchema tableSchema; - private JsonSchema securitySchema; - private JsonSchema variableSchema; - private JsonSchema dbConfigSchema; - private JsonSchema namespaceConfigSchema; - private static String NEWLINE = System.lineSeparator(); + private final JsonSchema tableSchema; + private final JsonSchema securitySchema; + private final JsonSchema variableSchema; + private final JsonSchema dbConfigSchema; + private final JsonSchema namespaceConfigSchema; + private final ObjectMapper objectMapper; public DynamicConfigSchemaValidator() { + this(new ObjectMapper()); + } - Library library = new DraftV4LibraryWithElideFormatAttr().getLibrary(); - - MessageBundle bundle = new MessageBundleWithElideMessages().getMsgBundle(); - - ValidationConfiguration cfg = ValidationConfiguration.newBuilder() - .setDefaultLibrary("http://my.site/myschema#", library) - .setValidationMessages(bundle) - .freeze(); - - JsonSchemaFactory factory = JsonSchemaFactory.newBuilder() - .setValidationConfiguration(cfg) - .freeze(); - - tableSchema = loadSchema(factory, Config.TABLE.getConfigSchema()); - securitySchema = loadSchema(factory, Config.SECURITY.getConfigSchema()); - variableSchema = loadSchema(factory, Config.MODELVARIABLE.getConfigSchema()); - dbConfigSchema = loadSchema(factory, Config.SQLDBConfig.getConfigSchema()); - namespaceConfigSchema = loadSchema(factory, Config.NAMESPACEConfig.getConfigSchema()); + public DynamicConfigSchemaValidator(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + JsonMetaSchema jsonMetaSchema = ElideMetaSchema.getInstance(); + JsonSchemaFactory factory = JsonSchemaFactory.builder() + .defaultMetaSchemaIri(jsonMetaSchema.getIri()) + .metaSchema(jsonMetaSchema) + .build(); + + tableSchema = loadSchema(factory, objectMapper, Config.TABLE.getConfigSchema()); + securitySchema = loadSchema(factory, objectMapper, Config.SECURITY.getConfigSchema()); + variableSchema = loadSchema(factory, objectMapper, Config.MODELVARIABLE.getConfigSchema()); + dbConfigSchema = loadSchema(factory, objectMapper, Config.SQLDBConfig.getConfigSchema()); + namespaceConfigSchema = loadSchema(factory, objectMapper, Config.NAMESPACEConfig.getConfigSchema()); } /** @@ -68,98 +57,48 @@ public DynamicConfigSchemaValidator() { * @param jsonConfig HJSON file content as JSON string. * @param fileName Name of HJSON file. * @return whether config is valid - * @throws IOException If an I/O error occurs. - * @throws ProcessingException If a processing error occurred during validation. + * @throws IOException If an I/O error occurs or processing error occurred during validation. */ public boolean verifySchema(Config configType, String jsonConfig, String fileName) - throws IOException, ProcessingException { - ProcessingReport results = null; + throws IOException { + Set results = null; switch (configType) { case TABLE : - results = this.tableSchema.validate(new ObjectMapper().readTree(jsonConfig), true); + results = this.tableSchema.validate(objectMapper.readTree(jsonConfig)); break; case SECURITY : - results = this.securitySchema.validate(new ObjectMapper().readTree(jsonConfig), true); + results = this.securitySchema.validate(objectMapper.readTree(jsonConfig)); break; case MODELVARIABLE : case DBVARIABLE : - results = this.variableSchema.validate(new ObjectMapper().readTree(jsonConfig), true); + results = this.variableSchema.validate(objectMapper.readTree(jsonConfig)); break; case SQLDBConfig : - results = this.dbConfigSchema.validate(new ObjectMapper().readTree(jsonConfig), true); + results = this.dbConfigSchema.validate(objectMapper.readTree(jsonConfig)); break; case NAMESPACEConfig : - results = this.namespaceConfigSchema.validate(new ObjectMapper().readTree(jsonConfig), true); + results = this.namespaceConfigSchema.validate(objectMapper.readTree(jsonConfig)); break; default : log.error("Not a valid config type :" + configType); break; } - if (results == null || !results.isSuccess()) { - throw new IllegalStateException("Schema validation failed for: " + fileName + getErrorMessages(results)); + if (results != null && !results.isEmpty()) { + throw new InvalidSchemaException(fileName, results); } return true; } - private static String getErrorMessages(ProcessingReport report) { - if (report == null) { - return null; - } - List list = new ArrayList<>(); - report.forEach(msg -> addEmbeddedMessages(msg.asJson(), list, 0)); - - return NEWLINE + String.join(NEWLINE, list); - } - - private static void addEmbeddedMessages(JsonNode root, List list, int depth) { - - if (root.has("level") && root.has("message")) { - String level = root.get("level").asText(); - - if (level.equalsIgnoreCase(LogLevel.ERROR.name()) || level.equalsIgnoreCase(LogLevel.FATAL.name())) { - String msg = root.get("message").asText(); - String instancePointer = extractPointer(root, "instance"); - String schemaPointer = extractPointer(root, "schema"); - - if (StringUtils.isNoneBlank(instancePointer, schemaPointer)) { - msg = "Instance[" + instancePointer + "] failed to validate against schema[" + schemaPointer + "]. " - + msg; - } - list.add((depth == 0) ? "[ERROR]" + NEWLINE + msg - : String.format("%" + (4 * depth + msg.length()) + "s", msg)); - - if (root.has("reports")) { - Iterator> fields = root.get("reports").fields(); - while (fields.hasNext()) { - ArrayNode arrayNode = (ArrayNode) fields.next().getValue(); - for (int i = 0; i < arrayNode.size(); i++) { - addEmbeddedMessages(arrayNode.get(i), list, depth + 1); - } - } - } - } - } - } - - private static String extractPointer(JsonNode root, String fieldName) { - String pointer = null; - if (root.has(fieldName)) { - JsonNode node = root.get(fieldName); - if (node.has("pointer")) { - pointer = node.get("pointer").asText(); - } - } - - return pointer; - } - - private static JsonSchema loadSchema(JsonSchemaFactory factory, String resource) { - + private static JsonSchema loadSchema(JsonSchemaFactory factory, ObjectMapper objectMapper, String resource) { try (InputStream is = DynamicConfigHelpers.class.getResourceAsStream(resource)) { - return factory.getJsonSchema(new ObjectMapper().readTree(is)); - } catch (IOException | ProcessingException e) { + SchemaValidatorsConfig config = SchemaValidatorsConfig.builder() + .formatAssertionsEnabled(true) + .errorMessageKeyword("errorMessage") + .build(); + return factory.getSchema(objectMapper.readTree(is), config); + } catch (IOException e) { log.error("Error loading schema file " + resource + " to verify"); - throw new IllegalStateException(e.getMessage()); + throw new UncheckedIOException(e.getMessage(), e); } } } diff --git a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/ElideMetaSchema.java b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/ElideMetaSchema.java new file mode 100644 index 0000000000..a6bd7153d8 --- /dev/null +++ b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/ElideMetaSchema.java @@ -0,0 +1,42 @@ +/* + * Copyright 2023, the original author or authors. + * Licensed under the Apache License, Version 2.0 + * See LICENSE file in project root for terms. + */ +package com.yahoo.elide.modelconfig; + +import com.yahoo.elide.modelconfig.jsonformats.ElideRSQLFilterFormat; + +import com.networknt.schema.Format; +import com.networknt.schema.JsonMetaSchema; + +import java.util.ArrayList; +import java.util.List; + +/** + * The Elide {@link JsonMetaSchema}. + */ +public class ElideMetaSchema { + public static final List FORMATS; + + static { + List result = new ArrayList<>(); + result.add(new ElideRSQLFilterFormat()); + FORMATS = result; + } + + private static class Holder { + static final JsonMetaSchema INSTANCE; + static { + INSTANCE = JsonMetaSchema.builder(JsonMetaSchema.getV202012()) + .formats(FORMATS) + // add your custom keywords + .build(); + + } + } + + public static JsonMetaSchema getInstance() { + return Holder.INSTANCE; + } +} diff --git a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/InvalidSchemaException.java b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/InvalidSchemaException.java new file mode 100644 index 0000000000..2bc909b5f0 --- /dev/null +++ b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/InvalidSchemaException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023, the original author or authors. + * Licensed under the Apache License, Version 2.0 + * See LICENSE file in project root for terms. + */ +package com.yahoo.elide.modelconfig; + + +import com.networknt.schema.ValidationMessage; + +import java.io.IOException; +import java.util.Set; + +/** + * Indicates that the schema is not valid. + */ +public class InvalidSchemaException extends IOException { + + private static final long serialVersionUID = 1L; + + private final Set validationMessages; + private final String fileName; + + public InvalidSchemaException(String fileName, Set validationMessages) { + super("Schema validation failed for: " + fileName + ValidationMessages.toString(validationMessages)); + this.fileName = fileName; + this.validationMessages = validationMessages; + } + + public Set getValidationMessages() { + return validationMessages; + } + + public String getFileName() { + return fileName; + } +} diff --git a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/MessageBundleWithElideMessages.java b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/MessageBundleWithElideMessages.java deleted file mode 100644 index 9cb0936c43..0000000000 --- a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/MessageBundleWithElideMessages.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2020, Yahoo Inc. - * Licensed under the Apache License, Version 2.0 - * See LICENSE file in project root for terms. - */ -package com.yahoo.elide.modelconfig; - -import com.yahoo.elide.modelconfig.jsonformats.ElideArgumentNameFormatAttr; -import com.yahoo.elide.modelconfig.jsonformats.ElideCardinalityFormatAttr; -import com.yahoo.elide.modelconfig.jsonformats.ElideFieldNameFormatAttr; -import com.yahoo.elide.modelconfig.jsonformats.ElideFieldTypeFormatAttr; -import com.yahoo.elide.modelconfig.jsonformats.ElideGrainTypeFormatAttr; -import com.yahoo.elide.modelconfig.jsonformats.ElideJDBCUrlFormatAttr; -import com.yahoo.elide.modelconfig.jsonformats.ElideJoinKindFormatAttr; -import com.yahoo.elide.modelconfig.jsonformats.ElideJoinTypeFormatAttr; -import com.yahoo.elide.modelconfig.jsonformats.ElideNameFormatAttr; -import com.yahoo.elide.modelconfig.jsonformats.ElideNamespaceNameFormatAttr; -import com.yahoo.elide.modelconfig.jsonformats.ElideRSQLFilterFormatAttr; -import com.yahoo.elide.modelconfig.jsonformats.ElideRoleFormatAttr; -import com.yahoo.elide.modelconfig.jsonformats.ElideTimeFieldTypeFormatAttr; -import com.yahoo.elide.modelconfig.jsonformats.JavaClassNameFormatAttr; -import com.yahoo.elide.modelconfig.jsonformats.JavaClassNameWithExtFormatAttr; -import com.yahoo.elide.modelconfig.jsonformats.ValidateArgsPropertiesValidator; -import com.yahoo.elide.modelconfig.jsonformats.ValidateDimPropertiesValidator; -import com.yahoo.elide.modelconfig.jsonformats.ValidateTimeDimPropertiesValidator; -import com.github.fge.jsonschema.messages.JsonSchemaValidationBundle; -import com.github.fge.msgsimple.bundle.MessageBundle; -import com.github.fge.msgsimple.load.MessageBundles; -import com.github.fge.msgsimple.source.MapMessageSource; - -import lombok.Getter; - -/** - * Augment the {@link MessageBundle} with error messages for custom format attributes and keywords. - */ -public class MessageBundleWithElideMessages { - @Getter - private MessageBundle msgBundle; - - public MessageBundleWithElideMessages() { - this.msgBundle = MessageBundles.getBundle(JsonSchemaValidationBundle.class).thaw() - .appendSource(MapMessageSource.newBuilder() - - // For Format errors - .put(ElideFieldNameFormatAttr.FORMAT_KEY, ElideFieldNameFormatAttr.FORMAT_MSG) - .put(ElideFieldNameFormatAttr.NAME_KEY, ElideFieldNameFormatAttr.NAME_MSG) - .put(ElideArgumentNameFormatAttr.FORMAT_KEY, - ElideArgumentNameFormatAttr.FORMAT_MSG) - .put(ElideArgumentNameFormatAttr.NAME_KEY, ElideArgumentNameFormatAttr.NAME_MSG) - .put(ElideCardinalityFormatAttr.TYPE_KEY, ElideCardinalityFormatAttr.TYPE_MSG) - .put(ElideFieldTypeFormatAttr.TYPE_KEY, ElideFieldTypeFormatAttr.TYPE_MSG) - .put(ElideGrainTypeFormatAttr.TYPE_KEY, ElideGrainTypeFormatAttr.TYPE_MSG) - .put(ElideJoinTypeFormatAttr.TYPE_KEY, ElideJoinTypeFormatAttr.TYPE_MSG) - .put(ElideJoinKindFormatAttr.TYPE_KEY, ElideJoinKindFormatAttr.TYPE_MSG) - .put(ElideTimeFieldTypeFormatAttr.TYPE_KEY, - ElideTimeFieldTypeFormatAttr.TYPE_MSG) - .put(ElideNameFormatAttr.FORMAT_KEY, ElideNameFormatAttr.FORMAT_MSG) - .put(ElideNamespaceNameFormatAttr.FORMAT_KEY, - ElideNamespaceNameFormatAttr.FORMAT_KEY_MSG) - .put(ElideNamespaceNameFormatAttr.FORMAT_ADDITIONAL_KEY, - ElideNamespaceNameFormatAttr.FORMAT_ADDITIONAL_KEY_MSG) - .put(ElideRSQLFilterFormatAttr.FORMAT_KEY, ElideRSQLFilterFormatAttr.FORMAT_MSG) - .put(JavaClassNameWithExtFormatAttr.FORMAT_KEY, - JavaClassNameWithExtFormatAttr.FORMAT_MSG) - .put(JavaClassNameFormatAttr.FORMAT_KEY, JavaClassNameFormatAttr.FORMAT_MSG) - .put(ElideJDBCUrlFormatAttr.FORMAT_KEY, ElideJDBCUrlFormatAttr.FORMAT_MSG) - .put(ElideRoleFormatAttr.FORMAT_KEY, ElideRoleFormatAttr.FORMAT_MSG) - - // for Keyword errors - .put(ValidateDimPropertiesValidator.ATMOST_ONE_KEY, - ValidateDimPropertiesValidator.ATMOST_ONE_MSG) - .put(ValidateDimPropertiesValidator.ADDITIONAL_KEY, - ValidateDimPropertiesValidator.ADDITIONAL_MSG) - .put(ValidateTimeDimPropertiesValidator.ADDITIONAL_KEY, - ValidateTimeDimPropertiesValidator.ADDITIONAL_MSG) - .put(ValidateArgsPropertiesValidator.ATMOST_ONE_KEY, - ValidateArgsPropertiesValidator.ATMOST_ONE_MSG) - .build()) - .freeze(); - } -} diff --git a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/ValidationMessages.java b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/ValidationMessages.java new file mode 100644 index 0000000000..783c03cdab --- /dev/null +++ b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/ValidationMessages.java @@ -0,0 +1,23 @@ +/* + * Copyright 2023, the original author or authors. + * Licensed under the Apache License, Version 2.0 + * See LICENSE file in project root for terms. + */ +package com.yahoo.elide.modelconfig; + +import com.networknt.schema.ValidationMessage; + +import java.util.List; +import java.util.Set; + +public class ValidationMessages { + private static final String NEWLINE = System.lineSeparator(); + + public static String toString(Set results) { + if (results == null || results.isEmpty()) { + return null; + } + List list = results.stream().map(message -> message.getMessage()).toList(); + return NEWLINE + String.join(NEWLINE, list); + } +} diff --git a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideArgumentNameFormatAttr.java b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideArgumentNameFormatAttr.java deleted file mode 100644 index 7e4ff4c2a6..0000000000 --- a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideArgumentNameFormatAttr.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2021, Yahoo Inc. - * Licensed under the Apache License, Version 2.0 - * See LICENSE file in project root for terms. - */ -package com.yahoo.elide.modelconfig.jsonformats; - -import com.github.fge.jackson.NodeType; -import com.github.fge.jsonschema.core.exceptions.ProcessingException; -import com.github.fge.jsonschema.core.report.ProcessingReport; -import com.github.fge.jsonschema.format.AbstractFormatAttribute; -import com.github.fge.jsonschema.processors.data.FullData; -import com.github.fge.msgsimple.bundle.MessageBundle; - -/** - * Format specifier for {@code elideArgumentName} format attribute. - *

- * This specifier will check if a string instance is a valid Elide Argument Name. - *

- */ -public class ElideArgumentNameFormatAttr extends AbstractFormatAttribute { - - public static final String FORMAT_NAME = "elideArgumentName"; - public static final String NAME_KEY = "elideArgumentName.error.name"; - public static final String NAME_MSG = "Argument name [%s] is not allowed. Argument name cannot be 'grain'."; - public static final String FORMAT_KEY = "elideArgumentName.error.format"; - public static final String FORMAT_MSG = "Argument name [%s] is not allowed. Name must start with an alphabetic " - + "character and can include alaphabets, numbers and '_' only."; - - public ElideArgumentNameFormatAttr() { - super(FORMAT_NAME, NodeType.STRING); - } - - @Override - public void validate(final ProcessingReport report, final MessageBundle bundle, final FullData data) - throws ProcessingException { - final String input = data.getInstance().getNode().textValue(); - - if (!ElideNameFormatAttr.NAME_FORMAT_REGEX.matcher(input).matches()) { - report.error(newMsg(data, bundle, FORMAT_KEY).putArgument("value", input)); - } - - if (input.equalsIgnoreCase("grain")) { - report.error(newMsg(data, bundle, NAME_KEY).putArgument("value", input)); - } - } -} diff --git a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideCardinalityFormatAttr.java b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideCardinalityFormatAttr.java deleted file mode 100644 index 13343e39d7..0000000000 --- a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideCardinalityFormatAttr.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2020, Yahoo Inc. - * Licensed under the Apache License, Version 2.0 - * See LICENSE file in project root for terms. - */ -package com.yahoo.elide.modelconfig.jsonformats; - -import com.github.fge.jackson.NodeType; -import com.github.fge.jsonschema.core.exceptions.ProcessingException; -import com.github.fge.jsonschema.core.report.ProcessingReport; -import com.github.fge.jsonschema.format.AbstractFormatAttribute; -import com.github.fge.jsonschema.processors.data.FullData; -import com.github.fge.msgsimple.bundle.MessageBundle; - -import java.util.regex.Pattern; - -/** - * Format specifier for {@code elideCardiality} format attribute. - *

- * This specifier will check if a string instance is one of {@code Tiny, Small, Medium, Large, Huge}. - *

- */ -public class ElideCardinalityFormatAttr extends AbstractFormatAttribute { - private static final Pattern CARDINALITY_PATTERN = Pattern.compile("^(?i)(Tiny|Small|Medium|Large|Huge)$"); - - public static final String FORMAT_NAME = "elideCardiality"; - public static final String TYPE_KEY = "elideCardiality.error.enum"; - public static final String TYPE_MSG = "Cardinality type [%s] is not allowed. Supported value is one of " - + "[Tiny, Small, Medium, Large, Huge]."; - - public ElideCardinalityFormatAttr() { - super(FORMAT_NAME, NodeType.STRING); - } - - @Override - public void validate(final ProcessingReport report, final MessageBundle bundle, final FullData data) - throws ProcessingException { - final String input = data.getInstance().getNode().textValue(); - - if (!CARDINALITY_PATTERN.matcher(input).matches()) { - report.error(newMsg(data, bundle, TYPE_KEY).putArgument("value", input)); - } - } -} diff --git a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideFieldNameFormatAttr.java b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideFieldNameFormatAttr.java deleted file mode 100644 index 6c98c0eb9b..0000000000 --- a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideFieldNameFormatAttr.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2020, Yahoo Inc. - * Licensed under the Apache License, Version 2.0 - * See LICENSE file in project root for terms. - */ -package com.yahoo.elide.modelconfig.jsonformats; - -import com.github.fge.jackson.NodeType; -import com.github.fge.jsonschema.core.exceptions.ProcessingException; -import com.github.fge.jsonschema.core.report.ProcessingReport; -import com.github.fge.jsonschema.format.AbstractFormatAttribute; -import com.github.fge.jsonschema.processors.data.FullData; -import com.github.fge.msgsimple.bundle.MessageBundle; - -import java.util.Set; -import java.util.TreeSet; -import java.util.regex.Pattern; - -/** - * Format specifier for {@code elideFieldName} format attribute. - *

- * This specifier will check if a string instance is a valid Elide Field Name. - *

- */ -public class ElideFieldNameFormatAttr extends AbstractFormatAttribute { - private static final Pattern FIELD_NAME_FORMAT_REGEX = Pattern.compile("^[a-z][0-9A-Za-z_]*$"); - private static final Set RESERVED_KEYWORDS_FOR_COLUMN_NAME = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); - - static { - RESERVED_KEYWORDS_FOR_COLUMN_NAME.add("id"); - RESERVED_KEYWORDS_FOR_COLUMN_NAME.add("sql"); - } - - public static final String FORMAT_NAME = "elideFieldName"; - public static final String NAME_KEY = "elideFieldName.error.name"; - public static final String NAME_MSG = - "Field name [%s] is not allowed. Field name cannot be one of " + RESERVED_KEYWORDS_FOR_COLUMN_NAME; - public static final String FORMAT_KEY = "elideFieldName.error.format"; - public static final String FORMAT_MSG = "Field name [%s] is not allowed. Field name must start with " - + "lower case alphabet and can include alaphabets, numbers and '_' only."; - - public ElideFieldNameFormatAttr() { - super(FORMAT_NAME, NodeType.STRING); - } - - @Override - public void validate(final ProcessingReport report, final MessageBundle bundle, final FullData data) - throws ProcessingException { - final String input = data.getInstance().getNode().textValue(); - - if (!FIELD_NAME_FORMAT_REGEX.matcher(input).matches()) { - report.error(newMsg(data, bundle, FORMAT_KEY).putArgument("value", input)); - } - - if (RESERVED_KEYWORDS_FOR_COLUMN_NAME.contains(input)) { - report.error(newMsg(data, bundle, NAME_KEY).putArgument("value", input)); - } - } -} diff --git a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideFieldTypeFormatAttr.java b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideFieldTypeFormatAttr.java deleted file mode 100644 index 4c7584dd35..0000000000 --- a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideFieldTypeFormatAttr.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2020, Yahoo Inc. - * Licensed under the Apache License, Version 2.0 - * See LICENSE file in project root for terms. - */ -package com.yahoo.elide.modelconfig.jsonformats; - -import com.github.fge.jackson.NodeType; -import com.github.fge.jsonschema.core.exceptions.ProcessingException; -import com.github.fge.jsonschema.core.report.ProcessingReport; -import com.github.fge.jsonschema.format.AbstractFormatAttribute; -import com.github.fge.jsonschema.processors.data.FullData; -import com.github.fge.msgsimple.bundle.MessageBundle; - -import java.util.regex.Pattern; - -/** - * Format specifier for {@code elideFieldType} format attribute. - *

- * This specifier will check if a string instance is one of {@code Integer, Decimal, Money, Text, Coordinate, Boolean}. - *

- */ -public class ElideFieldTypeFormatAttr extends AbstractFormatAttribute { - public static final Pattern FIELD_TYPE_PATTERN = - Pattern.compile("^(?i)(Integer|Decimal|Money|Text|Coordinate|Boolean|Enum_Text|Enum_Ordinal)$"); - - public static final String FORMAT_NAME = "elideFieldType"; - public static final String TYPE_KEY = "elideFieldType.error.enum"; - public static final String TYPE_MSG = "Field type [%s] is not allowed. Supported value is one of " - + "[Integer, Decimal, Money, Text, Coordinate, Boolean, Enum_Text, Enum_Ordinal]."; - - public ElideFieldTypeFormatAttr() { - this(FORMAT_NAME); - } - - public ElideFieldTypeFormatAttr(String formatName) { - super(formatName, NodeType.STRING); - } - - @Override - public void validate(final ProcessingReport report, final MessageBundle bundle, final FullData data) - throws ProcessingException { - final String input = data.getInstance().getNode().textValue(); - - if (!FIELD_TYPE_PATTERN.matcher(input).matches()) { - report.error(newMsg(data, bundle, TYPE_KEY).putArgument("value", input)); - } - } -} diff --git a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideGrainTypeFormatAttr.java b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideGrainTypeFormatAttr.java deleted file mode 100644 index e3bb8215c8..0000000000 --- a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideGrainTypeFormatAttr.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2020, Yahoo Inc. - * Licensed under the Apache License, Version 2.0 - * See LICENSE file in project root for terms. - */ -package com.yahoo.elide.modelconfig.jsonformats; - -import com.github.fge.jackson.NodeType; -import com.github.fge.jsonschema.core.exceptions.ProcessingException; -import com.github.fge.jsonschema.core.report.ProcessingReport; -import com.github.fge.jsonschema.format.AbstractFormatAttribute; -import com.github.fge.jsonschema.processors.data.FullData; -import com.github.fge.msgsimple.bundle.MessageBundle; - -import java.util.regex.Pattern; - -/** - * Format specifier for {@code elideGrainType} format attribute. - *

- * This specifier will check if a string instance is one of - * {@code Second, Minute, Hour, Day, IsoWeek, Week, Month, Quarter, Year}. - *

- */ -public class ElideGrainTypeFormatAttr extends AbstractFormatAttribute { - private static final Pattern GRAIN_TYPE_PATTERN = - Pattern.compile("^(?i)(Second|Minute|Hour|Day|IsoWeek|Week|Month|Quarter|Year)$"); - - public static final String FORMAT_NAME = "elideGrainType"; - public static final String TYPE_KEY = "elideGrainType.error.enum"; - public static final String TYPE_MSG = "Grain type [%s] is not allowed. Supported value is one of " - + "[Second, Minute, Hour, Day, IsoWeek, Week, Month, Quarter, Year]."; - - public ElideGrainTypeFormatAttr() { - super(FORMAT_NAME, NodeType.STRING); - } - - @Override - public void validate(final ProcessingReport report, final MessageBundle bundle, final FullData data) - throws ProcessingException { - final String input = data.getInstance().getNode().textValue(); - - if (!GRAIN_TYPE_PATTERN.matcher(input).matches()) { - report.error(newMsg(data, bundle, TYPE_KEY).putArgument("value", input)); - } - } -} diff --git a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideJDBCUrlFormatAttr.java b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideJDBCUrlFormatAttr.java deleted file mode 100644 index 9cb8275942..0000000000 --- a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideJDBCUrlFormatAttr.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2020, Yahoo Inc. - * Licensed under the Apache License, Version 2.0 - * See LICENSE file in project root for terms. - */ -package com.yahoo.elide.modelconfig.jsonformats; - -import com.github.fge.jackson.NodeType; -import com.github.fge.jsonschema.core.exceptions.ProcessingException; -import com.github.fge.jsonschema.core.report.ProcessingReport; -import com.github.fge.jsonschema.format.AbstractFormatAttribute; -import com.github.fge.jsonschema.processors.data.FullData; -import com.github.fge.msgsimple.bundle.MessageBundle; - -/** - * Format specifier for {@code elideJdbcUrl} format attribute. - *

- * This specifier will check if a string instance is a valid JDBC url. - *

- */ -public class ElideJDBCUrlFormatAttr extends AbstractFormatAttribute { - - public static final String FORMAT_NAME = "elideJdbcUrl"; - public static final String FORMAT_KEY = "elideJdbcUrl.error.format"; - public static final String FORMAT_MSG = "Input value [%s] is not a valid JDBC url, it must start with 'jdbc:'."; - - public ElideJDBCUrlFormatAttr() { - super(FORMAT_NAME, NodeType.STRING); - } - - @Override - public void validate(final ProcessingReport report, final MessageBundle bundle, final FullData data) - throws ProcessingException { - final String input = data.getInstance().getNode().textValue(); - - if (!input.startsWith("jdbc:")) { - report.error(newMsg(data, bundle, FORMAT_KEY).putArgument("value", input)); - } - } -} diff --git a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideJoinKindFormatAttr.java b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideJoinKindFormatAttr.java deleted file mode 100644 index a45212fdd4..0000000000 --- a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideJoinKindFormatAttr.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2020, Yahoo Inc. - * Licensed under the Apache License, Version 2.0 - * See LICENSE file in project root for terms. - */ -package com.yahoo.elide.modelconfig.jsonformats; - -import com.github.fge.jackson.NodeType; -import com.github.fge.jsonschema.core.exceptions.ProcessingException; -import com.github.fge.jsonschema.core.report.ProcessingReport; -import com.github.fge.jsonschema.format.AbstractFormatAttribute; -import com.github.fge.jsonschema.processors.data.FullData; -import com.github.fge.msgsimple.bundle.MessageBundle; - -import java.util.regex.Pattern; - -/** - * Format specifier for {@code elideJoinKind} format attribute. - *

- * This specifier will check if a string instance is one of {@code ToOne, ToMany}. - *

- */ -public class ElideJoinKindFormatAttr extends AbstractFormatAttribute { - private static final Pattern JOIN_KIND_PATTERN = Pattern.compile("^(?i)(ToOne|ToMany)$"); - - public static final String FORMAT_NAME = "elideJoinKind"; - public static final String TYPE_KEY = "elideJoinKind.error.enum"; - public static final String TYPE_MSG = "Join kind [%s] is not allowed. Supported value is one of [ToOne, ToMany]."; - - public ElideJoinKindFormatAttr() { - super(FORMAT_NAME, NodeType.STRING); - } - - @Override - public void validate(final ProcessingReport report, final MessageBundle bundle, final FullData data) - throws ProcessingException { - final String input = data.getInstance().getNode().textValue(); - - if (!JOIN_KIND_PATTERN.matcher(input).matches()) { - report.error(newMsg(data, bundle, TYPE_KEY).putArgument("value", input)); - } - } -} diff --git a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideJoinTypeFormatAttr.java b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideJoinTypeFormatAttr.java deleted file mode 100644 index 0200e4ed40..0000000000 --- a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideJoinTypeFormatAttr.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2020, Yahoo Inc. - * Licensed under the Apache License, Version 2.0 - * See LICENSE file in project root for terms. - */ -package com.yahoo.elide.modelconfig.jsonformats; - -import com.github.fge.jackson.NodeType; -import com.github.fge.jsonschema.core.exceptions.ProcessingException; -import com.github.fge.jsonschema.core.report.ProcessingReport; -import com.github.fge.jsonschema.format.AbstractFormatAttribute; -import com.github.fge.jsonschema.processors.data.FullData; -import com.github.fge.msgsimple.bundle.MessageBundle; - -import java.util.regex.Pattern; - -/** - * Format specifier for {@code elideJoinType} format attribute. - *

- * This specifier will check if a string instance is one of {@code left, inner, full, cross}. - *

- */ -public class ElideJoinTypeFormatAttr extends AbstractFormatAttribute { - private static final Pattern JOIN_TYPE_PATTERN = Pattern.compile("^(?i)(left|inner|full|cross)$"); - - public static final String FORMAT_NAME = "elideJoinType"; - public static final String TYPE_KEY = "elideJoinType.error.enum"; - public static final String TYPE_MSG = - "Join type [%s] is not allowed. Supported value is one of [left, inner, full, cross]."; - - public ElideJoinTypeFormatAttr() { - super(FORMAT_NAME, NodeType.STRING); - } - - @Override - public void validate(final ProcessingReport report, final MessageBundle bundle, final FullData data) - throws ProcessingException { - final String input = data.getInstance().getNode().textValue(); - - if (!JOIN_TYPE_PATTERN.matcher(input).matches()) { - report.error(newMsg(data, bundle, TYPE_KEY).putArgument("value", input)); - } - } -} diff --git a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideNameFormatAttr.java b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideNameFormatAttr.java deleted file mode 100644 index 5d60adb495..0000000000 --- a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideNameFormatAttr.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2020, Yahoo Inc. - * Licensed under the Apache License, Version 2.0 - * See LICENSE file in project root for terms. - */ -package com.yahoo.elide.modelconfig.jsonformats; - -import com.github.fge.jackson.NodeType; -import com.github.fge.jsonschema.core.exceptions.ProcessingException; -import com.github.fge.jsonschema.core.report.ProcessingReport; -import com.github.fge.jsonschema.format.AbstractFormatAttribute; -import com.github.fge.jsonschema.processors.data.FullData; -import com.github.fge.msgsimple.bundle.MessageBundle; - -import java.util.regex.Pattern; - -/** - * Format specifier for {@code elideName} format attribute. - *

- * This specifier will check if a string instance is a valid Elide Name. - *

- */ -public class ElideNameFormatAttr extends AbstractFormatAttribute { - public static final Pattern NAME_FORMAT_REGEX = Pattern.compile("^[A-Za-z][0-9A-Za-z_]*$"); - - public static final String FORMAT_NAME = "elideName"; - public static final String FORMAT_KEY = "elideName.error.format"; - public static final String FORMAT_MSG = - "Name [%s] is not allowed. Name must start with an alphabetic character and can include " - + "alaphabets, numbers and '_' only."; - - public ElideNameFormatAttr() { - super(FORMAT_NAME, NodeType.STRING); - } - - @Override - public void validate(final ProcessingReport report, final MessageBundle bundle, final FullData data) - throws ProcessingException { - final String input = data.getInstance().getNode().textValue(); - - if (!NAME_FORMAT_REGEX.matcher(input).matches()) { - report.error(newMsg(data, bundle, FORMAT_KEY).putArgument("value", input)); - } - } -} diff --git a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideNamespaceNameFormatAttr.java b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideNamespaceNameFormatAttr.java deleted file mode 100644 index 6ecfc0a84a..0000000000 --- a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideNamespaceNameFormatAttr.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2020, Yahoo Inc. - * Licensed under the Apache License, Version 2.0 - * See LICENSE file in project root for terms. - */ -package com.yahoo.elide.modelconfig.jsonformats; - -import com.github.fge.jackson.NodeType; -import com.github.fge.jsonschema.core.exceptions.ProcessingException; -import com.github.fge.jsonschema.core.report.ProcessingReport; -import com.github.fge.jsonschema.format.AbstractFormatAttribute; -import com.github.fge.jsonschema.processors.data.FullData; -import com.github.fge.msgsimple.bundle.MessageBundle; - -import java.util.Locale; -import java.util.regex.Pattern; - -/** - * Format specifier for {@code elideNamespaceName} format attribute. - *

- * This specifier will check if a string instance is a valid Elide Name. - *

- */ -public class ElideNamespaceNameFormatAttr extends AbstractFormatAttribute { - public static final Pattern NAME_FORMAT_REGEX = ElideNameFormatAttr.NAME_FORMAT_REGEX; - - public static final String FORMAT_NAME = "elideNamespaceName"; - public static final String FORMAT_KEY = "elideNamespaceName.error.format"; - public static final String FORMAT_ADDITIONAL_KEY = "elideNamespaceName.error.additional"; - public static final String FORMAT_KEY_MSG = - "Name [%s] is not allowed. Name must start with an alphabetic character and can include " - + "alaphabets, numbers and '_' only."; - public static final String FORMAT_ADDITIONAL_KEY_MSG = - "Name [%s] clashes with the 'default' namespace. Either change the case or pick a different namespace " - + "name."; - public static final String DEFAULT_NAME = "default"; - - public ElideNamespaceNameFormatAttr() { - super(FORMAT_NAME, NodeType.STRING); - } - - @Override - public void validate(final ProcessingReport report, final MessageBundle bundle, final FullData data) - throws ProcessingException { - final String input = data.getInstance().getNode().textValue(); - - if (!NAME_FORMAT_REGEX.matcher(input).matches()) { - report.error(newMsg(data, bundle, FORMAT_KEY).putArgument("value", input)); - } - - if (!input.equals(DEFAULT_NAME) && input.toLowerCase(Locale.ENGLISH).equals(DEFAULT_NAME)) { - report.error(newMsg(data, bundle, FORMAT_ADDITIONAL_KEY).putArgument("value", input)); - } - } -} diff --git a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideRSQLFilterFormat.java b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideRSQLFilterFormat.java new file mode 100644 index 0000000000..bf89606459 --- /dev/null +++ b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideRSQLFilterFormat.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020, Yahoo Inc. + * Licensed under the Apache License, Version 2.0 + * See LICENSE file in project root for terms. + */ +package com.yahoo.elide.modelconfig.jsonformats; + +import static com.yahoo.elide.core.filter.dialect.RSQLFilterDialect.getDefaultOperatorsWithIsnull; + +import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Format; + +import cz.jirutka.rsql.parser.RSQLParser; +import cz.jirutka.rsql.parser.RSQLParserException; + +/** + * Format specifier for {@code elideRSQLFilter} format attribute. + *

+ * This specifier will check if a string instance is a valid RSQL filter. + *

+ */ +public class ElideRSQLFilterFormat implements Format { + + public static final String NAME = "elideRSQLFilter"; + public static final String ERROR_MESSAGE_DESCRIPTION = "{0}: does not match the elideRSQLFilter pattern " + + "is not a valid RSQL filter expression." + + " Please visit page https://elide.io/pages/guide/v5/11-graphql.html#operators for samples."; + + @Override + public boolean matches(ExecutionContext executionContext, String value) { + try { + new RSQLParser(getDefaultOperatorsWithIsnull()).parse(value); + } catch (RSQLParserException e) { + return false; + } + return true; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public String getMessageKey() { + return ERROR_MESSAGE_DESCRIPTION; + } +} diff --git a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideRSQLFilterFormatAttr.java b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideRSQLFilterFormatAttr.java deleted file mode 100644 index 4d9934cd5e..0000000000 --- a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideRSQLFilterFormatAttr.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2020, Yahoo Inc. - * Licensed under the Apache License, Version 2.0 - * See LICENSE file in project root for terms. - */ -package com.yahoo.elide.modelconfig.jsonformats; - -import static com.yahoo.elide.core.filter.dialect.RSQLFilterDialect.getDefaultOperatorsWithIsnull; - -import com.github.fge.jackson.NodeType; -import com.github.fge.jsonschema.core.exceptions.ProcessingException; -import com.github.fge.jsonschema.core.report.ProcessingReport; -import com.github.fge.jsonschema.format.AbstractFormatAttribute; -import com.github.fge.jsonschema.processors.data.FullData; -import com.github.fge.msgsimple.bundle.MessageBundle; - -import cz.jirutka.rsql.parser.RSQLParser; -import cz.jirutka.rsql.parser.RSQLParserException; - -/** - * Format specifier for {@code elideRSQLFilter} format attribute. - *

- * This specifier will check if a string instance is a valid RSQL filter. - *

- */ -public class ElideRSQLFilterFormatAttr extends AbstractFormatAttribute { - - public static final String FORMAT_NAME = "elideRSQLFilter"; - public static final String FORMAT_KEY = "elideRSQLFilter.error.format"; - public static final String FORMAT_MSG = "Input value[%s] is not a valid RSQL filter expression. Please visit page " - + "https://elide.io/pages/guide/v5/11-graphql.html#operators for samples."; - - public ElideRSQLFilterFormatAttr() { - super(FORMAT_NAME, NodeType.STRING); - } - - @Override - public void validate(final ProcessingReport report, final MessageBundle bundle, final FullData data) - throws ProcessingException { - final String input = data.getInstance().getNode().textValue(); - - try { - new RSQLParser(getDefaultOperatorsWithIsnull()).parse(input); - } catch (RSQLParserException e) { - report.error(newMsg(data, bundle, FORMAT_KEY).putArgument("value", input)); - } - } -} diff --git a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideRoleFormatAttr.java b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideRoleFormatAttr.java deleted file mode 100644 index e030391f22..0000000000 --- a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideRoleFormatAttr.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2020, Yahoo Inc. - * Licensed under the Apache License, Version 2.0 - * See LICENSE file in project root for terms. - */ -package com.yahoo.elide.modelconfig.jsonformats; - -import com.github.fge.jackson.NodeType; -import com.github.fge.jsonschema.core.exceptions.ProcessingException; -import com.github.fge.jsonschema.core.report.ProcessingReport; -import com.github.fge.jsonschema.format.AbstractFormatAttribute; -import com.github.fge.jsonschema.processors.data.FullData; -import com.github.fge.msgsimple.bundle.MessageBundle; - -import java.util.regex.Pattern; - -/** - * Format specifier for {@code elideRole} format attribute. - *

- * This specifier will check if a string instance is a valid Elide role. - *

- */ -public class ElideRoleFormatAttr extends AbstractFormatAttribute { - private static final Pattern ROLE_FORMAT_REGEX = Pattern.compile("^[A-Za-z][0-9A-Za-z. ]*$"); - - public static final String FORMAT_NAME = "elideRole"; - public static final String FORMAT_KEY = "elideRole.error.format"; - public static final String FORMAT_MSG = - "Role [%s] is not allowed. Role must start with an alphabetic character and can include " - + "alaphabets, numbers, spaces and '.' only."; - - public ElideRoleFormatAttr() { - super(FORMAT_NAME, NodeType.STRING); - } - - @Override - public void validate(final ProcessingReport report, final MessageBundle bundle, final FullData data) - throws ProcessingException { - final String input = data.getInstance().getNode().textValue(); - - if (!ROLE_FORMAT_REGEX.matcher(input).matches()) { - report.error(newMsg(data, bundle, FORMAT_KEY).putArgument("value", input)); - } - } -} diff --git a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideTimeFieldTypeFormatAttr.java b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideTimeFieldTypeFormatAttr.java deleted file mode 100644 index 6558d09ae5..0000000000 --- a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ElideTimeFieldTypeFormatAttr.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2020, Yahoo Inc. - * Licensed under the Apache License, Version 2.0 - * See LICENSE file in project root for terms. - */ -package com.yahoo.elide.modelconfig.jsonformats; - -import com.github.fge.jackson.NodeType; -import com.github.fge.jsonschema.core.exceptions.ProcessingException; -import com.github.fge.jsonschema.core.report.ProcessingReport; -import com.github.fge.jsonschema.format.AbstractFormatAttribute; -import com.github.fge.jsonschema.processors.data.FullData; -import com.github.fge.msgsimple.bundle.MessageBundle; - -import java.util.regex.Pattern; - -/** - * Format specifier for {@code elideTimeFieldType} format attribute. - *

- * This specifier will check if a string instance is {@code Time}. - *

- */ -public class ElideTimeFieldTypeFormatAttr extends AbstractFormatAttribute { - private static final Pattern TIME_FIELD_TYPE_PATTERN = Pattern.compile("^(?i)(Time)$"); - - public static final String FORMAT_NAME = "elideTimeFieldType"; - public static final String TYPE_KEY = "elideTimeFieldType.error.enum"; - public static final String TYPE_MSG = "Field type [%s] is not allowed. Field type must be " - + "[Time] for any time dimension."; - - public ElideTimeFieldTypeFormatAttr() { - super(FORMAT_NAME, NodeType.STRING); - } - - @Override - public void validate(final ProcessingReport report, final MessageBundle bundle, final FullData data) - throws ProcessingException { - final String input = data.getInstance().getNode().textValue(); - - if (!TIME_FIELD_TYPE_PATTERN.matcher(input).matches()) { - report.error(newMsg(data, bundle, TYPE_KEY).putArgument("value", input)); - } - } -} diff --git a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/JavaClassNameFormatAttr.java b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/JavaClassNameFormatAttr.java deleted file mode 100644 index 268186d356..0000000000 --- a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/JavaClassNameFormatAttr.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2020, Yahoo Inc. - * Licensed under the Apache License, Version 2.0 - * See LICENSE file in project root for terms. - */ -package com.yahoo.elide.modelconfig.jsonformats; - -import com.github.fge.jackson.NodeType; -import com.github.fge.jsonschema.core.exceptions.ProcessingException; -import com.github.fge.jsonschema.core.report.ProcessingReport; -import com.github.fge.jsonschema.format.AbstractFormatAttribute; -import com.github.fge.jsonschema.processors.data.FullData; -import com.github.fge.msgsimple.bundle.MessageBundle; - -import java.util.regex.Pattern; - -/** - * Format specifier for {@code javaClassName} format attribute. - *

- * This specifier will check if a string instance is a valid JAVA Class Name. - *

- */ -public class JavaClassNameFormatAttr extends AbstractFormatAttribute { - private static final String ID_PATTERN = "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"; - public static final Pattern CLASS_NAME_FORMAT_PATTERN = Pattern.compile(ID_PATTERN + "(\\." + ID_PATTERN + ")*"); - - public static final String FORMAT_NAME = "javaClassName"; - public static final String FORMAT_KEY = "javaClassName.error.format"; - public static final String FORMAT_MSG = "Input value[%s] is not a valid Java class name."; - - public JavaClassNameFormatAttr() { - super(FORMAT_NAME, NodeType.STRING); - } - - @Override - public void validate(final ProcessingReport report, final MessageBundle bundle, final FullData data) - throws ProcessingException { - final String input = data.getInstance().getNode().textValue(); - - if (!CLASS_NAME_FORMAT_PATTERN.matcher(input).matches()) { - report.error(newMsg(data, bundle, FORMAT_KEY).putArgument("value", input)); - } - } -} diff --git a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/JavaClassNameWithExtFormatAttr.java b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/JavaClassNameWithExtFormatAttr.java deleted file mode 100644 index d31255dfe5..0000000000 --- a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/JavaClassNameWithExtFormatAttr.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2020, Yahoo Inc. - * Licensed under the Apache License, Version 2.0 - * See LICENSE file in project root for terms. - */ -package com.yahoo.elide.modelconfig.jsonformats; - -import com.github.fge.jackson.NodeType; -import com.github.fge.jsonschema.core.exceptions.ProcessingException; -import com.github.fge.jsonschema.core.report.ProcessingReport; -import com.github.fge.jsonschema.format.AbstractFormatAttribute; -import com.github.fge.jsonschema.processors.data.FullData; -import com.github.fge.msgsimple.bundle.MessageBundle; - -import java.util.regex.Pattern; - -/** - * Format specifier for {@code javaClassNameWithExt} format attribute. - *

- * This specifier will check if a string instance is a valid JAVA Class Name with {@code .class} extension. - *

- */ -public class JavaClassNameWithExtFormatAttr extends AbstractFormatAttribute { - private static final Pattern CLASS_NAME_FORMAT_PATTERN = - Pattern.compile("^(\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*\\.)+class$"); - - public static final String FORMAT_NAME = "javaClassNameWithExt"; - public static final String FORMAT_KEY = "javaClassNameWithExt.error.format"; - public static final String FORMAT_MSG = "Input value[%s] is not a valid Java class name with .class extension."; - - public JavaClassNameWithExtFormatAttr() { - super(FORMAT_NAME, NodeType.STRING); - } - - @Override - public void validate(final ProcessingReport report, final MessageBundle bundle, final FullData data) - throws ProcessingException { - final String input = data.getInstance().getNode().textValue(); - - if (!CLASS_NAME_FORMAT_PATTERN.matcher(input).matches()) { - report.error(newMsg(data, bundle, FORMAT_KEY).putArgument("value", input)); - } - } -} diff --git a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ValidateArgsPropertiesKeyword.java b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ValidateArgsPropertiesKeyword.java deleted file mode 100644 index 6b5b6041d7..0000000000 --- a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ValidateArgsPropertiesKeyword.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2021, Yahoo Inc. - * Licensed under the Apache License, Version 2.0 - * See LICENSE file in project root for terms. - */ -package com.yahoo.elide.modelconfig.jsonformats; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.github.fge.jackson.NodeType; -import com.github.fge.jackson.jsonpointer.JsonPointer; -import com.github.fge.jsonschema.core.exceptions.ProcessingException; -import com.github.fge.jsonschema.core.keyword.syntax.checkers.AbstractSyntaxChecker; -import com.github.fge.jsonschema.core.report.ProcessingReport; -import com.github.fge.jsonschema.core.tree.SchemaTree; -import com.github.fge.jsonschema.keyword.digest.AbstractDigester; -import com.github.fge.jsonschema.library.Keyword; -import com.github.fge.msgsimple.bundle.MessageBundle; - -import lombok.Getter; - -import java.util.Collection; - -/** - * Creates custom keyword for {@code validateArgumentProperties}. - */ -public class ValidateArgsPropertiesKeyword { - - @Getter - private Keyword keyword; - - public ValidateArgsPropertiesKeyword() { - keyword = Keyword.newBuilder(ValidateArgsPropertiesValidator.KEYWORD) - .withSyntaxChecker(new ValidateArgsPropertiesSyntaxChecker()) - .withDigester(new ValidateArgsPropertiesDigester()) - .withValidatorClass(ValidateArgsPropertiesValidator.class) - .freeze(); - } - - /** - * Defines custom SyntaxChecker for {@code validateArgumentProperties}. - */ - private class ValidateArgsPropertiesSyntaxChecker extends AbstractSyntaxChecker { - - public ValidateArgsPropertiesSyntaxChecker() { - super(ValidateArgsPropertiesValidator.KEYWORD, NodeType.BOOLEAN); - } - - @Override - protected void checkValue(Collection pointers, MessageBundle bundle, ProcessingReport report, - SchemaTree tree) throws ProcessingException { - // AbstractSyntaxChecker has already verified that value is of type Boolean - // No additional Checks Required - } - } - - /** - * Defines custom Digester for {@code validateArgumentProperties}. - */ - private class ValidateArgsPropertiesDigester extends AbstractDigester { - - public ValidateArgsPropertiesDigester() { - super(ValidateArgsPropertiesValidator.KEYWORD, NodeType.OBJECT); - } - - @Override - public JsonNode digest(final JsonNode schema) { - final ObjectNode node = FACTORY.objectNode(); - node.put(keyword, schema.get(keyword).asBoolean(true)); - - return node; - } - } -} diff --git a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ValidateArgsPropertiesValidator.java b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ValidateArgsPropertiesValidator.java deleted file mode 100644 index 98bbbc6750..0000000000 --- a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ValidateArgsPropertiesValidator.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2021, Yahoo Inc. - * Licensed under the Apache License, Version 2.0 - * See LICENSE file in project root for terms. - */ -package com.yahoo.elide.modelconfig.jsonformats; - -import com.fasterxml.jackson.databind.JsonNode; -import com.github.fge.jsonschema.core.exceptions.ProcessingException; -import com.github.fge.jsonschema.core.processing.Processor; -import com.github.fge.jsonschema.core.report.ProcessingReport; -import com.github.fge.jsonschema.keyword.validator.AbstractKeywordValidator; -import com.github.fge.jsonschema.processors.data.FullData; -import com.github.fge.msgsimple.bundle.MessageBundle; -import com.google.common.collect.Sets; - -import java.util.Set; - -/** - * Defines custom Keyword Validator for {@code validateArgumentProperties}. - *

- * This validator checks not both {@code tableSource} and {@code values} property is defined for any argument. - *

- */ -public class ValidateArgsPropertiesValidator extends AbstractKeywordValidator { - - public static final String KEYWORD = "validateArgumentProperties"; - public static final String ATMOST_ONE_KEY = "validateArgumentProperties.error.atmostOne"; - public static final String ATMOST_ONE_MSG = - "tableSource and values cannot both be defined for an argument. Choose One or None."; - - private boolean validate; - - public ValidateArgsPropertiesValidator(final JsonNode digest) { - super(KEYWORD); - validate = digest.get(keyword).booleanValue(); - } - - @Override - public void validate(Processor processor, ProcessingReport report, MessageBundle bundle, - FullData data) throws ProcessingException { - - if (validate) { - JsonNode instance = data.getInstance().getNode(); - Set fields = Sets.newHashSet(instance.fieldNames()); - - if (fields.contains("values") && fields.contains("tableSource")) { - report.error(newMsg(data, bundle, ATMOST_ONE_KEY)); - } - - } - } - - @Override - public String toString() { - return keyword; - } -} diff --git a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ValidateDimPropertiesKeyword.java b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ValidateDimPropertiesKeyword.java deleted file mode 100644 index ae99cd1650..0000000000 --- a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ValidateDimPropertiesKeyword.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2020, Yahoo Inc. - * Licensed under the Apache License, Version 2.0 - * See LICENSE file in project root for terms. - */ -package com.yahoo.elide.modelconfig.jsonformats; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.github.fge.jackson.NodeType; -import com.github.fge.jackson.jsonpointer.JsonPointer; -import com.github.fge.jsonschema.core.exceptions.ProcessingException; -import com.github.fge.jsonschema.core.keyword.syntax.checkers.AbstractSyntaxChecker; -import com.github.fge.jsonschema.core.report.ProcessingReport; -import com.github.fge.jsonschema.core.tree.SchemaTree; -import com.github.fge.jsonschema.keyword.digest.AbstractDigester; -import com.github.fge.jsonschema.library.Keyword; -import com.github.fge.msgsimple.bundle.MessageBundle; - -import lombok.Getter; - -import java.util.Collection; - -/** - * Creates custom keyword for {@code validateDimensionProperties}. - */ -public class ValidateDimPropertiesKeyword { - - @Getter - private Keyword keyword; - - public ValidateDimPropertiesKeyword() { - keyword = Keyword.newBuilder(ValidateDimPropertiesValidator.KEYWORD) - .withSyntaxChecker(new ValidateDimPropertiesSyntaxChecker()) - .withDigester(new ValidateDimPropertiesDigester()) - .withValidatorClass(ValidateDimPropertiesValidator.class) - .freeze(); - } - - /** - * Defines custom SyntaxChecker for {@code validateDimensionProperties}. - */ - private class ValidateDimPropertiesSyntaxChecker extends AbstractSyntaxChecker { - - public ValidateDimPropertiesSyntaxChecker() { - super(ValidateDimPropertiesValidator.KEYWORD, NodeType.BOOLEAN); - } - - @Override - protected void checkValue(Collection pointers, MessageBundle bundle, ProcessingReport report, - SchemaTree tree) throws ProcessingException { - // AbstractSyntaxChecker has already verified that value is of type Boolean - // No additional Checks Required - } - } - - /** - * Defines custom Digester for {@code validateDimensionProperties}. - */ - private class ValidateDimPropertiesDigester extends AbstractDigester { - - public ValidateDimPropertiesDigester() { - super(ValidateDimPropertiesValidator.KEYWORD, NodeType.OBJECT); - } - - @Override - public JsonNode digest(final JsonNode schema) { - final ObjectNode node = FACTORY.objectNode(); - node.put(keyword, schema.get(keyword).asBoolean(true)); - return node; - } - } -} diff --git a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ValidateDimPropertiesValidator.java b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ValidateDimPropertiesValidator.java deleted file mode 100644 index 718a1430f1..0000000000 --- a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ValidateDimPropertiesValidator.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2020, Yahoo Inc. - * Licensed under the Apache License, Version 2.0 - * See LICENSE file in project root for terms. - */ -package com.yahoo.elide.modelconfig.jsonformats; - -import com.fasterxml.jackson.databind.JsonNode; -import com.github.fge.jsonschema.core.exceptions.ProcessingException; -import com.github.fge.jsonschema.core.processing.Processor; -import com.github.fge.jsonschema.core.report.ProcessingReport; -import com.github.fge.jsonschema.keyword.validator.AbstractKeywordValidator; -import com.github.fge.jsonschema.processors.data.FullData; -import com.github.fge.msgsimple.bundle.MessageBundle; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Sets; - -import java.util.Set; - -/** - * Defines custom Keyword Validator for {@code validateDimensionProperties}. - *

- * This validator checks neither additional properties are defined for any dimension nor not both {@code tableSource} - * and {@code values} property is defined for any dimension. - *

- */ -public class ValidateDimPropertiesValidator extends AbstractKeywordValidator { - - public static final Set COMMON_DIM_PROPERTIES = ImmutableSet.of("name", "friendlyName", - "description", "category", "hidden", "readAccess", "definition", "cardinality", "tags", "type", - "arguments", "filterTemplate"); - private static final Set ADDITIONAL_DIM_PROPERTIES = ImmutableSet.of("values", "tableSource"); - - public static final String KEYWORD = "validateDimensionProperties"; - public static final String ATMOST_ONE_KEY = "validateDimensionProperties.error.atmostOne"; - public static final String ATMOST_ONE_MSG = - "tableSource and values cannot both be defined for a dimension. Choose One or None."; - public static final String ADDITIONAL_KEY = "validateDimensionProperties.error.addtional"; - public static final String ADDITIONAL_MSG = "Properties: %s are not allowed for dimensions."; - - private boolean validate; - - public ValidateDimPropertiesValidator(final JsonNode digest) { - super(KEYWORD); - validate = digest.get(keyword).booleanValue(); - } - - @Override - public void validate(Processor processor, ProcessingReport report, MessageBundle bundle, - FullData data) throws ProcessingException { - - if (validate) { - JsonNode instance = data.getInstance().getNode(); - Set fields = Sets.newHashSet(instance.fieldNames()); - - if (fields.contains("values") && fields.contains("tableSource")) { - report.error(newMsg(data, bundle, ATMOST_ONE_KEY)); - } - - fields.removeAll(COMMON_DIM_PROPERTIES); - fields.removeAll(ADDITIONAL_DIM_PROPERTIES); - if (!fields.isEmpty()) { - report.error(newMsg(data, bundle, ADDITIONAL_KEY).putArgument("value", fields.toString())); - } - } - } - - @Override - public String toString() { - return keyword; - } -} diff --git a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ValidateTimeDimPropertiesKeyword.java b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ValidateTimeDimPropertiesKeyword.java deleted file mode 100644 index 7705380e15..0000000000 --- a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ValidateTimeDimPropertiesKeyword.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2020, Yahoo Inc. - * Licensed under the Apache License, Version 2.0 - * See LICENSE file in project root for terms. - */ -package com.yahoo.elide.modelconfig.jsonformats; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.github.fge.jackson.NodeType; -import com.github.fge.jackson.jsonpointer.JsonPointer; -import com.github.fge.jsonschema.core.exceptions.ProcessingException; -import com.github.fge.jsonschema.core.keyword.syntax.checkers.AbstractSyntaxChecker; -import com.github.fge.jsonschema.core.report.ProcessingReport; -import com.github.fge.jsonschema.core.tree.SchemaTree; -import com.github.fge.jsonschema.keyword.digest.AbstractDigester; -import com.github.fge.jsonschema.library.Keyword; -import com.github.fge.msgsimple.bundle.MessageBundle; - -import lombok.Getter; - -import java.util.Collection; - -/** - * Creates custom keyword for {@code validateTimeDimensionProperties}. - */ -public class ValidateTimeDimPropertiesKeyword { - - @Getter - private Keyword keyword; - - public ValidateTimeDimPropertiesKeyword() { - keyword = Keyword.newBuilder(ValidateTimeDimPropertiesValidator.KEYWORD) - .withSyntaxChecker(new ValidateTimeDimPropertiesSyntaxChecker()) - .withDigester(new ValidateTimeDimPropertiesDigester()) - .withValidatorClass(ValidateTimeDimPropertiesValidator.class) - .freeze(); - } - - /** - * Defines custom SyntaxChecker for {@code validateTimeDimensionProperties}. - */ - private class ValidateTimeDimPropertiesSyntaxChecker extends AbstractSyntaxChecker { - - public ValidateTimeDimPropertiesSyntaxChecker() { - super(ValidateTimeDimPropertiesValidator.KEYWORD, NodeType.BOOLEAN); - } - - @Override - protected void checkValue(Collection pointers, MessageBundle bundle, ProcessingReport report, - SchemaTree tree) throws ProcessingException { - // AbstractSyntaxChecker has already verified that value is of type Boolean - // No additional Checks Required - } - } - - /** - * Defines custom Digester for {@code validateTimeDimensionProperties}. - */ - private class ValidateTimeDimPropertiesDigester extends AbstractDigester { - - public ValidateTimeDimPropertiesDigester() { - super(ValidateTimeDimPropertiesValidator.KEYWORD, NodeType.OBJECT); - } - - @Override - public JsonNode digest(final JsonNode schema) { - final ObjectNode node = FACTORY.objectNode(); - node.put(keyword, schema.get(keyword).asBoolean(true)); - return node; - } - } -} diff --git a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ValidateTimeDimPropertiesValidator.java b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ValidateTimeDimPropertiesValidator.java deleted file mode 100644 index 1b4194875d..0000000000 --- a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/jsonformats/ValidateTimeDimPropertiesValidator.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2020, Yahoo Inc. - * Licensed under the Apache License, Version 2.0 - * See LICENSE file in project root for terms. - */ -package com.yahoo.elide.modelconfig.jsonformats; - -import static com.yahoo.elide.modelconfig.jsonformats.ValidateDimPropertiesValidator.COMMON_DIM_PROPERTIES; - -import com.fasterxml.jackson.databind.JsonNode; -import com.github.fge.jsonschema.core.exceptions.ProcessingException; -import com.github.fge.jsonschema.core.processing.Processor; -import com.github.fge.jsonschema.core.report.ProcessingReport; -import com.github.fge.jsonschema.keyword.validator.AbstractKeywordValidator; -import com.github.fge.jsonschema.processors.data.FullData; -import com.github.fge.msgsimple.bundle.MessageBundle; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Sets; - -import java.util.Set; - -/** - * Defines custom Keyword Validator for {@code validateTimeDimensionProperties}. - *

- * This validator checks no additional properties are defined for any time dimension. - *

- */ -public class ValidateTimeDimPropertiesValidator extends AbstractKeywordValidator { - - private static final Set ADDITIONAL_TIME_DIM_PROPERTIES = ImmutableSet.of("grains"); - - public static final String KEYWORD = "validateTimeDimensionProperties"; - public static final String ADDITIONAL_KEY = "validateTimeDimensionProperties.error.addtional"; - public static final String ADDITIONAL_MSG = "Properties: %s are not allowed for time dimensions."; - - private boolean validate; - - public ValidateTimeDimPropertiesValidator(final JsonNode digest) { - super(KEYWORD); - validate = digest.get(keyword).booleanValue(); - } - - @Override - public void validate(Processor processor, ProcessingReport report, MessageBundle bundle, - FullData data) throws ProcessingException { - - if (validate) { - JsonNode instance = data.getInstance().getNode(); - Set fields = Sets.newHashSet(instance.fieldNames()); - - fields.removeAll(COMMON_DIM_PROPERTIES); - fields.removeAll(ADDITIONAL_TIME_DIM_PROPERTIES); - if (!fields.isEmpty()) { - report.error(newMsg(data, bundle, ADDITIONAL_KEY).putArgument("value", fields.toString())); - } - } - } - - @Override - public String toString() { - return keyword; - } -} diff --git a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/validator/DynamicConfigValidator.java b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/validator/DynamicConfigValidator.java index 72524e99a4..f71bc64501 100644 --- a/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/validator/DynamicConfigValidator.java +++ b/elide-model-config/src/main/java/com/yahoo/elide/modelconfig/validator/DynamicConfigValidator.java @@ -46,12 +46,11 @@ import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; -import org.apache.commons.collections.CollectionUtils; import lombok.Getter; -import lombok.extern.slf4j.Slf4j; import java.io.IOException; +import java.io.UncheckedIOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -69,7 +68,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -@Slf4j /** * Util class to validate and parse the config files. Optionally compiles config files. */ @@ -81,11 +79,6 @@ public class DynamicConfigValidator implements DynamicConfiguration, Validator { private static final String SQL_SPLIT_REGEX = "\\s+"; private static final String SEMI_COLON = ";"; private static final Pattern HANDLEBAR_REGEX = Pattern.compile("<%(.*?)%>"); - private static final String RESOURCES = "resources"; - private static final int RESOURCES_LENGTH = 9; //"resources".length() - private static final String CLASSPATH_PATTERN = "classpath*:"; - private static final String FILEPATH_PATTERN = "file:"; - private static final String HJSON_EXTN = "**/*.hjson"; @Getter private final ElideTableConfig elideTableConfig = new ElideTableConfig(); @Getter private ElideSecurityConfig elideSecurityConfig; @@ -323,7 +316,7 @@ public interface Inheritance { } private Map getInheritedMeasures(Table table, Map measures) { - Inheritance action = () -> { + Inheritance action = () -> { table.getMeasures().forEach(measure -> { if (!measures.containsKey(measure.getName())) { measures.put(measure.getName(), measure); @@ -337,7 +330,7 @@ private Map getInheritedMeasures(Table table, Map getInheritedDimensions(Table table, Map dimensions) { - Inheritance action = () -> { + Inheritance action = () -> { table.getDimensions().forEach(dimension -> { if (!dimensions.containsKey(dimension.getName())) { dimensions.put(dimension.getName(), dimension); @@ -351,7 +344,7 @@ private Map getInheritedDimensions(Table table, Map getInheritedJoins(Table table, Map joins) { - Inheritance action = () -> { + Inheritance action = () -> { table.getJoins().forEach(join -> { if (!joins.containsKey(join.getName())) { joins.put(join.getName(), join); @@ -364,42 +357,32 @@ private Map getInheritedJoins(Table table, Map joins return joins; } - private T getInheritedAttribute(Inheritance action, T property) { - return property == null ? (T) action.inherit() : property; + private T getInheritedAttribute(Inheritance action, T property) { + return property == null ? action.inherit() : property; } - private Collection getInheritedAttribute(Inheritance action, Collection property) { - return CollectionUtils.isEmpty(property) ? (Collection) action.inherit() : property; + private > T getInheritedAttribute(Inheritance action, T property) { + return property == null || property.isEmpty() ? action.inherit() : property; } private String getInheritedSchema(Table table, String schema) { - Inheritance action = table::getSchema; - - return getInheritedAttribute(action, schema); + return getInheritedAttribute(table::getSchema, schema); } private String getInheritedConnection(Table table, String connection) { - Inheritance action = table::getDbConnectionName; - - return getInheritedAttribute(action, connection); + return getInheritedAttribute(table::getDbConnectionName, connection); } private String getInheritedSql(Table table, String sql) { - Inheritance action = table::getSql; - - return getInheritedAttribute(action, sql); + return getInheritedAttribute(table::getSql, sql); } private String getInheritedTable(Table table, String tableName) { - Inheritance action = table::getTable; - - return getInheritedAttribute(action, tableName); + return getInheritedAttribute(table::getTable, tableName); } private List getInheritedArguments(Table table, List arguments) { - Inheritance action = table::getArguments; - - return (List) getInheritedAttribute(action, arguments); + return getInheritedAttribute(table::getArguments, arguments); } /** @@ -418,7 +401,7 @@ private Map readVariableConfig(Config config, Map resourceM return DynamicConfigHelpers.stringToElideSecurityPojo(entry.getKey(), content, this.modelVariables, schemaValidator); } catch (IOException e) { - throw new IllegalStateException(e); + throw new UncheckedIOException(e.getMessage(), e); } }) .findAny() @@ -465,7 +448,7 @@ private Set readDbConfig(Map resourceMap) { return DynamicConfigHelpers.stringToElideDBConfigPojo(entry.getKey(), content, this.dbVariables, schemaValidator); } catch (IOException e) { - throw new IllegalStateException(e); + throw new UncheckedIOException(e.getMessage(), e); } }) .flatMap(dbconfig -> dbconfig.getDbconfigs().stream()) @@ -490,7 +473,7 @@ private Set readNamespaceConfig(Map resourc return DynamicConfigHelpers.stringToElideNamespaceConfigPojo(fileName, content, this.modelVariables, schemaValidator); } catch (IOException e) { - throw new IllegalStateException(e); + throw new UncheckedIOException(e.getMessage(), e); } }) .flatMap(namespaceconfig -> namespaceconfig.getNamespaceconfigs().stream()) @@ -513,7 +496,7 @@ private Set readTableConfig(Map resourceMap) { return DynamicConfigHelpers.stringToElideTablePojo(entry.getKey(), content, this.modelVariables, schemaValidator); } catch (IOException e) { - throw new IllegalStateException(e); + throw new UncheckedIOException(e.getMessage(), e); } }) .flatMap(table -> table.getTables().stream()) @@ -604,7 +587,7 @@ private boolean validateNamespaceConfig() { extractChecksFromExpr(namespace.getReadAccess(), extractedChecks, visitor); } - validateChecks(extractedChecks, Collections.EMPTY_SET); + validateChecks(extractedChecks, Collections.emptySet()); return true; } diff --git a/elide-model-config/src/main/resources/elideDBConfigSchema.json b/elide-model-config/src/main/resources/elideDBConfigSchema.json index 371cbc2627..ce3d6a7d46 100644 --- a/elide-model-config/src/main/resources/elideDBConfigSchema.json +++ b/elide-model-config/src/main/resources/elideDBConfigSchema.json @@ -1,5 +1,28 @@ { - "$schema": "https://json-schema.org/draft-04/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "elideName" : { + "type": "string", + "pattern": "^[A-Za-z][0-9A-Za-z_]*$", + "errorMessage": { + "pattern": "{0}: does not match the elideName pattern must start with an alphabetic character and can include alphabets, numbers and ''_'' only." + } + }, + "elideJdbcUrl" : { + "type": "string", + "pattern": "^(jdbc:).*$", + "errorMessage": { + "pattern": "{0}: does not match the elideJdbcUrl pattern must start with ''jdbc:''." + } + }, + "javaClassName" : { + "type": "string", + "pattern": "^(\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)+(\\.\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)*$", + "errorMessage": { + "pattern": "{0}: does not match the javaClassName pattern is not a valid Java class name." + } + } + }, "type": "object", "title": "Elide DB Config Root Schema", "description": "Elide database connection config json/hjson schema", @@ -28,28 +51,25 @@ "additionalProperties": false, "properties": { "name": { - "type": "string", "title": "DB Connection Name", "description": "Name of the database connection. This will be used for the persistent unit name.", - "format": "elideName", + "$ref": "#/$defs/elideName", "examples": [ "MySQLConnection" ] }, "url": { - "type": "string", "title": "JDBC URL", "description": "JDBC URL for the database connection i.e. javax.persistence.jdbc.URL", - "format": "elideJdbcUrl", + "$ref": "#/$defs/elideJdbcUrl", "examples": [ "jdbc:mysql://localhost/elide?serverTimezone=UTC" ] }, "driver": { - "type": "string", "title": "JDBC Driver Name", "description": "JDBC Driver for the database connection i.e. javax.persistence.jdbc.driver", - "format": "javaClassName", + "$ref": "#/$defs/javaClassName", "examples": [ "com.mysql.jdbc.Driver" ] @@ -63,10 +83,9 @@ ] }, "dialect": { - "type": "string", "title": "Elide Dialect", "description": "The Elide Dialect to use for query generation.", - "format": "javaClassName", + "$ref": "#/$defs/javaClassName", "examples": [ "com.yahoo.elide.datastores.aggregation.queryengines.sql.dialects.impl.H2Dialect" ] diff --git a/elide-model-config/src/main/resources/elideNamespaceConfigSchema.json b/elide-model-config/src/main/resources/elideNamespaceConfigSchema.json index 98ff167149..ac859c0551 100644 --- a/elide-model-config/src/main/resources/elideNamespaceConfigSchema.json +++ b/elide-model-config/src/main/resources/elideNamespaceConfigSchema.json @@ -1,5 +1,14 @@ { - "$schema": "https://json-schema.org/draft-04/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "elideNamespaceName" : { + "type": "string", + "pattern": "^default$|^(?!(?i)default$)^[A-Za-z][0-9A-Za-z_]*$", + "errorMessage": { + "pattern": "{0}: does not match the elideNamespaceName pattern must start with an alphabetic character and can include alphabets, numbers and ''_'' only and must not clash with the ''default'' namespace." + } + } + }, "type": "object", "title": "Elide Namespace Config Root Schema", "description": "Elide Namespace config json/hjson schema", @@ -24,10 +33,9 @@ "additionalProperties": false, "properties": { "name": { - "type": "string", "title": "Namespace Name", "description": "Name of the Namespace. This can used for grouping the tables by subject area.", - "format": "elideNamespaceName", + "$ref": "#/$defs/elideNamespaceName", "examples": [ "MyNamespace" ] diff --git a/elide-model-config/src/main/resources/elideSecuritySchema.json b/elide-model-config/src/main/resources/elideSecuritySchema.json index fa27e55313..d64c4f9f5f 100644 --- a/elide-model-config/src/main/resources/elideSecuritySchema.json +++ b/elide-model-config/src/main/resources/elideSecuritySchema.json @@ -1,5 +1,14 @@ { - "$schema": "https://json-schema.org/draft-04/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "elideRole" : { + "type": "string", + "pattern": "^[A-Za-z][0-9A-Za-z. ]*$", + "errorMessage": { + "pattern": "{0}: does not match the elideRole pattern must start with an alphabetic character and can include alphabets, numbers, spaces and ''.'' only." + } + } + }, "description": "Elide Security config json/hjson schema", "type": "object", "properties": { @@ -9,8 +18,7 @@ "type": "array", "uniqueItems": true, "items": { - "type": "string", - "format": "elideRole" + "$ref": "#/$defs/elideRole" } }, "rules": { diff --git a/elide-model-config/src/main/resources/elideTableSchema.json b/elide-model-config/src/main/resources/elideTableSchema.json index e3eb50549e..ab52b75b7f 100644 --- a/elide-model-config/src/main/resources/elideTableSchema.json +++ b/elide-model-config/src/main/resources/elideTableSchema.json @@ -1,6 +1,90 @@ { - "$schema": "https://json-schema.org/draft-04/schema#", - "definitions": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "elideArgumentName" : { + "type": "string", + "pattern": "^(?!(?i)grain$)^[A-Za-z][0-9A-Za-z_]*$", + "errorMessage": { + "pattern": "{0}: does not match the elideArgumentName pattern must start with an alphabetic character and can include alphabets, numbers and ''_'' only and cannot be ''grain''." + } + }, + "elideFieldType" : { + "type": "string", + "pattern": "^(?i)(Integer|Decimal|Money|Text|Coordinate|Boolean|Enum_Text|Enum_Ordinal)$", + "errorMessage": { + "pattern": "{0}: does not match the elideFieldType pattern must be one of [Integer, Decimal, Money, Text, Coordinate, Boolean, Enum_Text, Enum_Ordinal]." + } + }, + "elideFieldName" : { + "type": "string", + "pattern": "^(?!(?i)(id|sql)$)^[a-z][0-9A-Za-z_]*$", + "errorMessage": { + "pattern": "{0}: does not match the elideFieldName pattern must start with lower case alphabet and can include alphabets, numbers and ''_'' only and cannot be one of [id, sql]" + } + }, + "elideName" : { + "type": "string", + "pattern": "^[A-Za-z][0-9A-Za-z_]*$", + "errorMessage": { + "pattern": "{0}: does not match the elideName pattern must start with an alphabetic character and can include alphabets, numbers and ''_'' only." + } + }, + "elideNamespaceName" : { + "type": "string", + "pattern": "^default$|^(?!(?i)default$)^[A-Za-z][0-9A-Za-z_]*$", + "errorMessage": { + "pattern": "{0}: does not match the elideNamespaceName pattern must start with an alphabetic character and can include alphabets, numbers and ''_'' only and must not clash with the ''default'' namespace." + } + }, + "elideJoinType" : { + "type": "string", + "pattern": "^(?i)(left|inner|full|cross)$", + "errorMessage": { + "pattern": "{0}: does not match the elideJoinType pattern must be one of [left, inner, full, cross]." + } + }, + "elideJoinKind" : { + "type": "string", + "pattern": "^(?i)(ToOne|ToMany)$", + "errorMessage": { + "pattern": "{0}: does not match the elideJoinKind pattern must be one of [ToOne, ToMany]." + } + }, + "javaClassName" : { + "type": "string", + "pattern": "^(\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)+(\\.\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)*$", + "errorMessage": { + "pattern": "{0}: does not match the javaClassName pattern is not a valid Java class name." + } + }, + "javaClassNameWithExt" : { + "type": "string", + "pattern": "^(\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)+(\\.\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)*class$", + "errorMessage": { + "pattern": "{0}: does not match the javaClassNameWithExt pattern is not a valid Java class name with .class extension." + } + }, + "elideCardinality" : { + "type": "string", + "pattern": "^(?i)(Tiny|Small|Medium|Large|Huge)$", + "errorMessage": { + "pattern": "{0}: does not match the elideCardinality pattern must be one of [Tiny, Small, Medium, Large, Huge]." + } + }, + "elideTimeFieldType" : { + "type": "string", + "pattern": "^(?i)(Time)$", + "errorMessage": { + "pattern": "{0}: does not match the elideTimeFieldType pattern must be [Time] for any time dimension." + } + }, + "elideGrainType" : { + "type": "string", + "pattern": "^(?i)(Second|Minute|Hour|Day|IsoWeek|Week|Month|Quarter|Year)$", + "errorMessage": { + "pattern": "{0}: does not match the elideGrainType pattern must be one of [Second, Minute, Hour, Day, IsoWeek, Week, Month, Quarter, Year]." + } + }, "argument" : { "title" : "Arguments", "description" : "Arguments are supported for table, measures and dimensions.", @@ -8,9 +92,8 @@ "properties": { "name": { "title": "Argument name", - "description": "Name must start with an alphabetic character and can include alaphabets, numbers and '_' only.", - "type": "string", - "format": "elideArgumentName" + "description": "Name must start with an alphabetic character and can include alphabets, numbers and '_' only.", + "$ref": "#/$defs/elideArgumentName" }, "description": { "title": "Argument description", @@ -20,8 +103,7 @@ "type": { "title": "Argument type", "description": "Must be one of Integer, Decimal, Money, Text, Coordinate, Boolean", - "type": "string", - "format": "elideFieldType" + "$ref": "#/$defs/elideFieldType" }, "values": { "title": "Argument values", @@ -33,7 +115,7 @@ } }, "tableSource": { - "$ref": "#/definitions/tableSource" + "$ref": "#/$defs/tableSource" }, "default": { "title": "Default argument value", @@ -50,8 +132,16 @@ "type", "default" ], - "validateArgumentProperties": true, - "additionalProperties": false + "not": { + "required": [ + "tableSource", + "values" + ] + }, + "additionalProperties": false, + "errorMessage": { + "not": "{0}: tableSource and values cannot both be defined for an argument. Choose One or None." + } }, "join": { "title": "Join", @@ -61,33 +151,28 @@ "name": { "title": "Join name", "description": "The name of the join relationship.", - "type": "string", - "format": "elideFieldName" + "$ref": "#/$defs/elideFieldName" }, "namespace": { "title": "Join Namespace", "description": "Namespace for the Join.", - "type": "string", - "format": "elideNamespaceName", + "$ref": "#/$defs/elideNamespaceName", "default": "default" }, "to": { "title": "Join table name", "description": "The name of the table that is being joined to", - "type": "string", - "format": "elideName" + "$ref": "#/$defs/elideName" }, "type": { "title": "Type of Join", "description": "Type of the join - left, inner, full or cross", - "type": "string", - "format": "elideJoinType" + "$ref": "#/$defs/elideJoinType" }, "kind": { "title": "Kind of Join", "description": "Kind of the join - toOne or toMany", - "type": "string", - "format": "elideJoinKind" + "$ref": "#/$defs/elideJoinKind" }, "definition": { "title": "Join definition SQL", @@ -109,21 +194,18 @@ "table": { "title": "Source table", "description": "The source table that contains the colum or argument values.", - "type": "string", - "format": "elideName" + "$ref": "#/$defs/elideName" }, "namespace": { "title": "Source table namespace", "description": "Namespace for the source table.", - "type": "string", - "format": "elideNamespaceName", + "$ref": "#/$defs/elideNamespaceName", "default": "default" }, "column": { "title": "Primary source table column name", "description": "The column that provides a unique list of values for the given column or argument", - "type": "string", - "format": "elideFieldName" + "$ref": "#/$defs/elideFieldName" }, "suggestionColumns": { "title": "Secondary search columns", @@ -131,8 +213,7 @@ "type": "array", "uniqueItems": true, "items": { - "type": "string", - "format": "elideFieldName" + "$ref": "#/$defs/elideFieldName" } } }, @@ -150,8 +231,7 @@ "name": { "title": "Metric name", "description": "The name of the metric. This will be the same as the POJO field name.", - "type": "string", - "format": "elideFieldName" + "$ref": "#/$defs/elideFieldName" }, "friendlyName": { "title": "Metric friendly name", @@ -188,14 +268,12 @@ "maker": { "title": "Metric Projection Maker", "description": "Registers a custom function to create a projection for this metric", - "type": "string", - "format": "javaClassName" + "$ref": "#/$defs/javaClassName" }, "type": { "title": "Measure field type", "description": "The data type of the measure field", - "type": "string", - "format": "elideFieldType" + "$ref": "#/$defs/elideFieldType" }, "tags": { "title": "Measure tags", @@ -211,7 +289,7 @@ "description": "An array of supported arguments for measure", "type": "array", "items": { - "$ref": "#/definitions/argument" + "$ref": "#/$defs/argument" } }, "filterTemplate": { @@ -247,8 +325,7 @@ "name": { "title": "Dimension name", "description": "The name of the dimension. This will be the same as the POJO field name.", - "type": "string", - "format": "elideFieldName" + "$ref": "#/$defs/elideFieldName" }, "friendlyName": { "title": "Dimension friendly name", @@ -285,8 +362,7 @@ "cardinality": { "title": "Dimension cardinality", "description": "Dimension cardinality: (tiny, small, medium, large, huge). The relative sizes are decided by the table designer(s).", - "type": "string", - "format": "elideCardiality" + "$ref": "#/$defs/elideCardinality" }, "tags": { "title": "Dimension tags", @@ -302,7 +378,7 @@ "description": "An array of supported arguments for dimensions", "type": "array", "items": { - "$ref": "#/definitions/argument" + "$ref": "#/$defs/argument" } }, "filterTemplate": { @@ -319,15 +395,14 @@ "type": "object", "allOf": [ { - "$ref": "#/definitions/dimensionRef" + "$ref": "#/$defs/dimensionRef" }, { "properties": { "type": { "title": "Dimension field type", "description": "The data type of the dimension field", - "type": "string", - "format": "elideFieldType" + "$ref": "#/$defs/elideFieldType" }, "values": { "title": "Dimension values", @@ -339,17 +414,42 @@ } }, "tableSource": { - "$ref": "#/definitions/tableSource" + "$ref": "#/$defs/tableSource" } } } ], - "validateDimensionProperties": true, "required": [ "name", "type", "definition" - ] + ], + "properties": { + "name": true, + "friendlyName": true, + "description": true, + "category": true, + "hidden": true, + "readAccess": true, + "definition": true, + "cardinality": true, + "tags": true, + "type": true, + "arguments": true, + "filterTemplate": true, + "values": true, + "tableSource": true + }, + "additionalProperties": false, + "not": { + "required": [ + "tableSource", + "values" + ] + }, + "errorMessage": { + "not": "{0}: tableSource and values cannot both be defined for a dimension. Choose One or None." + } }, "timeDimension": { "title": "Time Dimension", @@ -357,15 +457,14 @@ "type": "object", "allOf": [ { - "$ref": "#/definitions/dimensionRef" + "$ref": "#/$defs/dimensionRef" }, { "properties": { "type": { "title": "Dimension field type", "description": "The data type of the dimension field", - "type": "string", - "format": "elideTimeFieldType" + "$ref": "#/$defs/elideTimeFieldType" }, "grains" : { "title" : "Time Dimension grains", @@ -379,8 +478,7 @@ "type": { "title": "Time granularity", "description": "Indicates grain time granularity", - "type": "string", - "format": "elideGrainType" + "$ref": "#/$defs/elideGrainType" }, "sql": { "title": "Grain SQL", @@ -395,12 +493,27 @@ } } ], - "validateTimeDimensionProperties": true, "required": [ "name", "type", "definition" - ] + ], + "properties": { + "name": true, + "friendlyName": true, + "description": true, + "category": true, + "hidden": true, + "readAccess": true, + "definition": true, + "cardinality": true, + "tags": true, + "type": true, + "arguments": true, + "filterTemplate": true, + "grains": true + }, + "additionalProperties": false } }, "type": "object", @@ -421,8 +534,7 @@ "name": { "title": "Table Model Name", "description": "The name of the model. This will be the same as the POJO class name.", - "type": "string", - "format": "elideName" + "$ref": "#/$defs/elideName" }, "friendlyName": { "title": "Table friendly name", @@ -455,8 +567,7 @@ "cardinality": { "title": "Table cardinality", "description": "Table cardinality: (tiny, small, medium, large, huge). The relative sizes are decided by the table designer(s).", - "type": "string", - "format": "elideCardiality" + "$ref": "#/$defs/elideCardinality" }, "readAccess": { "title": "Table read access", @@ -467,8 +578,7 @@ "namespace": { "title": "Table Namespace", "description": "Namespace for the table.", - "type": "string", - "format": "elideNamespaceName", + "$ref": "#/$defs/elideNamespaceName", "default": "default" }, "hints": { @@ -485,7 +595,7 @@ "description": "Describes SQL joins to other tables for column references.", "type": "array", "items": { - "$ref": "#/definitions/join" + "$ref": "#/$defs/join" } }, "measures": { @@ -493,7 +603,7 @@ "description": "Zero or more metric definitions.", "type": "array", "items": { - "$ref": "#/definitions/measure" + "$ref": "#/$defs/measure" } }, "dimensions": { @@ -503,10 +613,10 @@ "items": { "oneOf": [ { - "$ref": "#/definitions/dimension" + "$ref": "#/$defs/dimension" }, { - "$ref": "#/definitions/timeDimension" + "$ref": "#/$defs/timeDimension" } ] } @@ -525,7 +635,7 @@ "description": "An array of supported arguments for tables.", "type": "array", "items": { - "$ref": "#/definitions/argument" + "$ref": "#/$defs/argument" } } }, @@ -540,8 +650,7 @@ "dbConnectionName": { "title": "DB Connection Name", "description": "The database connection name for this model.", - "type": "string", - "format": "elideName" + "$ref": "#/$defs/elideName" } }, "required": [ @@ -555,14 +664,12 @@ "maker": { "title": "Table SQL maker function", "description": "JVM function to invoke to generate the SQL query which is used to populate the table.", - "type": "string", - "format": "javaClassName" + "$ref": "#/$defs/javaClassName" }, "dbConnectionName": { "title": "DB Connection Name", "description": "The database connection name for this model.", - "type": "string", - "format": "elideName" + "$ref": "#/$defs/elideName" } }, "required": [ @@ -586,8 +693,7 @@ "dbConnectionName": { "title": "DB Connection Name", "description": "The database connection name for this model.", - "type": "string", - "format": "elideName" + "$ref": "#/$defs/elideName" } }, "required": [ @@ -601,8 +707,7 @@ "extend": { "title": "Table Extends", "description": "Extends another logical table.", - "type": "string", - "format": "elideName" + "$ref": "#/$defs/elideName" } }, "required": [ diff --git a/elide-model-config/src/main/resources/elideVariableSchema.json b/elide-model-config/src/main/resources/elideVariableSchema.json index 3b8d46d1a8..c17110edb0 100644 --- a/elide-model-config/src/main/resources/elideVariableSchema.json +++ b/elide-model-config/src/main/resources/elideVariableSchema.json @@ -1,5 +1,5 @@ { - "$schema": "https://json-schema.org/draft-04/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "description": "Elide Variable config json/hjson schema", "type": "object", "patternProperties": { diff --git a/elide-model-config/src/test/java/com/yahoo/elide/modelconfig/DynamicConfigSchemaValidatorTest.java b/elide-model-config/src/test/java/com/yahoo/elide/modelconfig/DynamicConfigSchemaValidatorTest.java index ad31cd445f..65c451c5c3 100644 --- a/elide-model-config/src/test/java/com/yahoo/elide/modelconfig/DynamicConfigSchemaValidatorTest.java +++ b/elide-model-config/src/test/java/com/yahoo/elide/modelconfig/DynamicConfigSchemaValidatorTest.java @@ -31,11 +31,15 @@ public void testValidSecuritySchemas() throws Exception { @Test public void testInvalidSecuritySchema() throws Exception { String jsonConfig = loadHjsonFromClassPath("/validator/invalid_schema/security_invalid.hjson"); - Exception e = assertThrows(IllegalStateException.class, + Exception e = assertThrows(InvalidSchemaException.class, () -> testClass.verifySchema(Config.SECURITY, jsonConfig, "security_invalid.hjson")); - String expectedMessage = "Schema validation failed for: security_invalid.hjson\n" - + "[ERROR]\n" - + "object instance has properties which are not allowed by the schema: [\"cardinality\",\"description\",\"name\",\"schema$\",\"table\"]"; + String expectedMessage = """ + Schema validation failed for: security_invalid.hjson + : property 'name' is not defined in the schema and the schema does not allow additional properties + : property 'table' is not defined in the schema and the schema does not allow additional properties + : property 'schema$' is not defined in the schema and the schema does not allow additional properties + : property 'description' is not defined in the schema and the schema does not allow additional properties + : property 'cardinality' is not defined in the schema and the schema does not allow additional properties"""; assertEquals(expectedMessage.replaceAll("\n", System.lineSeparator()), e.getMessage()); } @@ -48,13 +52,12 @@ public void testValidVariableSchema() throws Exception { @Test public void testInvalidVariableSchema() throws Exception { String jsonConfig = loadHjsonFromClassPath("/validator/invalid_schema/variables_invalid.hjson"); - Exception e = assertThrows(IllegalStateException.class, + Exception e = assertThrows(InvalidSchemaException.class, () -> testClass.verifySchema(Config.MODELVARIABLE, jsonConfig, "variables.hjson")); - String expectedMessage = "Schema validation failed for: variables.hjson\n" - + "[ERROR]\n" - + "object instance has properties which are not allowed by the schema: [\"schema$\"]\n" - + "[ERROR]\n" - + "Instance[/cardinality] failed to validate against schema[/patternProperties/^([A-Za-z0-9_]+[.]?)+$]. instance type (null) does not match any allowed primitive type (allowed: [\"array\",\"boolean\",\"integer\",\"number\",\"object\",\"string\"])"; + String expectedMessage = """ + Schema validation failed for: variables.hjson + /cardinality: null found, [string, number, boolean, array, object] expected + : property 'schema$' is not defined in the schema and the schema does not allow additional properties"""; assertEquals(expectedMessage.replaceAll("\n", System.lineSeparator()), e.getMessage()); } @@ -80,7 +83,7 @@ public void testValidTableSchema(String resource) throws Exception { public void testInvalidTableSchema(String resource) throws Exception { String jsonConfig = loadHjsonFromClassPath(resource); String fileName = getFileName(resource); - Exception e = assertThrows(IllegalStateException.class, + Exception e = assertThrows(InvalidSchemaException.class, () -> testClass.verifySchema(Config.TABLE, jsonConfig, fileName)); assertTrue(e.getMessage().startsWith("Schema validation failed for: " + fileName)); } @@ -91,61 +94,68 @@ public void testInvalidTableSchema(String resource) throws Exception { public void testInvalidTableSchemaMultipleErrors(String resource) throws Exception { String jsonConfig = loadHjsonFromClassPath(resource); String fileName = getFileName(resource); - Exception e = assertThrows(IllegalStateException.class, + Exception e = assertThrows(InvalidSchemaException.class, () -> testClass.verifySchema(Config.TABLE, jsonConfig, fileName)); - String expectedMessage = "Schema validation failed for: table_schema_with_multiple_errors.hjson\n" - + "[ERROR]\n" - + "object instance has properties which are not allowed by the schema: [\"name\"]\n" - + "[ERROR]\n" - + "Instance[/tables/0/arguments/0] failed to validate against schema[/definitions/argument]. object has missing required properties ([\"default\"])\n" - + "[ERROR]\n" - + "Instance[/tables/0/arguments/0/type] failed to validate against schema[/definitions/argument/properties/type]. Field type [Number] is not allowed. Supported value is one of [Integer, Decimal, Money, Text, Coordinate, Boolean, Enum_Text, Enum_Ordinal].\n" - + "[ERROR]\n" - + "Instance[/tables/0/arguments/1] failed to validate against schema[/definitions/argument]. object has missing required properties ([\"default\"])\n" - + "[ERROR]\n" - + "Instance[/tables/0/arguments/1/name] failed to validate against schema[/definitions/argument/properties/name]. Argument name [Grain] is not allowed. Argument name cannot be 'grain'.\n" - + "[ERROR]\n" - + "Instance[/tables/0/arguments/2] failed to validate against schema[/definitions/argument]. tableSource and values cannot both be defined for an argument. Choose One or None.\n" - + "[ERROR]\n" - + "Instance[/tables/0/cardinality] failed to validate against schema[/properties/tables/items/properties/cardinality]. Cardinality type [Extra Large] is not allowed. Supported value is one of [Tiny, Small, Medium, Large, Huge].\n" - + "[ERROR]\n" - + "Instance[/tables/0/dimensions/0] failed to validate against schema[/properties/tables/items/properties/dimensions/items]. instance failed to match exactly one schema (matched 0 out of 2)\n" - + " Instance[/tables/0/dimensions/0] failed to validate against schema[/definitions/dimension]. instance failed to match all required schemas (matched only 0 out of 2)\n" - + " Instance[/tables/0/dimensions/0/cardinality] failed to validate against schema[/definitions/dimensionRef/properties/cardinality]. Cardinality type [Extra small] is not allowed. Supported value is one of [Tiny, Small, Medium, Large, Huge].\n" - + " Instance[/tables/0/dimensions/0/name] failed to validate against schema[/definitions/dimensionRef/properties/name]. Field name [id] is not allowed. Field name cannot be one of [id, sql]\n" - + " Instance[/tables/0/dimensions/0/type] failed to validate against schema[/definitions/dimension/allOf/1/properties/type]. Field type [Float] is not allowed. Supported value is one of [Integer, Decimal, Money, Text, Coordinate, Boolean, Enum_Text, Enum_Ordinal].\n" - + " Instance[/tables/0/dimensions/0] failed to validate against schema[/definitions/timeDimension]. instance failed to match all required schemas (matched only 0 out of 2)\n" - + " Instance[/tables/0/dimensions/0/cardinality] failed to validate against schema[/definitions/dimensionRef/properties/cardinality]. Cardinality type [Extra small] is not allowed. Supported value is one of [Tiny, Small, Medium, Large, Huge].\n" - + " Instance[/tables/0/dimensions/0/name] failed to validate against schema[/definitions/dimensionRef/properties/name]. Field name [id] is not allowed. Field name cannot be one of [id, sql]\n" - + " Instance[/tables/0/dimensions/0/type] failed to validate against schema[/definitions/timeDimension/allOf/1/properties/type]. Field type [Float] is not allowed. Field type must be [Time] for any time dimension.\n" - + " Instance[/tables/0/dimensions/0] failed to validate against schema[/definitions/timeDimension]. Properties: [tableSource] are not allowed for time dimensions.\n" - + "[ERROR]\n" - + "Instance[/tables/0/dimensions/1] failed to validate against schema[/properties/tables/items/properties/dimensions/items]. instance failed to match exactly one schema (matched 0 out of 2)\n" - + " Instance[/tables/0/dimensions/1] failed to validate against schema[/definitions/dimension]. instance failed to match all required schemas (matched only 1 out of 2)\n" - + " Instance[/tables/0/dimensions/1/name] failed to validate against schema[/definitions/dimensionRef/properties/name]. Field name [_region] is not allowed. Field name must start with lower case alphabet and can include alaphabets, numbers and '_' only.\n" - + " Instance[/tables/0/dimensions/1/tags] failed to validate against schema[/definitions/dimensionRef/properties/tags]. instance type (string) does not match any allowed primitive type (allowed: [\"array\"])\n" - + " Instance[/tables/0/dimensions/1] failed to validate against schema[/definitions/dimension]. tableSource and values cannot both be defined for a dimension. Choose One or None.\n" - + " Instance[/tables/0/dimensions/1] failed to validate against schema[/definitions/timeDimension]. instance failed to match all required schemas (matched only 0 out of 2)\n" - + " Instance[/tables/0/dimensions/1/name] failed to validate against schema[/definitions/dimensionRef/properties/name]. Field name [_region] is not allowed. Field name must start with lower case alphabet and can include alaphabets, numbers and '_' only.\n" - + " Instance[/tables/0/dimensions/1/tags] failed to validate against schema[/definitions/dimensionRef/properties/tags]. instance type (string) does not match any allowed primitive type (allowed: [\"array\"])\n" - + " Instance[/tables/0/dimensions/1/type] failed to validate against schema[/definitions/timeDimension/allOf/1/properties/type]. Field type [Text] is not allowed. Field type must be [Time] for any time dimension.\n" - + " Instance[/tables/0/dimensions/1] failed to validate against schema[/definitions/timeDimension]. Properties: [values, tableSource] are not allowed for time dimensions.\n" - + "[ERROR]\n" - + "Instance[/tables/0/dimensions/2] failed to validate against schema[/properties/tables/items/properties/dimensions/items]. instance failed to match exactly one schema (matched 0 out of 2)\n" - + " Instance[/tables/0/dimensions/2] failed to validate against schema[/definitions/dimension]. instance failed to match all required schemas (matched only 1 out of 2)\n" - + " Instance[/tables/0/dimensions/2/type] failed to validate against schema[/definitions/dimension/allOf/1/properties/type]. Field type [TIMEX] is not allowed. Supported value is one of [Integer, Decimal, Money, Text, Coordinate, Boolean, Enum_Text, Enum_Ordinal].\n" - + " Instance[/tables/0/dimensions/2] failed to validate against schema[/definitions/dimension]. Properties: [grains] are not allowed for dimensions.\n" - + " Instance[/tables/0/dimensions/2] failed to validate against schema[/definitions/timeDimension]. instance failed to match all required schemas (matched only 1 out of 2)\n" - + " Instance[/tables/0/dimensions/2/grains/0/type] failed to validate against schema[/definitions/timeDimension/allOf/1/properties/grains/items/properties/type]. Grain type [Days] is not allowed. Supported value is one of [Second, Minute, Hour, Day, IsoWeek, Week, Month, Quarter, Year].\n" - + " Instance[/tables/0/dimensions/2/type] failed to validate against schema[/definitions/timeDimension/allOf/1/properties/type]. Field type [TIMEX] is not allowed. Field type must be [Time] for any time dimension.\n" - + "[ERROR]\n" - + "Instance[/tables/0/filterTemplate] failed to validate against schema[/properties/tables/items/properties/filterTemplate]. Input value[countryIsoCode={{code}};startTime=={{start}}] is not a valid RSQL filter expression. Please visit page https://elide.io/pages/guide/v5/11-graphql.html#operators for samples.\n" - + "[ERROR]\n" - + "Instance[/tables/0/measures/0] failed to validate against schema[/definitions/measure]. instance failed to match exactly one schema (matched 2 out of 2)\n" - + "[ERROR]\n" - + "Instance[/tables/0/measures/0/maker] failed to validate against schema[/definitions/measure/properties/maker]. Input value[com.yahoo.elide.datastores.aggregation.query@DefaultMetricProjectionMaker.class] is not a valid Java class name.\n" - + "[ERROR]\n" - + "Instance[/tables/0/name] failed to validate against schema[/properties/tables/items/properties/name]. Name [Country@10] is not allowed. Name must start with an alphabetic character and can include alaphabets, numbers and '_' only."; + String expectedMessage = """ + Schema validation failed for: table_schema_with_multiple_errors.hjson + /tables/0/name: does not match the elideName pattern must start with an alphabetic character and can include alphabets, numbers and '_' only. + /tables/0/filterTemplate: does not match the elideRSQLFilter pattern is not a valid RSQL filter expression. Please visit page https://elide.io/pages/guide/v5/11-graphql.html#operators for samples. + /tables/0/cardinality: does not match the elideCardinality pattern must be one of [Tiny, Small, Medium, Large, Huge]. + /tables/0/measures/0/maker: does not match the javaClassName pattern is not a valid Java class name. + /tables/0/measures/0: must be valid to one and only one schema, but 2 are valid with indexes '0, 1' + /tables/0/dimensions/0: must be valid to one and only one schema, but 0 are valid + /tables/0/dimensions/0/name: does not match the elideFieldName pattern must start with lower case alphabet and can include alphabets, numbers and '_' only and cannot be one of [id, sql] + /tables/0/dimensions/0/cardinality: does not match the elideCardinality pattern must be one of [Tiny, Small, Medium, Large, Huge]. + /tables/0/dimensions/0/type: does not match the elideFieldType pattern must be one of [Integer, Decimal, Money, Text, Coordinate, Boolean, Enum_Text, Enum_Ordinal]. + /tables/0/dimensions/0/name: does not match the elideFieldName pattern must start with lower case alphabet and can include alphabets, numbers and '_' only and cannot be one of [id, sql] + /tables/0/dimensions/0/cardinality: does not match the elideCardinality pattern must be one of [Tiny, Small, Medium, Large, Huge]. + /tables/0/dimensions/0/type: does not match the elideTimeFieldType pattern must be [Time] for any time dimension. + /tables/0/dimensions/0: property 'tableSource' is not defined in the schema and the schema does not allow additional properties + /tables/0/dimensions/1: must be valid to one and only one schema, but 0 are valid + /tables/0/dimensions/1/name: does not match the elideFieldName pattern must start with lower case alphabet and can include alphabets, numbers and '_' only and cannot be one of [id, sql] + /tables/0/dimensions/1/tags: string found, array expected + /tables/0/dimensions/1: tableSource and values cannot both be defined for a dimension. Choose One or None. + /tables/0/dimensions/1/name: does not match the elideFieldName pattern must start with lower case alphabet and can include alphabets, numbers and '_' only and cannot be one of [id, sql] + /tables/0/dimensions/1/tags: string found, array expected + /tables/0/dimensions/1/type: does not match the elideTimeFieldType pattern must be [Time] for any time dimension. + /tables/0/dimensions/1: property 'values' is not defined in the schema and the schema does not allow additional properties + /tables/0/dimensions/1: property 'tableSource' is not defined in the schema and the schema does not allow additional properties + /tables/0/dimensions/2: must be valid to one and only one schema, but 0 are valid + /tables/0/dimensions/2/type: does not match the elideFieldType pattern must be one of [Integer, Decimal, Money, Text, Coordinate, Boolean, Enum_Text, Enum_Ordinal]. + /tables/0/dimensions/2: property 'grains' is not defined in the schema and the schema does not allow additional properties + /tables/0/dimensions/2/type: does not match the elideTimeFieldType pattern must be [Time] for any time dimension. + /tables/0/dimensions/2/grains/0/type: does not match the elideGrainType pattern must be one of [Second, Minute, Hour, Day, IsoWeek, Week, Month, Quarter, Year]. + /tables/0/arguments/0/type: does not match the elideFieldType pattern must be one of [Integer, Decimal, Money, Text, Coordinate, Boolean, Enum_Text, Enum_Ordinal]. + /tables/0/arguments/0: required property 'default' not found + /tables/0/arguments/1/name: does not match the elideArgumentName pattern must start with an alphabetic character and can include alphabets, numbers and '_' only and cannot be 'grain'. + /tables/0/arguments/1: required property 'default' not found + /tables/0/arguments/2: tableSource and values cannot both be defined for an argument. Choose One or None. + : property 'name' is not defined in the schema and the schema does not allow additional properties"""; + + assertEquals(expectedMessage.replaceAll("\n", System.lineSeparator()), e.getMessage()); + } + + @DisplayName("Invalid Table config") + @ParameterizedTest + @ValueSource(strings = {"/validator/invalid_schema/table_schema_with_arguments.hjson"}) + public void testInvalidTableSchemaArgument(String resource) throws Exception { + String jsonConfig = loadHjsonFromClassPath(resource); + String fileName = getFileName(resource); + Exception e = assertThrows(InvalidSchemaException.class, + () -> testClass.verifySchema(Config.TABLE, jsonConfig, fileName)); + String expectedMessage = """ + Schema validation failed for: table_schema_with_arguments.hjson + /tables/0/filterTemplate: does not match the elideRSQLFilter pattern is not a valid RSQL filter expression. Please visit page https://elide.io/pages/guide/v5/11-graphql.html#operators for samples. + /tables/0/cardinality: does not match the elideCardinality pattern must be one of [Tiny, Small, Medium, Large, Huge]. + /tables/0/arguments/0: tableSource and values cannot both be defined for an argument. Choose One or None. + /tables/0: must be valid to one and only one schema, but 0 are valid + /tables/0: required property 'sql' not found + /tables/0: required property 'dimensions' not found + /tables/0: required property 'maker' not found + /tables/0: required property 'dimensions' not found + /tables/0: required property 'dimensions' not found + /tables/0: required property 'extend' not found + : property 'name' is not defined in the schema and the schema does not allow additional properties"""; assertEquals(expectedMessage.replaceAll("\n", System.lineSeparator()), e.getMessage()); } @@ -165,19 +175,15 @@ public void testValidDbSchema(String resource) throws Exception { @Test public void testInvalidDbSchema() throws Exception { String jsonConfig = loadHjsonFromClassPath("/validator/invalid_schema/db_invalid.hjson"); - Exception e = assertThrows(IllegalStateException.class, + Exception e = assertThrows(InvalidSchemaException.class, () -> testClass.verifySchema(Config.SQLDBConfig, jsonConfig, "db_invalid.hjson")); - String expectedMessage = "Schema validation failed for: db_invalid.hjson\n" - + "[ERROR]\n" - + "Instance[/dbconfigs/0/driver] failed to validate against schema[/properties/dbconfigs/items/properties/driver]. Input value[11COM.ibm.db2.jdbc.net.DB2Driver] is not a valid Java class name.\n" - + "[ERROR]\n" - + "Instance[/dbconfigs/0/name] failed to validate against schema[/properties/dbconfigs/items/properties/name]. Name [11MyDB2Connection] is not allowed. Name must start with an alphabetic character and can include alaphabets, numbers and '_' only.\n" - + "[ERROR]\n" - + "Instance[/dbconfigs/0/propertyMap/hibernate.show_sql] failed to validate against schema[/properties/dbconfigs/items/properties/propertyMap/patternProperties/^([A-Za-z0-9_]+[.]?)+$]. instance type (null) does not match any allowed primitive type (allowed: [\"array\",\"boolean\",\"integer\",\"number\",\"object\",\"string\"])\n" - + "[ERROR]\n" - + "Instance[/dbconfigs/1/dialect] failed to validate against schema[/properties/dbconfigs/items/properties/dialect]. instance type (integer) does not match any allowed primitive type (allowed: [\"string\"])\n" - + "[ERROR]\n" - + "Instance[/dbconfigs/1/url] failed to validate against schema[/properties/dbconfigs/items/properties/url]. Input value [ojdbc:mysql://localhost/testdb?serverTimezone=UTC] is not a valid JDBC url, it must start with 'jdbc:'."; + String expectedMessage = """ + Schema validation failed for: db_invalid.hjson + /dbconfigs/0/name: does not match the elideName pattern must start with an alphabetic character and can include alphabets, numbers and '_' only. + /dbconfigs/0/driver: does not match the javaClassName pattern is not a valid Java class name. + /dbconfigs/0/propertyMap/hibernate.show_sql: null found, [string, number, boolean, array, object] expected + /dbconfigs/1/url: does not match the elideJdbcUrl pattern must start with 'jdbc:'. + /dbconfigs/1/dialect: integer found, string expected"""; assertEquals(expectedMessage.replaceAll("\n", System.lineSeparator()), e.getMessage()); } diff --git a/elide-model-config/src/test/java/com/yahoo/elide/modelconfig/validator/DynamicConfigValidatorTest.java b/elide-model-config/src/test/java/com/yahoo/elide/modelconfig/validator/DynamicConfigValidatorTest.java index db0ed49802..875838c9e5 100644 --- a/elide-model-config/src/test/java/com/yahoo/elide/modelconfig/validator/DynamicConfigValidatorTest.java +++ b/elide-model-config/src/test/java/com/yahoo/elide/modelconfig/validator/DynamicConfigValidatorTest.java @@ -235,11 +235,11 @@ public void testBadSecurityRoleConfig() throws Exception { assertEquals(2, exitStatus); }); - String expectedError = "Schema validation failed for: models/security.hjson\n" - + "[ERROR]\n" - + "Instance[/roles/0] failed to validate against schema[/properties/roles/items]. Role [admin,] is not allowed. Role must start with an alphabetic character and can include alaphabets, numbers, spaces and '.' only.\n" - + "[ERROR]\n" - + "Instance[/roles/1] failed to validate against schema[/properties/roles/items]. Role [guest,] is not allowed. Role must start with an alphabetic character and can include alaphabets, numbers, spaces and '.' only.\n"; + String expectedError = """ + Schema validation failed for: models/security.hjson + /roles/0: does not match the elideRole pattern must start with an alphabetic character and can include alphabets, numbers, spaces and '.' only. + /roles/1: does not match the elideRole pattern must start with an alphabetic character and can include alphabets, numbers, spaces and '.' only. + """; assertEquals(expectedError.replaceAll("\n", System.lineSeparator()), error); } @@ -262,9 +262,10 @@ public void testNamespaceBadDefaultName() throws Exception { assertEquals(2, exitStatus); }); - String expected = "Schema validation failed for: models/namespaces/test_namespace.hjson\n" - + "[ERROR]\n" - + "Instance[/namespaces/0/name] failed to validate against schema[/properties/namespaces/items/properties/name]. Name [Default] clashes with the 'default' namespace. Either change the case or pick a different namespace name.\n"; + String expected = """ + Schema validation failed for: models/namespaces/test_namespace.hjson + /namespaces/0/name: does not match the elideNamespaceName pattern must start with an alphabetic character and can include alphabets, numbers and '_' only and must not clash with the 'default' namespace. + """; assertEquals(expected.replaceAll("\n", System.lineSeparator()), error); } @@ -297,32 +298,28 @@ public void testBadTableConfigJoinType() throws Exception { DynamicConfigValidator.main(new String[] { "--configDir", "src/test/resources/validator/bad_table_join_type"})); assertEquals(2, exitStatus); }); - String expected = "Schema validation failed for: models/tables/table1.hjson\n" - + "[ERROR]\n" - + "Instance[/tables/0/joins/0/kind] failed to validate against schema[/definitions/join/properties/kind]. Join kind [toAll] is not allowed. Supported value is one of [ToOne, ToMany].\n" - + "[ERROR]\n" - + "Instance[/tables/0/joins/1/type] failed to validate against schema[/definitions/join/properties/type]. Join type [full outer] is not allowed. Supported value is one of [left, inner, full, cross].\n"; + String expected = """ + Schema validation failed for: models/tables/table1.hjson + /tables/0/joins/0/kind: does not match the elideJoinKind pattern must be one of [ToOne, ToMany]. + /tables/0/joins/1/type: does not match the elideJoinType pattern must be one of [left, inner, full, cross]. + """; assertEquals(expected.replaceAll("\n", System.lineSeparator()), error); } @Test public void testBadDimName() throws Exception { - String expectedMessage = "Schema validation failed for: models/tables/table1.hjson\n" - + "[ERROR]\n" - + "Instance[/tables/0/dimensions/0] failed to validate against schema[/properties/tables/items/properties/dimensions/items]. instance failed to match exactly one schema (matched 0 out of 2)\n" - + " Instance[/tables/0/dimensions/0] failed to validate against schema[/definitions/dimension]. instance failed to match all required schemas (matched only 1 out of 2)\n" - + " Instance[/tables/0/dimensions/0/name] failed to validate against schema[/definitions/dimensionRef/properties/name]. Field name [id] is not allowed. Field name cannot be one of [id, sql]\n" - + " Instance[/tables/0/dimensions/0] failed to validate against schema[/definitions/timeDimension]. instance failed to match all required schemas (matched only 0 out of 2)\n" - + " Instance[/tables/0/dimensions/0/name] failed to validate against schema[/definitions/dimensionRef/properties/name]. Field name [id] is not allowed. Field name cannot be one of [id, sql]\n" - + " Instance[/tables/0/dimensions/0/type] failed to validate against schema[/definitions/timeDimension/allOf/1/properties/type]. Field type [Text] is not allowed. Field type must be [Time] for any time dimension.\n" - + "[ERROR]\n" - + "Instance[/tables/0/dimensions/1] failed to validate against schema[/properties/tables/items/properties/dimensions/items]. instance failed to match exactly one schema (matched 0 out of 2)\n" - + " Instance[/tables/0/dimensions/1] failed to validate against schema[/definitions/dimension]. instance failed to match all required schemas (matched only 1 out of 2)\n" - + " Instance[/tables/0/dimensions/1/name] failed to validate against schema[/definitions/dimensionRef/properties/name]. Field name [_region] is not allowed. Field name must start with lower case alphabet and can include alaphabets, numbers and '_' only.\n" - + " Instance[/tables/0/dimensions/1] failed to validate against schema[/definitions/timeDimension]. instance failed to match all required schemas (matched only 0 out of 2)\n" - + " Instance[/tables/0/dimensions/1/name] failed to validate against schema[/definitions/dimensionRef/properties/name]. Field name [_region] is not allowed. Field name must start with lower case alphabet and can include alaphabets, numbers and '_' only.\n" - + " Instance[/tables/0/dimensions/1/type] failed to validate against schema[/definitions/timeDimension/allOf/1/properties/type]. Field type [Text] is not allowed. Field type must be [Time] for any time dimension.\n"; + String expectedMessage = """ + Schema validation failed for: models/tables/table1.hjson + /tables/0/dimensions/0: must be valid to one and only one schema, but 0 are valid + /tables/0/dimensions/0/name: does not match the elideFieldName pattern must start with lower case alphabet and can include alphabets, numbers and '_' only and cannot be one of [id, sql] + /tables/0/dimensions/0/name: does not match the elideFieldName pattern must start with lower case alphabet and can include alphabets, numbers and '_' only and cannot be one of [id, sql] + /tables/0/dimensions/0/type: does not match the elideTimeFieldType pattern must be [Time] for any time dimension. + /tables/0/dimensions/1: must be valid to one and only one schema, but 0 are valid + /tables/0/dimensions/1/name: does not match the elideFieldName pattern must start with lower case alphabet and can include alphabets, numbers and '_' only and cannot be one of [id, sql] + /tables/0/dimensions/1/name: does not match the elideFieldName pattern must start with lower case alphabet and can include alphabets, numbers and '_' only and cannot be one of [id, sql] + /tables/0/dimensions/1/type: does not match the elideTimeFieldType pattern must be [Time] for any time dimension. + """; String error = tapSystemErr(() -> { int exitStatus = catchSystemExit(() -> diff --git a/elide-model-config/src/test/resources/validator/invalid_schema/table_schema_with_arguments.hjson b/elide-model-config/src/test/resources/validator/invalid_schema/table_schema_with_arguments.hjson new file mode 100644 index 0000000000..f92c57507e --- /dev/null +++ b/elide-model-config/src/test/resources/validator/invalid_schema/table_schema_with_arguments.hjson @@ -0,0 +1,29 @@ +{ + // unsupported: additional property 'name' + name: Geography + tables: + [ + { + name: Country + table: country + // unsupported: cardinality value + cardinality: Extra Large + filterTemplate: countryIsoCode={{code}};startTime=={{start}} + arguments: + [ + { + // both tableSource and values are not supported. Choose One or None + name: aggregation + description: Aggregation + type : TEXT + values: ['SUM', 'MIN', 'MAX'] + tableSource: { + table: abc + column: def + } + default: SUM + } + ] + } + ] +} diff --git a/pom.xml b/pom.xml index 92e5e7b0fb..e57ad6f7c7 100644 --- a/pom.xml +++ b/pom.xml @@ -122,7 +122,7 @@ 5.1.3 1.5.1 2.9.0 - 2.2.14 + 1.4.2 5.10.2 1.5.6 1.18.32 @@ -457,7 +457,7 @@ ${json-path.version} - com.github.java-json-tools + com.networknt json-schema-validator ${json-schema-validator.version}