Skip to content

Conversation

@kankankanp
Copy link

@kankankanp kankankanp commented Oct 28, 2025

Add formbind package for nested form data binding

#137

Summary

Introduces a new formbind package to echo-contrib that provides comprehensive support for binding nested structs, arrays, and pointer fields from form data. This enhancement allows developers to seamlessly bind complex, deeply nested web form data (such as user.profile.address.street=123 Main St or items[0].name=Product A) directly to Go structs with nested slices, pointer fields, and multiple levels of depth.

Changes

  • New formbind package: Implements a complete form binding solution with recursive parsing and binding capabilities
  • Nested struct support: Handles dot notation for nested field access (e.g., user.profile.name)
  • Array/slice binding: Supports array indices with bracket notation (e.g., items[0].name, users[1].email)
  • Pointer field handling: Automatically initializes nil pointers and binds values to pointer fields
  • Group-based array parsing: Implements intelligent array grouping to prevent memory exhaustion attacks from large indices
  • Comprehensive type support: Handles all basic Go types (int, string, bool, float, time.Time) plus complex nested structures
  • Security features: Includes protection against memory exhaustion via maximum slice index limits
  • Error handling: Provides detailed error reporting with field-specific error context

Key Features

Nested Structure Binding

type User struct {
    Name    string  `form:"name"`
    Profile Profile `form:"profile"`
}

type Profile struct {
    Email   string  `form:"email"`
    Address Address `form:"address"`
}

// Binds: profile.address.street=123 Main St

Array and Slice Support

type Team struct {
    Name    string   `form:"name"`
    Members []Member `form:"members"`
}

// Binds: members[0].name=Alice&members[1].name=Bob

Pointer Field Support

type User struct {
    Name    string   `form:"name"`
    Profile *Profile `form:"profile"`
}

// Automatically initializes nil pointers

Security Features

  • Maximum slice index protection (prevents items[999999999] attacks)
  • Group-based array parsing creates compact arrays regardless of sparse indices
  • Input validation and sanitization

Benefits

  • Enhanced developer experience: Eliminates manual form data parsing for complex structures
  • Security focused: Built-in protection against common form-based attacks
  • Type safety: Leverages Go's type system for safe form data binding
  • Performance optimized: Efficient parsing with minimal memory allocation
  • Backward compatible: Does not affect existing Echo functionality
  • Comprehensive testing: 100% test coverage with extensive edge case handling

Test Plan

  • 100% test coverage: All statements covered by comprehensive test suite
  • Linting passes: All staticcheck and go vet checks pass without warnings
  • Race condition testing: No data races detected with go test -race
  • Edge case coverage: Extensive testing of error conditions, malformed input, and security scenarios
  • Manual verification: Tested with real form data scenarios including:
    • Deeply nested structures (4+ levels)
    • Arrays with sparse indices
    • Mixed pointer and value types
    • Complex real-world form structures
  • Security testing: Verified protection against memory exhaustion and malformed input
  • Performance testing: Confirmed efficient parsing of large form datasets

Examples

Basic Nested Binding

formData := url.Values{
    "name":           {"John Doe"},
    "address.street": {"123 Main St"},
    "address.city":   {"Tokyo"},
}

var user User
err := formbind.Bind(&user, formData)
// user.Address.Street = "123 Main St"
// user.Address.City = "Tokyo"

Array Binding

formData := url.Values{
    "items[0].name":  {"Product A"},
    "items[0].price": {"100"},
    "items[1].name":  {"Product B"},
    "items[1].price": {"200"},
}

var order Order
err := formbind.Bind(&order, formData)
// order.Items[0] = {Name: "Product A", Price: 100}
// order.Items[1] = {Name: "Product B", Price: 200}

Complex Nested Arrays

formData := url.Values{
    "teams[0].members[0].name": {"Alice"},
    "teams[0].members[1].name": {"Bob"},
    "teams[1].members[0].name": {"Charlie"},
}

var tournament Tournament
err := formbind.Bind(&tournament, formData)
// Correctly parses multi-dimensional nested structures

@kankankanp
Copy link
Author

kankankanp commented Oct 28, 2025

@aldas
If possible, I would be grateful if you could review this PR.
I have also addressed the parts that were reviewed on labstack/echo#2834.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant