diff --git a/packages/bruno-converters/src/openapi/openapi-to-bruno.js b/packages/bruno-converters/src/openapi/openapi-to-bruno.js
index b910c443e..4435f6420 100644
--- a/packages/bruno-converters/src/openapi/openapi-to-bruno.js
+++ b/packages/bruno-converters/src/openapi/openapi-to-bruno.js
@@ -117,6 +117,130 @@ const getBodyTypeFromContentType = (contentType) => {
return 'text';
};
+/**
+ * Gets a default value for a schema based on its type, format, and constraints
+ * Prioritizes: explicit example > enum first value > format-specific example > type default
+ * @param {Object} schema - The OpenAPI schema object
+ * @param {Map} visited - Map to track circular references
+ * @returns {*} - The default value for the schema
+ */
+const getDefaultValueForSchema = (schema, visited = new Map()) => {
+ // Check for explicit example first
+ if (schema.example !== undefined) {
+ return schema.example;
+ }
+
+ // Check for enum and use first value
+ if (schema.enum && schema.enum.length > 0) {
+ return schema.enum[0];
+ }
+
+ // Handle different types
+ if (schema.type === 'object' || schema.properties) {
+ return buildEmptyJsonBody(schema, visited);
+ }
+
+ if (schema.type === 'array') {
+ // Check for array-level example
+ if (schema.example !== undefined) {
+ return schema.example;
+ }
+
+ if (schema.items) {
+ if (schema.items.type === 'object' || schema.items.properties) {
+ return [buildEmptyJsonBody(schema.items, visited)];
+ }
+ // For primitive arrays, get example from items
+ if (schema.items.example !== undefined) {
+ return Array.isArray(schema.items.example) ? schema.items.example : [schema.items.example];
+ }
+ // Return array with a single default primitive value
+ const itemDefault = getDefaultValueForSchema(schema.items, visited);
+ if (itemDefault !== '' && itemDefault !== 0 && itemDefault !== false) {
+ return [itemDefault];
+ }
+ }
+ return [];
+ }
+
+ if (schema.type === 'integer' || schema.type === 'number') {
+ return 0;
+ }
+
+ if (schema.type === 'boolean') {
+ return false;
+ }
+
+ // Default for strings and other types
+ return '';
+};
+
+/**
+ * Builds XML string from OpenAPI schema
+ * @param {Object} bodySchema - The OpenAPI schema object
+ * @returns {string} - XML string
+ */
+const buildXmlBody = (bodySchema) => {
+ if (!bodySchema) return '';
+
+ // String example = raw XML, return as-is
+ if (typeof bodySchema.example === 'string') {
+ return bodySchema.example;
+ }
+
+ const exampleValues = typeof bodySchema.example === 'object' ? bodySchema.example : null;
+
+ if (!bodySchema.properties && !exampleValues) return '';
+
+ const rootName = bodySchema.xml?.name || 'root';
+
+ // Build a single XML element
+ const buildElement = (name, prop = {}, value, indent = ' ') => {
+ const xmlName = prop.xml?.name || name;
+
+ if (prop.xml?.attribute) return null;
+
+ // Nested object - recurse into children
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
+ const children = Object.entries(value)
+ .map(([k, v]) => buildElement(k, prop.properties?.[k] || {}, v, indent + ' '))
+ .filter(Boolean);
+ return `${indent}<${xmlName}>${children.length ? '\n' + children.join('\n') + '\n' + indent : ''}${xmlName}>`;
+ }
+
+ // Object schema without value - build empty structure from schema
+ if (prop.type === 'object' || prop.properties) {
+ const children = Object.entries(prop.properties || {})
+ .map(([k, p]) => buildElement(k, p, undefined, indent + ' '))
+ .filter(Boolean);
+ return `${indent}<${xmlName}>${children.length ? '\n' + children.join('\n') + '\n' + indent : ''}${xmlName}>`;
+ }
+
+ // Primitive value
+ const content = value != null ? String(value) : '';
+ return `${indent}<${xmlName}>${content}${xmlName}>`;
+ };
+
+ // Collect attributes
+ const attributes = Object.entries(bodySchema.properties || {})
+ .filter(([, p]) => p.xml?.attribute)
+ .map(([name, p]) => `${p.xml?.name || name}="${exampleValues?.[name] ?? ''}"`);
+
+ // Build child elements
+ const entries = bodySchema.properties
+ ? Object.entries(bodySchema.properties).map(([k, p]) => [k, p, exampleValues?.[k]])
+ : Object.entries(exampleValues || {}).map(([k, v]) => [k, {}, v]);
+
+ const children = entries
+ .map(([name, prop, value]) => buildElement(name, prop, value))
+ .filter(Boolean);
+
+ const attrStr = attributes.length ? ' ' + attributes.join(' ') : '';
+ const childrenStr = children.length ? '\n' + children.join('\n') + '\n' : '';
+
+ return `\n<${rootName}${attrStr}>${childrenStr}${rootName}>`;
+};
+
const buildEmptyJsonBody = (bodySchema, visited = new Map()) => {
// Check for circular references
if (visited.has(bodySchema)) {
@@ -128,25 +252,101 @@ const buildEmptyJsonBody = (bodySchema, visited = new Map()) => {
let _jsonBody = {};
each(bodySchema.properties || {}, (prop, name) => {
- if (prop.type === 'object' || prop.properties) {
- _jsonBody[name] = buildEmptyJsonBody(prop, visited);
- } else if (prop.type === 'array') {
- if (prop.items && (prop.items.type === 'object' || prop.items.properties)) {
- _jsonBody[name] = [buildEmptyJsonBody(prop.items, visited)];
- } else {
- _jsonBody[name] = [];
- }
- } else if (prop.type === 'integer' || prop.type === 'number') {
- _jsonBody[name] = 0;
- } else if (prop.type === 'boolean') {
- _jsonBody[name] = false;
- } else {
- _jsonBody[name] = '';
- }
+ _jsonBody[name] = getDefaultValueForSchema(prop, visited);
});
return _jsonBody;
};
+/**
+ * Body type handlers for different content types
+ * Each handler has:
+ * - match: function to test if this handler should process the mime type
+ * - mode: the Bruno body mode to set
+ * - handle: function to populate the body content
+ */
+const BODY_TYPE_HANDLERS = [
+ {
+ match: (mimeType) => CONTENT_TYPE_PATTERNS.JSON.test(mimeType),
+ mode: 'json',
+ handle: (body, bodySchema) => {
+ if (bodySchema) {
+ if (bodySchema.example !== undefined) {
+ body.json = JSON.stringify(bodySchema.example, null, 2);
+ } else if (bodySchema.type === 'array') {
+ body.json = JSON.stringify(bodySchema.items ? [buildEmptyJsonBody(bodySchema.items)] : [], null, 2);
+ } else {
+ body.json = JSON.stringify(buildEmptyJsonBody(bodySchema), null, 2);
+ }
+ }
+ }
+ },
+ {
+ match: (mimeType) => mimeType === 'application/x-www-form-urlencoded',
+ mode: 'formUrlEncoded',
+ handle: (body, bodySchema) => {
+ if (!bodySchema) return;
+ const fields = bodySchema.example || bodySchema.properties || {};
+ const isExample = !!bodySchema.example;
+
+ each(fields, (prop, name) => {
+ const value = isExample ? prop : (prop.example ?? prop.default ?? '');
+ body.formUrlEncoded.push({
+ uid: uuid(),
+ name,
+ value: value !== undefined ? String(value) : '',
+ description: prop.description || '',
+ enabled: true
+ });
+ });
+ }
+ },
+ {
+ match: (mimeType) => mimeType === 'multipart/form-data',
+ mode: 'multipartForm',
+ handle: (body, bodySchema) => {
+ if (!bodySchema) return;
+ const fields = bodySchema.example || bodySchema.properties || {};
+ const isExample = !!bodySchema.example;
+
+ each(fields, (prop, name) => {
+ const isFileField = !isExample && prop.type === 'string' && prop.format === 'binary';
+ const value = isFileField ? [] : isExample ? prop : (prop.example ?? prop.default ?? '');
+ body.multipartForm.push({
+ uid: uuid(),
+ type: isFileField ? 'file' : 'text',
+ name,
+ value: isFileField ? [] : (value !== undefined ? String(value) : ''),
+ description: prop.description || '',
+ enabled: true
+ });
+ });
+ }
+ },
+ {
+ match: (mimeType) => CONTENT_TYPE_PATTERNS.XML.test(mimeType) || mimeType === 'application/xml',
+ mode: 'xml',
+ handle: (body, bodySchema) => {
+ body.xml = buildXmlBody(bodySchema);
+ }
+ },
+ {
+ match: (mimeType) => mimeType === 'application/sparql-query',
+ mode: 'sparql',
+ handle: (body, bodySchema) => {
+ // Use example from schema if available
+ body.sparql = bodySchema?.example !== undefined ? String(bodySchema.example) : '';
+ }
+ },
+ {
+ match: (mimeType) => ['text/plain', 'application/octet-stream', '*/*'].includes(mimeType),
+ mode: 'text',
+ handle: (body, bodySchema) => {
+ // Use example from schema if available
+ body.text = bodySchema?.example !== undefined ? String(bodySchema.example) : '';
+ }
+ }
+];
+
/**
* Extracts or generates an example value from an OpenAPI schema
* Handles objects, arrays, primitives, and explicit examples
@@ -191,34 +391,33 @@ const getExampleFromSchema = (schema) => {
};
/**
- * Populates request body in Bruno example from a value
- * Uses pattern matching to handle various MIME type variants
+ * Populates request body in Bruno example from schema
+ * Reuses BODY_TYPE_HANDLERS for consistent body generation
* @param {Object} params - Parameters object
* @param {Object} params.body - The Bruno request body object to populate
- * @param {*} params.requestBodyValue - The request body value to set
+ * @param {Object} params.bodySchema - The OpenAPI schema for the request body
* @param {string} params.contentType - Content type (e.g., 'application/json', 'application/ld+json')
*/
-const populateRequestBody = ({ body, requestBodyValue, contentType }) => {
- if (!requestBodyValue || !contentType || typeof contentType !== 'string') return;
+const populateRequestBody = ({ body, bodySchema, contentType }) => {
+ if (!contentType || typeof contentType !== 'string') return;
// Normalize: lowercase (content types from OpenAPI spec object keys may vary in case)
const normalizedContentType = contentType.toLowerCase();
- if (CONTENT_TYPE_PATTERNS.JSON.test(normalizedContentType)) {
- body.mode = 'json';
- body.json = typeof requestBodyValue === 'object' ? JSON.stringify(requestBodyValue, null, 2) : requestBodyValue;
- } else if (normalizedContentType === 'application/x-www-form-urlencoded') {
- body.mode = 'formUrlEncoded';
- // Handle form data if needed
- } else if (normalizedContentType === 'multipart/form-data') {
- body.mode = 'multipartForm';
- // Handle multipart form data if needed
- } else if (normalizedContentType === 'text/plain') {
- body.mode = 'text';
- body.text = typeof requestBodyValue === 'object' ? JSON.stringify(requestBodyValue) : String(requestBodyValue);
- } else if (CONTENT_TYPE_PATTERNS.XML.test(normalizedContentType)) {
- body.mode = 'xml';
- body.xml = typeof requestBodyValue === 'object' ? JSON.stringify(requestBodyValue) : String(requestBodyValue);
+ // Find matching handler and use it (same as main request body)
+ const handler = BODY_TYPE_HANDLERS.find((h) => h.match(normalizedContentType));
+ if (handler) {
+ body.mode = handler.mode;
+
+ // Clear arrays for form-based content types to avoid duplicates
+ // (since the body was deep-copied from the main request)
+ if (normalizedContentType === 'application/x-www-form-urlencoded') {
+ body.formUrlEncoded = [];
+ } else if (normalizedContentType === 'multipart/form-data') {
+ body.multipartForm = [];
+ }
+
+ handler.handle(body, bodySchema);
}
};
@@ -231,11 +430,22 @@ const populateRequestBody = ({ body, requestBodyValue, contentType }) => {
* @param {string} params.exampleDescription - Description of the example
* @param {string|number} params.statusCode - HTTP status code (for response examples)
* @param {string} params.contentType - Content type (e.g., 'application/json')
- * @param {*} [params.requestBodyValue] - Optional request body value to populate in the example
+ * @param {Object} [params.requestBodySchema] - Optional request body schema to populate in the example
* @param {string} [params.requestBodyContentType] - Optional request body content type
* @returns {Object} Bruno example object
*/
-const createBrunoExample = ({ brunoRequestItem, exampleValue, exampleName, exampleDescription, statusCode, contentType, requestBodyValue = null, requestBodyContentType = null }) => {
+const createBrunoExample = ({ brunoRequestItem, exampleValue, exampleName, exampleDescription, statusCode, contentType, requestBodySchema = null, requestBodyContentType = null }) => {
+ // Deep copy the body to avoid shared references
+ const bodyCopy = {
+ mode: brunoRequestItem.request.body.mode,
+ json: brunoRequestItem.request.body.json,
+ text: brunoRequestItem.request.body.text,
+ xml: brunoRequestItem.request.body.xml,
+ sparql: brunoRequestItem.request.body.sparql || null,
+ formUrlEncoded: [...(brunoRequestItem.request.body.formUrlEncoded || [])],
+ multipartForm: [...(brunoRequestItem.request.body.multipartForm || [])]
+ };
+
const brunoExample = {
uid: uuid(),
itemUid: brunoRequestItem.uid,
@@ -247,7 +457,7 @@ const createBrunoExample = ({ brunoRequestItem, exampleValue, exampleName, examp
method: brunoRequestItem.request.method,
headers: [...brunoRequestItem.request.headers],
params: [...brunoRequestItem.request.params],
- body: { ...brunoRequestItem.request.body }
+ body: bodyCopy
},
response: {
status: String(statusCode),
@@ -268,9 +478,9 @@ const createBrunoExample = ({ brunoRequestItem, exampleValue, exampleName, examp
}
};
- // Populate request body if provided
- if (requestBodyValue !== null) {
- populateRequestBody({ body: brunoExample.request.body, requestBodyValue, contentType: requestBodyContentType });
+ // Populate request body from schema if provided (reuses BODY_TYPE_HANDLERS)
+ if (requestBodySchema !== null) {
+ populateRequestBody({ body: brunoExample.request.body, bodySchema: requestBodySchema, contentType: requestBodyContentType });
}
return brunoExample;
@@ -518,56 +728,19 @@ const transformOpenapiRequestItem = (request, usedNames = new Set()) => {
// TODO: handle allOf/anyOf/oneOf
if (_operationObject.requestBody) {
- let content = get(_operationObject, 'requestBody.content', {});
- let mimeType = Object.keys(content)[0];
- let body = content[mimeType] || {};
- let bodySchema = body.schema;
+ const content = get(_operationObject, 'requestBody.content', {});
+ const mimeType = Object.keys(content)[0];
+ const bodyContent = content[mimeType] || {};
+ const bodySchema = bodyContent.schema;
// Normalize: lowercase (object keys may vary in case)
const normalizedMimeType = typeof mimeType === 'string' ? mimeType.toLowerCase() : '';
- if (CONTENT_TYPE_PATTERNS.JSON.test(normalizedMimeType)) {
- brunoRequestItem.request.body.mode = 'json';
- if (bodySchema && (bodySchema.type === 'object' || bodySchema.properties)) {
- let _jsonBody = buildEmptyJsonBody(bodySchema);
- brunoRequestItem.request.body.json = JSON.stringify(_jsonBody, null, 2);
- }
- if (bodySchema && bodySchema.type === 'array') {
- brunoRequestItem.request.body.json = JSON.stringify([buildEmptyJsonBody(bodySchema.items)], null, 2);
- }
- } else if (normalizedMimeType === 'application/x-www-form-urlencoded') {
- brunoRequestItem.request.body.mode = 'formUrlEncoded';
- if (bodySchema && (bodySchema.type === 'object' || bodySchema.properties)) {
- each(bodySchema.properties || {}, (prop, name) => {
- brunoRequestItem.request.body.formUrlEncoded.push({
- uid: uuid(),
- name: name,
- value: '',
- description: prop.description || '',
- enabled: true
- });
- });
- }
- } else if (normalizedMimeType === 'multipart/form-data') {
- brunoRequestItem.request.body.mode = 'multipartForm';
- if (bodySchema && (bodySchema.type === 'object' || bodySchema.properties)) {
- each(bodySchema.properties || {}, (prop, name) => {
- brunoRequestItem.request.body.multipartForm.push({
- uid: uuid(),
- type: 'text',
- name: name,
- value: '',
- description: prop.description || '',
- enabled: true
- });
- });
- }
- } else if (normalizedMimeType === 'text/plain') {
- brunoRequestItem.request.body.mode = 'text';
- brunoRequestItem.request.body.text = '';
- } else if (CONTENT_TYPE_PATTERNS.XML.test(normalizedMimeType)) {
- brunoRequestItem.request.body.mode = 'xml';
- brunoRequestItem.request.body.xml = '';
+ // Find matching handler for this content type
+ const handler = BODY_TYPE_HANDLERS.find((h) => h.match(normalizedMimeType));
+ if (handler) {
+ brunoRequestItem.request.body.mode = handler.mode;
+ handler.handle(brunoRequestItem.request.body, bodySchema);
}
}
@@ -627,7 +800,7 @@ const transformOpenapiRequestItem = (request, usedNames = new Set()) => {
exampleDescription,
statusCode,
contentType: responseContentType,
- requestBodyValue: matchingRequestBodyExample.value,
+ requestBodySchema: matchingRequestBodyExample.schema,
requestBodyContentType: matchingRequestBodyExample.contentType
}));
} else if (requestBodyExamplesWithKeys.length > 0) {
@@ -642,7 +815,7 @@ const transformOpenapiRequestItem = (request, usedNames = new Set()) => {
exampleDescription: combinedExampleDescription,
statusCode,
contentType: responseContentType,
- requestBodyValue: rbExample.value,
+ requestBodySchema: rbExample.schema,
requestBodyContentType: rbExample.contentType
}));
});
@@ -656,7 +829,7 @@ const transformOpenapiRequestItem = (request, usedNames = new Set()) => {
exampleDescription,
statusCode,
contentType: responseContentType,
- requestBodyValue: rbExample.value,
+ requestBodySchema: rbExample.schema,
requestBodyContentType: rbExample.contentType
}));
} else {
@@ -677,32 +850,32 @@ const transformOpenapiRequestItem = (request, usedNames = new Set()) => {
if (content.examples) {
// Multiple request body examples
Object.entries(content.examples).forEach(([exampleKey, example]) => {
+ const exampleValue = example.value !== undefined ? example.value : example;
requestBodyExamples.push({
key: exampleKey,
- value: example.value !== undefined ? example.value : example,
+ schema: { example: exampleValue }, // Wrap in schema format for BODY_TYPE_HANDLERS
summary: example.summary,
description: example.description,
contentType: contentType
});
});
} else if (content.example !== undefined) {
- // Single request body example - convert to unified structure
+ // Single request body example - wrap in schema-like object
requestBodyExamples.push({
- key: null, // No key for single example
- value: content.example,
+ key: null,
+ schema: { example: content.example }, // Wrap in schema format for BODY_TYPE_HANDLERS
summary: null,
description: null,
contentType: contentType
});
} else if (content.schema) {
- // Schema-based request body - convert to unified structure
+ // Schema-based request body - pass schema directly
requestBodyExamples.push({
- key: null, // No key for schema
- value: getExampleFromSchema(content.schema),
+ key: null,
+ schema: content.schema,
summary: null,
description: null,
- contentType: contentType,
- isSchema: true
+ contentType: contentType
});
}
});
diff --git a/packages/bruno-converters/tests/openapi/openapi-to-bruno/openapi-body.spec.js b/packages/bruno-converters/tests/openapi/openapi-to-bruno/openapi-body.spec.js
index 670b51756..24a319612 100644
--- a/packages/bruno-converters/tests/openapi/openapi-to-bruno/openapi-body.spec.js
+++ b/packages/bruno-converters/tests/openapi/openapi-to-bruno/openapi-body.spec.js
@@ -232,3 +232,680 @@ paths:
expect(bodyJson.active).toBe(false);
});
});
+
+describe('openapi requestBody content types', () => {
+ it('should handle raw body with */* content type as text', () => {
+ const openApiSpec = `
+openapi: "3.0.0"
+info:
+ version: "1.0.0"
+ title: "Raw Body Test"
+servers:
+ - url: "https://api.example.com"
+paths:
+ /raw:
+ post:
+ summary: "Raw body endpoint"
+ operationId: "postRaw"
+ requestBody:
+ required: true
+ content:
+ "*/*":
+ schema:
+ type: string
+ responses:
+ '200':
+ description: "Success"
+`;
+
+ const result = openApiToBruno(openApiSpec);
+
+ expect(result.items.length).toBe(1);
+ const request = result.items[0];
+
+ expect(request.request.body.mode).toBe('text');
+ expect(request.request.body.text).toBe('');
+ });
+
+ it('should handle binary body with application/octet-stream as text', () => {
+ const openApiSpec = `
+openapi: "3.0.0"
+info:
+ version: "1.0.0"
+ title: "Binary Body Test"
+servers:
+ - url: "https://api.example.com"
+paths:
+ /binary:
+ post:
+ summary: "Binary body endpoint"
+ operationId: "postBinary"
+ requestBody:
+ required: true
+ content:
+ application/octet-stream:
+ schema:
+ type: string
+ format: binary
+ responses:
+ '200':
+ description: "Success"
+`;
+
+ const result = openApiToBruno(openApiSpec);
+
+ expect(result.items.length).toBe(1);
+ const request = result.items[0];
+
+ expect(request.request.body.mode).toBe('text');
+ expect(request.request.body.text).toBe('');
+ });
+
+ it('should handle XML body with application/xml content type', () => {
+ const openApiSpec = `
+openapi: "3.0.0"
+info:
+ version: "1.0.0"
+ title: "XML Body Test"
+servers:
+ - url: "https://api.example.com"
+paths:
+ /xml:
+ post:
+ summary: "XML body endpoint"
+ operationId: "postXml"
+ requestBody:
+ required: true
+ content:
+ application/xml:
+ schema:
+ type: object
+ properties:
+ name:
+ type: string
+ responses:
+ '200':
+ description: "Success"
+`;
+
+ const result = openApiToBruno(openApiSpec);
+
+ expect(result.items.length).toBe(1);
+ const request = result.items[0];
+
+ expect(request.request.body.mode).toBe('xml');
+ expect(request.request.body.xml).toContain('');
+ expect(request.request.body.xml).toContain('');
+ });
+
+ it('should handle XML body with text/xml content type', () => {
+ const openApiSpec = `
+openapi: "3.0.0"
+info:
+ version: "1.0.0"
+ title: "Text XML Body Test"
+servers:
+ - url: "https://api.example.com"
+paths:
+ /xml:
+ post:
+ summary: "XML body endpoint"
+ operationId: "postXml"
+ requestBody:
+ required: true
+ content:
+ text/xml:
+ schema:
+ type: object
+ responses:
+ '200':
+ description: "Success"
+`;
+
+ const result = openApiToBruno(openApiSpec);
+
+ expect(result.items.length).toBe(1);
+ const request = result.items[0];
+
+ expect(request.request.body.mode).toBe('xml');
+ });
+
+ it('should handle SPARQL query with application/sparql-query content type', () => {
+ const openApiSpec = `
+openapi: "3.0.0"
+info:
+ version: "1.0.0"
+ title: "SPARQL Query Test"
+servers:
+ - url: "https://api.example.com"
+paths:
+ /sparql:
+ post:
+ summary: "SPARQL query endpoint"
+ operationId: "postSparql"
+ requestBody:
+ required: true
+ content:
+ application/sparql-query:
+ schema:
+ type: string
+ responses:
+ '200':
+ description: "Success"
+`;
+
+ const result = openApiToBruno(openApiSpec);
+
+ expect(result.items.length).toBe(1);
+ const request = result.items[0];
+
+ expect(request.request.body.mode).toBe('sparql');
+ expect(request.request.body.sparql).toBe('');
+ });
+
+ it('should handle text/plain content type', () => {
+ const openApiSpec = `
+openapi: "3.0.0"
+info:
+ version: "1.0.0"
+ title: "Text Plain Test"
+servers:
+ - url: "https://api.example.com"
+paths:
+ /text:
+ post:
+ summary: "Text body endpoint"
+ operationId: "postText"
+ requestBody:
+ required: true
+ content:
+ text/plain:
+ schema:
+ type: string
+ responses:
+ '200':
+ description: "Success"
+`;
+
+ const result = openApiToBruno(openApiSpec);
+
+ expect(result.items.length).toBe(1);
+ const request = result.items[0];
+
+ expect(request.request.body.mode).toBe('text');
+ expect(request.request.body.text).toBe('');
+ });
+
+ it('should detect file fields in multipart/form-data based on format: binary', () => {
+ const openApiSpec = `
+openapi: "3.0.0"
+info:
+ version: "1.0.0"
+ title: "Multipart File Detection Test"
+servers:
+ - url: "https://api.example.com"
+paths:
+ /upload:
+ post:
+ summary: "Upload with file and text fields"
+ operationId: "uploadFile"
+ requestBody:
+ required: true
+ content:
+ multipart/form-data:
+ schema:
+ type: object
+ properties:
+ file:
+ type: string
+ format: binary
+ description: "The file to upload"
+ description:
+ type: string
+ description: "File description"
+ userId:
+ type: integer
+ description: "User ID"
+ responses:
+ '200':
+ description: "Success"
+`;
+
+ const result = openApiToBruno(openApiSpec);
+
+ expect(result.items.length).toBe(1);
+ const request = result.items[0];
+
+ expect(request.request.body.mode).toBe('multipartForm');
+ expect(request.request.body.multipartForm.length).toBe(3);
+
+ // Find the file field
+ const fileField = request.request.body.multipartForm.find((f) => f.name === 'file');
+ expect(fileField).toBeDefined();
+ expect(fileField.type).toBe('file');
+ expect(fileField.value).toEqual([]); // File fields should have array value
+
+ // Find the text fields
+ const descField = request.request.body.multipartForm.find((f) => f.name === 'description');
+ expect(descField).toBeDefined();
+ expect(descField.type).toBe('text');
+ expect(descField.value).toBe(''); // Text fields should have string value
+
+ const userIdField = request.request.body.multipartForm.find((f) => f.name === 'userId');
+ expect(userIdField).toBeDefined();
+ expect(userIdField.type).toBe('text');
+ expect(userIdField.value).toBe('');
+ });
+
+ it('should handle JSON variants like application/ld+json', () => {
+ const openApiSpec = `
+openapi: "3.0.0"
+info:
+ version: "1.0.0"
+ title: "JSON-LD Test"
+servers:
+ - url: "https://api.example.com"
+paths:
+ /jsonld:
+ post:
+ summary: "JSON-LD endpoint"
+ operationId: "postJsonLd"
+ requestBody:
+ required: true
+ content:
+ application/ld+json:
+ schema:
+ type: object
+ properties:
+ "@context":
+ type: string
+ name:
+ type: string
+ responses:
+ '200':
+ description: "Success"
+`;
+
+ const result = openApiToBruno(openApiSpec);
+
+ expect(result.items.length).toBe(1);
+ const request = result.items[0];
+
+ expect(request.request.body.mode).toBe('json');
+ expect(request.request.body.json).not.toBeNull();
+ });
+
+ it('should handle XML variants like application/atom+xml', () => {
+ const openApiSpec = `
+openapi: "3.0.0"
+info:
+ version: "1.0.0"
+ title: "Atom XML Test"
+servers:
+ - url: "https://api.example.com"
+paths:
+ /feed:
+ post:
+ summary: "Atom feed endpoint"
+ operationId: "postFeed"
+ requestBody:
+ required: true
+ content:
+ application/atom+xml:
+ schema:
+ type: object
+ responses:
+ '200':
+ description: "Success"
+`;
+
+ const result = openApiToBruno(openApiSpec);
+
+ expect(result.items.length).toBe(1);
+ const request = result.items[0];
+
+ expect(request.request.body.mode).toBe('xml');
+ });
+});
+
+describe('openapi example request body - should match main request body handling', () => {
+ const bodyTypesOpenApiSpec = `
+openapi: 3.1.0
+info:
+ title: Body Types Demo API
+ version: 1.0.0
+servers:
+ - url: https://api.example.com
+paths:
+ /raw-body:
+ post:
+ summary: Raw body
+ requestBody:
+ content:
+ "*/*":
+ schema:
+ type: string
+ responses:
+ "200":
+ description: Success
+ /json-body:
+ post:
+ summary: JSON body
+ requestBody:
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ name:
+ type: string
+ example: "John"
+ responses:
+ "200":
+ description: Success
+ content:
+ application/json:
+ schema:
+ type: object
+ /xml-body:
+ post:
+ summary: XML body
+ requestBody:
+ content:
+ application/xml:
+ schema:
+ type: object
+ xml:
+ name: Root
+ properties:
+ name:
+ type: string
+ responses:
+ "200":
+ description: Success
+ /multipart-body:
+ post:
+ summary: Multipart body
+ requestBody:
+ content:
+ multipart/form-data:
+ schema:
+ type: object
+ properties:
+ file:
+ type: string
+ format: binary
+ desc:
+ type: string
+ responses:
+ "200":
+ description: Success
+ /form-body:
+ post:
+ summary: Form body
+ requestBody:
+ content:
+ application/x-www-form-urlencoded:
+ schema:
+ type: object
+ properties:
+ query:
+ type: string
+ page:
+ type: integer
+ default: 1
+ responses:
+ "200":
+ description: Success
+ /sparql-body:
+ post:
+ summary: SPARQL body
+ requestBody:
+ content:
+ application/sparql-query:
+ schema:
+ type: string
+ example: "SELECT * WHERE { ?s ?p ?o }"
+ responses:
+ "200":
+ description: Success
+`;
+
+ it('should match body mode between request and example for all content types', () => {
+ const result = openApiToBruno(bodyTypesOpenApiSpec);
+ const tests = [
+ { name: 'Raw body', mode: 'text' },
+ { name: 'JSON body', mode: 'json' },
+ { name: 'XML body', mode: 'xml' },
+ { name: 'Multipart body', mode: 'multipartForm' },
+ { name: 'Form body', mode: 'formUrlEncoded' },
+ { name: 'SPARQL body', mode: 'sparql' }
+ ];
+
+ tests.forEach(({ name, mode }) => {
+ const request = result.items.find((item) => item.name === name);
+ expect(request.request.body.mode).toBe(mode);
+ expect(request.examples[0].request.body.mode).toBe(mode);
+ });
+ });
+
+ it('should generate proper XML in example (not JSON)', () => {
+ const result = openApiToBruno(bodyTypesOpenApiSpec);
+ const xmlRequest = result.items.find((item) => item.name === 'XML body');
+
+ expect(xmlRequest.examples[0].request.body.xml).toContain(' {
+ const result = openApiToBruno(bodyTypesOpenApiSpec);
+ const multipartRequest = result.items.find((item) => item.name === 'Multipart body');
+ const fileField = multipartRequest.examples[0].request.body.multipartForm.find((f) => f.name === 'file');
+ expect(fileField.type).toBe('file');
+ });
+
+ it('should use default values in form example', () => {
+ const result = openApiToBruno(bodyTypesOpenApiSpec);
+ const formRequest = result.items.find((item) => item.name === 'Form body');
+ const pageField = formRequest.examples[0].request.body.formUrlEncoded.find((f) => f.name === 'page');
+ expect(pageField.value).toBe('1');
+ });
+
+ it('should use example and enum values from schema in request body', () => {
+ const openApiSpec = `
+openapi: "3.0.0"
+info:
+ version: "1.0.0"
+ title: "Test"
+servers:
+ - url: "https://api.example.com"
+paths:
+ /test:
+ post:
+ summary: "Test"
+ requestBody:
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ name:
+ type: string
+ example: "John"
+ status:
+ type: string
+ enum: [active, inactive]
+ responses:
+ "200":
+ description: "OK"
+`;
+ const result = openApiToBruno(openApiSpec);
+ const bodyJson = JSON.parse(result.items[0].request.body.json);
+ expect(bodyJson.name).toBe('John');
+ expect(bodyJson.status).toBe('active');
+ });
+
+ it('should use schema example values in main request body (not just examples)', () => {
+ const openApiSpec = `
+openapi: "3.0.0"
+info:
+ version: "1.0.0"
+ title: "Schema Example Values Test"
+servers:
+ - url: "https://api.example.com"
+paths:
+ /users:
+ post:
+ summary: "Create user"
+ requestBody:
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ id:
+ type: integer
+ example: 9007199254740991
+ name:
+ type: string
+ example: "example string"
+ email:
+ type: string
+ format: email
+ example: "user@example.com"
+ status:
+ type: string
+ enum: [pending, active, inactive]
+ createdDate:
+ type: string
+ format: date
+ example: "2025-01-01"
+ score:
+ type: number
+ example: 3.1415926535
+ responses:
+ "201":
+ description: "Created"
+`;
+ const result = openApiToBruno(openApiSpec);
+ const request = result.items[0];
+
+ // Main request body should use example values from schema
+ const bodyJson = JSON.parse(request.request.body.json);
+ expect(bodyJson.id).toBe(9007199254740991);
+ expect(bodyJson.name).toBe('example string');
+ expect(bodyJson.email).toBe('user@example.com');
+ expect(bodyJson.status).toBe('pending'); // first enum value
+ expect(bodyJson.createdDate).toBe('2025-01-01');
+ expect(bodyJson.score).toBe(3.1415926535);
+ });
+
+ it('should handle XML body with object example (not produce [object Object])', () => {
+ const openApiSpec = `
+openapi: "3.0.0"
+info:
+ version: "1.0.0"
+ title: "XML Object Example Test"
+servers:
+ - url: "https://api.example.com"
+paths:
+ /user:
+ post:
+ summary: "Create user"
+ operationId: "createUser"
+ requestBody:
+ required: true
+ content:
+ application/xml:
+ schema:
+ type: object
+ example:
+ name: "John"
+ age: 30
+ properties:
+ name:
+ type: string
+ age:
+ type: integer
+ responses:
+ "201":
+ description: "Created"
+`;
+ const result = openApiToBruno(openApiSpec);
+ const request = result.items[0];
+
+ expect(request.request.body.mode).toBe('xml');
+ // Should NOT contain [object Object]
+ expect(request.request.body.xml).not.toContain('[object Object]');
+ // Should contain the example values
+ expect(request.request.body.xml).toContain('John');
+ expect(request.request.body.xml).toContain('30');
+ });
+
+ it('should handle XML body with string example (raw XML)', () => {
+ const openApiSpec = `
+openapi: "3.0.0"
+info:
+ version: "1.0.0"
+ title: "XML String Example Test"
+servers:
+ - url: "https://api.example.com"
+paths:
+ /user:
+ post:
+ summary: "Create user"
+ operationId: "createUser"
+ requestBody:
+ required: true
+ content:
+ application/xml:
+ schema:
+ type: string
+ example: 'John'
+ responses:
+ "201":
+ description: "Created"
+`;
+ const result = openApiToBruno(openApiSpec);
+ const request = result.items[0];
+
+ expect(request.request.body.mode).toBe('xml');
+ // Should preserve the raw XML string
+ expect(request.request.body.xml).toBe('John');
+ });
+
+ it('should not crash when array schema has no items defined', () => {
+ const openApiSpec = `
+openapi: "3.0.0"
+info:
+ version: "1.0.0"
+ title: "Array Without Items Test"
+servers:
+ - url: "https://api.example.com"
+paths:
+ /items:
+ post:
+ summary: "Create items"
+ operationId: "createItems"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: array
+ responses:
+ "201":
+ description: "Created"
+`;
+ // Should not throw an error
+ expect(() => openApiToBruno(openApiSpec)).not.toThrow();
+
+ const result = openApiToBruno(openApiSpec);
+ const request = result.items[0];
+
+ expect(request.request.body.mode).toBe('json');
+ // Should produce an empty array
+ expect(request.request.body.json).toBe('[]');
+ });
+});
diff --git a/packages/bruno-lang/v2/src/example/request/bruToJson.js b/packages/bruno-lang/v2/src/example/request/bruToJson.js
index 9340cf651..162e5f98d 100644
--- a/packages/bruno-lang/v2/src/example/request/bruToJson.js
+++ b/packages/bruno-lang/v2/src/example/request/bruToJson.js
@@ -102,16 +102,12 @@ const astRequestAttribute = {
},
requestmode(_1, _2, _3, _4, value) {
const modeValue = value.sourceString ? value.sourceString.trim() : '';
- // If mode is "none", return a body with mode: "none"
- if (modeValue === 'none') {
- return {
- body: {
- mode: 'none'
- }
- };
- }
- // For other modes, return nothing since the body parser will handle it
- return {};
+ // Return body with the mode set
+ return {
+ body: {
+ mode: modeValue || 'none'
+ }
+ };
},
requestparamspath(_1, _2, _3, _4, dictionary) {
return {