Grandas
Grandas

Reputation: 549

Is there a way to partially match a Typescript string type?

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.

Upvotes: 2

Views: 1443

Answers (4)

Hashbrown
Hashbrown

Reputation: 13013

Going by the accepted answer it looks like Grandas meant they wanted to match the root of the route (a string partial match to the first slash only or end-of-string), not a partial match of the route (a string partial match to any slash or end-of-string).

I'm going to make my answer about the latter in case anyone needs it, because unfortunately caTS' elegant inference wont work for this; as template literal inference only extracts up to the first slash, not exhaustively searches to infer all possible splits.

You would just need to more-descriptively list your "RoutePermissions", which will allow us to utilise template literal types without infer (basically building up the union of all possible types, as opposed to trying to crack apart a simplified union into an exhaustive list which isn't possible to an unknown depth of path).

type Route<
    T extends string,
    U extends string|never = never
> = `${T}${ '' | `/${U}` }`;
type ValidRoute = (
    Route<'page:mypage0'> |
    Route<'page:mypage1',
        'subroute1'|'subroute2'|'subroute3'
    > |
    Route<'page:mypage2',
        Route<'subroute1',
            'subroute2'
        >
    > |
    Route<'page:mypage3',
        'subroute1'
    >
);
const route = (x :ValidRoute) => {};

//correct
route('page:mypage0');
route('page:mypage1');
route('page:mypage1/subroute3');
route('page:mypage2/subroute1');
route('page:mypage2/subroute1/subroute2');
route('page:mypage3/subroute1');

//errors
route('page:mypage2/subroute2');
route('page:mypage12');
route('page');
route('page:mypage1/');
route('page:mypage1/subroute');
route('page:mypage1/subroute4');

Upvotes: 0

tenshi
tenshi

Reputation: 26324

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

Upvotes: 2

Tobias S.
Tobias S.

Reputation: 23825

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

Upvotes: 1

Alex Wayne
Alex Wayne

Reputation: 187024

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

Upvotes: 0

Related Questions