Fix module's root POST request payload 06/108306/6
authorOleksandr Zharov <oleksandr.zharov@pantheon.tech>
Tue, 10 Oct 2023 10:24:18 +0000 (12:24 +0200)
committerIvan Hrasko <ivan.hrasko@pantheon.tech>
Mon, 23 Oct 2023 13:52:12 +0000 (15:52 +0200)
Removed “processModule” method from DefinitionGenerator class.
This method is invoked only when a specific single model is requested
directly and generated POST request had an incorrect payload.

Fixed “addRootPostLink” method to correct POST payload containing
the first container/list child.
Adapted affected unit tests.

JIRA: NETCONF-1179
Change-Id: Ic10280770cfceedc2c9c5099cdbdab14cdc28ad6
Signed-off-by: Oleksandr Zharov <oleksandr.zharov@pantheon.tech>
Signed-off-by: Ivan Hrasko <ivan.hrasko@pantheon.tech>
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/BaseYangOpenApiGenerator.java
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/DefinitionGenerator.java
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/builder/OperationBuilder.java
restconf/restconf-openapi/src/test/java/org/opendaylight/restconf/openapi/impl/DefinitionGeneratorTest.java
restconf/restconf-openapi/src/test/java/org/opendaylight/restconf/openapi/impl/OpenApiGeneratorRFC8040Test.java
restconf/restconf-openapi/src/test/resources/openapi-document/controller-toaster.json
restconf/restconf-openapi/src/test/resources/openapi-document/device-toaster.json

