Here is an interesting TypeScript validator:
type HexNumber = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
type HexString = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f'
type StringNumber<T extends number> = `${T}`
type HEX = HexNumber | StringNumber<HexNumber> | HexString;
type Check<T extends string, Cache extends readonly string[] = []> =
T extends `${infer A}${infer Rest}`
? A extends HEX
? Check<Rest, [...Cache, A]> : A extends ''
? 1 : 2 : T extends '' ? Cache extends { length: 6 }
? Cache : never : never;
type Elem = string;
type Mapper<
Arr extends ReadonlyArray<Elem>,
Result extends string = ''
> = Arr extends []
? Result
: Arr extends [infer H]
? H extends Elem
? `${Result}${H}`
: never
: Arr extends readonly [infer H, ...infer Tail]
? Tail extends ReadonlyArray<Elem>
? H extends Elem
? Mapper<Tail, `${Result}${H}`>
: never
: never
: never;
type Result = Mapper<Check<'abcdef'>> // allow
type Result2 = Mapper<Check<'00cdef'>> // allow
type Result3 = Mapper<Check<'z0cdef'>> // not allow
type Result4 = Mapper<Check<'00cdem'>> // not allow
type Result5 = Mapper<Check<'aaaaa'>> // too few arguments
type Color<S extends string> = {
primary: S;
secondary: S;
heading: S;
}
type Size<T extends string> = {
extraLarge: T;
large: T;
medium: T;
small: T;
hyperlink: T;
paragraph: T;
}
interface DefaultTheme<C extends string, S extends string> {
color: Color<C>;
font: {
size: Size<S>;
family: string;
};
}
type RemoveHash<T extends string> =
T extends `${infer Hash}${infer Rest}`
? Hash extends '#' ? Rest
: never
: never;
const theme = {
color: {
primary: '#5039E7',
secondary: '#372E4B',
heading: '#4D5062',
},
font: {
size: {
extraLarge: '7.2rem',
large: '5.2rem',
medium: '3.6rem',
small: '2.4rem',
hyperlink: '1.8rem',
paragraph: '1.6rem',
},
family: 'sans-serif',
},
} as const;
type HEXValidation<T extends string> = Check<RemoveHash<T>> extends string[] ? Mapper<Check<RemoveHash<T>>> extends RemoveHash<T> ? [] : [never] : [never]
// credits to https://stackoverflow.com/a/50375286
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I
) => void
? I
: never;
type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true;
type IsRem<T extends string> = T extends `${number}.${number}rem` ? 1 : 2;
type UNITvalidation<T> = IsUnion<T> extends true ? [never] : [];
declare function validator<T extends string, S extends string>
(config: DefaultTheme<T, S>, ...flag:[...HEXValidation<T>, ...UNITvalidation<IsRem<S>>]):void;
const result = validator(theme) // okay
/**
* Tests
*/
const result2=validator({
...theme,
font:{
...theme.font,
size:{
...theme.font.size,
paragraph: '1.6rm', // <---error because of type, should be rem
}
}
})
const result3=validator({
...theme,
color:{
...theme.color,
primary: '#5039E-', // <--- error because of invalid HEX
}
})
Playground
Explore more about HEX validation here