Contract extensibility as it relates to enums and type unions
Say I have a contract returning a type: type CreditCard = { scheme: "Visa" | "Mastercard" } and later we decided to include Amex as card type, then making this change: type CreditCard = { - scheme: "Visa" | "Mastercard" + scheme: "Visa" | "Mastercard" | "Amex" } would be breaking a change. That is, some code like: function processCard(card: CreditCard): number { switch(card.scheme) { case "Visa": return 1; case "Mastercard": return 2; } } would now show a type error. So if I'm designing this API, I figure the only way prevent this is to do one of the following: 1. Preemptively list all card types: type CreditCard = { scheme: "Visa" | "Mastercard" | "Amex" | "DinersClub" | "Discover" } Not a great solution, there could always be a new scheme in the future that we're not aware of. 2. Just make it a string type CreditCard = { scheme: string; } We lose a bunch of type assistance, code complete etc, and now the consumer of our API has to be aware of the behaviour of this API in a way that isn't strictly defined by the spec. 2b. Add a | string catch all type CreditCard = { scheme: "Visa" | "Mastercard" | string; } Basically the same as 2. but at least might provide some type assistance. 3. Accept that adding type union possibilities is always a breaking change.
Say I have a contract returning a type:
type CreditCard = {
scheme: "Visa" | "Mastercard"
}
and later we decided to include Amex as card type, then making this change:
type CreditCard = {
- scheme: "Visa" | "Mastercard"
+ scheme: "Visa" | "Mastercard" | "Amex"
}
would be breaking a change.
That is, some code like:
function processCard(card: CreditCard): number {
switch(card.scheme) {
case "Visa": return 1;
case "Mastercard": return 2;
}
}
would now show a type error.
So if I'm designing this API, I figure the only way prevent this is to do one of the following:
1. Preemptively list all card types:
type CreditCard = {
scheme: "Visa" | "Mastercard" | "Amex" | "DinersClub" | "Discover"
}
Not a great solution, there could always be a new scheme in the future that we're not aware of.
2. Just make it a string
type CreditCard = {
scheme: string;
}
We lose a bunch of type assistance, code complete etc, and now the consumer of our API has to be aware of the behaviour of this API in a way that isn't strictly defined by the spec.
2b. Add a | string
catch all
type CreditCard = {
scheme: "Visa" | "Mastercard" | string;
}
Basically the same as 2. but at least might provide some type assistance.
3. Accept that adding type union possibilities is always a breaking change.