Regex-validated strings are not a thing in Typescript.
Still, the need is here: in my case, immutable strings that are validated against a Regex for UUIDv4, URIs, Email, etc.
I've got something close but I'm not happy with it, I have to rewrite the regex access and test function every time.
Here's what I have so far:
An abstract class "ValidatedString"
export interface Predicate<T> { (x: T): boolean }
export abstract class ValidatedString {
#str: string
constructor(str: string, predicate: Predicate<string>, className?: string) {
if (!predicate(str)) throw new Error(`Invalid ${className ? className : 'format'}: ${str}`)
this.#str = str
}
get length() { return this.#str.length }
toString = () => this.#str
}
And classes that implement it:
import { v4 } from 'uuid'
/** Regex-validated string class for UUIDv4 */
export class UUIDv4 extends ValidatedString {
constructor(str: string) { super(str, UUIDv4.test, 'UUID v4') }
static readonly regex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
/** Test if a string matches the class predicate */
static test = (str: string) => UUIDv4.regex.test(str)
/** Generate a random string that matches the class predicate */
static generate = () => new UUIDv4(v4())
}
/** Regex-validated string class for UUIDv4 */
export class ApiVersion extends ValidatedString {
constructor(str: string) { super(str, ApiVersion.test, 'API version') }
static readonly regex = /^[0-9]+\.[0-9]+(\.[0-9]+)?[a-z]*$/
/** Test if a string matches the class predicate */
static test = (str: string) => ApiVersion.regex.test(str)
}
Despite my efforts to eliminate the obvious redundancies between the inheriting classes, I have failed: one cannot use a protected static field in the abstract class, so I'm left with rewriting the test function and the regex access every time.
I can't offer a generic signature for the generate function either.
I would like to basically build UUIDv4 as a ValidatedString with UUIDv4 regex and offer a regex access,test method and optional generate function signature.
Do you see how to implement this properly? Is there a way to build a type from a static parameter such as a Regex literal?