diff --git a/packages/mock/src/faker/getters/combine.ts b/packages/mock/src/faker/getters/combine.ts index af175d2e3..724754b03 100644 --- a/packages/mock/src/faker/getters/combine.ts +++ b/packages/mock/src/faker/getters/combine.ts @@ -67,143 +67,74 @@ export const combineSchemasMock = ({ includedProperties.push(...(itemResolvedValue?.includedProperties ?? [])); combineImports.push(...(itemResolvedValue?.imports ?? [])); - const value = (item[separator] ?? []).reduce((acc, val, index, arr) => { - if ( - '$ref' in val && - existingReferencedProperties.includes(pascal(val.$ref.split('/').pop()!)) - ) { - if (arr.length === 1) { - return 'undefined'; - } - - return acc; - } - - // the required fields in this schema need to be considered - // in the sub schema under the allOf key - if (separator === 'allOf' && item.required) { - if (isSchema(val) && val.required) { - val = { ...val, required: [...item.required, ...val.required] }; - } else { - val = { ...val, required: item.required }; - } - } - - const resolvedValue = resolveMockValue({ - schema: { - ...val, - name: item.name, - path: item.path ? item.path : '#', - }, - combine: { - separator, - includedProperties: - separator !== 'oneOf' - ? includedProperties - : itemResolvedValue?.includedProperties ?? [], - }, - mockOptions, - operationId, - tags, - context, - imports, - existingReferencedProperties, - splitMockImplementations, - }); - - combineImports.push(...resolvedValue.imports); - includedProperties.push(...(resolvedValue.includedProperties ?? [])); - - const isLastElement = index === arr.length - 1; - - let currentValue = resolvedValue.value; - - if (itemResolvedValue?.value && separator === 'oneOf') { - const splitValues = resolvedValue.value.split('},{'); - const joined = splitValues.join(`,${itemResolvedValue.value}},{`); - currentValue = `${joined.slice(0, -1)},${itemResolvedValue.value}}`; - } - - if (itemResolvedValue?.value && separator !== 'oneOf' && isLastElement) { - currentValue = `${currentValue ? `${currentValue},` : ''}${itemResolvedValue.value}`; - } - - if ( - resolvedValue.type === undefined && - currentValue && - separator === 'allOf' - ) { - currentValue = `...${currentValue}`; - } - - const isObjectBounds = - !combine || - (['oneOf', 'anyOf'].includes(combine.separator) && separator === 'allOf'); - - if (!index && isObjectBounds) { + const value = (item[separator] ?? []).reduce( + (acc, val, _, arr) => { if ( - resolvedValue.enums || - separator === 'oneOf' || - separator === 'anyOf' || - resolvedValue.type === 'array' + '$ref' in val && + existingReferencedProperties.includes( + pascal(val.$ref.split('/').pop()!), + ) ) { if (arr.length === 1) { - return `faker.helpers.arrayElement([${currentValue}])`; + return 'undefined'; } - return `faker.helpers.arrayElement([${currentValue},`; - } - - if (arr.length === 1) { - if (resolvedValue.type && resolvedValue.type !== 'object') { - return currentValue; - } - return `{${currentValue}}`; - } - if (currentValue) { - return `{${currentValue},`; + return acc; } - return '{'; - } - if (isLastElement) { - if ( - resolvedValue.enums || - separator === 'oneOf' || - separator === 'anyOf' || - resolvedValue.type === 'array' - ) { - return `${acc}${currentValue}${!combine ? '])' : ''}`; - } - - if (currentValue === '{}') { - currentValue = ''; - - if (acc.toString().endsWith(',')) { - acc = acc.toString().slice(0, -1); + // the required fields in this schema need to be considered + // in the sub schema under the allOf key + if (separator === 'allOf' && item.required) { + if (isSchema(val) && val.required) { + val = { ...val, required: [...item.required, ...val.required] }; + } else { + val = { ...val, required: item.required }; } } - return `${acc}${currentValue}${isObjectBounds ? '}' : ''}`; - } - - if (currentValue === '{}') { - currentValue = ''; - - if (acc.toString().endsWith(',')) { - acc = acc.toString().slice(0, -1); + const resolvedValue = resolveMockValue({ + schema: { + ...val, + name: item.name, + path: item.path ? item.path : '#', + }, + combine: { + separator, + includedProperties: + separator !== 'oneOf' + ? includedProperties + : itemResolvedValue?.includedProperties ?? [], + }, + mockOptions, + operationId, + tags, + context, + imports, + existingReferencedProperties, + splitMockImplementations, + }); + + combineImports.push(...resolvedValue.imports); + includedProperties.push(...(resolvedValue.includedProperties ?? [])); + + if (resolvedValue.value === '{}') return acc; + if (resolvedValue.value.startsWith('{')) { + return `${acc}${separator === 'allOf' ? '...' : ''}${resolvedValue.value},`; } - } - - if (!currentValue) { - return acc; - } - - return `${acc}${currentValue},`; - }, ''); + return `${acc}${resolvedValue.value},`; + }, + `${combine ? '...' : ''}${separator === 'allOf' ? '{' : 'faker.helpers.arrayElement(['}`, + ); + let finalValue = + value === 'undefined' + ? value + : `${value}${separator === 'allOf' ? '}' : '])'}`; + if (itemResolvedValue) { + finalValue = `{${finalValue.startsWith('...') ? '' : '...'}${finalValue}, ${itemResolvedValue.value}}`; + } return { - value: value, + value: finalValue, imports: combineImports, name: item.name, includedProperties, diff --git a/packages/mock/src/faker/resolvers/value.ts b/packages/mock/src/faker/resolvers/value.ts index cced8b669..82ca2b9bf 100644 --- a/packages/mock/src/faker/resolvers/value.ts +++ b/packages/mock/src/faker/resolvers/value.ts @@ -141,10 +141,7 @@ export const resolveMockValue = ({ } const args = `${overrideVarName}: ${type} = {}`; - const value = newSchema.oneOf - ? `faker.helpers.arrayElement([${scalar.value}])` - : scalar.value; - const func = `export const ${funcName} = (${args}): ${newSchema.name} => ({...${value}, ...${overrideVarName}});`; + const func = `export const ${funcName} = (${args}): ${newSchema.name} => ({${scalar.value.startsWith('...') ? '' : '...'}${scalar.value}, ...${overrideVarName}});`; splitMockImplementations?.push(func); } diff --git a/tests/configs/default.config.ts b/tests/configs/default.config.ts index d233eda9f..3b634e0ac 100644 --- a/tests/configs/default.config.ts +++ b/tests/configs/default.config.ts @@ -100,6 +100,14 @@ export default defineConfig({ target: '../generated/default/default-status/endpoints.ts', }, }, + 'all-of-one-of': { + input: '../specifications/all-of-one-of.yaml', + output: { + schemas: '../generated/default/all-of-one-of/model', + target: '../generated/default/all-of-one-of/endpoints.ts', + mock: true, + }, + }, 'circular-v2': { input: '../specifications/circular-v2.yaml', output: { diff --git a/tests/specifications/all-of-one-of.yaml b/tests/specifications/all-of-one-of.yaml new file mode 100644 index 000000000..edd4f084a --- /dev/null +++ b/tests/specifications/all-of-one-of.yaml @@ -0,0 +1,38 @@ +openapi: 3.0.1 +info: + version: 1.0.0 + title: any-of-one-of +paths: + /something: + post: + responses: + '200': + description: Something + content: + application/json: + schema: + type: object + properties: + uploads: + type: array + items: + allOf: + - type: object + properties: + file_type: + type: string + - oneOf: + - type: object + properties: + type: + type: string + enum: + - a + - type: object + properties: + type: + type: string + enum: + - b + other: + type: string diff --git a/tests/specifications/all-of.yaml b/tests/specifications/all-of.yaml index 21f7b8247..ca8d20b62 100644 --- a/tests/specifications/all-of.yaml +++ b/tests/specifications/all-of.yaml @@ -65,17 +65,31 @@ components: allOf: - $ref: '#/components/schemas/Pet' - $ref: '#/components/schemas/PetDetail' - Pet: + NestedPet: + allOf: + - $ref: '#/components/schemas/DoublyNestedPet' + - type: object + properties: + nest: + type: integer + DoublyNestedPet: type: object - required: - - id - - name properties: - id: + doubleNest: type: integer - format: int64 - name: - type: string + Pet: + allOf: + - $ref: '#/components/schemas/NestedPet' + - type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string PetDetail: type: object required: