A pattern for forward-compatible enums with Valibot #1532
rannuo
started this conversation in
Show and tell
Replies: 3 comments
|
A proper solution import * as v from 'valibot';
export type UnknownEnumValue = { readonly type: 'unknown'; readonly rawValue: unknown };
export const PHASE_STATUSES = ['pending', 'success', 'failed', 'timeout'] as const;
const ABSENT_UNKNOWN: UnknownEnumValue = { type: 'unknown', rawValue: undefined };
const isUnknownEnumValue = (value: unknown): value is UnknownEnumValue =>
typeof value === 'object' && value !== null && (value as UnknownEnumValue).type === 'unknown';
export function enumWithRawUnknownDefaulting<const T extends readonly string[]>(values: T) {
return v.optional(
v.pipe(
v.any(),
v.transform((input): T[number] | UnknownEnumValue => {
if (isUnknownEnumValue(input)) return input;
if (input == null) return { type: 'unknown', rawValue: input };
if (typeof input === 'string' && (values as readonly string[]).includes(input)) {
return input as T[number];
}
return { type: 'unknown', rawValue: input };
}),
),
ABSENT_UNKNOWN,
);
}
export const PhaseStatusSchema = enumWithRawUnknownDefaulting(PHASE_STATUSES);
const Schema = v.object({
status: PhaseStatusSchema,
});
const result = v.safeParse(Schema, {
});
console.log(result);
// {
// status: {
// type: "unknown",
// rawValue: undefined
// }
// }
|
0 replies
|
One spontaneous idea I have is to use a custom schema with type KnownTypes = 'A' | 'B' | 'C';
type AllowAnyString = KnownTypes & ({} & string);
const CustomSchema = v.custom<AllowAnyString>((input) => typeof input === 'string'); |
0 replies
|
What I was aiming for: case: missing key -> { type: 'unknown'; rawValue: undefined };
case: undefined | null -> { type: 'unknown'; rawValue: undefined | null }
case: someValueNotKnown -> { type: 'unknown'; someValueNotKnown: }const renderState = (s) => {
return match(s)
.with(‘pending’, () => <Pending />)
.with(‘success’, () => <Success />)
.with(‘failed’, () => <Failed />)
.with(‘timeout’, () => <Timeout />)
.with({ type: ‘unknown’ }, ({ rawValue }) => <Unknown rawValue={rawValue} /> )
.exhaustive() // old page not break when backend extend status enum
} |
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
I wanted to share a pattern for handling forward-compatible enums when consuming evolving APIs with Valibot.
A common challenge with API clients is that servers may introduce new enum values over time. Strict parsing is useful for catching unexpected values, but failing completely on a newly introduced enum value can sometimes be undesirable. On the other hand, widening everything to
stringloses the type safety and autocomplete benefits of literal unions.One approach is to explicitly model unknown values:
With Valibot's
pipeandtransformAPIs, this pattern can be implemented while keeping:I thought this was a useful pattern for clients consuming APIs that evolve over time, so I wanted to share it here and see how others approach this problem.
All reactions