You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2024/03/11 10:46:19 UTC

(camel) 01/03: CAMEL-19284: restdsl-openapi-generator - Add type and outType to Rest DSL

This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch rest-out
in repository https://gitbox.apache.org/repos/asf/camel.git

commit f1868c73fcce6a58a2b6b93b574cdbe7f5d748ef
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Mon Mar 11 11:13:46 2024 +0100

    CAMEL-19284: restdsl-openapi-generator - Add type and outType to Rest DSL
---
 .../apache/camel/model/rest/RestDefinition.java    |  22 ++
 .../dsl/jbang/core/commands/CodeRestGenerator.java |  24 +-
 .../camel/generator/openapi/OperationVisitor.java  |  66 +++++-
 .../camel/generator/openapi/PathVisitor.java       |  10 +-
 .../openapi/RestDslDefinitionGenerator.java        |   2 +-
 .../camel/generator/openapi/RestDslGenerator.java  |  35 +--
 .../openapi/RestDslSourceCodeGenerator.java        |   2 +-
 .../generator/openapi/RestDslXmlGenerator.java     |   3 +-
 .../generator/openapi/RestDslYamlGenerator.java    |   3 +-
 .../generator/openapi/OperationVisitorTest.java    |   5 +-
 .../openapi/RestDslYamlGeneratorV3Test.java        |  20 +-
 .../resources/OpenApiV3PetstoreWithModelYaml.txt   | 255 +++++++++++++++++++++
 12 files changed, 386 insertions(+), 61 deletions(-)

diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/rest/RestDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/rest/RestDefinition.java
index a2ebaa6404e..cb17ab754f9 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/rest/RestDefinition.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/rest/RestDefinition.java
@@ -564,6 +564,17 @@ public class RestDefinition extends OptionalIdentifiedDefinition<RestDefinition>
         return this;
     }
 
