1 /* 2 * Copyright (c) 2016 Network New Technologies Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.networknt.schema; 18 19 import com.fasterxml.jackson.core.JsonProcessingException; 20 import com.fasterxml.jackson.databind.JsonNode; 21 import com.fasterxml.jackson.databind.ObjectMapper; 22 import org.junit.jupiter.api.Test; 23 24 import java.io.IOException; 25 import java.util.ArrayList; 26 import java.util.HashSet; 27 import java.util.List; 28 import java.util.Set; 29 30 import static org.junit.jupiter.api.Assertions.assertEquals; 31 32 public class CustomMetaSchemaTest { 33 34 /** 35 * Introduces the keyword "enumNames". 36 * <p> 37 * The keyword is used together with "enum" and must have the same length. 38 * <p> 39 * This keyword always produces a warning during validation - 40 * so it makes no sense in reality but should be useful for demonstration / testing purposes. 41 * 42 * @author klaskalass 43 */ 44 public static class EnumNamesKeyword extends AbstractKeyword { 45 46 private static final class Validator extends AbstractJsonValidator { 47 private final List<String> enumValues; 48 private final List<String> enumNames; 49 private final String keyword; 50 Validator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, String keyword, List<String> enumValues, List<String> enumNames, JsonNode schemaNode)51 private Validator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, String keyword, 52 List<String> enumValues, List<String> enumNames, JsonNode schemaNode) { 53 super(schemaLocation, evaluationPath, new EnumNamesKeyword(), schemaNode); 54 if (enumNames.size() != enumValues.size()) { 55 throw new IllegalArgumentException("enum and enumNames need to be of same length"); 56 } 57 this.enumNames = enumNames; 58 this.enumValues = enumValues; 59 this.keyword = keyword; 60 } 61 62 @Override validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation)63 public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) { 64 String value = node.asText(); 65 int idx = enumValues.indexOf(value); 66 if (idx < 0) { 67 throw new IllegalArgumentException("value not found in enum. value: " + value + " enum: " + enumValues); 68 } 69 String valueName = enumNames.get(idx); 70 Set<ValidationMessage> messages = new HashSet<>(); 71 ValidationMessage validationMessage = ValidationMessage.builder().type(keyword) 72 .schemaNode(node) 73 .instanceNode(node) 74 .code("tests.example.enumNames").message("{0}: enumName is {1}").instanceLocation(instanceLocation) 75 .arguments(valueName).build(); 76 messages.add(validationMessage); 77 return messages; 78 } 79 } 80 81 EnumNamesKeyword()82 public EnumNamesKeyword() { 83 super("enumNames"); 84 } 85 86 @Override newValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext)87 public JsonValidator newValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, 88 JsonSchema parentSchema, ValidationContext validationContext) throws JsonSchemaException, Exception { 89 /* 90 * You can access the schema node here to read data from your keyword 91 */ 92 if (!schemaNode.isArray()) { 93 throw new JsonSchemaException("Keyword enumNames needs to receive an array"); 94 } 95 JsonNode parentSchemaNode = parentSchema.getSchemaNode(); 96 if (!parentSchemaNode.has("enum")) { 97 throw new JsonSchemaException("Keyword enumNames needs to have a sibling enum keyword"); 98 } 99 JsonNode enumSchemaNode = parentSchemaNode.get("enum"); 100 101 return new Validator(schemaLocation, evaluationPath, getValue(), readStringList(enumSchemaNode), 102 readStringList(schemaNode), schemaNode); 103 } 104 readStringList(JsonNode node)105 private List<String> readStringList(JsonNode node) { 106 if (!node.isArray()) { 107 throw new JsonSchemaException("Keyword enum needs to receive an array"); 108 } 109 ArrayList<String> result = new ArrayList<String>(node.size()); 110 for (JsonNode child : node) { 111 result.add(child.asText()); 112 } 113 return result; 114 } 115 } 116 117 @Test customMetaSchemaWithIgnoredKeyword()118 public void customMetaSchemaWithIgnoredKeyword() throws JsonProcessingException, IOException { 119 ObjectMapper objectMapper = new ObjectMapper(); 120 final JsonMetaSchema metaSchema = JsonMetaSchema 121 .builder("https://github.com/networknt/json-schema-validator/tests/schemas/example01", JsonMetaSchema.getV4()) 122 // Generated UI uses enumNames to render Labels for enum values 123 .keyword(new EnumNamesKeyword()) 124 .build(); 125 126 final JsonSchemaFactory validatorFactory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4)).metaSchema(metaSchema).build(); 127 final JsonSchema schema = validatorFactory.getSchema("{\n" + 128 " \"$schema\":\n" + 129 " \"https://github.com/networknt/json-schema-validator/tests/schemas/example01\",\n" + 130 " \"enum\": [\"foo\", \"bar\"],\n" + 131 " \"enumNames\": [\"Foo !\", \"Bar !\"]\n" + 132 "}"); 133 134 Set<ValidationMessage> messages = schema.validate(objectMapper.readTree("\"foo\"")); 135 assertEquals(1, messages.size()); 136 137 ValidationMessage message = messages.iterator().next(); 138 assertEquals("$: enumName is Foo !", message.getMessage()); 139 } 140 } 141