feat(interactive): add reflection-driven interactive prompt engine#249
feat(interactive): add reflection-driven interactive prompt engine#249ben-kalmus wants to merge 1 commit into
Conversation
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 236 |
| Duplication | 7 |
TIP This summary will be updated as you push new changes.
b28c05b to
c17e3a6
Compare
There was a problem hiding this comment.
Pull request overview
Introduces a new pkg/interactive package intended to build request bodies interactively by reflecting over Go structs and prompting the user for each field, enabling future --interactive command modes across the CLI.
Changes:
- Added a reflection-driven
Builderthat traverses structs (including optional pointers, unions, slices, maps, and “parameter bag” structs) and assigns values from prompts. - Added a
Prompterabstraction with a survey-backed implementation for production and a label-keyed scripted fake for stable unit tests. - Added reusable input validators and unit tests covering the builder, prompters, and classification helpers.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/interactive/validators.go | Adds reusable validator builders for interactive text input. |
| pkg/interactive/validators_test.go | Unit tests for the validator builders and user-facing messages. |
| pkg/interactive/scripted_prompter.go | Adds a label-keyed scripted Prompter fake for deterministic tests. |
| pkg/interactive/scripted_prompter_test.go | Tests matching/defaulting behavior of ScriptedPrompter. |
| pkg/interactive/prompter.go | Defines Prompter and implements SurveyPrompter via pkg/prompt + survey. |
| pkg/interactive/prompter_test.go | Tests that SurveyPrompter routes calls through prompt.SurveyAskOne. |
| pkg/interactive/classify.go | Reflection helpers for identifying unions, param-bags, required fields, etc. |
| pkg/interactive/classify_test.go | Unit tests for type/field classification helpers. |
| pkg/interactive/builder.go | Core reflection traversal + prompting and assignment logic. |
| pkg/interactive/builder_test.go | Unit tests covering scalar/composite traversal, enums, guards, and error propagation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
c17e3a6 to
9a22ae5
Compare
A generic package that builds a request body by walking any struct via reflection and prompting for each field through a small Prompter interface (survey-backed in production, a scripted fake for tests). Handles scalars, optional pointers, enums (validated through the SDK's own IsValid method), oneOf unions, parameter bags, slices and maps, with per-field input validation and re-prompt on invalid entries. No SDK or command coupling.
9a22ae5 to
d0c4a17
Compare
LorrisSaintGenez
left a comment
There was a problem hiding this comment.
Love this system 👏 Left a couple of comments, and both are suggestions for an improved UX (warning/error on overwritting + select for enum field).
| if err := b.buildValue(fmt.Sprintf("%s[%q]", label, key), valPtr.Elem(), depth+1, stack); err != nil { | ||
| return err | ||
| } | ||
| out.SetMapIndex(reflect.ValueOf(key), valPtr.Elem()) |
There was a problem hiding this comment.
(Non-blocking) We can technically overwrite a previously set key/value pair here. We should first check if the key exist in the map and print something to the user, either error or warning (+ confirmation?) imo
| var validate func(string) error | ||
| switch { | ||
| case isEnumType(v.Type()): | ||
| validate = enumValidator(v.Type(), label, optional) |
There was a problem hiding this comment.
(Non-blocking) I made a PR to add a Values method for enum field for the Go package.
We should be able to use reflect on it, and display a Select instead of free-text. This can be done in a follow-up PR, not blocking for this review
Summary
Adds
pkg/interactive, a generic package that builds a request body by walking any Go struct with reflection and prompting for each field. It is the foundation for--interactivemodes across the CLI.This PR introduces the engine only. No command is wired to it yet (see the follow-up PR adding
--interactiveto compositions upsert).Demo
Stacked PRs
How it works
Builder.Build(&v)walks the struct and prompts for inputs: optional pointer fields can be skipped, large optional-only parameter objects let you multi-select which fields to fill.Enums (named string types the SDK generates with an
IsValid() boolmethod) are validated against the SDK's own check via reflection.Bad input does not abort the build. It re-prompts in place (non-numeric integer, malformed JSON, empty required field) using validators.
A depth/cycle guard degrades self-referential structures to a raw-JSON prompt instead of looping forever.
Design notes
SDK-agnostic: the package imports only
pkg/iostreamsandpkg/prompt, no SDK or command packages, so any command can reuse it.Input is gathered through a small
Prompterinterface (Input/Confirm/Select/MultiSelect). Production usesSurveyPrompter; tests useScriptedPrompter, a label-keyed fake whose answers match by field label rather than call order, so tests do not break when SDK changes.Changes
pkg/interactive/prompter.go:Prompterinterface andSurveyPrompter(survey/v2 viapkg/prompt).pkg/interactive/scripted_prompter.go:ScriptedPrompter, the reusable label-keyed test fake.pkg/interactive/builder.go: the reflection walk (scalars, optional pointers, unions, parameter bags, slices, maps, enum and input validation).pkg/interactive/classify.go: reflection helpers (union, parameter-bag, required-field detection).pkg/interactive/validators.go: input validator builders (required string, integer, number, boolean, JSON).Tests
Unit tests under
pkg/interactivecovering:map[string]string