+    public RestDefinition type(String classType) {
+        // add to last verb
+        if (getVerbs().isEmpty()) {
+            throw new IllegalArgumentException(MISSING_VERB);
+        }
+
+        VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
+        verb.setType(classType);
+        return this;
+    }
+
     public RestDefinition type(Class<?> classType) {
         // add to last verb
         if (getVerbs().isEmpty()) {
@@ -576,6 +587,17 @@ public class RestDefinition extends OptionalIdentifiedDefinition<RestDefinition>
         return this;
     }
 
+    public RestDefinition outType(String classType) {
+        // add to last verb
+        if (getVerbs().isEmpty()) {
+            throw new IllegalArgumentException(MISSING_VERB);
+        }
+
+        VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
+        verb.setOutType(classType);
+        return this;
+    }
+
     public RestDefinition outType(Class<?> classType) {
         // add to last verb
         if (getVerbs().isEmpty()) {
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CodeRestGenerator.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CodeRestGenerator.java
index f92682ac6d2..12df871ed08 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CodeRestGenerator.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CodeRestGenerator.java
@@ -49,23 +49,23 @@ import static org.openapitools.codegen.CodegenConstants.SERIALIZABLE_MODEL;
 @CommandLine.Command(name = "rest", description = "Generate REST DSL source code from OpenApi specification")
 public class CodeRestGenerator extends CamelCommand {
 
-    @CommandLine.Option(names = { "-i", "--input" }, required = true, description = "OpenApi specification file name")
+    @CommandLine.Option(names = { "--input" }, required = true, description = "OpenApi specification file name")
     private String input;
-    @CommandLine.Option(names = { "-o", "--output" }, description = "Output REST DSL file name")
+    @CommandLine.Option(names = { "--output" }, description = "Output REST DSL file name")
     private String output;
-    @CommandLine.Option(names = { "-t", "--type" }, description = "REST DSL type (YAML or XML)", defaultValue = "yaml")
+    @CommandLine.Option(names = { "--type" }, description = "REST DSL type (YAML or XML)", defaultValue = "yaml")
     private String type;
-    @CommandLine.Option(names = { "-r", "--routes" }, description = "Generate routes (only in YAML)")
+    @CommandLine.Option(names = { "--routes" }, description = "Generate routes (only in YAML)")
     private boolean generateRoutes;
-    @CommandLine.Option(names = { "-d", "--dto" }, description = "Generate Java Data Objects")
+    @CommandLine.Option(names = { "--dto" }, description = "Generate Java Data Objects")
     private boolean generateDataObjects;
-    @CommandLine.Option(names = { "-run", "--runtime" }, description = "Runtime (quarkus, or spring-boot)",
+    @CommandLine.Option(names = { "--runtime" }, description = "Runtime (quarkus, or spring-boot)",
                         defaultValue = "quarkus")
     private String runtime;
-    @CommandLine.Option(names = { "-p", "--package" }, description = "Package for generated Java models",
+    @CommandLine.Option(names = { "--package" }, description = "Package for generated Java models",
                         defaultValue = "model")
     private String packageName;
-    @CommandLine.Option(names = { "-v", "--openapi-version" }, description = "Openapi specification 3.0 or 3.1",
+    @CommandLine.Option(names = { "--openapi-version" }, description = "Openapi specification 3.0 or 3.1",
                         defaultValue = "3.0")
     private String openApiVersion = "3.0";
 
@@ -93,9 +93,13 @@ public class CodeRestGenerator extends CamelCommand {
         try (CamelContext context = new DefaultCamelContext()) {
             String text = null;
             if ("yaml".equalsIgnoreCase(type)) {
-                text = RestDslGenerator.toYaml(doc).generate(context, generateRoutes);
+                text = RestDslGenerator.toYaml(doc)
+                        .withDtoPackageName(generateDataObjects ? packageName : null)
+                        .generate(context, generateRoutes);
             } else if ("xml".equalsIgnoreCase(type)) {
-                text = RestDslGenerator.toXml(doc).generate(context);
+                text = RestDslGenerator.toXml(doc)
+                        .withDtoPackageName(generateDataObjects ? packageName : null)
+                        .generate(context);
             }
             if (text != null) {
                 if (output == null) {
diff --git a/tooling/openapi-rest-dsl-generator/src/main/java/org/apache/camel/generator/openapi/OperationVisitor.java b/tooling/openapi-rest-dsl-generator/src/main/java/org/apache/camel/generator/openapi/OperationVisitor.java
index 0dbd063d88f..6c6cbd14c12 100644
--- a/tooling/openapi-rest-dsl-generator/src/main/java/org/apache/camel/generator/openapi/OperationVisitor.java
+++ b/tooling/openapi-rest-dsl-generator/src/main/java/org/apache/camel/generator/openapi/OperationVisitor.java
@@ -24,6 +24,7 @@ import java.util.List;
 import java.util.Map.Entry;
 import java.util.Set;
 
+import io.apicurio.datamodels.models.Referenceable;
 import io.apicurio.datamodels.models.Schema;
 import io.apicurio.datamodels.models.openapi.OpenApiMediaType;
 import io.apicurio.datamodels.models.openapi.OpenApiOperation;
@@ -48,19 +49,18 @@ import org.apache.camel.util.StringHelper;
 class OperationVisitor<T> {
 
     private final DestinationGenerator destinationGenerator;
-
     private final CodeEmitter<T> emitter;
-
     private final OperationFilter filter;
-
     private final String path;
+    private final String dtoPackageName;
 
     OperationVisitor(final CodeEmitter<T> emitter, final OperationFilter filter, final String path,
-                     final DestinationGenerator destinationGenerator) {
+                     final DestinationGenerator destinationGenerator, final String dtoPackageName) {
         this.emitter = emitter;
         this.filter = filter;
         this.path = path;
         this.destinationGenerator = destinationGenerator;
+        this.dtoPackageName = dtoPackageName;
     }
 
     List<String> asStringList(final List<?> values) {
@@ -238,18 +238,18 @@ class OperationVisitor<T> {
 
             emitter.emit("to", destinationGenerator.generateDestinationFor(operation));
         }
-
     }
 
     private CodeEmitter<T> emitOas30Operation(final OpenApi30Operation operation) {
         if (operation.getRequestBody() != null) {
+            String dto = null;
             boolean foundForm = false;
             final OpenApi30RequestBody requestBody = operation.getRequestBody();
             for (final Entry<String, OpenApiMediaType> entry : requestBody.getContent().entrySet()) {
                 final String ct = entry.getKey();
-                final OpenApi30MediaType mediaType = (OpenApi30MediaType) entry.getValue();
-                if (ct.contains("form") && mediaType.getSchema().getProperties() != null) {
-                    for (final Entry<String, Schema> entrySchema : mediaType.getSchema().getProperties().entrySet()) {
+                OpenApi30MediaType mt = (OpenApi30MediaType) entry.getValue();
+                if (ct.contains("form") && mt.getSchema().getProperties() != null) {
+                    for (final Entry<String, Schema> entrySchema : mt.getSchema().getProperties().entrySet()) {
                         OpenApi30Schema openApi30Schema = (OpenApi30Schema) entrySchema.getValue();
                         foundForm = true;
                         emitter.emit("param");
@@ -261,6 +261,20 @@ class OperationVisitor<T> {
                         emitter.emit("endParam");
                     }
                 }
+                if (dto == null && mt.getSchema() instanceof Referenceable ref) {
+                    OpenApi30Schema schema = (OpenApi30Schema) mt.getSchema();
+                    boolean array = "array".equals(schema.getType());
+                    if (array) {
+                        ref = schema.getItems();
+                    }
+                    String r = ref.get$ref();
+                    if (r != null && r.startsWith("#/components/schemas/")) {
+                        dto = r.substring(21);
+                        if (array) {
+                            dto += "[]";
+                        }
+                    }
+                }
             }
             if (!foundForm) {
                 emitter.emit("param");
@@ -270,9 +284,43 @@ class OperationVisitor<T> {
                 emit("description", requestBody.getDescription());
                 emitter.emit("endParam");
             }
+            if (dtoPackageName != null && dto != null) {
+                emit("type", dtoPackageName + "." + dto);
+            }
         }
 
-        return emitter;
+        if (operation.getResponses() != null) {
+            String dto = null;
+            for (String key : operation.getResponses().getItemNames()) {
+                if ("200".equals(key)) {
+                    var response = operation.getResponses().getItem(key);
+                    if (response instanceof OpenApi30Response res30) {
+                        for (final Entry<String, OpenApi30MediaType> entry : res30.getContent().entrySet()) {
+                            final OpenApi30MediaType mediaType = entry.getValue();
+                            if (dto == null && mediaType.getSchema() instanceof Referenceable ref) {
+                                OpenApi30Schema schema = (OpenApi30Schema) mediaType.getSchema();
+                                boolean array = "array".equals(schema.getType());
+                                if (array) {
+                                    ref = schema.getItems();
+                                }
+                                String r = ref.get$ref();
+                                if (r != null && r.startsWith("#/components/schemas/")) {
+                                    dto = r.substring(21);
+                                    if (array) {
+                                        dto += "[]";
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            if (dtoPackageName != null && dto != null) {
+                emit("outType", dtoPackageName + "." + dto);
+            }
+        }
 
+        return emitter;
     }
+
 }
diff --git a/tooling/openapi-rest-dsl-generator/src/main/java/org/apache/camel/generator/openapi/PathVisitor.java b/tooling/openapi-rest-dsl-generator/src/main/java/org/apache/camel/generator/openapi/PathVisitor.java
index ff0f2f01cf6..de47790c72f 100644
--- a/tooling/openapi-rest-dsl-generator/src/main/java/org/apache/camel/generator/openapi/PathVisitor.java
+++ b/tooling/openapi-rest-dsl-generator/src/main/java/org/apache/camel/generator/openapi/PathVisitor.java
@@ -26,10 +26,9 @@ import org.apache.camel.util.ObjectHelper;
 class PathVisitor<T> {
 
     private final DestinationGenerator destinationGenerator;
-
     private final CodeEmitter<T> emitter;
-
     private final OperationFilter filter;
+    private final String dtoPackageName;
 
     public enum HttpMethod {
         DELETE,
@@ -42,10 +41,11 @@ class PathVisitor<T> {
     }
 
     PathVisitor(final String basePath, final CodeEmitter<T> emitter, final OperationFilter filter,
-                final DestinationGenerator destinationGenerator) {
+                final DestinationGenerator destinationGenerator, final String dtoPackageName) {
         this.emitter = emitter;
         this.filter = filter;
         this.destinationGenerator = destinationGenerator;
+        this.dtoPackageName = dtoPackageName;
 
         if (ObjectHelper.isEmpty(basePath)) {
             emitter.emit("rest");
@@ -55,8 +55,8 @@ class PathVisitor<T> {
     }
 
     void visit(final String path, final OpenApiPathItem definition) {
-        final OperationVisitor<T> restDslOperation = new OperationVisitor<>(emitter, filter, path, destinationGenerator);
-
+        final OperationVisitor<T> restDslOperation
+                = new OperationVisitor<>(emitter, filter, path, destinationGenerator, dtoPackageName);
         operationMapFrom(definition).forEach(restDslOperation::visit);
     }
 
diff --git a/tooling/openapi-rest-dsl-generator/src/main/java/org/apache/camel/generator/openapi/RestDslDefinitionGenerator.java b/tooling/openapi-rest-dsl-generator/src/main/java/org/apache/camel/generator/openapi/RestDslDefinitionGenerator.java
index 033cbb581db..814fc1e9926 100644
--- a/tooling/openapi-rest-dsl-generator/src/main/java/org/apache/camel/generator/openapi/RestDslDefinitionGenerator.java
+++ b/tooling/openapi-rest-dsl-generator/src/main/java/org/apache/camel/generator/openapi/RestDslDefinitionGenerator.java
@@ -30,7 +30,7 @@ public final class RestDslDefinitionGenerator extends RestDslGenerator<RestDslDe
         final RestDefinitionEmitter emitter = new RestDefinitionEmitter();
         final String basePath = RestDslGenerator.determineBasePathFrom(this.basePath, document);
         final PathVisitor<RestsDefinition> restDslStatement
-                = new PathVisitor<>(basePath, emitter, filter, destinationGenerator());
+                = new PathVisitor<>(basePath, emitter, filter, destinationGenerator(), dtoPackageName);
 
         for (String name : document.getPaths().getItemNames()) {
             OpenApiPathItem item = document.getPaths().getItem(name);
diff --git a/tooling/openapi-rest-dsl-generator/src/main/java/org/apache/camel/generator/openapi/RestDslGenerator.java b/tooling/openapi-rest-dsl-generator/src/main/java/org/apache/camel/generator/openapi/RestDslGenerator.java
index c7b8722fab2..4d8ee1cb123 100644
--- a/tooling/openapi-rest-dsl-generator/src/main/java/org/apache/camel/generator/openapi/RestDslGenerator.java
+++ b/tooling/openapi-rest-dsl-generator/src/main/java/org/apache/camel/generator/openapi/RestDslGenerator.java
@@ -50,6 +50,7 @@ public abstract class RestDslGenerator<G> {
     boolean springBootProject;
     boolean springComponent;
     String basePath;
+    String dtoPackageName;
 
     RestDslGenerator(final OpenApiDocument document) {
         this.document = notNull(document, "document");
@@ -57,55 +58,43 @@ public abstract class RestDslGenerator<G> {
 
     public G asSpringBootProject() {
         this.springBootProject = true;
-
         @SuppressWarnings("unchecked")
         final G that = (G) this;
-
         return that;
     }
 
     public G asSpringComponent() {
         this.springComponent = true;
-
         @SuppressWarnings("unchecked")
         final G that = (G) this;
-
         return that;
     }
 
     public G withApiContextPath(final String contextPath) {
         this.apiContextPath = contextPath;
-
         @SuppressWarnings("unchecked")
         final G that = (G) this;
-
         return that;
     }
 
     public G withClientRequestValidation() {
         this.clientRequestValidation = true;
-
         @SuppressWarnings("unchecked")
         final G that = (G) this;
-
         return that;
     }
 
     public G withBasePath(final String basePath) {
         this.basePath = basePath;
-
         @SuppressWarnings("unchecked")
         final G that = (G) this;
-
         return that;
     }
 
     public G withDestinationGenerator(final DestinationGenerator destinationGenerator) {
         this.destinationGenerator = destinationGenerator;
-
         @SuppressWarnings("unchecked")
         final G that = (G) this;
-
         return that;
     }
 
@@ -116,46 +105,43 @@ public abstract class RestDslGenerator<G> {
      */
     public G withDestinationToSyntax(final String destinationToSyntax) {
         this.destinationToSyntax = destinationToSyntax;
-
         @SuppressWarnings("unchecked")
         final G that = (G) this;
-
         return that;
     }
 
     public G withOperationFilter(final OperationFilter filter) {
         this.filter = filter;
-
         @SuppressWarnings("unchecked")
         final G that = (G) this;
-
         return that;
     }
 
     public G withOperationFilter(final String include) {
         this.filter.setIncludes(include);
-
         @SuppressWarnings("unchecked")
         final G that = (G) this;
-
         return that;
     }
 
     public G withRestComponent(final String restComponent) {
         this.restComponent = restComponent;
-
         @SuppressWarnings("unchecked")
         final G that = (G) this;
-
         return that;
     }
 
     public G withRestContextPath(final String contextPath) {
         this.restContextPath = contextPath;
-
         @SuppressWarnings("unchecked")
         final G that = (G) this;
+        return that;
+    }
 
+    public G withDtoPackageName(final String dtoPackageName) {
+        this.dtoPackageName = dtoPackageName;
+        @SuppressWarnings("unchecked")
+        final G that = (G) this;
         return that;
     }
 
@@ -174,7 +160,6 @@ public abstract class RestDslGenerator<G> {
 
     public static String determineBasePathFrom(final String parameter) {
         Objects.requireNonNull(parameter, "parameter");
-
         return prepareBasePath(parameter.trim());
     }
 
@@ -207,12 +192,10 @@ public abstract class RestDslGenerator<G> {
         if (basePath.charAt(0) != '/') {
             basePath = "/" + basePath;
         }
-
         if (basePath.indexOf("//") == 0) {
             // strip off the first "/" if double "/" exists
             basePath = basePath.substring(1);
         }
-
         if ("/".equals(basePath)) {
             basePath = "";
         }
@@ -230,11 +213,8 @@ public abstract class RestDslGenerator<G> {
             if (servers == null || servers.get(0) == null) {
                 return "";
             }
-
             final OpenApi30Server firstServer = servers.get(0);
-
             final URI serverUrl = URI.create(resolveVariablesIn(firstServer.getUrl(), firstServer));
-
             return serverUrl.getHost();
         }
 
@@ -250,7 +230,6 @@ public abstract class RestDslGenerator<G> {
                 withoutPlaceholders = withoutPlaceholders.replace(name, entry.getValue().getDefault());
             }
         }
-
         return withoutPlaceholders;
     }
 
diff --git a/tooling/openapi-rest-dsl-generator/src/main/java/org/apache/camel/generator/openapi/RestDslSourceCodeGenerator.java b/tooling/openapi-rest-dsl-generator/src/main/java/org/apache/camel/generator/openapi/RestDslSourceCodeGenerator.java
index 4388a464f3b..52a2aeda281 100644
--- a/tooling/openapi-rest-dsl-generator/src/main/java/org/apache/camel/generator/openapi/RestDslSourceCodeGenerator.java
+++ b/tooling/openapi-rest-dsl-generator/src/main/java/org/apache/camel/generator/openapi/RestDslSourceCodeGenerator.java
@@ -138,7 +138,7 @@ public abstract class RestDslSourceCodeGenerator<T> extends RestDslGenerator<Res
                 if (anyAccepted) {
                     // create new rest statement per path to avoid a giant chained single method
                     PathVisitor<MethodSpec> restDslStatement
-                            = new PathVisitor<>(basePath, emitter, filter, destinationGenerator());
+                            = new PathVisitor<>(basePath, emitter, filter, destinationGenerator(), dtoPackageName);
                     restDslStatement.visit(name, s);
                     emitter.endEmit();
                 }
diff --git a/tooling/openapi-rest-dsl-generator/src/main/java/org/apache/camel/generator/openapi/RestDslXmlGenerator.java b/tooling/openapi-rest-dsl-generator/src/main/java/org/apache/camel/generator/openapi/RestDslXmlGenerator.java
index 36f20c2c821..a1ebe22e705 100644
--- a/tooling/openapi-rest-dsl-generator/src/main/java/org/apache/camel/generator/openapi/RestDslXmlGenerator.java
+++ b/tooling/openapi-rest-dsl-generator/src/main/java/org/apache/camel/generator/openapi/RestDslXmlGenerator.java
@@ -53,7 +53,8 @@ public class RestDslXmlGenerator extends RestDslGenerator<RestDslXmlGenerator> {
         final String basePath = RestDslGenerator.determineBasePathFrom(this.basePath, document);
         final PathVisitor<RestsDefinition> restDslStatement = new PathVisitor<>(
                 basePath, emitter, filter,
-                destinationGenerator());
+                destinationGenerator(),
+                dtoPackageName);
 
         for (String name : document.getPaths().getItemNames()) {
             OpenApiPathItem item = document.getPaths().getItem(name);
diff --git a/tooling/openapi-rest-dsl-generator/src/main/java/org/apache/camel/generator/openapi/RestDslYamlGenerator.java b/tooling/openapi-rest-dsl-generator/src/main/java/org/apache/camel/generator/openapi/RestDslYamlGenerator.java
index 8a3636c1577..9303c3553e7 100644
--- a/tooling/openapi-rest-dsl-generator/src/main/java/org/apache/camel/generator/openapi/RestDslYamlGenerator.java
+++ b/tooling/openapi-rest-dsl-generator/src/main/java/org/apache/camel/generator/openapi/RestDslYamlGenerator.java
@@ -74,7 +74,8 @@ public class RestDslYamlGenerator extends RestDslGenerator<RestDslYamlGenerator>
         final String basePath = RestDslGenerator.determineBasePathFrom(this.basePath, document);
         final PathVisitor<RestsDefinition> restDslStatement = new PathVisitor<>(
                 basePath, emitter, filter,
-                destinationGenerator());
+                destinationGenerator(),
+                dtoPackageName);
 
         for (String name : document.getPaths().getItemNames()) {
             OpenApiPathItem item = document.getPaths().getItem(name);
diff --git a/tooling/openapi-rest-dsl-generator/src/test/java/org/apache/camel/generator/openapi/OperationVisitorTest.java b/tooling/openapi-rest-dsl-generator/src/test/java/org/apache/camel/generator/openapi/OperationVisitorTest.java
index d98ea155b27..774a160659b 100644
--- a/tooling/openapi-rest-dsl-generator/src/test/java/org/apache/camel/generator/openapi/OperationVisitorTest.java
+++ b/tooling/openapi-rest-dsl-generator/src/test/java/org/apache/camel/generator/openapi/OperationVisitorTest.java
@@ -37,7 +37,7 @@ public class OperationVisitorTest {
     public void shouldEmitCodeForOas2ParameterInQuery() {
         final Builder method = MethodSpec.methodBuilder("configure");
         final MethodBodySourceCodeEmitter emitter = new MethodBodySourceCodeEmitter(method);
-        final OperationVisitor<?> visitor = new OperationVisitor<>(emitter, null, null, null);
+        final OperationVisitor<?> visitor = new OperationVisitor<>(emitter, null, null, null, null);
 
         final OpenApi20Parameter parameter = new OpenApi20ParameterImpl();
         parameter.setName("param");
@@ -59,7 +59,8 @@ public class OperationVisitorTest {
         final Builder method = MethodSpec.methodBuilder("configure");
         final MethodBodySourceCodeEmitter emitter = new MethodBodySourceCodeEmitter(method);
         final OperationVisitor<?> visitor
-                = new OperationVisitor<>(emitter, new OperationFilter(), "/path/{param}", new DefaultDestinationGenerator());
+                = new OperationVisitor<>(
+                        emitter, new OperationFilter(), "/path/{param}", new DefaultDestinationGenerator(), null);
 
         final OpenApi20Document document = new OpenApi20DocumentImpl();
         final OpenApiPaths paths = document.createPaths();
diff --git a/tooling/openapi-rest-dsl-generator/src/test/java/org/apache/camel/generator/openapi/RestDslYamlGeneratorV3Test.java b/tooling/openapi-rest-dsl-generator/src/test/java/org/apache/camel/generator/openapi/RestDslYamlGeneratorV3Test.java
index c3774382335..97d5beb24d5 100644
--- a/tooling/openapi-rest-dsl-generator/src/test/java/org/apache/camel/generator/openapi/RestDslYamlGeneratorV3Test.java
+++ b/tooling/openapi-rest-dsl-generator/src/test/java/org/apache/camel/generator/openapi/RestDslYamlGeneratorV3Test.java
@@ -41,7 +41,7 @@ public class RestDslYamlGeneratorV3Test {
         try (CamelContext context = new DefaultCamelContext()) {
             final String yaml = RestDslGenerator.toYaml(document).generate(context);
 
-            final URI file = RestDslXmlGeneratorV3Test.class.getResource("/OpenApiV3PetstoreYaml.txt").toURI();
+            final URI file = RestDslYamlGeneratorV3Test.class.getResource("/OpenApiV3PetstoreYaml.txt").toURI();
             final String expectedContent = new String(Files.readAllBytes(Paths.get(file)), StandardCharsets.UTF_8);
 
             assertThat(yaml).isEqualTo(expectedContent);
@@ -49,14 +49,28 @@ public class RestDslYamlGeneratorV3Test {
     }
 
     @Test
-    public void shouldGenerateXmlWithRestComponent() throws Exception {
+    public void shouldGenerateYamlWithRestComponent() throws Exception {
         try (CamelContext context = new DefaultCamelContext()) {
             final String yaml = RestDslGenerator.toYaml(document)
                     .withRestComponent("servlet")
                     .withRestContextPath("/foo")
                     .generate(context);
 
-            final URI file = RestDslXmlGeneratorV3Test.class.getResource("/OpenApiV3PetstoreWithRestComponentYaml.txt").toURI();
+            final URI file
+                    = RestDslYamlGeneratorV3Test.class.getResource("/OpenApiV3PetstoreWithRestComponentYaml.txt").toURI();
+            final String expectedContent = new String(Files.readAllBytes(Paths.get(file)), StandardCharsets.UTF_8);
+            assertThat(yaml).isEqualTo(expectedContent);
+        }
+    }
+
+    @Test
+    public void shouldGenerateYamlWithModel() throws Exception {
+        try (CamelContext context = new DefaultCamelContext()) {
+            final String yaml = RestDslGenerator.toYaml(document)
+                    .withDtoPackageName("model")
+                    .generate(context);
+
+            final URI file = RestDslYamlGeneratorV3Test.class.getResource("/OpenApiV3PetstoreWithModelYaml.txt").toURI();
             final String expectedContent = new String(Files.readAllBytes(Paths.get(file)), StandardCharsets.UTF_8);
             assertThat(yaml).isEqualTo(expectedContent);
         }
diff --git a/tooling/openapi-rest-dsl-generator/src/test/resources/OpenApiV3PetstoreWithModelYaml.txt b/tooling/openapi-rest-dsl-generator/src/test/resources/OpenApiV3PetstoreWithModelYaml.txt
new file mode 100644
index 00000000000..8c3052f2cf1
--- /dev/null
+++ b/tooling/openapi-rest-dsl-generator/src/test/resources/OpenApiV3PetstoreWithModelYaml.txt
@@ -0,0 +1,255 @@
+- rest:
+    path: "/api/v3"
+    put:
+    - id: "updatePet"
+      path: "/pet"
+      consumes: "application/json,application/xml"
+      type: "model.Pet"
+      param:
+      - description: "Pet object that needs to be added to the store"
+        name: "body"
+        required: true
+        type: "body"
+      to: "direct:updatePet"
+    - id: "updateUser"
+      path: "/user/{username}"
+      description: "This can only be done by the logged in user."
+      consumes: "*/*"
+      type: "model.User"
+      param:
+      - dataType: "string"
+        description: "name that need to be updated"
+        name: "username"
+        required: true
+        type: "path"
+      - description: "Updated user object"
+        name: "body"
+        required: true
+        type: "body"
+      to: "direct:updateUser"
+    post:
+    - id: "addPet"
+      path: "/pet"
+      consumes: "application/json,application/xml"
+      type: "model.Pet"
+      param:
+      - dataType: "boolean"
+        defaultValue: "false"
+        description: "Verbose data"
+        name: "verbose"
+        required: false
+        type: "query"
+      - description: "Pet object that needs to be added to the store"
+        name: "body"
+        required: true
+        type: "body"
+      to: "direct:addPet"
+    - id: "updatePetWithForm"
+      path: "/pet/{petId}"
+      consumes: "application/x-www-form-urlencoded"
+      param:
+      - dataType: "integer"
+        description: "ID of pet that needs to be updated"
+        name: "petId"
+        required: true
+        type: "path"
+      - dataType: "string"
+        description: "Updated name of the pet"
+        name: "name"
+        required: true
+        type: "formData"
+      - dataType: "string"
+        description: "Updated status of the pet"
+        name: "status"
+        required: true
+        type: "formData"
+      to: "direct:updatePetWithForm"
+    - id: "uploadFile"
+      path: "/pet/{petId}/uploadImage"
+      consumes: "multipart/form-data"
+      produces: "application/json"
+      outType: "model.ApiResponse"
+      param:
+      - dataType: "integer"
+        description: "ID of pet to update"
+        name: "petId"
+        required: true
+        type: "path"
+      - dataType: "string"
+        description: "Additional data to pass to server"
+        name: "additionalMetadata"
+        required: true
+        type: "formData"
+      - dataType: "string"
+        description: "file to upload"
+        name: "file"
+        required: true
+        type: "formData"
+      to: "direct:uploadFile"
+    - id: "placeOrder"
+      path: "/store/order"
+      consumes: "*/*"
+      produces: "application/xml,application/json"
+      type: "model.Order"
+      outType: "model.Order"
+      param:
+      - description: "order placed for purchasing the pet"
+        name: "body"
+        required: true
+        type: "body"
+      to: "direct:placeOrder"
+    - id: "createUser"
+      path: "/user"
+      description: "This can only be done by the logged in user."
+      consumes: "*/*"
+      type: "model.User"
+      param:
+      - description: "Created user object"
+        name: "body"
+        required: true
+        type: "body"
+      to: "direct:createUser"
+    - id: "createUsersWithArrayInput"
+      path: "/user/createWithArray"
+      consumes: "*/*"
+      type: "model.User[]"
+      param:
+      - description: "List of user object"
+        name: "body"
+        required: true
+        type: "body"
+      to: "direct:createUsersWithArrayInput"
+    - id: "createUsersWithListInput"
+      path: "/user/createWithList"
+      consumes: "*/*"
+      type: "model.User[]"
+      param:
+      - description: "List of user object"
+        name: "body"
+        required: true
+        type: "body"
+      to: "direct:createUsersWithListInput"
+    get:
+    - id: "findPetsByStatus"
+      path: "/pet/findByStatus"
+      description: "Multiple status values can be provided with comma separated strings"
+      produces: "application/xml,application/json"
+      outType: "model.Pet[]"
+      param:
+      - arrayType: "string"
+        collectionFormat: "multi"
+        dataType: "array"
+        description: "Status values that need to be considered for filter"
+        name: "status"
+        required: true
+        type: "query"
+      to: "direct:findPetsByStatus"
+    - id: "findPetsByTags"
+      path: "/pet/findByTags"
+      description: "Muliple tags can be provided with comma separated strings. Use\
+        \ tag1, tag2, tag3 for testing."
+      produces: "application/xml,application/json"
+      outType: "model.Pet[]"
+      param:
+      - arrayType: "string"
+        collectionFormat: "multi"
+        dataType: "array"
+        description: "Tags to filter by"
+        name: "tags"
+        required: true
+        type: "query"
+      to: "direct:findPetsByTags"
+    - id: "getPetById"
+      path: "/pet/{petId}"
+      description: "Returns a single pet"
+      produces: "application/xml,application/json"
+      outType: "model.Pet"
+      param:
+      - dataType: "integer"
+        description: "ID of pet to return"
+        name: "petId"
+        required: true
+        type: "path"
+      to: "direct:getPetById"
+    - id: "getInventory"
+      path: "/store/inventory"
+      description: "Returns a map of status codes to quantities"
+      produces: "application/json"
+      to: "direct:getInventory"
+    - id: "getOrderById"
+      path: "/store/order/{orderId}"
+      description: "For valid response try integer IDs with value >= 1 and <= 10.\
+        \ Other values will generated exceptions"
+      produces: "application/xml,application/json"
+      outType: "model.Order"
+      param:
+      - dataType: "integer"
+        description: "ID of pet that needs to be fetched"
+        name: "orderId"
+        required: true
+        type: "path"
+      to: "direct:getOrderById"
+    - id: "loginUser"
+      path: "/user/login"
+      produces: "application/xml,application/json"
+      param:
+      - dataType: "string"
+        description: "The user name for login"
+        name: "username"
+        required: true
+        type: "query"
+      - dataType: "string"
+        description: "The password for login in clear text"
+        name: "password"
+        required: true
+        type: "query"
+      to: "direct:loginUser"
+    - id: "logoutUser"
+      path: "/user/logout"
+      to: "direct:logoutUser"
+    - id: "getUserByName"
+      path: "/user/{username}"
+      produces: "application/xml,application/json"
+      outType: "model.User"
+      param:
+      - dataType: "string"
+        description: "The name that needs to be fetched. Use user1 for testing. "
+        name: "username"
+        required: true
+        type: "path"
+      to: "direct:getUserByName"
+    delete:
+    - id: "deletePet"
+      path: "/pet/{petId}"
+      param:
+      - dataType: "string"
+        name: "api_key"
+        required: false
+        type: "header"
+      - dataType: "integer"
+        description: "Pet id to delete"
+        name: "petId"
+        required: true
+        type: "path"
+      to: "direct:deletePet"
+    - id: "deleteOrder"
+      path: "/store/order/{orderId}"
+      description: "For valid response try integer IDs with positive integer value.\
+        \ Negative or non-integer values will generate API errors"
+      param:
+      - dataType: "integer"
+        description: "ID of the order that needs to be deleted"
+        name: "orderId"
+        required: true
+        type: "path"
+      to: "direct:deleteOrder"
+    - id: "deleteUser"
+      path: "/user/{username}"
+      description: "This can only be done by the logged in user."
+      param:
+      - dataType: "string"
+        description: "The name that needs to be deleted"
+        name: "username"
+        required: true
+        type: "path"
+      to: "direct:deleteUser"