OpenaApi: Use empty content for operational GET 47/106747/17
authorPeter Suna <peter.suna@pantheon.tech>
Wed, 28 Jun 2023 10:29:58 +0000 (12:29 +0200)
committerIvan Hrasko <ivan.hrasko@pantheon.tech>
Mon, 14 Aug 2023 13:55:58 +0000 (15:55 +0200)
Previously, operational data was included in schemas
to provide a GET example for operational containers and lists.
However, removing this operational data caused an error in
the OpenAPI UI since the expected data was not found.

To address this issue, the operational data can be replaced
with empty content instead. Creating a duplicate schema
specifically for this use case is both memory-intensive
and unnecessary.

This commit solves the problem by adding empty example
for the operational GET data.

JIRA: NETCONF-1061
Change-Id: I1444aad588c42417e5ff21dee46ff91ec0a1a07d
Signed-off-by: Peter Suna <peter.suna@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/model/builder/OperationBuilder.java
restconf/restconf-openapi/src/test/java/org/opendaylight/restconf/openapi/OperationalDataTest.java

index 628d3585d639d65b62c465ab71a03a61d11806c3..08385c75664c4e4bfe789237cd49b2eeb9783596 100644 (file)
@@ -190,8 +190,9 @@ public abstract class BaseYangOpenApiGenerator {
         final Collection<? extends DataSchemaNode> dataSchemaNodes = module.getChildNodes();
         LOG.debug("child nodes size [{}]", dataSchemaNodes.size());
         for (final DataSchemaNode node : dataSchemaNodes) {
-            if (node.isConfiguration() && (node instanceof ListSchemaNode || node instanceof ContainerSchemaNode)) {
-                LOG.debug("Is Configuration node [{}]",  node.getQName().getLocalName());
+            if (node instanceof ListSchemaNode || node instanceof ContainerSchemaNode) {
+                final boolean isConfig = node.isConfiguration();
+                LOG.debug("Is Configuration node [{}] [{}]", isConfig, node.getQName().getLocalName());
 
                 final String localName = moduleName + ":" + node.getQName().getLocalName();
                 final String resourcePath  = getResourcePath("data", context);
@@ -202,14 +203,14 @@ public abstract class BaseYangOpenApiGenerator {
                  * whose config statement is true in module, make sure that
                  * only one root post link is added for this module.
                  */
-                if (isForSingleModule && !hasAddRootPostLink) {
+                if (isConfig && isForSingleModule && !hasAddRootPostLink) {
                     LOG.debug("Has added root post link for module {}", moduleName);
                     addRootPostLink(module, deviceName, pathParams, resourcePath, paths);
 
                     hasAddRootPostLink = true;
                 }
                 final String resourcePathPart = createPath(node, pathParams, localName);
-                addPaths(node, deviceName, moduleName, paths, pathParams, schemaContext,
+                addPaths(node, deviceName, moduleName, paths, pathParams, isConfig, schemaContext,
                     moduleName, definitionNames, resourcePathPart, context);
             }
         }
@@ -253,8 +254,8 @@ public abstract class BaseYangOpenApiGenerator {
 
     private void addPaths(final DataSchemaNode node, final String deviceName, final String moduleName,
             final Map<String, Path> paths, final List<Parameter> parentPathParams,
-            final EffectiveModelContext schemaContext, final String parentName, final DefinitionNames definitionNames,
-            final String resourcePathPart, final String context) {
+            final boolean isConfig, final EffectiveModelContext schemaContext, final String parentName,
+            final DefinitionNames definitionNames, final String resourcePathPart, final String context) {
         final String dataPath = getResourcePath("data", context) + "/" + resourcePathPart;
         LOG.debug("Adding path: [{}]", dataPath);
         final List<Parameter> pathParams = new ArrayList<>(parentPathParams);
@@ -262,7 +263,8 @@ public abstract class BaseYangOpenApiGenerator {
         if (node instanceof ListSchemaNode || node instanceof ContainerSchemaNode) {
             childSchemaNodes = ((DataNodeContainer) node).getChildNodes();
         }
-        paths.put(dataPath, operations(node, moduleName, deviceName, pathParams, parentName, definitionNames));
+        paths.put(dataPath, operations(node, moduleName, deviceName, pathParams, isConfig, parentName,
+            definitionNames));
 
         if (node instanceof ActionNodeContainer actionContainer) {
             actionContainer.getActions().forEach(actionDef -> {
@@ -275,12 +277,12 @@ public abstract class BaseYangOpenApiGenerator {
         }
 
         for (final DataSchemaNode childNode : childSchemaNodes) {
-            if (childNode.isConfiguration() && (childNode instanceof ListSchemaNode
-                    || childNode instanceof ContainerSchemaNode)) {
+            if (childNode instanceof ListSchemaNode || childNode instanceof ContainerSchemaNode) {
                 final String newParent = parentName + "_" + node.getQName().getLocalName();
                 final String localName = resolvePathArgumentsName(childNode.getQName(), node.getQName(), schemaContext);
                 final String newPathPart = resourcePathPart + "/" + createPath(childNode, pathParams, localName);
-                addPaths(childNode, deviceName, moduleName, paths, pathParams, schemaContext,
+                final boolean newIsConfig = isConfig && childNode.isConfiguration();
+                addPaths(childNode, deviceName, moduleName, paths, pathParams, newIsConfig, schemaContext,
                     newParent, definitionNames, newPathPart, context);
                 pathParams.clear();
                 pathParams.addAll(parentPathParams);
@@ -298,7 +300,7 @@ public abstract class BaseYangOpenApiGenerator {
     }
 
     private static Path operations(final DataSchemaNode node, final String moduleName,
-            final String deviceName, final List<Parameter> pathParams, final String parentName,
+            final String deviceName, final List<Parameter> pathParams, final boolean isConfig, final String parentName,
             final DefinitionNames definitionNames) {
         final Path.Builder operationsBuilder = new Path.Builder();
 
@@ -307,24 +309,25 @@ public abstract class BaseYangOpenApiGenerator {
 
         final String defName = parentName + "_" + nodeName + discriminator;
         final String defNameTop = parentName + "_" + nodeName + TOP + discriminator;
-        final Operation get = buildGet(node, moduleName, deviceName, pathParams, defName, defNameTop);
+        final Operation get = buildGet(node, moduleName, deviceName, pathParams, defName, defNameTop, isConfig);
         operationsBuilder.get(get);
 
-        final Operation put = buildPut(parentName, nodeName, discriminator, moduleName, deviceName,
-                node.getDescription().orElse(""), pathParams);
-        operationsBuilder.put(put);
+        if (isConfig) {
+            final Operation put = buildPut(parentName, nodeName, discriminator, moduleName, deviceName,
+                    node.getDescription().orElse(""), pathParams);
+            operationsBuilder.put(put);
 
-        final Operation patch = buildPatch(parentName, nodeName, moduleName, deviceName,
-                node.getDescription().orElse(""), pathParams);
-        operationsBuilder.patch(patch);
+            final Operation patch = buildPatch(parentName, nodeName, moduleName, deviceName,
+                    node.getDescription().orElse(""), pathParams);
+            operationsBuilder.patch(patch);
 
-        final Operation delete = buildDelete(node, moduleName, deviceName, pathParams);
-        operationsBuilder.delete(delete);
-
-        final Operation post = buildPost(parentName, nodeName, discriminator, moduleName, deviceName,
-                node.getDescription().orElse(""), pathParams);
-        operationsBuilder.post(post);
+            final Operation delete = buildDelete(node, moduleName, deviceName, pathParams);
+            operationsBuilder.delete(delete);
 
+            final Operation post = buildPost(parentName, nodeName, discriminator, moduleName, deviceName,
+                    node.getDescription().orElse(""), pathParams);
+            operationsBuilder.post(post);
+        }
         return operationsBuilder.build();
     }
 
index 04905e630ffcc7f8db7d0215418aa1a74f8fcf51..de3b443c4a18c3c18bf25438ea7107b2b4e98a40 100644 (file)
@@ -77,19 +77,20 @@ public final class OperationBuilder {
 
     public static Operation buildGet(final DataSchemaNode node, final String moduleName,
             final @NonNull String deviceName, final List<Parameter> pathParams, final String defName,
-            final String defNameTop) {
+            final String defNameTop, final boolean isConfig) {
         final String description = node.getDescription().orElse("");
         final String summary = SUMMARY_TEMPLATE.formatted(HttpMethod.GET, deviceName, moduleName,
                 node.getQName().getLocalName());
         final List<String> tags = List.of(deviceName + " " + moduleName);
         final List<Parameter> parameters = new ArrayList<>(pathParams);
-        parameters.add(buildQueryParameters());
+        parameters.add(buildQueryParameters(isConfig));
         final ObjectNode responses = JsonNodeFactory.instance.objectNode();
         final ObjectNode schema = JsonNodeFactory.instance.objectNode();
         final ObjectNode xmlSchema = JsonNodeFactory.instance.objectNode();
-        schema.put(REF_KEY, COMPONENTS_PREFIX + defNameTop);
-        xmlSchema.put(REF_KEY, COMPONENTS_PREFIX + defName);
-
+        if (node.isConfiguration()) {
+            schema.put(REF_KEY, COMPONENTS_PREFIX + defNameTop);
+            xmlSchema.put(REF_KEY, COMPONENTS_PREFIX + defName);
+        }
         responses.set(String.valueOf(Response.Status.OK.getStatusCode()),
                 buildResponse(Response.Status.OK.getReasonPhrase(), schema, xmlSchema));
 
@@ -102,7 +103,7 @@ public final class OperationBuilder {
             .build();
     }
 
-    private static Parameter buildQueryParameters() {
+    private static Parameter buildQueryParameters(final boolean isConfig) {
         final ArrayNode cases = JsonNodeFactory.instance.arrayNode()
             .add("config")
             .add("nonconfig")
@@ -111,6 +112,7 @@ public final class OperationBuilder {
         return new Parameter.Builder()
             .in("query")
             .name("content")
+            .required(!isConfig)
             .schema(new Schema.Builder().type("string").schemaEnum(cases).build())
             .build();
     }
@@ -283,17 +285,18 @@ public final class OperationBuilder {
     public static ObjectNode buildResponse(final String description, final ObjectNode schema,
             final ObjectNode xmlSchema) {
         final ObjectNode response = JsonNodeFactory.instance.objectNode();
+        if (!schema.isEmpty()) {
+            final ObjectNode content = JsonNodeFactory.instance.objectNode();
+            final ObjectNode body = JsonNodeFactory.instance.objectNode();
+            final ObjectNode xmlBody = JsonNodeFactory.instance.objectNode();
 
-        final ObjectNode content = JsonNodeFactory.instance.objectNode();
-        final ObjectNode body = JsonNodeFactory.instance.objectNode();
-        final ObjectNode xmlBody = JsonNodeFactory.instance.objectNode();
-
-        body.set(SCHEMA_KEY, schema);
-        xmlBody.set(SCHEMA_KEY, xmlSchema);
-        content.set(MediaType.APPLICATION_JSON, body);
-        content.set(MediaType.APPLICATION_XML, xmlBody);
+            body.set(SCHEMA_KEY, schema);
+            xmlBody.set(SCHEMA_KEY, xmlSchema);
+            content.set(MediaType.APPLICATION_JSON, body);
+            content.set(MediaType.APPLICATION_XML, xmlBody);
 
-        response.set(CONTENT_KEY, content);
+            response.set(CONTENT_KEY, content);
+        }
 
         response.put(DESCRIPTION_KEY, description);
         return response;
index 8b0db067a5bb8930f4c2c4be1ce315b7b2b54f31..5cd9ddac008fa8cd09c9a20324d22f5defdce703 100644 (file)
@@ -64,9 +64,13 @@ public class OperationalDataTest {
         OPERATIONS_MP_URI,
         DATA_MP_URI + "/action-types:list={name}",
         DATA_MP_URI + "/operational:root",
+        DATA_MP_URI + "/operational:root/oper-container/config-container",
+        DATA_MP_URI + "/operational:root/oper-container/oper-container-list={oper-container-list-leaf}",
         DATA_MP_URI + "/action-types:multi-container",
         DATA_MP_URI + "/action-types:multi-container/inner-container",
+        DATA_MP_URI + "/operational:root/oper-container",
         DATA_MP_URI + "/action-types:container",
+        DATA_MP_URI + "/operational:root/config-container/config-container-oper-list={oper-container-list-leaf}",
         DATA_MP_URI + "/operational:root/config-container",
         DATA_MP_URI);
     private static final String HTTP_URL = "http://localhost/path";
@@ -105,7 +109,7 @@ public class OperationalDataTest {
                 final var responses = path.get().responses();
                 final var response = responses.elements().next();
                 final var content = response.get("content");
-                // In case of 200 no content
+                // In case of 200 no content and Operational data
                 if (content != null) {
                     verifyOperationHaveCorrectReference(content.get("application/xml"));
                     verifyOperationHaveCorrectReference(content.get("application/json"));