orval icon indicating copy to clipboard operation
orval copied to clipboard

Mock: missing required fields in generated mock when using recursive schemas with oneof

Open daniatic opened this issue 7 months ago • 3 comments

Thank you very much for your excellent work on this repository. So far, we have had great success with Orval. Unfortunately, today we identified a potential bug, and we would be grateful if you could investigate it.

openapi spec:

{
  "openapi": "3.0.4",
  "info": { "title": "test", "version": "0.2" },

  "paths": {
    "/derived1": {
      "get": {
        "responses": {
          "200": {
            "description": "test derived1",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Derived1" }
              }
            }
          }
        }
      }
    },
    "/derived2": {
      "get": {
        "responses": {
          "200": {
            "description": "test Derived2",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Derived2" }
              }
            }
          }
        }
      }
    }
  },

  "components": {
    "schemas": {
      "Base": {
        "type": "object",
        "required": ["$type", "BaseProp"],
        "properties": {
          "discriminator": { "type": "string" },
          "BaseProp": { "type": "string", "nullable": true },
          "Parent": {
            "oneOf": [
              {"$ref": "#/components/schemas/Derived1"},
              {"$ref": "#/components/schemas/Derived2"}
            ],
            "nullable": true
          }
        },
        "discriminator": {
          "propertyName": "$type",
          "mapping": {
            "Derived1": "#/components/schemas/Derived1",
            "Derived2": "#/components/schemas/Derived2"
          }
        },
        "additionalProperties": false
      },

      "Derived1": {
        "allOf": [
          { "$ref": "#/components/schemas/Base" },
          {
            "type": "object",
            "required": ["Derived1Prop"],
            "properties": { "Derived1Prop": { "type": "integer", "format": "int32" } },
            "additionalProperties": false
          }
        ]
      },

      "Derived2": {
        "allOf": [
          { "$ref": "#/components/schemas/Base" },
          {
            "type": "object",
            "required": ["Derived2Prop"],
            "properties": { "Derived2Prop": { "type": "string" } },
            "additionalProperties": false
          }
        ]
      }
    }
  }
}

orval.config.ts:

const config = {
	test: {
		output: {
			clean: true,
			workspace: 'generated',
			schemas: './schemas',
			target: 'output.ts',
			mock: true,
			override: {
				useTypeOverInterface: true,
			},
			prettier: true,
		},
		input: 'spec.json'
	}
}
export default config;

excert output.ts:

...
export const getGetDerived1ResponseDerived1Mock = (
  overrideResponse: Partial<Derived1> = {},
): Derived1 => ({
  ...{
    ...{
      ...{ Derived1Prop: faker.number.int({ min: undefined, max: undefined }) },
    },
    $type: faker.helpers.arrayElement(["Derived1"] as const),
  },
  ...overrideResponse,
});

export const getGetDerived1ResponseDerived2Mock = (
  overrideResponse: Partial<Derived2> = {},
): Derived2 => ({
  ...{
    ...{ ...{ Derived2Prop: faker.string.alpha(20) } },
    $type: faker.helpers.arrayElement(["Derived2"] as const),
  },
  ...overrideResponse,
});
...
export const getGetDerived2ResponseDerived1Mock = (
  overrideResponse: Partial<Derived1> = {},
): Derived1 => ({
  ...{
    ...{
      ...{ Derived1Prop: faker.number.int({ min: undefined, max: undefined }) },
    },
    $type: faker.helpers.arrayElement(["Derived1"] as const),
  },
  ...overrideResponse,
});

export const getGetDerived2ResponseDerived2Mock = (
  overrideResponse: Partial<Derived2> = {},
): Derived2 => ({
  ...{
    ...{ ...{ Derived2Prop: faker.string.alpha(20) } },
    $type: faker.helpers.arrayElement(["Derived2"] as const),
  },
  ...overrideResponse,
});
...

In the generated output.ts the above shown functions the generated type instances are missing the required BaseProp property. Respecitve ts error:

TS2322: Type '{ discriminator?: string; BaseProp?: string | null; Parent?: BaseParent; Derived1Prop: any; $type: any; }' is not assignable to type 'Derived1'.
  Type '{ discriminator?: string; BaseProp?: string | null; Parent?: BaseParent; Derived1Prop: any; $type: any; }' is not assignable to type 'Omit<Base & Derived1AllOf, "$type">'.
    Property 'BaseProp' is optional in type '{ discriminator?: string; BaseProp?: string; Parent?: BaseParent; Derived1Prop: any; $type: any; }' but required in type 'Omit<Base & Derived1AllOf, "$type">'.

Orval Version being used: 7.9

The full output can be investigated in this and regenerated by running npx orval.

daniatic avatar May 07 '25 19:05 daniatic

@melloware were you already able to look into this issue? I tested it in 7.10 but the issue still remains. Can I assist you with anything in order to fix the issue?

daniatic avatar Jul 04 '25 10:07 daniatic

@daniatic i don;t really use the Mock features of Orval so I am not that familiar with it maybe one of the other devs will pick it up.

melloware avatar Jul 04 '25 14:07 melloware

@melloware thanks for the feedback.

daniatic avatar Jul 04 '25 14:07 daniatic