feat(form): Support default values for collections in form binding (#4048)
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				CodeQL / Analyze (go) (push) Has been cancelled
				
			
		
			
				
	
				Run Tests / lint (push) Has been cancelled
				
			
		
			
				
	
				Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.21, ~/.cache/go-build, ubuntu-latest, ) (push) Has been cancelled
				
			
		
			
				
	
				Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.21, ~/.cache/go-build, ubuntu-latest, -race) (push) Has been cancelled
				
			
		
			
				
	
				Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.21, ~/.cache/go-build, ubuntu-latest, -tags "sonic avx") (push) Has been cancelled
				
			
		
			
				
	
				Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.21, ~/.cache/go-build, ubuntu-latest, -tags go_json) (push) Has been cancelled
				
			
		
			
				
	
				Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.21, ~/.cache/go-build, ubuntu-latest, -tags nomsgpack) (push) Has been cancelled
				
			
		
			
				
	
				Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.21, ~/Library/Caches/go-build, macos-latest, ) (push) Has been cancelled
				
			
		
			
				
	
				Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.21, ~/Library/Caches/go-build, macos-latest, -race) (push) Has been cancelled
				
			
		
			
				
	
				Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.21, ~/Library/Caches/go-build, macos-latest, -tags "sonic avx") (push) Has been cancelled
				
			
		
			
				
	
				Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.21, ~/Library/Caches/go-build, macos-latest, -tags go_json) (push) Has been cancelled
				
			
		
			
				
	
				Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.21, ~/Library/Caches/go-build, macos-latest, -tags nomsgpack) (push) Has been cancelled
				
			
		
			
				
	
				Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.22, ~/.cache/go-build, ubuntu-latest, ) (push) Has been cancelled
				
			
		
			
				
	
				Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.22, ~/.cache/go-build, ubuntu-latest, -race) (push) Has been cancelled
				
			
		
			
				
	
				Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.22, ~/.cache/go-build, ubuntu-latest, -tags "sonic avx") (push) Has been cancelled
				
			
		
			
				
	
				Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.22, ~/.cache/go-build, ubuntu-latest, -tags go_json) (push) Has been cancelled
				
			
		
			
				
	
				Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.22, ~/.cache/go-build, ubuntu-latest, -tags nomsgpack) (push) Has been cancelled
				
			
		
			
				
	
				Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.22, ~/Library/Caches/go-build, macos-latest, ) (push) Has been cancelled
				
			
		
			
				
	
				Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.22, ~/Library/Caches/go-build, macos-latest, -race) (push) Has been cancelled
				
			
		
			
				
	
				Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.22, ~/Library/Caches/go-build, macos-latest, -tags "sonic avx") (push) Has been cancelled
				
			
		
			
				
	
				Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.22, ~/Library/Caches/go-build, macos-latest, -tags go_json) (push) Has been cancelled
				
			
		
			
				
	
				Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.22, ~/Library/Caches/go-build, macos-latest, -tags nomsgpack) (push) Has been cancelled
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	CodeQL / Analyze (go) (push) Has been cancelled
				
			Run Tests / lint (push) Has been cancelled
				
			Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.21, ~/.cache/go-build, ubuntu-latest, ) (push) Has been cancelled
				
			Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.21, ~/.cache/go-build, ubuntu-latest, -race) (push) Has been cancelled
				
			Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.21, ~/.cache/go-build, ubuntu-latest, -tags "sonic avx") (push) Has been cancelled
				
			Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.21, ~/.cache/go-build, ubuntu-latest, -tags go_json) (push) Has been cancelled
				
			Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.21, ~/.cache/go-build, ubuntu-latest, -tags nomsgpack) (push) Has been cancelled
				
			Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.21, ~/Library/Caches/go-build, macos-latest, ) (push) Has been cancelled
				
			Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.21, ~/Library/Caches/go-build, macos-latest, -race) (push) Has been cancelled
				
			Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.21, ~/Library/Caches/go-build, macos-latest, -tags "sonic avx") (push) Has been cancelled
				
			Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.21, ~/Library/Caches/go-build, macos-latest, -tags go_json) (push) Has been cancelled
				
			Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.21, ~/Library/Caches/go-build, macos-latest, -tags nomsgpack) (push) Has been cancelled
				
			Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.22, ~/.cache/go-build, ubuntu-latest, ) (push) Has been cancelled
				
			Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.22, ~/.cache/go-build, ubuntu-latest, -race) (push) Has been cancelled
				
			Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.22, ~/.cache/go-build, ubuntu-latest, -tags "sonic avx") (push) Has been cancelled
				
			Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.22, ~/.cache/go-build, ubuntu-latest, -tags go_json) (push) Has been cancelled
				
			Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.22, ~/.cache/go-build, ubuntu-latest, -tags nomsgpack) (push) Has been cancelled
				
			Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.22, ~/Library/Caches/go-build, macos-latest, ) (push) Has been cancelled
				
			Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.22, ~/Library/Caches/go-build, macos-latest, -race) (push) Has been cancelled
				
			Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.22, ~/Library/Caches/go-build, macos-latest, -tags "sonic avx") (push) Has been cancelled
				
			Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.22, ~/Library/Caches/go-build, macos-latest, -tags go_json) (push) Has been cancelled
				
			Run Tests / ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} (1.22, ~/Library/Caches/go-build, macos-latest, -tags nomsgpack) (push) Has been cancelled
				
			This commit is contained in:
		@ -159,6 +159,14 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter
 | 
			
		||||
		if k, v := head(opt, "="); k == "default" {
 | 
			
		||||
			setOpt.isDefaultExists = true
 | 
			
		||||
			setOpt.defaultValue = v
 | 
			
		||||
 | 
			
		||||
			// convert semicolon-separated default values to csv-separated values for processing in setByForm
 | 
			
		||||
			if field.Type.Kind() == reflect.Slice || field.Type.Kind() == reflect.Array {
 | 
			
		||||
				cfTag := field.Tag.Get("collection_format")
 | 
			
		||||
				if cfTag == "" || cfTag == "multi" || cfTag == "csv" {
 | 
			
		||||
					setOpt.defaultValue = strings.ReplaceAll(v, ";", ",")
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -224,6 +232,12 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][
 | 
			
		||||
	case reflect.Slice:
 | 
			
		||||
		if !ok {
 | 
			
		||||
			vs = []string{opt.defaultValue}
 | 
			
		||||
 | 
			
		||||
			// pre-process the default value for multi if present
 | 
			
		||||
			cfTag := field.Tag.Get("collection_format")
 | 
			
		||||
			if cfTag == "" || cfTag == "multi" {
 | 
			
		||||
				vs = strings.Split(opt.defaultValue, ",")
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if ok, err = trySetCustom(vs[0], value); ok {
 | 
			
		||||
@ -238,6 +252,12 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][
 | 
			
		||||
	case reflect.Array:
 | 
			
		||||
		if !ok {
 | 
			
		||||
			vs = []string{opt.defaultValue}
 | 
			
		||||
 | 
			
		||||
			// pre-process the default value for multi if present
 | 
			
		||||
			cfTag := field.Tag.Get("collection_format")
 | 
			
		||||
			if cfTag == "" || cfTag == "multi" {
 | 
			
		||||
				vs = strings.Split(opt.defaultValue, ",")
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if ok, err = trySetCustom(vs[0], value); ok {
 | 
			
		||||
 | 
			
		||||
@ -323,6 +323,72 @@ func TestMappingCollectionFormat(t *testing.T) {
 | 
			
		||||
	assert.Equal(t, [2]int{1, 2}, s.ArrayPipes)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMappingCollectionFormatInvalid(t *testing.T) {
 | 
			
		||||
	var s struct {
 | 
			
		||||
		SliceCsv []int `form:"slice_csv" collection_format:"xxx"`
 | 
			
		||||
	}
 | 
			
		||||
	err := mappingByPtr(&s, formSource{
 | 
			
		||||
		"slice_csv": {"1,2"},
 | 
			
		||||
	}, "form")
 | 
			
		||||
	require.Error(t, err)
 | 
			
		||||
 | 
			
		||||
	var s2 struct {
 | 
			
		||||
		ArrayCsv [2]int `form:"array_csv" collection_format:"xxx"`
 | 
			
		||||
	}
 | 
			
		||||
	err = mappingByPtr(&s2, formSource{
 | 
			
		||||
		"array_csv": {"1,2"},
 | 
			
		||||
	}, "form")
 | 
			
		||||
	require.Error(t, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMappingMultipleDefaultWithCollectionFormat(t *testing.T) {
 | 
			
		||||
	var s struct {
 | 
			
		||||
		SliceMulti       []int     `form:",default=1;2;3" collection_format:"multi"`
 | 
			
		||||
		SliceCsv         []int     `form:",default=1;2;3" collection_format:"csv"`
 | 
			
		||||
		SliceSsv         []int     `form:",default=1 2 3" collection_format:"ssv"`
 | 
			
		||||
		SliceTsv         []int     `form:",default=1\t2\t3" collection_format:"tsv"`
 | 
			
		||||
		SlicePipes       []int     `form:",default=1|2|3" collection_format:"pipes"`
 | 
			
		||||
		ArrayMulti       [2]int    `form:",default=1;2" collection_format:"multi"`
 | 
			
		||||
		ArrayCsv         [2]int    `form:",default=1;2" collection_format:"csv"`
 | 
			
		||||
		ArraySsv         [2]int    `form:",default=1 2" collection_format:"ssv"`
 | 
			
		||||
		ArrayTsv         [2]int    `form:",default=1\t2" collection_format:"tsv"`
 | 
			
		||||
		ArrayPipes       [2]int    `form:",default=1|2" collection_format:"pipes"`
 | 
			
		||||
		SliceStringMulti []string  `form:",default=1;2;3" collection_format:"multi"`
 | 
			
		||||
		SliceStringCsv   []string  `form:",default=1;2;3" collection_format:"csv"`
 | 
			
		||||
		SliceStringSsv   []string  `form:",default=1 2 3" collection_format:"ssv"`
 | 
			
		||||
		SliceStringTsv   []string  `form:",default=1\t2\t3" collection_format:"tsv"`
 | 
			
		||||
		SliceStringPipes []string  `form:",default=1|2|3" collection_format:"pipes"`
 | 
			
		||||
		ArrayStringMulti [2]string `form:",default=1;2" collection_format:"multi"`
 | 
			
		||||
		ArrayStringCsv   [2]string `form:",default=1;2" collection_format:"csv"`
 | 
			
		||||
		ArrayStringSsv   [2]string `form:",default=1 2" collection_format:"ssv"`
 | 
			
		||||
		ArrayStringTsv   [2]string `form:",default=1\t2" collection_format:"tsv"`
 | 
			
		||||
		ArrayStringPipes [2]string `form:",default=1|2" collection_format:"pipes"`
 | 
			
		||||
	}
 | 
			
		||||
	err := mappingByPtr(&s, formSource{}, "form")
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	assert.Equal(t, []int{1, 2, 3}, s.SliceMulti)
 | 
			
		||||
	assert.Equal(t, []int{1, 2, 3}, s.SliceCsv)
 | 
			
		||||
	assert.Equal(t, []int{1, 2, 3}, s.SliceSsv)
 | 
			
		||||
	assert.Equal(t, []int{1, 2, 3}, s.SliceTsv)
 | 
			
		||||
	assert.Equal(t, []int{1, 2, 3}, s.SlicePipes)
 | 
			
		||||
	assert.Equal(t, [2]int{1, 2}, s.ArrayMulti)
 | 
			
		||||
	assert.Equal(t, [2]int{1, 2}, s.ArrayCsv)
 | 
			
		||||
	assert.Equal(t, [2]int{1, 2}, s.ArraySsv)
 | 
			
		||||
	assert.Equal(t, [2]int{1, 2}, s.ArrayTsv)
 | 
			
		||||
	assert.Equal(t, [2]int{1, 2}, s.ArrayPipes)
 | 
			
		||||
	assert.Equal(t, []string{"1", "2", "3"}, s.SliceStringMulti)
 | 
			
		||||
	assert.Equal(t, []string{"1", "2", "3"}, s.SliceStringCsv)
 | 
			
		||||
	assert.Equal(t, []string{"1", "2", "3"}, s.SliceStringSsv)
 | 
			
		||||
	assert.Equal(t, []string{"1", "2", "3"}, s.SliceStringTsv)
 | 
			
		||||
	assert.Equal(t, []string{"1", "2", "3"}, s.SliceStringPipes)
 | 
			
		||||
	assert.Equal(t, [2]string{"1", "2"}, s.ArrayStringMulti)
 | 
			
		||||
	assert.Equal(t, [2]string{"1", "2"}, s.ArrayStringCsv)
 | 
			
		||||
	assert.Equal(t, [2]string{"1", "2"}, s.ArrayStringSsv)
 | 
			
		||||
	assert.Equal(t, [2]string{"1", "2"}, s.ArrayStringTsv)
 | 
			
		||||
	assert.Equal(t, [2]string{"1", "2"}, s.ArrayStringPipes)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMappingStructField(t *testing.T) {
 | 
			
		||||
	var s struct {
 | 
			
		||||
		J struct {
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user