Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,23 @@ type DocGenerationMultiValueFlag interface {
IsMultiValueFlag() bool
}

// SchemaTyper is an optional interface for flags that can report their
// JSON Schema type for programmatic introspection.
type SchemaTyper interface {
// SchemaType returns the JSON Schema type name for the value this
// flag accepts: "boolean", "integer", "number", "string", "array",
// "object". Returns "" if the flag does not map cleanly.
SchemaType() string
}

// SchemaItemsTyper is an optional interface for multi-value flags that
// can report the JSON Schema type of their elements.
type SchemaItemsTyper interface {
// SchemaItemsType returns the JSON Schema type of elements for
// array-type flags. Returns "" for single-value or object flags.
SchemaItemsType() string
}

// Countable is an interface to enable detection of flag values which support
// repetitive flags
type Countable interface {
Expand Down
8 changes: 8 additions & 0 deletions flag_bool_with_inverse.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,3 +263,11 @@ func (bif *BoolWithInverseFlag) IsDefaultVisible() bool {
func (bif *BoolWithInverseFlag) TypeName() string {
return "bool"
}

func (bif *BoolWithInverseFlag) SchemaType() string {
return "boolean"
}

func (bif *BoolWithInverseFlag) SchemaItemsType() string {
return ""
}
8 changes: 8 additions & 0 deletions flag_ext.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,11 @@ func (e *extFlag) GetDefaultText() string {
func (e *extFlag) GetEnvVars() []string {
return nil
}

func (e *extFlag) SchemaType() string {
return ""
}

func (e *extFlag) SchemaItemsType() string {
return ""
}
46 changes: 46 additions & 0 deletions flag_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"reflect"
"strings"
"time"
)

// Value represents a value as used by cli.
Expand Down Expand Up @@ -285,6 +286,51 @@ func (f *FlagBase[T, C, V]) RunAction(ctx context.Context, cmd *Command) error {
return nil
}

// SchemaType returns the JSON Schema type for the flag's value type.
func (f *FlagBase[T, C, V]) SchemaType() string {
var zero T
switch any(zero).(type) {
case bool:
return "boolean"
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
return "integer"
case float32, float64:
return "number"
case string:
return "string"
case time.Duration:
return "string"
case time.Time:
return "string"
case []string, []int, []int8, []int16, []int32, []int64,
[]uint, []uint8, []uint16, []uint32, []uint64,
[]float32, []float64:
return "array"
case map[string]string:
return "object"
default:
return ""
}
}

// SchemaItemsType returns the JSON Schema element type for slice flags.
func (f *FlagBase[T, C, V]) SchemaItemsType() string {
var zero T
t := reflect.TypeOf(zero)
if t.Kind() == reflect.Slice {
switch t.Elem().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return "integer"
case reflect.Float32, reflect.Float64:
return "number"
case reflect.String:
return "string"
}
}
return ""
}

// IsMultiValueFlag returns true if the value type T can take multiple
// values from cmd line. This is true for slice and map type flags
func (f *FlagBase[T, C, VC]) IsMultiValueFlag() bool {
Expand Down
161 changes: 161 additions & 0 deletions flag_schema_type_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package cli

import (
"testing"
"time"

"github.com/stretchr/testify/assert"
)

func TestFlag_SchemaType_Bool(t *testing.T) {
f := &BoolFlag{}
st, ok := any(f).(SchemaTyper)
assert.True(t, ok)
assert.Equal(t, "boolean", st.SchemaType())
_, ok = any(f).(SchemaItemsTyper)
assert.True(t, ok)
}

func TestFlag_SchemaType_String(t *testing.T) {
f := &StringFlag{}
st, ok := any(f).(SchemaTyper)
assert.True(t, ok)
assert.Equal(t, "string", st.SchemaType())
_, ok = any(f).(SchemaItemsTyper)
assert.True(t, ok)
}

func TestFlag_SchemaType_Int(t *testing.T) {
flags := []Flag{&IntFlag{}, &Int8Flag{}, &Int16Flag{}, &Int32Flag{}, &Int64Flag{}}
for _, f := range flags {
st, ok := f.(SchemaTyper)
assert.True(t, ok)
assert.Equal(t, "integer", st.SchemaType())
}
}

func TestFlag_SchemaType_Uint(t *testing.T) {
flags := []Flag{&UintFlag{}, &Uint8Flag{}, &Uint16Flag{}, &Uint32Flag{}, &Uint64Flag{}}
for _, f := range flags {
st, ok := f.(SchemaTyper)
assert.True(t, ok)
assert.Equal(t, "integer", st.SchemaType())
}
}

func TestFlag_SchemaType_Float(t *testing.T) {
flags := []Flag{&FloatFlag{}, &Float32Flag{}, &Float64Flag{}}
for _, f := range flags {
st, ok := f.(SchemaTyper)
assert.True(t, ok)
assert.Equal(t, "number", st.SchemaType())
}
}

func TestFlag_SchemaType_Duration(t *testing.T) {
f := &DurationFlag{}
st, ok := any(f).(SchemaTyper)
assert.True(t, ok)
assert.Equal(t, "string", st.SchemaType())
}

func TestFlag_SchemaType_Timestamp(t *testing.T) {
f := &TimestampFlag{}
st, ok := any(f).(SchemaTyper)
assert.True(t, ok)
assert.Equal(t, "string", st.SchemaType())
}

func TestFlag_SchemaType_Slice(t *testing.T) {
flags := []Flag{
&StringSliceFlag{},
&IntSliceFlag{},
&FloatSliceFlag{},
}
for _, f := range flags {
st, ok := f.(SchemaTyper)
assert.True(t, ok)
assert.Equal(t, "array", st.SchemaType())
}
}

func TestFlag_SchemaItemsType_Slice(t *testing.T) {
tests := []struct {
flag Flag
itemType string
}{
{&StringSliceFlag{}, "string"},
{&IntSliceFlag{}, "integer"},
{&Int8SliceFlag{}, "integer"},
{&Int16SliceFlag{}, "integer"},
{&Int32SliceFlag{}, "integer"},
{&Int64SliceFlag{}, "integer"},
{&UintSliceFlag{}, "integer"},
{&Uint8SliceFlag{}, "integer"},
{&Uint16SliceFlag{}, "integer"},
{&Uint32SliceFlag{}, "integer"},
{&Uint64SliceFlag{}, "integer"},
{&FloatSliceFlag{}, "number"},
{&Float32SliceFlag{}, "number"},
{&Float64SliceFlag{}, "number"},
}
for _, tc := range tests {
t.Run("", func(t *testing.T) {
st, ok := tc.flag.(SchemaItemsTyper)
assert.True(t, ok)
assert.Equal(t, tc.itemType, st.SchemaItemsType())
})
}
}

func TestFlag_SchemaType_Map(t *testing.T) {
f := &StringMapFlag{}
st, ok := any(f).(SchemaTyper)
assert.True(t, ok)
assert.Equal(t, "object", st.SchemaType())
sit, ok := any(f).(SchemaItemsTyper)
assert.True(t, ok)
assert.Equal(t, "", sit.SchemaItemsType())
}

func TestFlag_SchemaType_Generic(t *testing.T) {
f := &GenericFlag{}
st, ok := any(f).(SchemaTyper)
assert.True(t, ok)
assert.Equal(t, "", st.SchemaType())
}

func TestFlag_SchemaType_BoolWithInverse(t *testing.T) {
f := &BoolWithInverseFlag{}
st, ok := any(f).(SchemaTyper)
assert.True(t, ok)
assert.Equal(t, "boolean", st.SchemaType())
sit, ok := any(f).(SchemaItemsTyper)
assert.True(t, ok)
assert.Equal(t, "", sit.SchemaItemsType())
}

func TestFlag_SchemaType_NonSliceItemsType(t *testing.T) {
flags := []Flag{
&BoolFlag{},
&StringFlag{},
&IntFlag{},
&FloatFlag{},
&DurationFlag{},
&TimestampFlag{},
}
for _, f := range flags {
sit, ok := f.(SchemaItemsTyper)
assert.True(t, ok)
assert.Equal(t, "", sit.SchemaItemsType())
}
}

func TestFlag_SchemaType_PreservesPrecision(t *testing.T) {
created := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
f := &TimestampFlag{Config: TimestampConfig{Layouts: []string{time.RFC3339}}, Value: created}
assert.Equal(t, "string", f.SchemaType())

f2 := &DurationFlag{Value: 5 * time.Second}
assert.Equal(t, "string", f2.SchemaType())
}
3 changes: 3 additions & 0 deletions flag_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3312,6 +3312,9 @@ func TestExtFlag(t *testing.T) {
assert.Equal(t, "11", extF.GetValue())
assert.Equal(t, "10", extF.GetDefaultText())
assert.Nil(t, extF.GetEnvVars())

assert.Equal(t, "", extF.SchemaType())
assert.Equal(t, "", extF.SchemaItemsType())
}

func TestSliceValuesNil(t *testing.T) {
Expand Down
27 changes: 27 additions & 0 deletions godoc-current.txt
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,10 @@ func (bif *BoolWithInverseFlag) PreParse() error

func (bif *BoolWithInverseFlag) RunAction(ctx context.Context, cmd *Command) error

func (bif *BoolWithInverseFlag) SchemaItemsType() string

func (bif *BoolWithInverseFlag) SchemaType() string

func (bif *BoolWithInverseFlag) Set(name, val string) error

func (bif *BoolWithInverseFlag) SetCategory(c string)
Expand Down Expand Up @@ -1032,6 +1036,12 @@ func (f *FlagBase[T, C, V]) PreParse() error
func (f *FlagBase[T, C, V]) RunAction(ctx context.Context, cmd *Command) error
RunAction executes flag action if set

func (f *FlagBase[T, C, V]) SchemaItemsType() string
SchemaItemsType returns the JSON Schema element type for slice flags.

func (f *FlagBase[T, C, V]) SchemaType() string
SchemaType returns the JSON Schema type for the flag's value type.

func (f *FlagBase[T, C, V]) Set(_ string, val string) error
Set applies given value from string

Expand Down Expand Up @@ -1296,6 +1306,23 @@ type RequiredFlag interface {
it allows flags required flags to be backwards compatible with the Flag
interface

type SchemaItemsTyper interface {
// SchemaItemsType returns the JSON Schema type of elements for
// array-type flags. Returns "" for single-value or object flags.
SchemaItemsType() string
}
SchemaItemsTyper is an optional interface for multi-value flags that can
report the JSON Schema type of their elements.

type SchemaTyper interface {
// SchemaType returns the JSON Schema type name for the value this
// flag accepts: "boolean", "integer", "number", "string", "array",
// "object". Returns "" if the flag does not map cleanly.
SchemaType() string
}
SchemaTyper is an optional interface for flags that can report their JSON
Schema type for programmatic introspection.

type Serializer interface {
Serialize() string
}
Expand Down
27 changes: 27 additions & 0 deletions testdata/godoc-v3.x.txt
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,10 @@ func (bif *BoolWithInverseFlag) PreParse() error

func (bif *BoolWithInverseFlag) RunAction(ctx context.Context, cmd *Command) error

func (bif *BoolWithInverseFlag) SchemaItemsType() string

func (bif *BoolWithInverseFlag) SchemaType() string

func (bif *BoolWithInverseFlag) Set(name, val string) error

func (bif *BoolWithInverseFlag) SetCategory(c string)
Expand Down Expand Up @@ -1032,6 +1036,12 @@ func (f *FlagBase[T, C, V]) PreParse() error
func (f *FlagBase[T, C, V]) RunAction(ctx context.Context, cmd *Command) error
RunAction executes flag action if set

func (f *FlagBase[T, C, V]) SchemaItemsType() string
SchemaItemsType returns the JSON Schema element type for slice flags.

func (f *FlagBase[T, C, V]) SchemaType() string
SchemaType returns the JSON Schema type for the flag's value type.

func (f *FlagBase[T, C, V]) Set(_ string, val string) error
Set applies given value from string

Expand Down Expand Up @@ -1296,6 +1306,23 @@ type RequiredFlag interface {
it allows flags required flags to be backwards compatible with the Flag
interface

type SchemaItemsTyper interface {
// SchemaItemsType returns the JSON Schema type of elements for
// array-type flags. Returns "" for single-value or object flags.
SchemaItemsType() string
}
SchemaItemsTyper is an optional interface for multi-value flags that can
report the JSON Schema type of their elements.

type SchemaTyper interface {
// SchemaType returns the JSON Schema type name for the value this
// flag accepts: "boolean", "integer", "number", "string", "array",
// "object". Returns "" if the flag does not map cleanly.
SchemaType() string
}
SchemaTyper is an optional interface for flags that can report their JSON
Schema type for programmatic introspection.

type Serializer interface {
Serialize() string
}
Expand Down
Loading