Skip to main content

form

form is a directive executor who decodes/encodes a field from/to HTTP form data, i.e. http.Request.Form.

Signature

Name: "form"
Args: KEY1 [,KEY2 [,KEY3, ...]]

Decoding

form will examine values of the keys one by one (KEY1 -> KEY2 -> ...) from the form data, the first non-empty value will be used to set the corresponding field of the input struct.

caution

In most cases, the form directive just works like query. Because it also visits data from the URL querystring. But if the form data sent by the client was of application/x-www-form-urlencoded content type, form will visit the form data first, and then visit the URL querystring.

For more details, please refer to GH-5 add a query directive to pull values from querystring params #6 and the following quotes.

From the Go documentation:

Form contains the parsed form data, including both the URL field's query parameters and the PATCH, POST, or PUT form data. This field is only available after ParseForm is called.

And for http.Request.ParseForm, it is:

ParseForm populates r.Form and r.PostForm. For all requests, ParseForm parses the raw query from the URL and updates r.Form. For POST, PUT, and PATCH requests, it also reads the request body, parses it as a form and puts the results into both r.PostForm and r.Form. Request body parameters take precedence over URL query string values in r.Form.

Usage

type Profile struct {
Role string `in:"form=role"`
Hireable bool `in:"form=hireable"`
}
Request (Mixed, Body + URL query)Profile
GET /users?role=backend&hireable=true
{
Role: "backend",
Hireable: true,
}

// works just like query directive
POST /users HTTP/1.1
Host: foo.example
Content-Type: application/x-www-form-urlencoded

role=frontend&hireable=false
{
Role: "frontend",
Hireable: false,
}
POST /users?hireable=true HTTP/1.1
Host: foo.example
Content-Type: application/x-www-form-urlencoded

role=frontend&hireable=false
{
Role: "frontend",
Hireable: false,
}

// Hireable in the body overrides the URL query.
POST /users?hireable=true HTTP/1.1
Host: foo.example
Content-Type: application/x-www-form-urlencoded

role=frontend
{
Role: "frontend",
Hireable: true,
}

// Hireable is from URL query.

Runable Example

package main

import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/url"
"strings"

"github.com/ggicci/httpin"
"github.com/go-chi/chi/v5"
)

type UpdateAccountInput struct {
AccessToken string `in:"form=access_token"`
Bio string `in:"form=bio"`
}

func UpdateAccount(rw http.ResponseWriter, r *http.Request) {
input := r.Context().Value(httpin.Input).(*UpdateAccountInput)
fmt.Printf("input: %#v\n", input)
}

func main() {
router := chi.NewRouter()
router.With(
httpin.NewInput(UpdateAccountInput{}),
).Patch("/me", UpdateAccount)

// NOTE: form directive also visits querystring
r, _ := http.NewRequest("PATCH", "/me?access_token=rainbow", nil)
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
form := make(url.Values)
form.Add("bio", "ありがどう")
r.Body = io.NopCloser(strings.NewReader(form.Encode()))

rw := httptest.NewRecorder()
router.ServeHTTP(rw, r)
}

Output:

input: &main.UpdateAccountInput{AccessToken:"rainbow", Bio:"ありがどう"}

Encoding

form will encode the field value to the form data with the key specified in the directive argument, the first key will be used, i.e. KEY1, results in http.Request.Form[KEY1] = VALUE.

Runable Example

package main

import (
"fmt"
"net/http/httputil"

"github.com/ggicci/httpin"
)

type Questionare struct {
Name string `in:"form=name;nonzero"`
Gender int `in:"form=gender"`
Position string `in:"form=position"`
Height float32 `in:"form=height"`
Weight float32 `in:"form=weight"`
}

func main() {
payload := &Questionare{
Name: "Ggicci",
Gender: 0,
Position: "Stand up",
Height: 182.3,
Weight: 73.5,
}
r, _ := httpin.NewRequest("POST", "/forms/weight-and-height", payload)
data, _ := httputil.DumpRequest(r, true)
fmt.Printf("%s\n", data)
}

Output:

POST /forms/weight-and-height HTTP/1.1
Content-Type: application/x-www-form-urlencoded

gender=0&height=182.3&name=Ggicci&position=Stand+up&weight=73.5