wire format for a nonEmpty field in string/[]string params#1912
wire format for a nonEmpty field in string/[]string params#1912Berlioz wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a nonEmpty option to disallow empty strings or empty lists when prompting for input, updating defineString and defineList to support it. The review feedback suggests a cleaner approach to type safety: instead of manually intersecting a new SupportsNonEmpty type in multiple places, ParamOptions<T> can be conditionally typed to include nonEmpty only when T is a string or string[]. This simplifies the signatures of defineString, defineList, and the Param constructor, and allows the removal of the helper type entirely.
| /** Basic configuration options which can be used to customize the prompting behavior of a parameter. */ | ||
| export type ParamOptions<T extends string | number | boolean | string[]> = Omit< | ||
| ParamSpec<T>, | ||
| "name" | "type" | ||
| "name" | "type" | "nonEmpty" | ||
| >; | ||
| /** A configuration to disallow empty string/empty list, which is unioned to Paramoptions for types where it has meaning. */ | ||
| export type SupportsNonEmpty = { | ||
| nonEmpty?: boolean; | ||
| } |
There was a problem hiding this comment.
Instead of creating a separate SupportsNonEmpty type and manually intersecting it in multiple places, we can make ParamOptions<T> conditionally include nonEmpty only when T is a string or string[]. This improves type safety by preventing nonEmpty from being allowed on other parameter types (like IntParam or BooleanParam) and simplifies the function signatures.
| /** Basic configuration options which can be used to customize the prompting behavior of a parameter. */ | |
| export type ParamOptions<T extends string | number | boolean | string[]> = Omit< | |
| ParamSpec<T>, | |
| "name" | "type" | |
| "name" | "type" | "nonEmpty" | |
| >; | |
| /** A configuration to disallow empty string/empty list, which is unioned to Paramoptions for types where it has meaning. */ | |
| export type SupportsNonEmpty = { | |
| nonEmpty?: boolean; | |
| } | |
| /** Basic configuration options which can be used to customize the prompting behavior of a parameter. */ | |
| export type ParamOptions<T extends string | number | boolean | string[]> = Omit< | |
| ParamSpec<T>, | |
| "name" | "type" | "nonEmpty" | |
| > & (T extends string | string[] ? { nonEmpty?: boolean } : {}); |
There was a problem hiding this comment.
Typescript knowers: does this work? Is this remotely readable? I think I blacked out when I read this and saw a vision of Cthulhu as I was under, but maybe I'm not sufficiently rocq-brained.
There was a problem hiding this comment.
Yes, this works. There's other ways too.
| constructor(readonly name: string, readonly options: ParamOptions<T> & SupportsNonEmpty = {}) { | ||
| super(); | ||
| } |
There was a problem hiding this comment.
With ParamOptions<T> conditionally including nonEmpty based on T, we can simplify the Param constructor signature to just use ParamOptions<T> instead of intersecting it with SupportsNonEmpty.
| constructor(readonly name: string, readonly options: ParamOptions<T> & SupportsNonEmpty = {}) { | |
| super(); | |
| } | |
| constructor(readonly name: string, readonly options: ParamOptions<T> = {}) { | |
| super(); | |
| } |
| SecretParamOptions, | ||
| InternalExpression, | ||
| InterpolationExpression, | ||
| SupportsNonEmpty, | ||
| } from "./types"; |
| export function defineString(name: string, options: ParamOptions<string> & SupportsNonEmpty = {}): StringParam { | ||
| const param = new StringParam(name, options); | ||
| registerParam(param); | ||
| return param; |
There was a problem hiding this comment.
With the conditional ParamOptions<T> type, defineString no longer needs to manually intersect with SupportsNonEmpty.
| export function defineString(name: string, options: ParamOptions<string> & SupportsNonEmpty = {}): StringParam { | |
| const param = new StringParam(name, options); | |
| registerParam(param); | |
| return param; | |
| export function defineString(name: string, options: ParamOptions<string> = {}): StringParam { | |
| const param = new StringParam(name, options); | |
| registerParam(param); | |
| return param; |
| export function defineList(name: string, options: ParamOptions<string[]> & SupportsNonEmpty = {}): ListParam { | ||
| const param = new ListParam(name, options); | ||
| registerParam(param); | ||
| return param; |
There was a problem hiding this comment.
With the conditional ParamOptions<T> type, defineList no longer needs to manually intersect with SupportsNonEmpty.
| export function defineList(name: string, options: ParamOptions<string[]> & SupportsNonEmpty = {}): ListParam { | |
| const param = new ListParam(name, options); | |
| registerParam(param); | |
| return param; | |
| export function defineList(name: string, options: ParamOptions<string[]> = {}): ListParam { | |
| const param = new ListParam(name, options); | |
| registerParam(param); | |
| return param; |
inlined
left a comment
There was a problem hiding this comment.
I wondered if nonEmpty should be an input field, but I worry that this leads to a lot of duplication and makes it harder to use. WDYT? Otherwise this looks good with the gemini suggestion
| /** Basic configuration options which can be used to customize the prompting behavior of a parameter. */ | ||
| export type ParamOptions<T extends string | number | boolean | string[]> = Omit< | ||
| ParamSpec<T>, | ||
| "name" | "type" | ||
| "name" | "type" | "nonEmpty" | ||
| >; | ||
| /** A configuration to disallow empty string/empty list, which is unioned to Paramoptions for types where it has meaning. */ | ||
| export type SupportsNonEmpty = { | ||
| nonEmpty?: boolean; | ||
| } |
There was a problem hiding this comment.
Yes, this works. There's other ways too.
Obviously not going to do anything, and probably should not be merged until, we actually support this feature on the CLI side.