#Parsing user supplied value for string literal type

1 messages · Page 1 of 1 (latest)

trail nexus
#

Hello all,

I am familiar with Haskell and JavaScript but new to TypeScript. If I have a string literal type type StrLitType = 'foo' | 'bar' | 'baz' and a value:string that is a run-time string from the user is it possible to write a generic parseStringLiteral<E>(value : string) : E function that will throw an error if the value is not valid with a list of possible values?

parseStringLiteral<StrLitType>('foo') => 'foo' : StrLitType
parseStringLiteral<StrLitType>('quux') => Error('Value "quux" is not one of "foo, bar, baz".)

Another engineer on my team is doing this in a one-off way with:

const envs = ['test', 'local', 'dev', 'staging', 'prod'] as const
const envsSet = new Set(envs)
export type NodeEnv = typeof envs[number]

    const nodeEnv = env.NODE_ENV as NodeEnv
    if (envsSet.has(nodeEnv)) {
      return nodeEnv
    }

    throw new Error(`Invalid NODE_ENV: "${env.NODE_ENV ?? ''}". Should be something in ${envs.join(', ')}`)

But I am trying to abstract the pattern away and either can't get the incoming type correct, or can't get access to all the possible values. Current attempt:

function parseEnumValue<S extends Array<string>, E>(values : S, val : string) : E {
  const valAsEnumUnsafe : E = val as E
  if ( values.includes(val)) {
    return valAsEnumUnsafe;
  }

  // todo could convert to set here, but shouldn't matter
  throw new Error(`Invalid value "${val}" for string liter type of ${values.join(', ')}`)
}

But return parseEnumValue(envs, env.NODE_ENV || '') complains that "Argument of type 'readonly ["test", "local", "dev", "staging", "prod"]' is not assignable to parameter of type 'string[]'."

tawdry palm
#

You should have S extends ReadonlyArray<string> or equivalently S extends readonly string[], I don't think think it makes sense to have a second type parameter here...

#

!:includes

queen vigilBOT
#
Retsam19#2505
`!retsam19:includes`:

The type of Array.prototype.includes assumes that you're using the string to check something about the array. If you're trying to do the reverse, a modified type signature is useful:

function includes<S extends string>(haystack: readonly S[], needle: string): needle is S {
    const _haystack: readonly string[] = haystack;
    return _haystack.includes(needle)
}

declare const x: string;
if(includes(["a", "b", "c"], x)) {
    // x is "a" | "b" | "c"
}
tawdry palm
#

^ I'd probably just use something like this and potentially not even use a separate parse function

trail nexus
#

Hey @tawdry palm thanks for jumping in, this was helpful. The last nice to have I am hoping is possible which would make the parse function make sense is a way to get from the string literal union type to the list of possible string literals (this https://www.npmjs.com/package/ts-transformer-enumerate custom transformer does it but I can find anything built-in).

My final dream function signature is:

function parseRuntimeStringLiteral<E extends string>(val : string) : E

(maybe with restricting E with the trick from this stackoverflow to enforce that it is string literal(s))

tawdry palm
#

That signature isn't possible to implement without transformers -- types don't exist at runtime, so...

trail nexus
#

Thanks again @tawdry palm! I was getting the sense I was banging my head against a wall.

With being new to the ecosystem I would appreciate if you would be willing to share any of your opinions on how custom transformers are seen in the community. Are they something that end up being frequently used on large codebases? Or are they usually used for experiments that if they pan out are incorporated into TypeScript proper eventually? (i.e. considered for "advanced" users only)

tawdry palm
#

My opinion of them is that they should be avoided if at all possible. Every transformer I've seen someone use has been forcing TS to do something that is explicitly against its design goals (like this one, type directed emit is a non goal) or do something that ought to be done by a bundler in a build step after compiling TS... transformers work by monkey patching the compiler in a fragile way that tends to break, I know ttypescript is broken by the TS release coming out tomorrow