Re-implement paths for RPCs 65/108865/23
authorSamuel Schneider <samuel.schneider@pantheon.tech>
Mon, 6 Nov 2023 19:51:49 +0000 (20:51 +0100)
committerIvan Hrasko <ivan.hrasko@pantheon.tech>
Fri, 8 Dec 2023 13:52:11 +0000 (13:52 +0000)
Re-implement logic for generating paths for RPCs
with new input streams approach.

JIRA: NETCONF-938
Change-Id: I4957d303148fcdffc0fb09b393209d72e9f7b63b
Signed-off-by: Samuel Schneider <samuel.schneider@pantheon.tech>
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/OpenApiInputStream.java
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/PathsSteam.java
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/OperationEntity.java
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/PostEntity.java

index 018a9b1f86729a6695dcc9d58aa232cae9f2871c..7d7eea749278ba1f5220e00283f031e10af4eca4 100644 (file)
@@ -39,13 +39,14 @@ public final class OpenApiInputStream extends InputStream {
     private boolean eof;
 
     public OpenApiInputStream(final EffectiveModelContext context, final String openApiVersion, final Info info,
-            final List<Server> servers, final List<Map<String, List<String>>> security) throws IOException {
+            final List<Server> servers, final List<Map<String, List<String>>> security, final String deviceName,
+            final String urlPrefix) throws IOException {
         final OpenApiBodyWriter writer = new OpenApiBodyWriter(generator, stream);
         stack.add(new OpenApiVersionStream(new OpenApiVersionEntity(), writer));
         stack.add(new InfoStream(new InfoEntity(info.version(), info.title(), info.description()), writer));
         stack.add(new ServersStream(new ServersEntity(
             List.of(new ServerEntity(servers.iterator().next().url()))), writer));
-        stack.add(new PathsSteam(context, writer, generator, stream));
+        stack.add(new PathsSteam(context, writer, generator, stream, deviceName, urlPrefix));
         stack.add(new SchemasStream(context, writer, generator, stream));
         stack.add(new SecurityStream(writer));
     }
index 6c31d2c5ba1fa8c82932518404677c975290f613..058a14801f2792b4e93a3f22d615e68be18ad0fd 100644 (file)
@@ -29,16 +29,21 @@ public final class PathsSteam extends InputStream {
     private final JsonGenerator generator;
     private final OpenApiBodyWriter writer;
     private final ByteArrayOutputStream stream;
+    private final String deviceName;
+    private final String urlPrefix;
 
     private Reader reader;
     private boolean eof;
 
     public PathsSteam(final EffectiveModelContext context, final OpenApiBodyWriter writer,
-            final JsonGenerator generator, final ByteArrayOutputStream stream) {
+            final JsonGenerator generator, final ByteArrayOutputStream stream, final String deviceName,
+            final String urlPrefix) {
         iterator = context.getModules().iterator();
         this.generator = generator;
         this.writer = writer;
         this.stream = stream;
+        this.deviceName = deviceName;
+        this.urlPrefix = urlPrefix;
     }
 
     @Override
@@ -77,13 +82,13 @@ public final class PathsSteam extends InputStream {
         return super.read(array, off, len);
     }
 
-    private static Deque<PathEntity> toPaths(final Module module) {
+    private Deque<PathEntity> toPaths(final Module module) {
         final var result = new ArrayDeque<PathEntity>();
         // RPC operations (via post) - RPCs have their own path
         for (final var rpc : module.getRpcs()) {
             // TODO connect path with payload
-            final var post = new PostEntity(rpc, "controller", module.getName());
-            final String resolvedPath = "rests/operations/" + "/" + module.getName() + ":"
+            final var post = new PostEntity(rpc, deviceName, module.getName());
+            final String resolvedPath = "/rests/operations" + urlPrefix + "/" + module.getName() + ":"
                 + rpc.getQName().getLocalName();
             final var entity = new PathEntity(resolvedPath, post);
             result.add(entity);
index 9eba5855b48dfce19648004c3a139f597f0f5700..a263be0aed2774692d1477beb36cca7ad4ab5eed 100644 (file)
@@ -54,6 +54,10 @@ public abstract sealed class OperationEntity extends OpenApiEntity permits PostE
         if (summary != null) {
             generator.writeStringField("summary", summary);
         }
+        generateRequestBody(generator);
+        generateResponses(generator);
+        generateTags(generator);
+        generateParams(generator);
         generator.writeEndObject();
     }
 
@@ -64,8 +68,28 @@ public abstract sealed class OperationEntity extends OpenApiEntity permits PostE
     }
 
     @Nullable String description() {
-        return null;
+        return schema.getDescription().orElse("");
     }
 
     @Nullable abstract String summary();
+
+    void generateRequestBody(final @NonNull JsonGenerator generator) throws IOException {
+        // No-op
+    }
+
+    void generateResponses(final @NonNull JsonGenerator generator) throws IOException {
+        // No-op
+    }
+
+    void generateTags(final @NonNull JsonGenerator generator) throws IOException {
+        generator.writeArrayFieldStart("tags");
+        generator.writeString(deviceName + " " + moduleName);
+        generator.writeEndArray();
+    }
+
+    void generateParams(final @NonNull JsonGenerator generator) throws IOException {
+        generator.writeArrayFieldStart("parameters");
+        // need empty array here
+        generator.writeEndArray();
+    }
 }
index 7ef36a5d58f2a61371ab46fd050cfbf4355bdff1..40aa3f578c5d4101311ccd59386b342b47438ec0 100644 (file)
@@ -7,13 +7,28 @@
  */
 package org.opendaylight.restconf.openapi.model;
 
+import static javax.ws.rs.core.Response.Status.NO_CONTENT;
+import static javax.ws.rs.core.Response.Status.OK;
+import static org.opendaylight.restconf.openapi.impl.DefinitionGenerator.OUTPUT_SUFFIX;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import java.io.IOException;
 import javax.ws.rs.HttpMethod;
+import javax.ws.rs.core.MediaType;
+import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.yangtools.yang.model.api.OperationDefinition;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
 
 public final class PostEntity extends OperationEntity {
     private static final String SUMMARY_TEMPLATE = "%s - %s - %s - %s";
     private static final String INPUT_SUFFIX = "_input";
+    private static final String INPUT_KEY = "input";
+    private static final String OBJECT = "object";
+    private static final String SCHEMA = "schema";
+    private static final String TYPE = "type";
+    private static final String DESCRIPTION = "description";
+    private static final String COMPONENTS_PREFIX = "#/components/schemas/";
 
     public PostEntity(final OperationDefinition schema, final String deviceName, final String moduleName) {
         super(schema, deviceName, moduleName);
@@ -27,4 +42,95 @@ public final class PostEntity extends OperationEntity {
         final var operationName = schema().getQName().getLocalName() + INPUT_SUFFIX;
         return SUMMARY_TEMPLATE.formatted(HttpMethod.POST, deviceName(), moduleName(), operationName);
     }
+
+    @Override
+    void generateResponses(final @NonNull JsonGenerator generator) throws IOException {
+        if (schema() instanceof RpcDefinition rpc) {
+            generator.writeObjectFieldStart("responses");
+            final var output = rpc.getOutput();
+            final var operationName = rpc.getQName().getLocalName();
+            if (!output.getChildNodes().isEmpty()) {
+                // TODO: add proper discriminator from DefinitionNames when schemas re-implementation is done
+                final var ref = COMPONENTS_PREFIX + moduleName() + "_" + operationName + OUTPUT_SUFFIX;
+                generator.writeObjectFieldStart(String.valueOf(OK.getStatusCode()));
+                generator.writeStringField(DESCRIPTION, String.format("RPC %s success", operationName));
+
+                generator.writeObjectFieldStart("content");
+                generateMediaTypeSchemaRef(generator, MediaType.APPLICATION_JSON, ref);
+                generateMediaTypeSchemaRef(generator, MediaType.APPLICATION_XML, ref);
+                generator.writeEndObject();
+
+                generator.writeEndObject();
+
+            } else {
+                generator.writeObjectFieldStart(String.valueOf(NO_CONTENT.getStatusCode()));
+                generator.writeStringField(DESCRIPTION, String.format("RPC %s success", operationName));
+                generator.writeEndObject();
+
+            }
+            generator.writeEndObject();
+        }
+    }
+
+    @Override
+    void generateRequestBody(final @NonNull JsonGenerator generator) throws IOException {
+        if (schema() instanceof RpcDefinition rpc) {
+            generator.writeObjectFieldStart("requestBody");
+            final var input = rpc.getInput();
+            final var operationName = rpc.getQName().getLocalName();
+            generator.writeStringField(DESCRIPTION, operationName + INPUT_SUFFIX);
+            generator.writeObjectFieldStart("content");
+            if (!input.getChildNodes().isEmpty()) {
+                // TODO: add proper discriminator from DefinitionNames when schemas re-implementation is done
+                final var ref = COMPONENTS_PREFIX + moduleName() + "_" + operationName + INPUT_SUFFIX;
+                generator.writeObjectFieldStart(MediaType.APPLICATION_JSON);
+                generator.writeObjectFieldStart(SCHEMA);
+                generator.writeObjectFieldStart("properties");
+                generator.writeObjectFieldStart(INPUT_KEY);
+                generator.writeStringField("$ref", ref);
+                generator.writeStringField(TYPE, OBJECT);
+                generator.writeEndObject();
+                generator.writeEndObject();
+                generator.writeEndObject();
+                generator.writeEndObject();
+                generateMediaTypeSchemaRef(generator, MediaType.APPLICATION_XML, ref);
+            } else {
+                generator.writeObjectFieldStart(MediaType.APPLICATION_JSON);
+                generator.writeObjectFieldStart(SCHEMA);
+
+                generator.writeObjectFieldStart("properties");
+                generator.writeObjectFieldStart(INPUT_KEY);
+                generator.writeStringField(TYPE, OBJECT);
+                generator.writeEndObject();
+                generator.writeEndObject();
+
+                generator.writeStringField(TYPE, OBJECT);
+                generator.writeEndObject();
+                generator.writeEndObject();
+
+                generator.writeObjectFieldStart(MediaType.APPLICATION_XML);
+                generator.writeObjectFieldStart(SCHEMA);
+
+                generator.writeObjectFieldStart("xml");
+                generator.writeStringField("name", INPUT_KEY);
+                generator.writeStringField("namespace", input.getQName().getNamespace().toString());
+                generator.writeEndObject();
+
+                generator.writeStringField(TYPE, OBJECT);
+                generator.writeEndObject();
+                generator.writeEndObject();
+            }
+            generator.writeEndObject();
+            generator.writeEndObject();
+        }
+    }
+
+    private static void generateMediaTypeSchemaRef(final JsonGenerator generator, final String mediaType,
+            final String ref) throws IOException {
+        generator.writeObjectFieldStart(mediaType);
+        generator.writeObjectFieldStart(SCHEMA);
+        generator.writeStringField("$ref", ref);
+        generator.writeEndObject();
+        generator.writeEndObject();
+    }
 }