diff --git a/gen/cli_yargs_variadic_test.go b/gen/cli_yargs_variadic_test.go index b88fdad..0f737cf 100644 --- a/gen/cli_yargs_variadic_test.go +++ b/gen/cli_yargs_variadic_test.go @@ -172,3 +172,41 @@ func TestCLI_YargsGlobalVariadicBoolean(t *testing.T) { t.Error(`expected "verbose?: boolean[] | undefined;" in GlobalFlags interface`) } } + +// TestCLI_YargsRequiredVariadicFlag ensures a flag that is both required and +// variadic is registered as a demanded array option, so yargs rejects zero +// occurrences while still accepting many values. +func TestCLI_YargsRequiredVariadicFlag(t *testing.T) { + doc := &spec.Document{ + OpenCLIVersion: "1.0.0-alpha.14", + Info: spec.Info{Title: "VarTest CLI", Binary: "vartest"}, + Commands: &spec.CommandItem{ + Segment: "greet", + Flags: []spec.FlagItem{ + {Name: "items", Type: "string", Variadic: true, Required: true}, + }, + }, + } + + files, err := CLI(doc, GenCLIWithFramework(YargsFramework)) + if err != nil { + t.Fatalf("unexpected error generating yargs output: %v", err) + } + + var all strings.Builder + for _, content := range files { + all.Write(content) + all.WriteByte('\n') + } + got := all.String() + + if !strings.Contains(got, `type: "array",`) { + t.Error("expected required variadic flag to be registered as an array option") + } + if !strings.Contains(got, "demandOption: true,") { + t.Error("expected required variadic flag to emit demandOption: true") + } + if !strings.Contains(string(files["gencli/params.ts"]), " items: string[];\n") { + t.Error("expected required variadic flag to be a non-optional string[] field in the action flags interface") + } +} diff --git a/spec.schema.json b/spec.schema.json index a769042..b230ce9 100644 --- a/spec.schema.json +++ b/spec.schema.json @@ -385,7 +385,7 @@ }, "variadic": { "type": "boolean", - "description": "Indicates if the the flag value is variadic. Variadic flags can be supplied multiple times. e.g.: `--flag value --flag value_two --flag value_three`", + "description": "Indicates if the the flag value is variadic. Variadic flags can be supplied multiple times. e.g.: `--flag value --flag value_two --flag value_three`. A variadic flag may also be `required`, meaning it must be supplied at least once. Use `minItems`/`maxItems` to constrain the number of values when the flag is supplied.", "default": false }, "minItems": { @@ -434,7 +434,7 @@ }, "required": { "type": "boolean", - "description": "Determines if the flag is mandatory. If required flags are not supplied, the command will exit in error.", + "description": "Determines if the flag is mandatory. If required flags are not supplied, the command will exit in error. May be combined with `variadic` to require a flag that accepts multiple values.", "default": false }, "default": { diff --git a/validate/spec.schema.json b/validate/spec.schema.json index a769042..b230ce9 100644 --- a/validate/spec.schema.json +++ b/validate/spec.schema.json @@ -385,7 +385,7 @@ }, "variadic": { "type": "boolean", - "description": "Indicates if the the flag value is variadic. Variadic flags can be supplied multiple times. e.g.: `--flag value --flag value_two --flag value_three`", + "description": "Indicates if the the flag value is variadic. Variadic flags can be supplied multiple times. e.g.: `--flag value --flag value_two --flag value_three`. A variadic flag may also be `required`, meaning it must be supplied at least once. Use `minItems`/`maxItems` to constrain the number of values when the flag is supplied.", "default": false }, "minItems": { @@ -434,7 +434,7 @@ }, "required": { "type": "boolean", - "description": "Determines if the flag is mandatory. If required flags are not supplied, the command will exit in error.", + "description": "Determines if the flag is mandatory. If required flags are not supplied, the command will exit in error. May be combined with `variadic` to require a flag that accepts multiple values.", "default": false }, "default": { diff --git a/validate/validate.go b/validate/validate.go index c7e078f..8c60f3b 100644 --- a/validate/validate.go +++ b/validate/validate.go @@ -355,7 +355,7 @@ func validateFileReferences(cmdLine, itemName, itemType string, index int, altSo return nil } -// validateFlagConstraints checks for duplicate flag names/aliases and variadic+required constraints +// validateFlagConstraints checks for duplicate flag names/aliases and minItems/maxItems constraints func validateFlagConstraints(cmd *spec.CommandItem) error { // Check for duplicate flag names and aliases seen := make(map[string]int) @@ -389,14 +389,6 @@ func validateFlagConstraints(cmd *spec.CommandItem) error { seen[alias] = i } - // Validate variadic flags aren't required - if flag.Variadic && flag.Required { - return &ValidationError{ - Message: fmt.Sprintf("variadic flag '%s' cannot be marked as required (variadic flags can be provided 0 or more times)", flagName), - Path: fmt.Sprintf("command '%s', flags[%d]", cmd.CommandLine, i), - } - } - // Check minItems/maxItems are only used with variadic if (flag.MinItems > 0 || flag.MaxItems > 0) && !flag.Variadic { var field string diff --git a/validate/validate_test.go b/validate/validate_test.go index adc25f1..e9c6f6c 100644 --- a/validate/validate_test.go +++ b/validate/validate_test.go @@ -78,13 +78,6 @@ func TestValidateYAML_LogicalValidationErrors(t *testing.T) { " - name: \"name\"\n summary: \"A name to include in the greeting\"\n required: true\n type: \"string\"\n minItems: 1"), wantErr: "argument 'name' has minItems but is not variadic", }, - { - name: "variadic flag cannot be required", - input: replaceOnce(t, petstoreYAML, - " variadic: true", - " variadic: true\n required: true"), - wantErr: "variadic flag 'photo-urls' cannot be marked as required", - }, { name: "duplicate flag alias", input: replaceOnce(t, pleasantriesYAML, @@ -278,3 +271,15 @@ func TestValidateJSON_UnmarshalError(t *testing.T) { t.Fatalf("expected unmarshal error message, got: %v", err) } } + +// TestValidateYAML_RequiredVariadicFlag ensures a flag may be both required and +// variadic (must be supplied at least once, accepts multiple values). See +// https://github.com/bcdxn/opencli/issues/20. +func TestValidateYAML_RequiredVariadicFlag(t *testing.T) { + input := replaceOnce(t, petstoreYAML, + " variadic: true", + " variadic: true\n required: true") + if err := validate.ValidateYAML(input); err != nil { + t.Fatalf("required variadic flag should validate, got: %v", err) + } +} diff --git a/web/src/spec.schema.json b/web/src/spec.schema.json index a769042..b230ce9 100644 --- a/web/src/spec.schema.json +++ b/web/src/spec.schema.json @@ -385,7 +385,7 @@ }, "variadic": { "type": "boolean", - "description": "Indicates if the the flag value is variadic. Variadic flags can be supplied multiple times. e.g.: `--flag value --flag value_two --flag value_three`", + "description": "Indicates if the the flag value is variadic. Variadic flags can be supplied multiple times. e.g.: `--flag value --flag value_two --flag value_three`. A variadic flag may also be `required`, meaning it must be supplied at least once. Use `minItems`/`maxItems` to constrain the number of values when the flag is supplied.", "default": false }, "minItems": { @@ -434,7 +434,7 @@ }, "required": { "type": "boolean", - "description": "Determines if the flag is mandatory. If required flags are not supplied, the command will exit in error.", + "description": "Determines if the flag is mandatory. If required flags are not supplied, the command will exit in error. May be combined with `variadic` to require a flag that accepts multiple values.", "default": false }, "default": {