Skip to content

Typing on parse; parse returns Record<string, string | undefined> #252

@RWOverdijk

Description

@RWOverdijk

Hi,

I don't know if this is accurate to call a bug, so I'm filing it as a question 😄 If this is wrong, please let me know and I’ll change it.

I've been trying to figure out why the typing on parse defines a return type of Record<string, string | undefined>. From what I can tell, this seems to come from how ParseOptions is defined, specifically the decode function being typed as returning string | undefined. The default decode implementation always returns a string, so undefined doesn’t seem possible in normal usage.

My question is: what’s the reason for including undefined in the return type? Why not require ParseOptions.decode to return a string, or even better, make parse generic so its return type matches whatever the provided decoder returns? I might be missing a valid reason for the current typing.

The reason I noticed this is that it causes issues with code coverage. The typing forces me to handle the undefined case, but coverage then requires me to hit the “else” branch, which isn’t possible without either providing a custom decoder just to trigger it or using an istanbul ignore.

Assuming I’m not reading the code wrong, I’d propose making the typing more accurate and flexible. For example:

export type Decoder = (input: string) => unknown;
export type Cookies<V> = Record<string, V>;

export interface ParseOptions<Dec extends (str: string) => unknown = typeof decode> {
  decode?: Dec;
}

export function parseCookie<Dec extends Decoder = typeof decode>(
  str: string,
  options?: ParseOptions<Dec>,
): Cookies<ReturnType<Dec>>;

// Example 1: Override changes return type
const x1 = parseCookie("", {
  decode: (val: string): number => Number(val),
});

// Example 2: No override uses default return type
const x2 = parseCookie("");

// Example 3: Override that actually can return undefined
const x3 = parseCookie("", {
  decode: (val: string) => (val[0] === "a" ? val : undefined),
});

This way, the return type of parse is always linked to whatever the provided decode function actually returns, while keeping the default behavior as Record<string, string>.

Thanks for your time!

A screenshot from my IDE (have a giggle at my font) showing inference:

Image

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions