import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.restconf.openapi.model.Property;
import org.opendaylight.restconf.openapi.model.Schema;
import org.opendaylight.restconf.openapi.model.Xml;
+import org.opendaylight.yangtools.yang.common.AbstractQName;
import org.opendaylight.yangtools.yang.common.Decimal64;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.common.XMLNamespace;
import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
import org.opendaylight.yangtools.yang.model.api.SchemaNode;
import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.meta.ModelStatement;
import org.opendaylight.yangtools.yang.model.api.type.BinaryTypeDefinition;
import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition.Bit;
final String name = filename + discriminator;
final String ref = COMPONENTS_PREFIX + name;
- if (schemaNode instanceof ListSchemaNode) {
+ if (schemaNode instanceof ListSchemaNode node) {
dataNodeProperties.type(ARRAY_TYPE);
final Property items = new Property.Builder().ref(ref).build();
dataNodeProperties.items(items);
dataNodeProperties.description(schemaNode.getDescription().orElse(""));
+ if (node.getElementCountConstraint().isPresent()) {
+ final var minElements = node.getElementCountConstraint().orElse(null).getMinElements();
+ dataNodeProperties.minItems(minElements);
+ dataNodeProperties.maxItems(node.getElementCountConstraint().orElse(null).getMaxElements());
+ if (minElements != null) {
+ dataNodeProperties.example(createExamples(node, minElements));
+ }
+ }
} else {
/*
Description can't be added, because nothing allowed alongside $ref.
return dataNodeProperties.build();
}
+ private static List<Map<String, Object>> createExamples(final ListSchemaNode node,
+ @NonNull final Integer minElements) {
+ final var firstExampleMap = prepareFirstListExample(node);
+ final var examples = new ArrayList<Map<String, Object>>();
+ examples.add(firstExampleMap);
+
+ final var unqiueContraintsNameSet = node.getUniqueConstraints().stream()
+ .map(ModelStatement::argument)
+ .flatMap(uniqueSt -> uniqueSt.stream()
+ .map(schemaNI -> schemaNI.lastNodeIdentifier().getLocalName()))
+ .collect(Collectors.toSet());
+ final var keysNameSet = node.getKeyDefinition().stream()
+ .map(AbstractQName::getLocalName)
+ .collect(Collectors.toSet());
+ for (int i = 1; i < minElements; i++) {
+ final var exampleMap = new HashMap<String, Object>();
+ for (final var example : firstExampleMap.entrySet()) {
+ final Object exampleValue;
+ if (keysNameSet.contains(example.getKey()) || unqiueContraintsNameSet.contains(example.getKey())) {
+ exampleValue = editExample(example.getValue(), i);
+ } else {
+ exampleValue = example.getValue();
+ }
+ exampleMap.put(example.getKey(), exampleValue);
+ }
+ examples.add(exampleMap);
+ }
+ return examples;
+ }
+
+ private static HashMap<String, Object> prepareFirstListExample(final ListSchemaNode node) {
+ final var childNodes = node.getChildNodes();
+ final var firstExampleMap = new HashMap<String, Object>();
+ // Cycle for each child node
+ for (final var childNode : childNodes) {
+ if (childNode instanceof TypedDataSchemaNode leafSchemaNode) {
+ final var property = new Property.Builder();
+ processTypeDef(leafSchemaNode.getType(), leafSchemaNode, property, null);
+ final var exampleValue = property.build().example();
+ if (exampleValue != null) {
+ firstExampleMap.put(leafSchemaNode.getQName().getLocalName(), exampleValue);
+ }
+ }
+ }
+ return firstExampleMap;
+ }
+
+ private static Object editExample(final Object exampleValue, final int edit) {
+ if (exampleValue instanceof String string) {
+ return string + "_" + edit;
+ } else if (exampleValue instanceof Integer number) {
+ return number + edit;
+ } else if (exampleValue instanceof Long number) {
+ return number + edit;
+ } else if (exampleValue instanceof Decimal64 number) {
+ return Decimal64.valueOf(BigDecimal.valueOf(number.intValue() + edit));
+ }
+ return exampleValue;
+ }
+
private static Property processDataNodeContainer(final DataNodeContainer dataNode, final String parentName,
final Map<String, Schema> definitions, final DefinitionNames definitionNames,
final SchemaInferenceStack stack, final Module module, final boolean isParentConfig) throws IOException {
import java.io.IOException;
import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
import java.util.Set;
import org.junit.BeforeClass;
import org.junit.Test;
assertTrue(makeToast.enums().containsAll(Set.of("toast-type","white-bread", "wheat-bread", "frozen-waffle",
"hash-brown", "frozen-bagel", "wonder-bread")));
}
+
+ /**
+ * Test that checks if list min-elements and max-elements are present.
+ * Also checks if number of example elements meets the min-elements condition
+ */
+ @Test
+ public void testListExamples() throws IOException {
+ final var module = context.findModule("test-container-childs", Revision.of("2023-09-28")).orElseThrow();
+ final var jsonObject = DefinitionGenerator.convertToSchemas(module, context, new DefinitionNames(), true);
+ final var component = jsonObject.get("test-container-childs_root-container_nested-container");
+ assertNotNull(component);
+ assertNotNull(component.properties());
+ final var property = component.properties().get("mandatory-list");
+ assertNotNull(property);
+ assertNotNull(property.minItems());
+ assertNotNull(property.maxItems());
+ assertEquals(3, (int) property.minItems());
+ assertEquals(5, (int) property.maxItems());
+ final var example = property.example();
+ assertNotNull(example);
+ assertEquals(ArrayList.class, example.getClass());
+ assertEquals(3, ((List<?>)example).size());
+ }
+
+ /**
+ * Test that checks if list min-elements and max-elements are present.
+ * Also checks if number of example elements meets the min-elements condition
+ * and if key defined leaf have unique values.
+ */
+ @Test
+ public void testListExamplesWithNonKeyLeaf() throws IOException {
+ final var module = context.findModule("test-container-childs", Revision.of("2023-09-28")).orElseThrow();
+ final var jsonObject = DefinitionGenerator.convertToSchemas(module, context, new DefinitionNames(), true);
+ final var component = jsonObject.get("test-container-childs_root-container_nested-container");
+ assertNotNull(component);
+ assertNotNull(component.properties());
+ final var property = component.properties().get("mandatory-list");
+ assertNotNull(property);
+ assertNotNull(property.minItems());
+ assertNotNull(property.maxItems());
+ assertEquals(3, (int) property.minItems());
+ assertEquals(5, (int) property.maxItems());
+ final var example = property.example();
+ assertNotNull(example);
+ assertEquals(3, ((List<?>)example).size());
+ assertTrue(checkUniqueExample(example, "id"));
+ }
+
+ /**
+ * Test that checks if multiple key leafs have unique values.
+ * Also checks if nested container node is ignored.
+ */
+ @Test
+ public void testListExamplesWithTwoKeys() throws IOException {
+ final var module = context.findModule("test-container-childs", Revision.of("2023-09-28")).orElseThrow();
+ final var jsonObject = DefinitionGenerator.convertToSchemas(module, context, new DefinitionNames(), true);
+ final var component = jsonObject.get("test-container-childs_root-container-two-keys_nested-container-two-keys");
+ assertNotNull(component);
+ assertNotNull(component.properties());
+ final var property = component.properties().get("mandatory-list-two-keys");
+ assertNotNull(property);
+ final var example = property.example();
+ assertNotNull(example);
+ assertTrue(checkUniqueExample(example, "id"));
+ assertTrue(checkUniqueExample(example, "name"));
+ assertEquals(3, ((ArrayList<Map<?,?>>)example).get(0).size());
+ }
+
+ /**
+ * Test that checks if sets of unique defined leafs have unique combination of values.
+ */
+ @Test
+ public void testListExamplesWithUnique() throws IOException {
+ final var module = context.findModule("test-container-childs", Revision.of("2023-09-28")).orElseThrow();
+ final var jsonObject = DefinitionGenerator.convertToSchemas(module, context, new DefinitionNames(), true);
+ final var component = jsonObject.get("test-container-childs_root-container-unique_nested-container-unique");
+ assertNotNull(component);
+ assertNotNull(component.properties());
+ final var property = component.properties().get("mandatory-list-unique");
+ assertNotNull(property);
+ final var example = property.example();
+ assertNotNull(example);
+ assertTrue(checkUniqueExample(example, "id"));
+ assertTrue(checkUniqueExample(example, "name") || checkUniqueExample(example, "address"));
+ }
+
+ private static boolean checkUniqueExample(final Object examples, final String key) {
+ assertEquals(ArrayList.class, examples.getClass());
+ final var exampleValues = new HashSet<>();
+
+ for (final Map<String, Object> example : (ArrayList<Map<String, Object>>)examples) {
+ exampleValues.add(example.get(key));
+ }
+ return (exampleValues.size() == ((ArrayList<?>) examples).size());
+ }
}
assertEquals("urn:ietf:params:xml:ns:yang:test:action:types", namespace);
}
+ /**
+ * Test that checks if list min-elements and max-elements are present.
+ * Also checks if number of example elements meets the min-elements condition
+ * and if key defined leaf have unique values.
+ */
+ @Test
+ public void testListExamplesWithNonKeyLeaf() {
+ final var doc = generator.getApiDeclaration("test-container-childs", "2023-09-28", uriInfo);
+ assertNotNull("Failed to find Datastore API", doc);
+ final var components = doc.components();
+ final var component = components.schemas().get("test-container-childs_root-container_nested-container");
+ assertNotNull(component);
+ assertNotNull(component.properties());
+ final var property = component.properties().get("mandatory-list");
+ assertNotNull(property);
+ assertNotNull(property.minItems());
+ assertNotNull(property.maxItems());
+ assertEquals(3, (int) property.minItems());
+ assertEquals(5, (int) property.maxItems());
+ final var example = property.example();
+ assertNotNull(example);
+ assertEquals(3, ((List<?>)example).size());
+ assertTrue(checkUniqueExample(example, "id"));
+ }
+
+ /**
+ * Test that checks if multiple key leafs have unique values.
+ * Also checks if nested container node is ignored.
+ */
+ @Test
+ public void testListExamplesWithTwoKeys() {
+ final var doc = generator.getApiDeclaration("test-container-childs", "2023-09-28", uriInfo);
+ assertNotNull("Failed to find Datastore API", doc);
+ final var components = doc.components();
+ final var component = components.schemas()
+ .get("test-container-childs_root-container-two-keys_nested-container-two-keys");
+ assertNotNull(component);
+ assertNotNull(component.properties());
+ final var property = component.properties().get("mandatory-list-two-keys");
+ assertNotNull(property);
+ final var example = property.example();
+ assertNotNull(example);
+ assertTrue(checkUniqueExample(example, "id"));
+ assertTrue(checkUniqueExample(example, "name"));
+ assertEquals(3, ((ArrayList<Map<?,?>>)example).get(0).size());
+ }
+
+ /**
+ * Test that checks if sets of unique defined leafs have unique combination of values.
+ */
+ @Test
+ public void testListExamplesWithUnique() {
+ final var doc = generator.getApiDeclaration("test-container-childs", "2023-09-28", uriInfo);
+ assertNotNull("Failed to find Datastore API", doc);
+ final var components = doc.components();
+ final var component = components.schemas()
+ .get("test-container-childs_root-container-unique_nested-container-unique");
+ assertNotNull(component);
+ assertNotNull(component.properties());
+ final var property = component.properties().get("mandatory-list-unique");
+ assertNotNull(property);
+ final var example = property.example();
+ assertNotNull(example);
+ assertTrue(checkUniqueExample(example, "id"));
+ assertTrue(checkUniqueExample(example, "name") || checkUniqueExample(example, "address"));
+ }
+
+ private static boolean checkUniqueExample(final Object examples, final String key) {
+ assertEquals(ArrayList.class, examples.getClass());
+ final var exampleValues = new HashSet<>();
+
+ for (final Map<String, Object> example : (ArrayList<Map<String, Object>>)examples) {
+ exampleValues.add(example.get(key));
+ }
+ return (exampleValues.size() == ((ArrayList<?>) examples).size());
+ }
+
/**
* Test that number of elements in payload is correct.
*/
final Map<String, Path> paths = mountPointApi.paths();
assertNotNull(paths);
- assertEquals("Unexpected api list size", 68, paths.size());
+ assertEquals("Unexpected api list size", 78, paths.size());
final List<Operation> getOperations = new ArrayList<>();
final List<Operation> postOperations = new ArrayList<>();
Optional.ofNullable(path.getValue().delete()).ifPresent(deleteOperations::add);
}
- assertEquals("Unexpected GET paths size", 60, getOperations.size());
- assertEquals("Unexpected POST paths size", 15, postOperations.size());
- assertEquals("Unexpected PUT paths size", 58, putOperations.size());
- assertEquals("Unexpected PATCH paths size", 58, patchOperations.size());
- assertEquals("Unexpected DELETE paths size", 58, deleteOperations.size());
+ assertEquals("Unexpected GET paths size", 70, getOperations.size());
+ assertEquals("Unexpected POST paths size", 21, postOperations.size());
+ assertEquals("Unexpected PUT paths size", 68, putOperations.size());
+ assertEquals("Unexpected PATCH paths size", 68, patchOperations.size());
+ assertEquals("Unexpected DELETE paths size", 68, deleteOperations.size());
}
/**
--- /dev/null
+module test-container-childs {
+ yang-version 1.1;
+ namespace "http://example.com/test/container/child";
+ prefix "tcc";
+ revision 2023-09-28;
+
+ container root-container {
+ container nested-container {
+ list mandatory-list {
+ min-elements 3;
+ max-elements 5;
+ key id;
+ leaf id {
+ type uint32;
+ }
+ leaf name {
+ type string;
+ }
+ leaf address {
+ type string;
+ }
+ }
+ }
+ }
+
+ container root-container-two-keys {
+ container nested-container-two-keys {
+ list mandatory-list-two-keys {
+ min-elements 3;
+ max-elements 5;
+ key "id name";
+ leaf id {
+ type uint32;
+ }
+ leaf name {
+ type string;
+ }
+ leaf address {
+ type string;
+ }
+ container list-nested-container {
+ leaf nested-leaf {
+ type string;
+ }
+ }
+ }
+ }
+ }
+
+
+ container root-container-unique {
+ container nested-container-unique {
+ list mandatory-list-unique {
+ min-elements 3;
+ max-elements 5;
+ key id;
+ unique "name address";
+ leaf id {
+ type uint32;
+ }
+ leaf name {
+ type string;
+ }
+ leaf address {
+ type string;
+ }
+ leaf description {
+ type string;
+ }
+ }
+ }
+ }
+}