diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 7cad4db..aa339b8 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -4,6 +4,16 @@ To ensure the continued stability of this package, tests are required that cover the change in order for a pull request to be merged. +## AI Agents + +AI-assisted contributions are welcome but must be clearly attributed. If you are an AI agent or using an AI agent to contribute: + +- **Commits**: Include a `Co-Authored-By:` trailer identifying the AI model used. +- **Pull requests and issues**: State that the content was AI-generated or AI-assisted in the description. +- **Code review comments**: Prefix with `[AI]` when posting automated review comments. + +Contributions that obscure their AI origin may be closed. + ## Reporting issues Please open an issue or join the gitter chat [![Join the chat at https://gitter.im/go-playground/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) for any issues, questions or possible enhancements to the package. diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 21627e0..9c7247b 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -8,31 +8,23 @@ jobs: test: strategy: matrix: - go-version: [1.24.x, 1.25.x] + go-version: [1.26.x, 1.27.x] os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: - - name: Install Go - uses: actions/setup-go@v5 - with: - go-version: ${{ matrix.go-version }} - - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v7 - - name: Restore Cache - uses: actions/cache@v4 + - name: Install Go + uses: actions/setup-go@v7 with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-v1-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-v1-go- + go-version: ${{ matrix.go-version }} - name: Test run: go test -race -covermode=atomic -coverprofile="profile.cov" ./... - name: Send Coverage - if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.20.x' + if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.26.x' uses: shogo82148/actions-goveralls@v1 with: path-to-profile: profile.cov @@ -41,8 +33,11 @@ jobs: name: lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 + - uses: actions/setup-go@v7 + with: + go-version: 1.26.x - name: golangci-lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v9 with: - version: latest + version: v2.13.1 diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..3e5350f --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,56 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project + +`github.com/go-playground/form/v4` — a Go library that decodes `url.Values` into Go structs and encodes Go structs back into `url.Values`. Designed for HTML form parsing. Zero non-test dependencies. Minimum Go 1.21. + +## Commands + +```bash +make # lint + test + bench +make lint # golangci-lint run --timeout 5m +make test # go test -covermode=atomic -race ./... +make bench # go test -run=NONE -bench=. -benchmem ./... + +# Single test +go test -run TestDecoderNativeTime -race -v ./... +``` + +## Architecture + +The library has symmetrical Decode and Encode pipelines sharing a reflection cache and configuration layer. + +### Core Pipeline + +1. **`form_decoder.go` / `form_encoder.go`** — Public `Decoder`/`Encoder` types, configuration methods, and entry points (`Decode`/`Encode`). These own a `sync.Pool` of worker instances (`*decoder`/`*encoder`) to avoid allocations. + +2. **`decoder.go` / `encoder.go`** — Worker logic. `traverseStruct()` walks cached struct metadata and calls `setFieldByType()`, a large `reflect.Kind` switch that handles all Go types including nested structs, slices, arrays, maps, pointers, and `time.Time` (RFC3339). + +3. **`cache.go`** — Thread-safe struct reflection cache using `atomic.Value` with copy-on-write writes. `parseStruct()` introspects fields once per type, reads the `form` tag, and sorts results (anonymous fields last). Shared by both decoder and encoder. + +4. **`form.go`** — Shared constants: `Mode` (Implicit/Explicit controls whether untagged fields are processed) and `AnonymousMode` (Embed/Separate controls whether embedded struct fields are flattened or namespaced). + +5. **`util.go`** — `ExtractType()` (exported, dereferences pointers/interfaces), `parseBool()` (extended: accepts on/off/yes/no/ok), `hasValue()` (zero-value check for omitempty). + +### Key Design Decisions + +- **Namespace paths**: Fields resolve via dot-separated paths (`Address.Name`) with bracket notation for indices/keys (`Items[0].Price`, `Map[key]`). Prefix/suffix are configurable. +- **Lazy bracket parsing**: `parseMapData()` only parses bracketed keys when encountering a slice/array/map, avoiding overhead for simple structs. +- **Error accumulation**: Decode errors are collected in `DecodeErrors` (map of namespace→error) rather than failing fast. +- **DOS protection**: `maxArraySize` (default 10000) caps dynamically-created slices/arrays. +- **Custom type functions**: `RegisterCustomTypeFunc` lets users override decode/encode for specific types. The functions receive `[]string` (decode) or `interface{}` (encode) and return parsed values. + +### Extension Points + +- `RegisterCustomTypeFunc(fn, types...)` — override decode/encode per type +- `RegisterTagNameFunc(fn)` — customize how struct tags map to field names +- `SetMode()` — `ModeExplicit` requires `form` tags on all fields +- `SetAnonymousMode()` — control embedded struct flattening (encoder only) + +## Testing + +All tests are white-box (same package). Tests use `github.com/go-playground/assert/v2` (dot-imported). `benchmarks/` contains comparative benchmarks in a separate package. `race_test.go`/`norace_test.go` provide a build-tagged `raceEnabled` constant. + +Both `Decoder` and `Encoder` are safe for concurrent use — instantiate once, share across goroutines. diff --git a/decoder.go b/decoder.go index b0d2430..14aca2c 100644 --- a/decoder.go +++ b/decoder.go @@ -10,7 +10,7 @@ import ( ) const ( - errArraySize = "Array size of '%d' is larger than the maximum currently set on the decoder of '%d'. To increase this limit please see, SetMaxArraySize(size uint)" + errArraySize = "array size of '%d' is larger than the maximum currently set on the decoder of '%d'. To increase this limit please see, SetMaxArraySize(size uint)" errMissingStartBracket = "Invalid formatting for key '%s' missing '[' bracket" errMissingEndBracket = "Invalid formatting for key '%s' missing ']' bracket" ) @@ -218,7 +218,7 @@ func (d *decoder) setFieldByType(current reflect.Value, namespace []byte, idx in v.Set(reflect.ValueOf(arr[idx])) set = true - case reflect.Ptr: + case reflect.Pointer: newVal := reflect.New(v.Type().Elem()) if set = d.setFieldByType(newVal.Elem(), namespace, idx); set { v.Set(newVal) @@ -237,7 +237,7 @@ func (d *decoder) setFieldByType(current reflect.Value, namespace []byte, idx in } var u64 uint64 if u64, err = strconv.ParseUint(arr[idx], 10, 64); err != nil { - d.setError(namespace, fmt.Errorf("Invalid Unsigned Integer Value '%s' Type '%v' Namespace '%s'", arr[idx], v.Type(), string(namespace))) + d.setError(namespace, fmt.Errorf("invalid unsigned integer value '%s' type '%v' namespace '%s'", arr[idx], v.Type(), string(namespace))) return } v.SetUint(u64) @@ -249,7 +249,7 @@ func (d *decoder) setFieldByType(current reflect.Value, namespace []byte, idx in } var u64 uint64 if u64, err = strconv.ParseUint(arr[idx], 10, 8); err != nil { - d.setError(namespace, fmt.Errorf("Invalid Unsigned Integer Value '%s' Type '%v' Namespace '%s'", arr[idx], v.Type(), string(namespace))) + d.setError(namespace, fmt.Errorf("invalid unsigned integer value '%s' type '%v' namespace '%s'", arr[idx], v.Type(), string(namespace))) return } v.SetUint(u64) @@ -261,7 +261,7 @@ func (d *decoder) setFieldByType(current reflect.Value, namespace []byte, idx in } var u64 uint64 if u64, err = strconv.ParseUint(arr[idx], 10, 16); err != nil { - d.setError(namespace, fmt.Errorf("Invalid Unsigned Integer Value '%s' Type '%v' Namespace '%s'", arr[idx], v.Type(), string(namespace))) + d.setError(namespace, fmt.Errorf("invalid unsigned integer value '%s' type '%v' namespace '%s'", arr[idx], v.Type(), string(namespace))) return } v.SetUint(u64) @@ -273,7 +273,7 @@ func (d *decoder) setFieldByType(current reflect.Value, namespace []byte, idx in } var u64 uint64 if u64, err = strconv.ParseUint(arr[idx], 10, 32); err != nil { - d.setError(namespace, fmt.Errorf("Invalid Unsigned Integer Value '%s' Type '%v' Namespace '%s'", arr[idx], v.Type(), string(namespace))) + d.setError(namespace, fmt.Errorf("invalid unsigned integer value '%s' type '%v' namespace '%s'", arr[idx], v.Type(), string(namespace))) return } v.SetUint(u64) @@ -285,7 +285,7 @@ func (d *decoder) setFieldByType(current reflect.Value, namespace []byte, idx in } var i64 int64 if i64, err = strconv.ParseInt(arr[idx], 10, 64); err != nil { - d.setError(namespace, fmt.Errorf("Invalid Integer Value '%s' Type '%v' Namespace '%s'", arr[idx], v.Type(), string(namespace))) + d.setError(namespace, fmt.Errorf("invalid integer value '%s' type '%v' namespace '%s'", arr[idx], v.Type(), string(namespace))) return } v.SetInt(i64) @@ -297,7 +297,7 @@ func (d *decoder) setFieldByType(current reflect.Value, namespace []byte, idx in } var i64 int64 if i64, err = strconv.ParseInt(arr[idx], 10, 8); err != nil { - d.setError(namespace, fmt.Errorf("Invalid Integer Value '%s' Type '%v' Namespace '%s'", arr[idx], v.Type(), string(namespace))) + d.setError(namespace, fmt.Errorf("invalid integer value '%s' type '%v' namespace '%s'", arr[idx], v.Type(), string(namespace))) return } v.SetInt(i64) @@ -309,7 +309,7 @@ func (d *decoder) setFieldByType(current reflect.Value, namespace []byte, idx in } var i64 int64 if i64, err = strconv.ParseInt(arr[idx], 10, 16); err != nil { - d.setError(namespace, fmt.Errorf("Invalid Integer Value '%s' Type '%v' Namespace '%s'", arr[idx], v.Type(), string(namespace))) + d.setError(namespace, fmt.Errorf("invalid integer value '%s' type '%v' namespace '%s'", arr[idx], v.Type(), string(namespace))) return } v.SetInt(i64) @@ -321,7 +321,7 @@ func (d *decoder) setFieldByType(current reflect.Value, namespace []byte, idx in } var i64 int64 if i64, err = strconv.ParseInt(arr[idx], 10, 32); err != nil { - d.setError(namespace, fmt.Errorf("Invalid Integer Value '%s' Type '%v' Namespace '%s'", arr[idx], v.Type(), string(namespace))) + d.setError(namespace, fmt.Errorf("invalid integer value '%s' type '%v' namespace '%s'", arr[idx], v.Type(), string(namespace))) return } v.SetInt(i64) @@ -333,7 +333,7 @@ func (d *decoder) setFieldByType(current reflect.Value, namespace []byte, idx in } var f float64 if f, err = strconv.ParseFloat(arr[idx], 32); err != nil { - d.setError(namespace, fmt.Errorf("Invalid Float Value '%s' Type '%v' Namespace '%s'", arr[idx], v.Type(), string(namespace))) + d.setError(namespace, fmt.Errorf("invalid float value '%s' type '%v' namespace '%s'", arr[idx], v.Type(), string(namespace))) return } v.SetFloat(f) @@ -345,7 +345,7 @@ func (d *decoder) setFieldByType(current reflect.Value, namespace []byte, idx in } var f float64 if f, err = strconv.ParseFloat(arr[idx], 64); err != nil { - d.setError(namespace, fmt.Errorf("Invalid Float Value '%s' Type '%v' Namespace '%s'", arr[idx], v.Type(), string(namespace))) + d.setError(namespace, fmt.Errorf("invalid float value '%s' type '%v' namespace '%s'", arr[idx], v.Type(), string(namespace))) return } v.SetFloat(f) @@ -357,7 +357,7 @@ func (d *decoder) setFieldByType(current reflect.Value, namespace []byte, idx in } var b bool if b, err = parseBool(arr[idx]); err != nil { - d.setError(namespace, fmt.Errorf("Invalid Boolean Value '%s' Type '%v' Namespace '%s'", arr[idx], v.Type(), string(namespace))) + d.setError(namespace, fmt.Errorf("invalid boolean value '%s' type '%v' namespace '%s'", arr[idx], v.Type(), string(namespace))) return } v.SetBool(b) @@ -636,7 +636,7 @@ func (d *decoder) getMapKey(key string, current reflect.Value, namespace []byte) // say to a struct value we would not get here but kind would be struct. v.Set(reflect.ValueOf(key)) return - case reflect.Ptr: + case reflect.Pointer: newVal := reflect.New(v.Type().Elem()) if err = d.getMapKey(key, newVal.Elem(), namespace); err == nil { v.Set(newVal) @@ -649,7 +649,7 @@ func (d *decoder) getMapKey(key string, current reflect.Value, namespace []byte) u64, e := strconv.ParseUint(key, 10, 64) if e != nil { - err = fmt.Errorf("Invalid Unsigned Integer Value '%s' Type '%v' Namespace '%s'", key, v.Type(), string(namespace)) + err = fmt.Errorf("invalid unsigned integer value '%s' type '%v' namespace '%s'", key, v.Type(), string(namespace)) return } @@ -659,7 +659,7 @@ func (d *decoder) getMapKey(key string, current reflect.Value, namespace []byte) u64, e := strconv.ParseUint(key, 10, 8) if e != nil { - err = fmt.Errorf("Invalid Unsigned Integer Value '%s' Type '%v' Namespace '%s'", key, v.Type(), string(namespace)) + err = fmt.Errorf("invalid unsigned integer value '%s' type '%v' namespace '%s'", key, v.Type(), string(namespace)) return } @@ -669,7 +669,7 @@ func (d *decoder) getMapKey(key string, current reflect.Value, namespace []byte) u64, e := strconv.ParseUint(key, 10, 16) if e != nil { - err = fmt.Errorf("Invalid Unsigned Integer Value '%s' Type '%v' Namespace '%s'", key, v.Type(), string(namespace)) + err = fmt.Errorf("invalid unsigned integer value '%s' type '%v' namespace '%s'", key, v.Type(), string(namespace)) return } @@ -679,7 +679,7 @@ func (d *decoder) getMapKey(key string, current reflect.Value, namespace []byte) u64, e := strconv.ParseUint(key, 10, 32) if e != nil { - err = fmt.Errorf("Invalid Unsigned Integer Value '%s' Type '%v' Namespace '%s'", key, v.Type(), string(namespace)) + err = fmt.Errorf("invalid unsigned integer value '%s' type '%v' namespace '%s'", key, v.Type(), string(namespace)) return } @@ -689,7 +689,7 @@ func (d *decoder) getMapKey(key string, current reflect.Value, namespace []byte) i64, e := strconv.ParseInt(key, 10, 64) if e != nil { - err = fmt.Errorf("Invalid Integer Value '%s' Type '%v' Namespace '%s'", key, v.Type(), string(namespace)) + err = fmt.Errorf("invalid integer value '%s' type '%v' namespace '%s'", key, v.Type(), string(namespace)) return } @@ -699,7 +699,7 @@ func (d *decoder) getMapKey(key string, current reflect.Value, namespace []byte) i64, e := strconv.ParseInt(key, 10, 8) if e != nil { - err = fmt.Errorf("Invalid Integer Value '%s' Type '%v' Namespace '%s'", key, v.Type(), string(namespace)) + err = fmt.Errorf("invalid integer value '%s' type '%v' namespace '%s'", key, v.Type(), string(namespace)) return } @@ -709,7 +709,7 @@ func (d *decoder) getMapKey(key string, current reflect.Value, namespace []byte) i64, e := strconv.ParseInt(key, 10, 16) if e != nil { - err = fmt.Errorf("Invalid Integer Value '%s' Type '%v' Namespace '%s'", key, v.Type(), string(namespace)) + err = fmt.Errorf("invalid integer value '%s' type '%v' namespace '%s'", key, v.Type(), string(namespace)) return } @@ -719,7 +719,7 @@ func (d *decoder) getMapKey(key string, current reflect.Value, namespace []byte) i64, e := strconv.ParseInt(key, 10, 32) if e != nil { - err = fmt.Errorf("Invalid Integer Value '%s' Type '%v' Namespace '%s'", key, v.Type(), string(namespace)) + err = fmt.Errorf("invalid integer value '%s' type '%v' namespace '%s'", key, v.Type(), string(namespace)) return } @@ -729,7 +729,7 @@ func (d *decoder) getMapKey(key string, current reflect.Value, namespace []byte) f, e := strconv.ParseFloat(key, 32) if e != nil { - err = fmt.Errorf("Invalid Float Value '%s' Type '%v' Namespace '%s'", key, v.Type(), string(namespace)) + err = fmt.Errorf("invalid float value '%s' type '%v' namespace '%s'", key, v.Type(), string(namespace)) return } @@ -739,7 +739,7 @@ func (d *decoder) getMapKey(key string, current reflect.Value, namespace []byte) f, e := strconv.ParseFloat(key, 64) if e != nil { - err = fmt.Errorf("Invalid Float Value '%s' Type '%v' Namespace '%s'", key, v.Type(), string(namespace)) + err = fmt.Errorf("invalid float value '%s' type '%v' namespace '%s'", key, v.Type(), string(namespace)) return } @@ -749,14 +749,14 @@ func (d *decoder) getMapKey(key string, current reflect.Value, namespace []byte) b, e := parseBool(key) if e != nil { - err = fmt.Errorf("Invalid Boolean Value '%s' Type '%v' Namespace '%s'", key, v.Type(), string(namespace)) + err = fmt.Errorf("invalid boolean value '%s' type '%v' namespace '%s'", key, v.Type(), string(namespace)) return } v.SetBool(b) default: - err = fmt.Errorf("Unsupported Map Key '%s', Type '%v' Namespace '%s'", key, v.Type(), string(namespace)) + err = fmt.Errorf("unsupported map key '%s', type '%v' namespace '%s'", key, v.Type(), string(namespace)) } return diff --git a/decoder_test.go b/decoder_test.go index 7086a93..db121df 100644 --- a/decoder_test.go +++ b/decoder_test.go @@ -961,37 +961,37 @@ func TestDecoderErrors(t *testing.T) { Equal(t, len(err), 30) k := err["bool"] - Equal(t, k.Error(), "Invalid Boolean Value 'uh-huh' Type 'bool' Namespace 'bool'") + Equal(t, k.Error(), "invalid boolean value 'uh-huh' type 'bool' namespace 'bool'") k = err["Int"] - Equal(t, k.Error(), "Invalid Integer Value 'bad' Type 'int' Namespace 'Int'") + Equal(t, k.Error(), "invalid integer value 'bad' type 'int' namespace 'Int'") k = err["Int8"] - Equal(t, k.Error(), "Invalid Integer Value 'bad' Type 'int8' Namespace 'Int8'") + Equal(t, k.Error(), "invalid integer value 'bad' type 'int8' namespace 'Int8'") k = err["Int16"] - Equal(t, k.Error(), "Invalid Integer Value 'bad' Type 'int16' Namespace 'Int16'") + Equal(t, k.Error(), "invalid integer value 'bad' type 'int16' namespace 'Int16'") k = err["Int32"] - Equal(t, k.Error(), "Invalid Integer Value 'bad' Type 'int32' Namespace 'Int32'") + Equal(t, k.Error(), "invalid integer value 'bad' type 'int32' namespace 'Int32'") k = err["Uint"] - Equal(t, k.Error(), "Invalid Unsigned Integer Value 'bad' Type 'uint' Namespace 'Uint'") + Equal(t, k.Error(), "invalid unsigned integer value 'bad' type 'uint' namespace 'Uint'") k = err["Uint8"] - Equal(t, k.Error(), "Invalid Unsigned Integer Value 'bad' Type 'uint8' Namespace 'Uint8'") + Equal(t, k.Error(), "invalid unsigned integer value 'bad' type 'uint8' namespace 'Uint8'") k = err["Uint16"] - Equal(t, k.Error(), "Invalid Unsigned Integer Value 'bad' Type 'uint16' Namespace 'Uint16'") + Equal(t, k.Error(), "invalid unsigned integer value 'bad' type 'uint16' namespace 'Uint16'") k = err["Uint32"] - Equal(t, k.Error(), "Invalid Unsigned Integer Value 'bad' Type 'uint32' Namespace 'Uint32'") + Equal(t, k.Error(), "invalid unsigned integer value 'bad' type 'uint32' namespace 'Uint32'") k = err["Float32"] - Equal(t, k.Error(), "Invalid Float Value 'bad' Type 'float32' Namespace 'Float32'") + Equal(t, k.Error(), "invalid float value 'bad' type 'float32' namespace 'Float32'") k = err["Float64"] - Equal(t, k.Error(), "Invalid Float Value 'bad' Type 'float64' Namespace 'Float64'") + Equal(t, k.Error(), "invalid float value 'bad' type 'float64' namespace 'Float64'") k = err["String"] Equal(t, k.Error(), "Bad Type Conversion") @@ -1000,49 +1000,49 @@ func TestDecoderErrors(t *testing.T) { Equal(t, k.Error(), "parsing time \"bad\" as \"2006-01-02T15:04:05Z07:00\": cannot parse \"bad\" as \"2006\"") k = err["MapBadIntKey"] - Equal(t, k.Error(), "Invalid Integer Value 'key' Type 'int' Namespace 'MapBadIntKey'") + Equal(t, k.Error(), "invalid integer value 'key' type 'int' namespace 'MapBadIntKey'") k = err["MapBadInt8Key"] - Equal(t, k.Error(), "Invalid Integer Value 'key' Type 'int8' Namespace 'MapBadInt8Key'") + Equal(t, k.Error(), "invalid integer value 'key' type 'int8' namespace 'MapBadInt8Key'") k = err["MapBadInt16Key"] - Equal(t, k.Error(), "Invalid Integer Value 'key' Type 'int16' Namespace 'MapBadInt16Key'") + Equal(t, k.Error(), "invalid integer value 'key' type 'int16' namespace 'MapBadInt16Key'") k = err["MapBadInt32Key"] - Equal(t, k.Error(), "Invalid Integer Value 'key' Type 'int32' Namespace 'MapBadInt32Key'") + Equal(t, k.Error(), "invalid integer value 'key' type 'int32' namespace 'MapBadInt32Key'") k = err["MapBadUintKey"] - Equal(t, k.Error(), "Invalid Unsigned Integer Value 'key' Type 'uint' Namespace 'MapBadUintKey'") + Equal(t, k.Error(), "invalid unsigned integer value 'key' type 'uint' namespace 'MapBadUintKey'") k = err["MapBadUint8Key"] - Equal(t, k.Error(), "Invalid Unsigned Integer Value 'key' Type 'uint8' Namespace 'MapBadUint8Key'") + Equal(t, k.Error(), "invalid unsigned integer value 'key' type 'uint8' namespace 'MapBadUint8Key'") k = err["MapBadUint16Key"] - Equal(t, k.Error(), "Invalid Unsigned Integer Value 'key' Type 'uint16' Namespace 'MapBadUint16Key'") + Equal(t, k.Error(), "invalid unsigned integer value 'key' type 'uint16' namespace 'MapBadUint16Key'") k = err["MapBadUint32Key"] - Equal(t, k.Error(), "Invalid Unsigned Integer Value 'key' Type 'uint32' Namespace 'MapBadUint32Key'") + Equal(t, k.Error(), "invalid unsigned integer value 'key' type 'uint32' namespace 'MapBadUint32Key'") k = err["MapBadFloat32Key"] - Equal(t, k.Error(), "Invalid Float Value 'key' Type 'float32' Namespace 'MapBadFloat32Key'") + Equal(t, k.Error(), "invalid float value 'key' type 'float32' namespace 'MapBadFloat32Key'") k = err["MapBadFloat64Key"] - Equal(t, k.Error(), "Invalid Float Value 'key' Type 'float64' Namespace 'MapBadFloat64Key'") + Equal(t, k.Error(), "invalid float value 'key' type 'float64' namespace 'MapBadFloat64Key'") k = err["MapBadBoolKey"] - Equal(t, k.Error(), "Invalid Boolean Value 'uh-huh' Type 'bool' Namespace 'MapBadBoolKey'") + Equal(t, k.Error(), "invalid boolean value 'uh-huh' type 'bool' namespace 'MapBadBoolKey'") k = err["MapBadKeyType"] - Equal(t, k.Error(), "Unsupported Map Key '1.4', Type 'complex64' Namespace 'MapBadKeyType'") + Equal(t, k.Error(), "unsupported map key '1.4', type 'complex64' namespace 'MapBadKeyType'") k = err["BadArrayValue[0]"] - Equal(t, k.Error(), "Invalid Integer Value 'badintval' Type 'int' Namespace 'BadArrayValue[0]'") + Equal(t, k.Error(), "invalid integer value 'badintval' type 'int' namespace 'BadArrayValue[0]'") k = err["OverflowNilArray"] - Equal(t, k.Error(), "Array size of '1000' is larger than the maximum currently set on the decoder of '4'. To increase this limit please see, SetMaxArraySize(size uint)") + Equal(t, k.Error(), "array size of '1000' is larger than the maximum currently set on the decoder of '4'. To increase this limit please see, SetMaxArraySize(size uint)") k = err["OverFlowExistingArray"] - Equal(t, k.Error(), "Array size of '1000' is larger than the maximum currently set on the decoder of '4'. To increase this limit please see, SetMaxArraySize(size uint)") + Equal(t, k.Error(), "array size of '1000' is larger than the maximum currently set on the decoder of '4'. To increase this limit please see, SetMaxArraySize(size uint)") k = err["BadArrayIndex"] Equal(t, k.Error(), "invalid slice index 'bad index'") @@ -1068,7 +1068,7 @@ func TestDecoderErrors(t *testing.T) { NotEqual(t, e, "") k = err["BadMapKey"] - Equal(t, k.Error(), "Unsupported Map Key 'badtime', Type 'time.Time' Namespace 'BadMapKey'") + Equal(t, k.Error(), "unsupported map key 'badtime', type 'time.Time' namespace 'BadMapKey'") }) } } diff --git a/encoder.go b/encoder.go index eea0bd9..06e66ef 100644 --- a/encoder.go +++ b/encoder.go @@ -70,7 +70,7 @@ func (e *encoder) traverseStruct(v reflect.Value, namespace []byte, idx int) { func (e *encoder) setFieldByType(current reflect.Value, namespace []byte, idx int, isOmitEmpty bool) { - if idx > -1 && current.Kind() == reflect.Ptr { + if idx > -1 && current.Kind() == reflect.Pointer { namespace = append(namespace, '[') namespace = strconv.AppendInt(namespace, int64(idx), 10) namespace = append(namespace, ']') @@ -104,7 +104,7 @@ func (e *encoder) setFieldByType(current reflect.Value, namespace []byte, idx in } switch kind { - case reflect.Ptr, reflect.Interface, reflect.Invalid: + case reflect.Pointer, reflect.Interface, reflect.Invalid: return case reflect.String: @@ -233,7 +233,7 @@ func (e *encoder) getMapKey(key reflect.Value, namespace []byte) (string, bool) } switch kind { - case reflect.Interface, reflect.Ptr: + case reflect.Interface, reflect.Pointer: return "", false case reflect.String: @@ -255,7 +255,7 @@ func (e *encoder) getMapKey(key reflect.Value, namespace []byte) (string, bool) return strconv.FormatBool(v.Bool()), true default: - e.setError(namespace, fmt.Errorf("Unsupported Map Key '%v' Namespace '%s'", v.String(), namespace)) + e.setError(namespace, fmt.Errorf("unsupported map key '%v' namespace '%s'", v.String(), namespace)) return "", false } } diff --git a/encoder_test.go b/encoder_test.go index 3120efd..544aad6 100644 --- a/encoder_test.go +++ b/encoder_test.go @@ -1418,7 +1418,7 @@ func TestEncoderErrors(t *testing.T) { Equal(t, k.Error(), "Bad Type Conversion") k = ee["Struct"] - Equal(t, k.Error(), "Unsupported Map Key '' Namespace 'Struct'") + Equal(t, k.Error(), "unsupported map key '' namespace 'Struct'") } func TestEncoderPanicsAndBadValues(t *testing.T) { @@ -1539,13 +1539,13 @@ func TestOmitEmpty(t *testing.T) { } type Test struct { - String string `form:",omitempty"` - Array []string `form:",omitempty"` - Map map[string]string `form:",omitempty"` - String2 string `form:"str,omitempty"` - Array2 []string `form:"arr,omitempty"` - Map2 map[string]string `form:"map,omitempty"` - NotComparable `form:",omitempty"` + String string `form:",omitempty"` + Array []string `form:",omitempty"` + Map map[string]string `form:",omitempty"` + String2 string `form:"str,omitempty"` + Array2 []string `form:"arr,omitempty"` + Map2 map[string]string `form:"map,omitempty"` + NotComparable `form:",omitempty"` } var tst Test diff --git a/form_decoder.go b/form_decoder.go index ac131ea..8a324cd 100644 --- a/form_decoder.go +++ b/form_decoder.go @@ -40,7 +40,7 @@ func (e *InvalidDecoderError) Error() string { return "form: Decode(nil)" } - if e.Type.Kind() != reflect.Ptr { + if e.Type.Kind() != reflect.Pointer { return "form: Decode(non-pointer " + e.Type.String() + ")" } @@ -159,7 +159,7 @@ func (d *Decoder) Decode(v interface{}, values url.Values) (err error) { val := reflect.ValueOf(v) - if val.Kind() != reflect.Ptr || val.IsNil() { + if val.Kind() != reflect.Pointer || val.IsNil() { return &InvalidDecoderError{reflect.TypeOf(v)} } diff --git a/form_encoder.go b/form_encoder.go index 48960c9..0fd734e 100644 --- a/form_encoder.go +++ b/form_encoder.go @@ -131,7 +131,7 @@ func (e *Encoder) Encode(v interface{}) (values url.Values, err error) { val, kind := ExtractType(reflect.ValueOf(v)) - if kind == reflect.Ptr || kind == reflect.Interface || kind == reflect.Invalid { + if kind == reflect.Pointer || kind == reflect.Interface || kind == reflect.Invalid { return nil, &InvalidEncodeError{reflect.TypeOf(v)} } diff --git a/go.mod b/go.mod index 9f9ef4f..b59dc9c 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/go-playground/form/v4 -go 1.21 +go 1.26 retract ( v4.2.3 // For retractions and revert of that code. diff --git a/go.sum b/go.sum index 91f670f..1998fd7 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,2 @@ -github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= diff --git a/util.go b/util.go index 02c86af..2fd5234 100644 --- a/util.go +++ b/util.go @@ -10,10 +10,10 @@ import ( func ExtractType(current reflect.Value) (reflect.Value, reflect.Kind) { switch current.Kind() { - case reflect.Ptr: + case reflect.Pointer: if current.IsNil() { - return current, reflect.Ptr + return current, reflect.Pointer } return ExtractType(current.Elem()) @@ -48,7 +48,7 @@ func parseBool(str string) (bool, error) { // hasValue determines if a reflect.Value is it's default value func hasValue(field reflect.Value) bool { switch field.Kind() { - case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func: + case reflect.Slice, reflect.Map, reflect.Pointer, reflect.Interface, reflect.Chan, reflect.Func: return !field.IsNil() default: if !field.IsValid() {