form

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 4, 2024 License: MIT Imports: 7 Imported by: 0

README

Form

A Go library for parsing HTTP form data into struct fields with concrete types, including support for dynamic form data.

  • Parse Form Data into Structs: Parse HTTP form data into Go structs with concrete types.
  • Support for Dynamic Fields: Parse dynamic form data into a map[string]<type> embedded in structs.
  • Reduce Boilerplate: Simplify development by removing manual type conversions in HTTP handlers.

Installation

To install the library, run:

go get github.com/apt304/form

Usage

Here's an example to get started:

package main

import (
	"fmt"
	"net/http"

	"github.com/apt304/form"
)

type SampleForm struct {
	ID          int               `form:"id"`
	DynamicData map[string]string `form:"dynamicData"`
}

func handler(w http.ResponseWriter, r *http.Request) {
	err := r.ParseForm()
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	var sample SampleForm
	err = form.Unmarshal(r.Form, &sample)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	encodedForm, err := form.Marshal(sample)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	fmt.Fprintf(w, "Parsed form: %+v\nEncoded form: %+v", sample, encodedForm)
}

func main() {
	http.HandleFunc("/submit", handler)
	http.ListenAndServe(":8080", nil)
}

Form data is flat, with key/value pairings: field = val, where field is matched to a struct tag. Forms allow the same key to be reused: field = val1, field = val2. Multiple values can be handled by using a slice in the struct. Dynamic values are encoded in the keys with a field[key] = val syntax. Use map[string]<type> as the struct type to unmarshal these dynamic pairs.

These form values:
    id:               "123"
    dynamicData[one]: "val1"
    dynamicData[two]: "val2"

Unmarshal into:
    SampleForm {
        ID: 123,
        DynamicData: map[string]string{
            "one": "val1",
            "two": "val2"
        }
    }

You can send forms to the above web server like so:

curl \
    -X POST localhost:8080/submit \
    --data-urlencode "id=4" \
    --data-urlencode "dynamicData[first]=tavish" \
    --data-urlencode "dynamicData[last]=degroot"

# => Parsed form: {ID:4 DynamicData:map[first:tavish last:degroot]}
# => Encoded form: map[dynamicData[first]:[tavish] dynamicData[last]:[degroot] id:[4]]

Undefined keys are ignored:

curl \
    -X POST localhost:8080/submit \
    --data-urlencode "id=4" \
    --data-urlencode "dynamicData[first]=tavish" \
    --data-urlencode "dynamicData[last]=degroot" \
    --data-urlencode "city=ullapool" # Not supposed to be there!
 
# => Parsed form: {ID:4 DynamicData:map[first:tavish last:degroot]}
# => Encoded form: map[dynamicData[first]:[tavish] dynamicData[last]:[degroot] id:[4]]

Comparison to gorilla/schema

gorilla/schema enables marshaling and unmarshaling form values to and from typed structs. However, it does not support dynamic fields that map key/value pairs. This library was created to expand on gorilla/schema's base functionality by supporting typed struct conversion, as well as dynamic data pairs.

Benchmarks

This library decodes and encodes form values to and from structs. Performance for flat struct processing is compared to gorilla/schema.

  • Decode: apt304/form decodes form values into structs faster than gorilla/schema and has fewer memory allocations.
  • Encode: Both libraries have comparable speed when encoding structs into form values. gorilla/schema encodes with fewer memory allocations.
go test -bench=. -benchtime 10s -benchmem
goos: darwin
goarch: arm64
pkg: github.com/apt304/form
BenchmarkDecode-10                      	10825464	      1083   ns/op	     640 B/op	      17 allocs/op
BenchmarkGorillaSchemaDecode-10         	 5848353	      2057   ns/op	    1104 B/op	      49 allocs/op
BenchmarkDecodeLarge-10                 	 3880474	      3120   ns/op	     480 B/op	      40 allocs/op
BenchmarkGorillaSchemaDecodeLarge-10    	 1000000	     11523   ns/op	    3392 B/op	     184 allocs/op
BenchmarkEncode-10                      	12548168	       939.4 ns/op	     782 B/op	      22 allocs/op
BenchmarkGorillaSchemaEncode-10         	13078416	       922.2 ns/op	     776 B/op	      19 allocs/op
BenchmarkEncodeLarge-10                 	 2776134	      4310   ns/op	    4196 B/op	      81 allocs/op
BenchmarkGorillaSchemaEncodeLarge-10    	 2687842	      4424   ns/op	    3831 B/op	      66 allocs/op
PASS
ok  	github.com/apt304/form	113.075s

Benchmark run on Macbook Pro M1 Max

Contributions

Contributions are welcome! Feel free to open issues or submit pull requests. For more information, please see CONTRIBUTING.md.

License

This library is licensed under the MIT License. See LICENSE for more details.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Marshal

func Marshal(src any) (map[string][]string, error)

Marshal serializes the provided struct into a map containing form values. `src` is the struct to be serialized, and the resulting map is returned.

Example:

var data SampleForm
formData, err := form.Marshal(data)
if err != nil { ...	}

func Unmarshal

func Unmarshal(src map[string][]string, dest any) error

Unmarshal iterates over the fields in `dest`, populating them with the appropriate fields from the provided source map. `src` is a map containing form values, and `dest` is a pointer to the struct that will be populated.

Example:

var r *http.Request
err := r.ParseForm()
if err != nil { ... }

var submission SampleForm
err := form.Unmarshal(r.Form, &submission)
if err != nil { ... }

Form data is flat, with key/value pairings: `field = val`, where `field` is matched to a struct tag. Forms allow the same key to be reused: `field = val1, field = val2`. Multiple values can be handled by using a slice in the struct. Dynamic values are encoded in the keys with a `field[key] = val` syntax. Use `map[string]<type>` as the struct type to unmarshal these dynamic pairs.

If multiple form values are provided for a field, parse all values. If the value is not a slice, the first form value is set to the struct's field.

Types

type Decoder

type Decoder struct {
	// contains filtered or unexported fields
}

Decoder is responsible for decoding form data from the source map to the provided destination struct.

func NewDecoder

func NewDecoder(src map[string][]string) *Decoder

NewDecoder creates a new Decoder instance with the given source form data.

func (*Decoder) Decode

func (d *Decoder) Decode(dest any) error

Decode decodes the form data into the provided destination struct by iterating over the fields in `dest`. The `dest` must be a pointer to a struct.

type Encoder

type Encoder struct {
	// contains filtered or unexported fields
}

Encoder is responsible for encoding struct data into form values.

func NewEncoder

func NewEncoder(dest map[string][]string) *Encoder

NewEncoder creates a new Encoder instance with the given destination map.

func (*Encoder) Encode

func (e *Encoder) Encode(src any) error

Encode serializes the provided struct into the destination map. The `src` must be a struct or a pointer to a struct.

type ErrorDecode

type ErrorDecode struct {
	// contains filtered or unexported fields
}

ErrorDecode represents an error that occurs during the decoding process.

func (ErrorDecode) Error

func (e ErrorDecode) Error() string

Error returns the error message for ErrorDecode.

type ErrorEncode

type ErrorEncode struct {
	// contains filtered or unexported fields
}

ErrorEncode represents an error that occurs during the encoding process.

func (ErrorEncode) Error

func (e ErrorEncode) Error() string

Error returns the error message for ErrorEncode.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL