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

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

commit 158fd1d6b62c09c48e97b6e652c1f362d1b83a02
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Jul 16 12:43:47 2026 +0200

    CAMEL-24118: camel-openapi-java - Fix RestOpenApiReader to emit valid 
OpenAPI 3.x
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../apache/camel/openapi/RestOpenApiReader.java    | 156 +++++++++++++++++----
 1 file changed, 129 insertions(+), 27 deletions(-)

diff --git 
a/components/camel-openapi-java/src/main/java/org/apache/camel/openapi/RestOpenApiReader.java
 
b/components/camel-openapi-java/src/main/java/org/apache/camel/openapi/RestOpenApiReader.java
index a180bd29c79b..13ff8f7a4091 100644
--- 
a/components/camel-openapi-java/src/main/java/org/apache/camel/openapi/RestOpenApiReader.java
+++ 
b/components/camel-openapi-java/src/main/java/org/apache/camel/openapi/RestOpenApiReader.java
@@ -78,6 +78,7 @@ import org.apache.camel.model.rest.ParamDefinition;
 import org.apache.camel.model.rest.ResponseHeaderDefinition;
 import org.apache.camel.model.rest.ResponseMessageDefinition;
 import org.apache.camel.model.rest.RestDefinition;
+import org.apache.camel.model.rest.RestParamType;
 import org.apache.camel.model.rest.RestPropertyDefinition;
 import org.apache.camel.model.rest.RestSecuritiesDefinition;
 import org.apache.camel.model.rest.RestSecurityDefinition;
@@ -545,8 +546,19 @@ public class RestOpenApiReader {
             op.addSecurityItem(securityRequirement);
         }
 
+        // Collect formData params to build a single requestBody with object 
schema
+        List<ParamDefinition> formDataParams = new ArrayList<>();
+
         for (ParamDefinition param : verb.getParams()) {
-            Parameter parameter = new Parameter().in(param.getType().name());
+            RestParamType paramType = param.getType();
+
+            // In OAS 3.x, formData is not a valid "in" value - collect for 
requestBody handling
+            if (RestParamType.formData == paramType) {
+                formDataParams.add(param);
+                continue;
+            }
+
+            Parameter parameter = new Parameter().in(paramType.name());
 
             if (parameter != null) {
                 parameter.setName(getValue(camelContext, param.getName()));
@@ -611,7 +623,14 @@ public class RestOpenApiReader {
                         }
                     }
                     if (param.getCollectionFormat() != null) {
-                        
parameter.setStyle(convertToOpenApiStyle(getValue(camelContext, 
param.getCollectionFormat().name())));
+                        StyleEnum style
+                                = convertToOpenApiStyle(getValue(camelContext, 
param.getCollectionFormat().name()));
+                        parameter.setStyle(style);
+                        Boolean explode = convertToOpenApiExplode(
+                                getValue(camelContext, 
param.getCollectionFormat().name()));
+                        if (explode != null) {
+                            parameter.setExplode(explode);
+                        }
                     }
                     if (hasAllowableValues && !isArray) {
                         schema.setEnum(allowableValues);
@@ -638,7 +657,7 @@ public class RestOpenApiReader {
                     op.addParametersItem(parameter);
                 }
 
-                // In OpenAPI 3x, body or form parameters are replaced by 
requestBody
+                // In OpenAPI 3x, body parameters are replaced by requestBody
                 if (parameter.getIn().equals("body")) {
                     RequestBody reqBody = new RequestBody().content(new 
Content());
                     reqBody.setRequired(param.getRequired());
@@ -681,6 +700,51 @@ public class RestOpenApiReader {
             }
         }
 
+        // In OAS 3.x, formData params become a requestBody with object schema
+        if (!formDataParams.isEmpty()) {
+            RequestBody reqBody = op.getRequestBody();
+            if (reqBody == null) {
+                reqBody = new RequestBody().content(new Content());
+                op.setRequestBody(reqBody);
+            }
+            Schema formSchema = new Schema<>();
+            formSchema.setType("object");
+            if (openApi.getSpecVersion().equals(SpecVersion.V31)) {
+                formSchema.addType("object");
+            }
+            List<String> requiredFields = new ArrayList<>();
+            for (ParamDefinition param : formDataParams) {
+                String name = getValue(camelContext, param.getName());
+                String dataType = getValue(camelContext, param.getDataType() 
!= null ? param.getDataType() : "string");
+                Schema<?> fieldSchema = createTypedSchema(dataType, openApi);
+                if (param.getDataFormat() != null) {
+                    fieldSchema.setFormat(getValue(camelContext, 
param.getDataFormat()));
+                }
+                if 
(org.apache.camel.util.ObjectHelper.isNotEmpty(param.getDescription())) {
+                    fieldSchema.setDescription(getValue(camelContext, 
param.getDescription()));
+                }
+                if 
(org.apache.camel.util.ObjectHelper.isNotEmpty(param.getDefaultValue())) {
+                    fieldSchema.setDefault(getValue(camelContext, 
param.getDefaultValue()));
+                }
+                formSchema.addProperty(name, fieldSchema);
+                if (param.getRequired()) {
+                    requiredFields.add(name);
+                }
+            }
+            if (!requiredFields.isEmpty()) {
+                formSchema.setRequired(requiredFields);
+            }
+            // Use multipart/form-data if consumes includes it, otherwise 
application/x-www-form-urlencoded
+            String formMediaType = "application/x-www-form-urlencoded";
+            if (consumes != null && consumes.contains("multipart/form-data")) {
+                formMediaType = "multipart/form-data";
+            }
+            if (reqBody.getContent() == null) {
+                reqBody.setContent(new Content());
+            }
+            reqBody.getContent().addMediaType(formMediaType, new 
MediaType().schema(formSchema));
+        }
+
         // clear parameters if its empty
         if (op.getParameters() != null && op.getParameters().isEmpty()) {
             //            op.parameters.clear();
@@ -711,17 +775,26 @@ public class RestOpenApiReader {
     }
 
     private StyleEnum convertToOpenApiStyle(String value) {
-        //Should be a Collection Format name
         switch (CollectionFormat.valueOf(value)) {
             case csv:
+            case multi:
                 return StyleEnum.FORM;
             case ssv:
             case tsv:
                 return StyleEnum.SPACEDELIMITED;
             case pipes:
                 return StyleEnum.PIPEDELIMITED;
+            default:
+                return null;
+        }
+    }
+
+    private Boolean convertToOpenApiExplode(String value) {
+        switch (CollectionFormat.valueOf(value)) {
+            case csv:
+                return Boolean.FALSE;
             case multi:
-                return StyleEnum.DEEPOBJECT;
+                return Boolean.TRUE;
             default:
                 return null;
         }
@@ -861,9 +934,9 @@ public class RestOpenApiReader {
 
                 if ("string".equals(type) || "long".equals(type) || 
"float".equals(type)
                         || "double".equals(type) || "boolean".equals(type)) {
-                    setResponseHeader(camelContext, response, header, name, 
format, type);
+                    setResponseHeader(camelContext, openApi, response, header, 
name, format, type);
                 } else if ("int".equals(type) || "integer".equals(type)) {
-                    setResponseHeader(camelContext, response, header, name, 
format, "integer");
+                    setResponseHeader(camelContext, openApi, response, header, 
name, format, "integer");
                 } else if ("array".equals(type)) {
                     Header ap = new Header();
                     response.addHeaderObject(name, ap);
@@ -872,17 +945,10 @@ public class RestOpenApiReader {
                     }
                     if (header.getArrayType() != null) {
                         String arrayType = getValue(camelContext, 
header.getArrayType());
-                        if (arrayType.equalsIgnoreCase("string")
-                                || arrayType.equalsIgnoreCase("long")
-                                || arrayType.equalsIgnoreCase("float")
-                                || arrayType.equalsIgnoreCase("double")
-                                || arrayType.equalsIgnoreCase("boolean")) {
-                            setHeaderSchemaOas30(ap, arrayType);
-                        } else if (arrayType.equalsIgnoreCase("int")
-                                || arrayType.equalsIgnoreCase("integer")) {
-                            setHeaderSchemaOas30(ap, "integer");
+                        if (arrayType.equalsIgnoreCase("int") || 
arrayType.equalsIgnoreCase("integer")) {
+                            arrayType = "integer";
                         }
-
+                        setHeaderSchemaOas30(ap, arrayType, openApi);
                     }
                     // add example
                     if (header.getExample() != null) {
@@ -906,17 +972,17 @@ public class RestOpenApiReader {
         }
     }
 
-    private void setHeaderSchemaOas30(Header ap, String arrayType) {
-        Schema items = new Schema().type(arrayType);
-        ap.setSchema(items);
+    private void setHeaderSchemaOas30(Header ap, String arrayType, OpenAPI 
openApi) {
+        Schema<?> items = createHeaderTypedSchema(arrayType, openApi);
+        ap.setSchema(new ArraySchema().items(items));
     }
 
     private void setResponseHeader(
-            CamelContext camelContext, ApiResponse response, 
ResponseHeaderDefinition header,
+            CamelContext camelContext, OpenAPI openApi, ApiResponse response, 
ResponseHeaderDefinition header,
             String name, String format, String type) {
         Header ip = new Header();
         response.addHeaderObject(name, ip);
-        Schema schema = new Schema().type(type);
+        Schema schema = createHeaderTypedSchema(type, openApi);
         ip.setSchema(schema);
         if (format != null) {
             schema.setFormat(format);
@@ -937,6 +1003,41 @@ public class RestOpenApiReader {
         }
     }
 
+    private Schema<?> createHeaderTypedSchema(String type, OpenAPI openApi) {
+        switch (type) {
+            case "integer":
+                return new IntegerSchema();
+            case "long":
+                return new IntegerSchema().format("int64");
+            case "float":
+                return new NumberSchema().format("float");
+            case "double":
+                return new NumberSchema().format("double");
+            case "boolean":
+                return new BooleanSchema();
+            default:
+                return new StringSchema();
+        }
+    }
+
+    private Schema<?> createTypedSchema(String dataType, OpenAPI openApi) {
+        if ("int".equals(dataType) || "integer".equals(dataType)) {
+            return new IntegerSchema();
+        } else if ("long".equals(dataType)) {
+            return new IntegerSchema().format("int64");
+        } else if ("float".equals(dataType)) {
+            return new NumberSchema().format("float");
+        } else if ("double".equals(dataType)) {
+            return new NumberSchema().format("double");
+        } else if ("boolean".equals(dataType)) {
+            return new BooleanSchema();
+        } else if ("file".equals(dataType)) {
+            return new FileSchema();
+        } else {
+            return new StringSchema();
+        }
+    }
+
     private String modelTypeAsRef(String typeName, OpenAPI openApi) {
         boolean array = typeName.endsWith("[]");
         if (array) {
@@ -982,8 +1083,7 @@ public class RestOpenApiReader {
             // No explicit schema reference so handle primitive types
             // special for byte arrays
             if (array && ("byte".equals(typeName) || 
"java.lang.Byte".equals(typeName))) {
-                // Note built-in ByteArraySchema sets type="string" !
-                prop = new Schema<byte[]>().type("number").format("byte");
+                prop = new ByteArraySchema();
                 array = false;
             } else if ("string".equalsIgnoreCase(typeName) || 
"java.lang.String".equals(typeName)) {
                 prop = new StringSchema();
@@ -996,7 +1096,7 @@ public class RestOpenApiReader {
             } else if ("double".equals(typeName) || 
"java.lang.Double".equals(typeName)) {
                 prop = new NumberSchema().format("double");
             } else if ("boolean".equals(typeName) || 
"java.lang.Boolean".equals(typeName)) {
-                prop = new NumberSchema().format("boolean");
+                prop = new BooleanSchema();
             } else if ("file".equals(typeName) || 
"java.io.File".equals(typeName)) {
                 prop = new FileSchema();
             } else {
@@ -1005,11 +1105,13 @@ public class RestOpenApiReader {
         }
 
         if (array) {
-            Schema<?> items = new Schema<>();
             if (ref != null) {
+                Schema<?> items = new Schema<>();
                 items.set$ref(OAS30_SCHEMA_DEFINITION_PREFIX + ref);
+                prop = new ArraySchema().items(items);
+            } else {
+                prop = new ArraySchema().items(prop);
             }
-            prop = new ArraySchema().items(items);
         } else if (prop == null) {
             prop = new Schema<>().$ref(OAS30_SCHEMA_DEFINITION_PREFIX + ref);
         }

Reply via email to