Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions libV2/schemaUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2145,10 +2145,17 @@ let QUERYPARAM = 'query',

createProperties = (param) => {
const { schema } = param;
return {

// Handle union types (OpenAPI 3.1.x supports arrays of types like ["string", "integer"])
// Pick the first type if it's a union of types, but only if array is not empty
const resolvedType = Array.isArray(schema.type) ?
(schema.type.length > 0 ? schema.type[0] : undefined) :
schema.type;

const properties = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason to change this ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, had changed it when trying something out. Let me revert

description: schema.description,
title: schema.title,
type: schema.type,
type: resolvedType,
format: schema.format,
default: schema.default,
required: param.required,
Expand All @@ -2161,6 +2168,8 @@ let QUERYPARAM = 'query',
pattern: schema.pattern,
example: schema.example
};

return properties;
},

resolveQueryParamsForPostmanRequest = (context, operationItem, method) => {
Expand Down Expand Up @@ -2490,8 +2499,14 @@ let QUERYPARAM = 'query',
return;
}

// Handle union types (OpenAPI 3.1.x supports arrays of types like ["string", "integer"])
// Pick the first type if it's a union of types, but only if array is not empty
const resolvedType = Array.isArray(schema.type) ?
(schema.type.length > 0 ? schema.type[0] : undefined) :
schema.type;

properties = {
type: schema.type,
type: resolvedType,
description: schema.description,
title: schema.title,
format: schema.format,
Expand All @@ -2506,6 +2521,7 @@ let QUERYPARAM = 'query',
pattern: schema.pattern,
example: schema.example
};

headerTypeInfo = { keyName: headerData.name, properties };
headerTypes.push(headerTypeInfo);
}
Expand Down
159 changes: 158 additions & 1 deletion test/unit/convertV2WithTypes.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1162,5 +1162,162 @@ describe('convertV2WithTypes', function() {
done();
});
});
});

it('should pick up the first type for handling union types in parameters and response headers', function(done) {
const openApiWithUnionTypes = {
openapi: '3.1.0',
info: {
title: 'Union Types Test API',
version: '1.0.0'
},
paths: {
'/users/{id}': {
get: {
parameters: [
{
name: 'id',
in: 'path',
required: true,
schema: {
type: ['string', 'integer'], // Union type - should pick 'string' (first)
description: 'User ID as string or integer'
}
},
{
name: 'format',
in: 'query',
schema: {
type: ['integer', 'string'], // Union type - should pick 'integer' (first)
description: 'Format preference'
}
},
{
name: 'auth',
in: 'header',
schema: {
type: ['string', 'null'], // Union type - should pick 'string' (first)
description: 'Authorization header'
}
},
{
name: 'singleTypeParam',
in: 'query',
schema: {
type: 'string', // Single type - should remain 'string'
description: 'Simple string parameter'
}
},
{
name: 'emptyUnionParam',
in: 'query',
schema: {
type: [], // Empty union type - should not have type property
description: 'Empty union type parameter'
}
}
],
responses: {
'200': {
description: 'Success response with union type headers',
headers: {
'x-rate-limit': {
description: 'Rate limit counter',
schema: {
type: ['integer', 'string'] // Union type - should pick 'integer' (first)
}
},
'x-request-id': {
description: 'Request identifier',
schema: {
type: ['string', 'number'] // Union type - should pick 'string' (first)
}
},
'x-single-type-header': {
description: 'Simple header',
schema: {
type: 'boolean' // Single type - should remain 'boolean'
}
}
},
content: {
'application/json': {
schema: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' }
}
}
}
}
}
}
}
}
}
};

Converter.convertV2WithTypes({ type: 'json', data: openApiWithUnionTypes }, {}, (err, conversionResult) => {
expect(err).to.be.null;
expect(conversionResult.extractedTypes).to.be.an('object').that.is.not.empty;

const extractedTypes = conversionResult.extractedTypes;
const requestTypes = extractedTypes['get/users/{id}'];
expect(requestTypes).to.be.an('object');

// Check path parameters
const pathParams = JSON.parse(requestTypes.request.pathParam);
expect(pathParams).to.be.an('array').with.length(1);

const idParam = pathParams.find((p) => { return p.keyName === 'id'; });
expect(idParam).to.be.an('object');
expect(idParam.properties.type).to.equal('string'); // First type from ['string', 'integer']

// Check query parameters
const queryParams = JSON.parse(requestTypes.request.queryParam);
expect(queryParams).to.be.an('array').with.length(3);

const formatParam = queryParams.find((p) => { return p.keyName === 'format'; });
expect(formatParam).to.be.an('object');
expect(formatParam.properties.type).to.equal('integer'); // First type from ['integer', 'string']

const singleTypeParam = queryParams.find((p) => { return p.keyName === 'singleTypeParam'; });
expect(singleTypeParam).to.be.an('object');
expect(singleTypeParam.properties.type).to.equal('string'); // Single type should remain unchanged

const emptyUnionParam = queryParams.find((p) => { return p.keyName === 'emptyUnionParam'; });
expect(emptyUnionParam).to.be.an('object');
expect(emptyUnionParam.properties).to.not.have.property('type'); // Empty union should not have type property

// Check request header parameters
const headerParams = JSON.parse(requestTypes.request.headers);
expect(headerParams).to.be.an('array').with.length(1);

const authParam = headerParams.find((p) => { return p.keyName === 'auth'; });
expect(authParam).to.be.an('object');
expect(authParam.properties.type).to.equal('string'); // First type from ['string', 'null']

// Check response headers - use the first available response status for tests
const responseStatuses = Object.keys(requestTypes.response);
expect(responseStatuses).to.be.an('array').that.is.not.empty;

const firstResponseStatus = responseStatuses[0];
const responseHeaders = JSON.parse(requestTypes.response[firstResponseStatus].headers);
expect(responseHeaders).to.be.an('array').with.length(3);

const rateLimitHeader = responseHeaders.find((h) => { return h.keyName === 'x-rate-limit'; });
expect(rateLimitHeader).to.be.an('object');
expect(rateLimitHeader.properties.type).to.equal('integer'); // First type from ['integer', 'string']

const requestIdHeader = responseHeaders.find((h) => { return h.keyName === 'x-request-id'; });
expect(requestIdHeader).to.be.an('object');
expect(requestIdHeader.properties.type).to.equal('string'); // First type from ['string', 'number']

const singleTypeHeader = responseHeaders.find((h) => { return h.keyName === 'x-single-type-header'; });
expect(singleTypeHeader).to.be.an('object');
expect(singleTypeHeader.properties.type).to.equal('boolean'); // Single type should remain unchanged

done();
});
});
});
Loading