-
-
Notifications
You must be signed in to change notification settings - Fork 528
Bugfix for discriminators using allOf #1578
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
@@ -258,20 +258,12 @@ describe("composition", () => { | |||
"discriminator > oneOf", | |||
{ | |||
given: { | |||
type: "object", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@drwpow I was unsure why there is already an object here for the test of a oneOf discriminator, so I removed it. Maybe the implicit allOf merging here should be in a separate unit test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes this actually should be its own test, you’re right, but it’s important we keep this “wrong” testcase. We still get so many bug reports and code examples of people combining type: "object"
with its own properties + oneOf
(even though in the docs it suggests not doing that). You’re right—it shouldn’t be the ideal case, but we should keep it as a test case so we can just be sure that it generates something (logical collisions are the responsibility of the schema author, but openapi-typescript should still attempt to generate something that reflects what they’ve authored)
I just ran into another problem with multiple discriminator property values assigned to the same base class. Let me work it out as well, but please feel free to discuss the new PR. |
First try using the same discriminator property replacement as I used in my oneOf PR. |
This is great! I have not taken a look yet but will later today. |
I think we should decide on a consistent output of the discriminator patterns. Right now there are two patterns for allOf inheritance compositions: // discriminator property prepended + Omit
Dog: {
petType: "dog";
} & (Omit<components["schemas"]["Pet"], "petType"> & {
bark?: string;
}); // discriminator property appended + Omit
Dog: Omit<components["schemas"]["Pet"], "petType"> & {
bark?: string;
} & {
/**
* @description discriminator enum property added by openapi-typescript
* @enum {string}
*/
petType: "dog" | "poodle";
}; I implemented the second one because it was easier to cover all edge cases and it is more robust. I don't have to fiddle around with omitting and requiring discriminator properties: // discriminator property append + no omit because of type narrowing
export interface components {
schemas: {
baseObject: {
type: components["schemas"]["type"];
};
simpleObject: components["schemas"]["baseObject"] & {
simple?: boolean;
} & {
/**
* @description discriminator enum property added by openapi-typescript
* @enum {string}
*/
type: "simple";
};
complexObject: components["schemas"]["baseObject"] & {
complex?: boolean;
} & {
/**
* @description discriminator enum property added by openapi-typescript
* @enum {string}
*/
type: "complex" | "tooComplex";
};
/** @enum {string} */
type: "simple" | "complex" | "tooComplex";
};
} The existing code for that was harder to understand and already part of the transformation to Typescript, so I decided to go the other route for my oneOf implementation and don't omit the existing petType property (even if I can't replace it). This works because each appended discriminator property is narrowing the full union type. Fun fact: This fails if the appended discriminator is not part of the previously declared union! The property would then be evaluated by Typescript as never in the resulting type. I think this is a positive side-effect because it indicates an invalid schema. |
The last comment was meant as a future opportunity btw. I think the important part of this PR is that it fixes the allOf inheritance cases, so if this looks good to you, we should probably merge it. I will continue to look into it and I still owe you the backport to 6.x, which definitely takes longer than anticipated. A lot of code has changed for version 7 - for the better! |
); | ||
} catch (error) { | ||
console.error( | ||
`✘ [${schemasDoneCount}/${schemaTotalCount}] Failed to update ${name}`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice addition 👍
Sorry for the delay in reviewing! I am happy with this, and this is a fantastic fix! See the comment on the modified testcase of |
Thanks for the good input. I know bad schemas very well and if this gives the users more wiggle room, so be it. :) Please let me know if there is still something I have missed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great, thank you! Sorry for the delay in approving. Agree with your perspective; thanks for clarifying! 🙂 Discriminators just leave so much room open for interpretation, it’s fine if we have some minor breaking changes in 7.x for discriminators because it’s an ambiguous part of the spec (especially for generating deterministic TypeScript code). As long as we’re interpreting it in good faith as best we can, that’s all anyone can ask for. And your fix makes our support even better! Thank you.
Changes
I noticed that the current implementation for discriminators inherited from a base class is not working when the final objects are used in anyOf or oneOf. These combinations will omit the discriminator property from the final object and result in an invalid type:
The OpenAPI specification on discriminators states the following:
This is very confusing in my opinion. My interpretation is as follows: A discriminator from one of the base classes will not be automatically inherited unless it uses allOf. This makes sense looking at the result above, that never results in something useful (I can think of) in my opinion.
The anyOf case is currently not handled, but I can have a look as well. anyOf + discriminator is a rare combination, but I think it can be handled exactly like we handle oneOf.
Bonus: I improved Windows compatibility and error handling for updating the examples.
How to Review
The appropriate unit tests were adapted to test the changes. The DigitalOcean schema shows, that responses with discriminators now produce valid types.
Checklist
docs/
updated (if necessary)pnpm run update:examples
run (only applicable for openapi-typescript)