index 13e8a0a058090a089e254c20eea5171beba74123..2bc3c39b68e2806bb4a67b31e5200ca162145ddf 100644 (file)
@@ -238,11 +238,11 @@ public abstract class BaseYangOpenApiGenerator {
 
     private static void addRootPostLink(final Module module, final String deviceName,
             final List<Parameter> pathParams, final String resourcePath, final Map<String, Path> paths) {
-        if (containsListOrContainer(module.getChildNodes())) {
+        final var childNode = getListOrContainerChildNode(module);
+        if (childNode != null) {
             final String moduleName = module.getName();
-            final String name = moduleName + MODULE_NAME_SUFFIX;
             paths.put(resourcePath, new Path.Builder()
-                .post(buildPost(null, null, name, "", moduleName, deviceName,
+                .post(buildPost(childNode, null, moduleName, "", moduleName, deviceName,
                     module.getDescription().orElse(""), pathParams))
                 .build());
         }
index e9be71f75e59710a20ebe50b567680a54ebad413..09b60c7d26d8aecb062b8d9595cfb9a2802a0411 100644 (file)
@@ -122,15 +122,10 @@ public final class DefinitionGenerator {
      * @throws IOException if I/O operation fails
      */
     private static Map<String, Schema> convertToSchemas(final Module module, final EffectiveModelContext schemaContext,
-            final Map<String, Schema> definitions, final DefinitionNames definitionNames,
-            final boolean isForSingleModule) throws IOException {
+            final Map<String, Schema> definitions, final DefinitionNames definitionNames) throws IOException {
         processContainersAndLists(module, definitions, definitionNames, schemaContext);
         processRPCs(module, definitions, definitionNames, schemaContext);
 
-        if (isForSingleModule) {
-            processModule(module, definitions, definitionNames, schemaContext);
-        }
-
         return definitions;
     }
 
@@ -141,68 +136,7 @@ public final class DefinitionGenerator {
         if (isForSingleModule) {
             definitionNames.addUnlinkedName(module.getName() + MODULE_NAME_SUFFIX);
         }
-        return convertToSchemas(module, schemaContext, definitions, definitionNames, isForSingleModule);
-    }
-
-    private static void processModule(final Module module, final Map<String, Schema> definitions,
-            final DefinitionNames definitionNames, final EffectiveModelContext schemaContext) {
-        final Map<String, Property> properties = new HashMap<>();
-        final List<String> required = new ArrayList<>();
-        final String moduleName = module.getName();
-        final String definitionName = moduleName + MODULE_NAME_SUFFIX;
-        final SchemaInferenceStack stack = SchemaInferenceStack.of(schemaContext);
-        for (final DataSchemaNode node : module.getChildNodes()) {
-            stack.enterSchemaTree(node.getQName());
-            final String localName = node.getQName().getLocalName();
-            if (node.isConfiguration()) {
-                if (node instanceof ContainerSchemaNode || node instanceof ListSchemaNode) {
-                    if (isSchemaNodeMandatory(node)) {
-                        required.add(localName);
-                    }
-                    for (final DataSchemaNode childNode : ((DataNodeContainer) node).getChildNodes()) {
-                        final Property.Builder childNodeProperty = new Property.Builder();
-
-                        final String ref = COMPONENTS_PREFIX
-                                + moduleName
-                                + "_" + localName
-                                + definitionNames.getDiscriminator(node);
-
-                        if (node instanceof ListSchemaNode) {
-                            childNodeProperty.type(ARRAY_TYPE);
-                            final Property items = new Property.Builder().ref(ref).build();
-                            childNodeProperty.items(items);
-                            childNodeProperty.description(childNode.getDescription().orElse(""));
-                            childNodeProperty.title(localName);
-                        } else {
-                         /*
-                            Description can't be added, because nothing allowed alongside $ref.
-                            allOf is not an option, because ServiceNow can't parse it.
-                          */
-                            childNodeProperty.ref(ref);
-                        }
-                        //add module name prefix to property name, when ServiceNow can process colons
-                        properties.put(localName, childNodeProperty.build());
-                    }
-                } else if (node instanceof LeafSchemaNode) {
-                    /*
-                        Add module name prefix to property name, when ServiceNow can process colons(second parameter
-                        of processLeafNode).
-                     */
-                    final Property leafNode = processLeafNode((LeafSchemaNode) node, localName, required, stack,
-                            module.getNamespace());
-                    properties.put(localName, leafNode);
-                }
-            }
-            stack.exit();
-        }
-        final Schema.Builder definitionBuilder = new Schema.Builder()
-            .title(definitionName)
-            .type(OBJECT_TYPE)
-            .properties(properties)
-            .description(module.getDescription().orElse(""))
-            .required(required.size() > 0 ? required : null);
-
-        definitions.put(definitionName, definitionBuilder.build());
+        return convertToSchemas(module, schemaContext, definitions, definitionNames);
     }
 
     private static boolean isSchemaNodeMandatory(final DataSchemaNode node) {
index b34c72565c35da88454c4be54da2d534b5573d2d..16cc1a7c1f1c29feaeb391cba7ec0a5fc02d37e9 100644 (file)
@@ -53,9 +53,9 @@ public final class OperationBuilder {
         // Hidden on purpose
     }
 
-    public static Operation buildPost(final DataSchemaNode childNode, final String parentName, final String nodeName,
-            final String discriminator, final String moduleName, final @NonNull String deviceName,
-            final String description, final List<Parameter> pathParams) {
+    public static Operation buildPost(final @NonNull DataSchemaNode childNode, final String parentName,
+            final String nodeName, final String discriminator, final String moduleName,
+            final @NonNull String deviceName, final String description, final List<Parameter> pathParams) {
         final var summary = SUMMARY_TEMPLATE.formatted(HttpMethod.POST, deviceName, moduleName, nodeName);
         final List<String> tags = List.of(deviceName + " " + moduleName);
         final List<Parameter> parameters = new ArrayList<>(pathParams);
@@ -64,7 +64,7 @@ public final class OperationBuilder {
         if (parentName != null) {
             nameElements.add(parentName);
         }
-        if (childNode != null && childNode.isConfiguration()) {
+        if (childNode.isConfiguration()) {
             final String childNodeName = childNode.getQName().getLocalName();
             nameElements.add(nodeName);
             nameElements.add(childNodeName + discriminator);
index 2ed09d622d85fef71354f2fd192e94b3d82ec6e2..a64f7a3b44b2f120cfa168c1675345eb3d40c351 100644 (file)
@@ -192,7 +192,7 @@ public final class DefinitionGeneratorTest {
         assertNotNull(schemas);
 
         // correct number of schemas generated
-        assertEquals(3, schemas.size());
+        assertEquals(2, schemas.size());
         final var makeToast = schemas.get("toaster_make-toast_input").properties().get("toasterToastType");
 
         assertEquals("wheat-bread", makeToast.defaultValue().toString());
index 20595359a98618140eb25d549b6ea457ba1d2caa..4d2da507e474a1d1c2288f84b529c3eee8f077a7 100644 (file)
@@ -45,7 +45,6 @@ public final class OpenApiGeneratorRFC8040Test {
     private static final String CONFIG_ROOT_CONTAINER = "mandatory-test_root-container";
     private static final String CONFIG_MANDATORY_CONTAINER = "mandatory-test_root-container_mandatory-container";
     private static final String CONFIG_MANDATORY_LIST = "mandatory-test_root-container_mandatory-list";
-    private static final String MANDATORY_TEST_MODULE = "mandatory-test_module";
     private static final String CONTAINER = "container";
     private static final String LIST = "list";
 
@@ -212,10 +211,6 @@ public final class OpenApiGeneratorRFC8040Test {
         verifyRequiredField(schemas.get(CONFIG_MANDATORY_LIST), reqMandatoryListElements);
         containersWithRequired.add(CONFIG_MANDATORY_LIST);
 
-        final var testModuleMandatoryArray = List.of("root-container", "root-mandatory-list");
-        verifyRequiredField(schemas.get(MANDATORY_TEST_MODULE), testModuleMandatoryArray);
-        containersWithRequired.add(MANDATORY_TEST_MODULE);
-
         verifyThatOthersNodeDoesNotHaveRequiredField(containersWithRequired, schemas);
     }
 
@@ -323,9 +318,8 @@ public final class OpenApiGeneratorRFC8040Test {
 
         // Test `components/schemas` objects
         final var definitions = doc.components().schemas();
-        assertEquals(2, definitions.size());
+        assertEquals(1, definitions.size());
         assertTrue(definitions.containsKey("my-yang_data"));
-        assertTrue(definitions.containsKey("my-yang_module"));
     }
 
     @Test
@@ -371,7 +365,7 @@ public final class OpenApiGeneratorRFC8040Test {
 
         // Test `components/schemas` objects
         final var definitions = doc.components().schemas();
-        assertEquals(11, definitions.size());
+        assertEquals(10, definitions.size());
     }
 
     /**
@@ -441,26 +435,6 @@ public final class OpenApiGeneratorRFC8040Test {
         assertEquals("urn:ietf:params:xml:ns:yang:test:action:types", namespace);
     }
 
-    /**
-     *  Test JSON and XML references for request operation.
-     */
-    private static void verifyPostDataRequestRef(final Operation operation, final String expectedJsonRef,
-            final String expectedXmlRef) {
-        final Map<String, MediaTypeObject> postContent;
-        if (operation.requestBody() != null) {
-            postContent = operation.requestBody().content();
-        } else {
-            postContent = operation.responses().get("200").content();
-        }
-        assertNotNull(postContent);
-        final var postJsonRef = postContent.get("application/json").schema().ref();
-        assertNotNull(postJsonRef);
-        assertEquals(expectedJsonRef, postJsonRef);
-        final var postXmlRef = postContent.get("application/xml").schema().ref();
-        assertNotNull(postXmlRef);
-        assertEquals(expectedXmlRef, postXmlRef);
-    }
-
     private static void verifyRequestRef(final Operation operation, final String expectedRef, final String nodeType) {
         final Map<String, MediaTypeObject> postContent;
         if (operation.requestBody() != null) {
index c6e417891f85a86079b3c84c8811dc077c451659..8651a56e89c832dab11823a59d9d8cf92a1ae495 100644 (file)
         ],
         "parameters": [],
         "requestBody": {
-          "description": "toaster_module",
+          "description": "toaster",
           "content": {
             "application/json": {
               "schema": {
-                "$ref": "#/components/schemas/toaster_module"
+                "properties":{
+                  "toaster":{
+                    "$ref":"#/components/schemas/toaster_toaster",
+                    "type":"object"
+                  }
+                }
               }
             },
             "application/xml": {
               "schema": {
-                "$ref": "#/components/schemas/toaster_module"
+                "$ref": "#/components/schemas/toaster_toaster"
               }
             }
           }
@@ -38,7 +43,7 @@
           }
         },
         "description": "YANG version of the TOASTER-MIB.\n\nNote:\nIn example payload, you can see only the first data node child of the resource to be created, following the\nguidelines of RFC 8040, which allows us to create only one resource in POST request.\n",
-        "summary": "POST - Controller - toaster - toaster_module"
+        "summary": "POST - Controller - toaster - toaster"
       }
     },
     "/rests/operations/toaster:cancel-toast": {
         "title": "toaster_make-toast_input",
         "type": "object"
       },
-      "toaster_module": {
-        "properties": {
-          "toaster": {
-            "$ref": "#/components/schemas/toaster_toaster"
-          }
-        },
-        "description": "YANG version of the TOASTER-MIB.",
-        "title": "toaster_module",
-        "type": "object"
-      },
       "toaster_restock-toaster_input": {
         "properties": {
           "amountOfBreadToStock": {
index 27bc67a7728596d8bdc3c01e71cbc2be772c2edc..205928321698f59abd9c5577f09f1d77d66e1baa 100644 (file)
         ],
         "parameters": [],
         "requestBody": {
-          "description": "toaster_module",
+          "description": "toaster",
           "content": {
             "application/xml": {
               "schema": {
-                "$ref": "#/components/schemas/toaster_module"
+                "$ref": "#/components/schemas/toaster_toaster"
               }
             },
             "application/json": {
               "schema": {
-                "$ref": "#/components/schemas/toaster_module"
+                "properties":{
+                  "toaster":{
+                    "$ref":"#/components/schemas/toaster_toaster",
+                    "type":"object"
+                  }
+                }
               }
             }
           }
           }
         },
         "description": "YANG version of the TOASTER-MIB.\n\nNote:\nIn example payload, you can see only the first data node child of the resource to be created, following the\nguidelines of RFC 8040, which allows us to create only one resource in POST request.\n",
-        "summary": "POST - 123 - toaster - toaster_module"
+        "summary": "POST - 123 - toaster - toaster"
       }
     }
   },
         "title": "toaster_make-toast_input",
         "type": "object"
       },
-      "toaster_module": {
-        "properties": {
-          "toaster": {
-            "$ref": "#/components/schemas/toaster_toaster"
-          }
-        },
-        "description": "YANG version of the TOASTER-MIB.",
-        "title": "toaster_module",
-        "type": "object"
-      },
       "toaster_restock-toaster_input": {
         "properties": {
           "amountOfBreadToStock": {