io-ts icon indicating copy to clipboard operation
io-ts copied to clipboard

Support TS 4.1 Template Literal Types

Open egeozcan opened this issue 4 years ago • 3 comments

🚀 Feature request

TypeScript introduced Template Literal Types in version 4.1: https://devblogs.microsoft.com/typescript/announcing-typescript-4-1/#template-literal-types

image

https://www.typescriptlang.org/play?ts=4.1.0-pr-40336-88#code/JYOwLgpgTgZghgYwgAgILIN4ChnIMoD2AthACJyQBcy5kA3DvsRAJLjUByArkQEbQMAvlixgAngAcUAMWAAbSFAAqAHiUAaZEoAKUAhOQQAHpBAATAM7IA1hDEEYWgHzIAvJkYBtW2OShkAAYAJBgA5ACyoKHIAD7IEXBG0XGhoYIhFmBQoADmyABkWrr6grIK0AEAutRKnjp6EpVCIi0i4lLI0m6d8oqqqJqhhCS0EKFODK1TIkA

Current Behavior

There's no way (AFAIK) to use template literal types with io-ts.

Desired Behavior

I can replicate Template Literal Types with io-ts.

Suggested Solution

A function like t.templateLiteral(``) that replicates the behavior.

Who does this impact? Who is this for?

People generating types from types.

Describe alternatives you've considered

I'm currently using a script to generate these types. They have to be known at compile time anyway.

Your environment

Software Version(s)
io-ts 2.2.12
fp-ts 2.8.6
TypeScript 4.1.2

egeozcan avatar Nov 20 '20 11:11 egeozcan

@gcanti wouldn't a function like t.templateLiteral(``) solve this problem? I'm answering to your tag addition.

An example: https://github.com/colinhacks/zod/issues/419

egeozcan avatar Jun 09 '21 17:06 egeozcan

Template Literals could be modelled as a refinement of the string codec. Unfortunately, there seems to be no way to let TypeScript knows about the relationship between a template string or regex at runtime and the template literal type at compile time, so some duplication is inevitable when declaring the codec, but at least the usage would be coherent and correct from there. A proposal:

import * as t from 'io-ts';

export function templateLiteral<L extends string>(regex: RegExp | string, name?: string) {
  const regex_ = typeof regex === 'string' ? new RegExp(regex) : regex;
  const predicate = (s: string): s is L => regex_.test(s);
  return new t.RefinementType(
    name ?? `TemplateLiteral<${regex_.source}>`,
    (u): u is L => t.string.is(u) && predicate(u),
    (i, c) => {
      const e = t.string.validate(i, c);
      if (isLeft(e)) {
        return e;
      }
      const a = e.right;
      return predicate(a) ? t.success(a) : t.failure(a, c);
    },
    t.string.encode,
    t.string,
    predicate,
  );
}

// Usage
const hexString = templateLiteral<`0x{string}`>(/^0x.*/);
declare const str: unknown;
if (hexString.is(str)) {
  // type of str: `0x{string}`
}

andrevmatos avatar Aug 23 '21 16:08 andrevmatos

Thanks for the nice example!

Spotted a typo, missing $: it should probably be

const hexString = templateLiteral<`0x${string}`>(/^0x.*/);
                                     ^

jppellet avatar Apr 13 '23 23:04 jppellet