Home > OS >  Is there a way to partially match a Typescript string type?
Is there a way to partially match a Typescript string type?

Time:09-16

I have type that looks like this:

export type RoutePermissions =
  | 'page:mypage1/subroute1'
  | 'page:mypage1/subroute2'
  | 'page:mypage1/subroute3'
  | 'page:mypage2/subroute1'
  | 'page:mypage3/subroute1'

I want to add a type that matches the type partially, for example:

type PartialRouteMatchType = ???;
const partialRouteMatch = (partialRoute: PartialRouteMatchType) => {};
partialRouteMatch('page:mypage1'); // ok
partialRouteMatch('page:mypage2'); // ok
partialRouteMatch('page:mypage22'); // should fail

Is this possible in Typescript? Thank you!

EDIT: Apologies, the first example wasn't good. Added more detail.

CodePudding user response:

It is, with template literal types:

type PartialRouteMatchType = RoutePermissions extends `${infer S}/${string}` ? S : never;

Here we have just defined a type that infers all the strings that start each string in the type RoutePermissions. Pretty simple and efficient and requires no changes to anything else. Also, it works even if the routes contain more subroutes i.e. "page:mypage1/one/two/three" still results in "page:mypage1".

Playground

CodePudding user response:

If you are looking for a function which only accepts sub-strings of a union of string literal types, then maybe this will help you.

const partialRouteMatch = <T extends string>(
    partialRoute: RoutePermissions extends infer U 
      ? U extends `${string}${T}${string}`
        ? T 
        : never
      : never
) => {};
partialRouteMatch('page:mypage1');   // ok
partialRouteMatch('page:mypage2');   // ok
partialRouteMatch('page2/subroute'); // ok
partialRouteMatch('page:mypage22');  // fails

Playground


or maybe you want the substring to only start at the beginning of the string.

const partialRouteMatch = <T extends string>(
    partialRoute: RoutePermissions extends infer U 
      ? U extends `${T}${string}`
        ? T 
        : never
      : never
) => {};

partialRouteMatch('page:mypage1');   // ok
partialRouteMatch('page:mypage2');   // ok
partialRouteMatch('page2/subroute'); // fails
partialRouteMatch('page:mypage22');  // fails

Playground

CodePudding user response:

You want a string template literal type:

type PartialRouteMatchType = `page:mypage1${string}`;
const partialRouteMatch = (partialRoute: PartialRouteMatchType) => {};

partialRouteMatch('page:mypage1'); // fine
partialRouteMatch('bad'); // error

See Playground

CodePudding user response:

In the general case this will work for any route-"like"

type RouteLike = `page:mypage${number}${ '' | `/subroute${number}` }`;
const route = (x :RouteLike) => {};

//correct
route('page:mypage1');
route('page:mypage22');
route('page:mypage1/subroute3');

//errors
route('page');
route('page:mypage1/');
route('page:mypage1/subroute');
route('page:mypage1x');
route('page:mypage1/subroutex');

But if you really want it to only accept an exhaustive list, you'll need to do something like this:

type RouteLike<
    T extends number,
    U extends number
> = `page:mypage${T}${ '' | `/subroute${U}` }`;
type ValidRoute = (
    RouteLike<1, 1|2|3> |
    RouteLike<2, 1    > |
    RouteLike<3, 1    >
);
const route = (x :ValidRoute) => {};

//correct
route('page:mypage1');
route('page:mypage12'); //this now errors
route('page:mypage1/subroute3');
route('page:mypage3/subroute1');

//errors
route('page');
route('page:mypage1/');
route('page:mypage1/subroute');
route('page:mypage1x');
route('page:mypage1/subroutex');
  • Related