UPDATE 28 August 2022
See this answer and updated article
Here you have a solution:
UPDATE
Now, it throws an error if string length is not equal 6
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 : 'String should have 6 chars. No more, no less' : never;
type Elem = string;
// before reading keep in mind that `: never` indicates that this conditional type has reached an invalid state
// when type checking linter/compiler will throw an error on any invalid type instantion
// e.g. let a: string = 1
type Mapper<
Arr extends ReadonlyArray<Elem>,
Result extends string = ''
> =
Arr extends [] // is the array empty?
// yes, the array is empty
? Result
//no, the array is not empty,
: Arr extends [infer H] // does the array contain a single value of type H with properties as follows?
// here is a property of H such that H is valid in this context
? H extends Elem // does H exend Elem?
// yes, Arr extends [Elem]
? `${Result}${H}` // the type parameters are valid and Mapper is this type
: never // no, Mapper has invalid type parameters
// no, the array contains more than 1 value
: Arr extends readonly [infer H, ...infer Tail] // does Arr extend the type of an array with a single value of type H followed by the values within an array of type Tail? e.g. ['hellp', 1, 2, 3] or ['hello', 'world']
// here is a property of Tail such that Tail is valid in this context
? Tail extends ReadonlyArray<Elem> // does Tail extends a ReaonlyArray containing elements of type Elem e.g. [Elem] ?
// yes, Tail fits our assumption
? H extends Elem // does H extend Elem? (same as above)
? Mapper<Tail, `${Result}${H}`> // yes! now recurively build the `Result` type!
: never // no, H is invalid
: never // no, Tail is the wrong type since it contains values of a type other than Elem
: never // no, Arr doesn't extend an array of this shape;
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'>> // to few arguments
Playground
U can use my solution only for function arguments in practice
const hex = <T extends string, U extends {
'valid': 'valid',
'invalid': 'invalid',
}[Check<T> extends string[] ? Mapper<Check<T>> extends T ? 'valid' : never : 'invalid']>(value: T, ...rest: U extends 'valid' ? [] : [never]) => value
const result = hex('aaaaaf') // ok
const result2 = hex('aaaaaZ') // error
Playground 2