nonEmpty param input option support - #10678
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a 'nonEmpty' option to parameter definitions to reject empty strings and empty arrays during CLI prompting, propagating this configuration through various prompt helper functions. The review feedback highlights a critical TypeScript compilation error caused by changing the signature of 'promptResourceString' (which breaks an existing caller), and suggests correcting an inaccurate error message in 'promptSelectMultiple' when no options are selected.
inlined
left a comment
There was a problem hiding this comment.
I think I recommend that nonEmpty is a part of input? we can talk offline.
| prompt += ` \n(${param.description})`; | ||
| } | ||
| return promptText<boolean>(prompt, param.input, resolvedDefault, isTruthyInput); | ||
| return promptText<boolean>(prompt, param.input, resolvedDefault, false, isTruthyInput); |
There was a problem hiding this comment.
please always leave a comment on any boolean literal ever used as a parameter
| prompt: string, | ||
| input: ResourceInput, | ||
| projectId: string, | ||
| disallowEmpty: boolean | undefined, |
There was a problem hiding this comment.
why disallowEmpty and nonEmpty? Also, why mandatory but supports undefined?
There was a problem hiding this comment.
Yeah (naming aside) this could just be disallowEmpty: boolean = false so there's a default value for the case where we have undefined from it not being in functions.yaml right? (same elsewhere).
I have a hunch on why the name change from nonEmpty to disallowEmpty but I'd like to hear Victor's answer :) ... I do think it's a debateable shift.
There was a problem hiding this comment.
I think changing the var name from nonEmpty to disallowEmpty as it moves from the wire format into the function calling chain makes sense: nonEmpty is describing something about the input itself, while disallowEmpty is more a modifier on the behavior of the prompt: "this prompt is nonempty" seems more vague than "this prompt disallows empty".
That's very much a subjective take though and if you two feel like it's an unnecessary or bad naming decision I really don't mind reverting it.
The boolean | undefined thing was just me being dumb and forgetting that typescript had optional parameters for a second. Fixed.
There was a problem hiding this comment.
I get the move from describing the input to enforcing the constraint (that was what I thought you were doing). I'd say if we want to make that distinction we could have a slightly better throughline with a different name.
nonEmpty -> enforceNonEmpty or something like that.
There was a problem hiding this comment.
oh, yeah, that's a good compromise
| input?: ParamInput<T>; | ||
|
|
||
| // Reject the empty string and empty array as valid input. | ||
| nonEmpty?: boolean; |
There was a problem hiding this comment.
I wonder how hard it is to make the field only present for the right types. At minimum you can say:
type TypeIfExtends<Result, Test, Base> = Test extends Base ? Result : never;
// ...
nonEmpty: TypeIfExtends<boolean, T, string | string[]>
There was a problem hiding this comment.
I am not as confident about reading type system stuff as you are but my instinct is that this is a huge loss of readability to double-enforce a constraint that the SDK can already enforce much more clearly.
| prompt, | ||
| param.input, | ||
| resolvedDefault, | ||
| param.nonEmpty, |
There was a problem hiding this comment.
why is nonEmpty not part of input?
There was a problem hiding this comment.
If it is (which from what I understand with a bit of research I would support) do we also need to update the API proposal to be clear about that?
Or we enforce nonEmpty at deployment time and not just input prompting time?
I guess a difference between functions and extensions is users can edit params after the input form by hand editing .env files (or could add nonEmpty after a first deployment that writes an empty string).
There was a problem hiding this comment.
Update: we talked it out, updated the SDK, and yeah, moved to input fields.
ajperel
left a comment
There was a problem hiding this comment.
Chiming in with a few thoughts since I went through this PR trying to understand it. But overall, I think just having Thomas's LG (and not blocking on more review from me) is fine.
| prompt: string, | ||
| input: ResourceInput, | ||
| projectId: string, | ||
| disallowEmpty: boolean | undefined, |
There was a problem hiding this comment.
Yeah (naming aside) this could just be disallowEmpty: boolean = false so there's a default value for the case where we have undefined from it not being in functions.yaml right? (same elsewhere).
I have a hunch on why the name change from nonEmpty to disallowEmpty but I'd like to hear Victor's answer :) ... I do think it's a debateable shift.
| prompt, | ||
| param.input, | ||
| resolvedDefault, | ||
| param.nonEmpty, |
There was a problem hiding this comment.
If it is (which from what I understand with a bit of research I would support) do we also need to update the API proposal to be clear about that?
Or we enforce nonEmpty at deployment time and not just input prompting time?
I guess a difference between functions and extensions is users can edit params after the input form by hand editing .env files (or could add nonEmpty after a first deployment that writes an empty string).
| prompt, | ||
| param.input, | ||
| resolvedDefault, | ||
| false, // enforceNonEmpty |
There was a problem hiding this comment.
Doesn't text input support nonempty?
There was a problem hiding this comment.
Sure does. Straight up copy-paste error.
| return promptSelectMultiple<string>( | ||
| prompt, | ||
| forgedInput, | ||
| undefined, |
There was a problem hiding this comment.
please comment on what parameter a literal is
| `Warning: unknown resource type ${input.resource.type}; defaulting to raw text input...`, | ||
| ); | ||
| return promptText<string[]>(prompt, { text: {} }, undefined, (res: string) => res.split(",")); | ||
| return promptText<string[]>(prompt, { text: {} }, undefined, enforceNonEmpty, (res: string) => |
There was a problem hiding this comment.
same. Comment on what undefined is (I know this is preexisting)
| } | ||
| } | ||
| if (enforceNonEmpty && res === "") { | ||
| logger.error(`Input cannot be the empty string, retrying...`); |
There was a problem hiding this comment.
Can we be a little less nerdy? E.g. "A value is required, retrying..."
Also gets rid of that immutable copy-paste mistake which has never done anything.