form

package
v0.0.0-...-155e729 Latest Latest
Warning

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

Go to latest
Published: May 2, 2024 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package form provides a form abstraction for http

Index

Examples

Constants

This section is empty.

Variables

View Source
var FormTemplate = template.Must(template.New("form.html").Parse(string(formBytes)))

FormTemplate is a template to embed a form

Functions

This section is empty.

Types

type Form

type Form[Data any] struct {
	// Fields are the set of fields to be included in this form.
	Fields []field.Field

	// FieldTemplate is an optional template to be executed for each field.
	// FieldTemplate may be nil; in which case [DefaultFieldTemplate] is used.
	FieldTemplate *template.Template

	// SkipCSRF if CSRF should be explicitly omitted
	SkipCSRF bool

	// Skip determines if the form can be skipped and a result page can be rendered directly.
	// If so, values for the result have to be passed out of the request.
	//
	// A nil Skip is assumed to not allow the form to be skipped.
	Skip func(*http.Request) (data Data, skip bool)

	// Template represents the template to render for GET requests.
	// It is passed the return value of [TemplateContext].
	Template *template.Template

	// LogTemplateError is intended to log a non-nil error being returned from executing the template.
	// If it is nil, no logging occurs.
	LogTemplateError httpx.ErrorLogger

	// TemplateContext is the context to be used for Template.
	// A nil TemplateContext function returns the FormContext object as-is.
	TemplateContext func(FormContext, *http.Request) any

	// Validate is a function that validates submitted form values, and parses them into a data object.
	// See [Form.Values] on how this is used.
	//
	// A nil validation function is assumed to return the zero value of Data and nil error.
	Validate func(r *http.Request, values map[string]string) (Data, error)

	// Success is a function that renders a successfully parsed form (either via [Validate] or [SkipForm]) into a response.
	// Upon a non-nil error, the original form is rendered with an appropriate [TemplateContext] is rendered instead.
	// A nil Success function is an error.
	Success func(data Data, values map[string]string, w http.ResponseWriter, r *http.Request) error
}

Form provides a form that a user can submit via a http POST method call. It implements http.Handler, see Form.ServeHTTP for details on how form submission works.

Data is the type of data the form parses.

Example
//spellchecker:words form
package main

//spellchecker:words errors html template http httptest strings testing github pkglib httpx content form field
import (
	"errors"
	"fmt"
	"html/template"
	"io"
	"net/http"
	"net/http/httptest"
	"net/url"
	"strings"
	"testing"

	"github.com/tkw1536/pkglib/httpx/content"
	"github.com/tkw1536/pkglib/httpx/form"
	"github.com/tkw1536/pkglib/httpx/form/field"
)

func main() {
	formTemplate := template.Must(template.New("form").Parse("<!doctype html><title>Form</title>{{ if .Error }}<p>Error: {{ .Error }}</p>{{ end }}{{ .Form }}"))
	successTemplate := template.Must(template.New("success").Parse("<!doctype html><title>Success</title>Welcome {{ . }}"))

	form := form.Form[string]{
		Fields: []field.Field{
			{Name: "givenName", Type: field.Text, Label: "Given Name"},
			{Name: "familyName", Type: field.Text, Label: "Family Name"},
			{Name: "password", Type: field.Password, Label: "Password", EmptyOnError: true},
		},

		Template:        formTemplate,
		TemplateContext: func(fc form.FormContext, r *http.Request) any { return fc },

		Validate: func(r *http.Request, values map[string]string) (string, error) {
			given, family, password := values["givenName"], values["familyName"], values["password"]
			if given == "" {
				return "", errors.New("given name must not be empty")
			}
			if family == "" {
				return "", errors.New("family name must not be empty")
			}
			if password == "" {
				return "", errors.New("no password provided")
			}
			return family + ", " + given, nil
		},

		Success: func(data string, values map[string]string, w http.ResponseWriter, r *http.Request) error {
			return content.WriteHTML(data, nil, successTemplate, w, r)
		},
	}

	fmt.Println(makeFormRequest(nil, &form, nil))
	fmt.Println(makeFormRequest(nil, &form, map[string]string{"givenName": "Andrea", "familyName": "", "password": "something"}))
	fmt.Println(makeFormRequest(nil, &form, map[string]string{"givenName": "Andrea", "familyName": "Picard", "password": "something"}))

}

// makeFormRequest makes a request to a form
func makeFormRequest(t *testing.T, form http.Handler, body map[string]string) string {
	if t != nil {
		t.Helper()
	}

	var req *http.Request
	if body == nil {
		var err error
		req, err = http.NewRequest(http.MethodGet, "/", nil)
		if err != nil {
			panic(err)
		}
	} else {
		form := url.Values{}
		for name, value := range body {
			form.Set(name, value)
		}

		var err error
		req, err = http.NewRequest(http.MethodPost, "/", strings.NewReader(form.Encode()))
		if err != nil {
			panic(err)
		}
		req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	}

	rr := httptest.NewRecorder()
	form.ServeHTTP(rr, req)

	rrr := rr.Result()
	result, _ := io.ReadAll(rrr.Body)
	return fmt.Sprintf("%q returned code %d with %s %q", req.Method, rrr.StatusCode, rrr.Header.Get("Content-Type"), string(result))
}
Output:

"GET" returned code 200 with text/html; charset=utf-8 "<!doctype html><title>Form</title><input name=givenName placeholder>\n<input name=familyName placeholder>\n<input type=password name=password placeholder>"
"POST" returned code 200 with text/html; charset=utf-8 "<!doctype html><title>Form</title><p>Error: family name must not be empty</p><input value=Andrea name=givenName placeholder>\n<input name=familyName placeholder>\n<input type=password name=password placeholder>"
"POST" returned code 200 with text/html; charset=utf-8 "<!doctype html><title>Success</title>Welcome Picard, Andrea"

func (*Form[D]) HTML

func (form *Form[D]) HTML(values map[string]string, IsError bool) template.HTML

HTML renders the values for the given html fields into a html template. IsError indicates if fields with the EmptyOnError flag should be omitted.

func (*Form[Data]) ServeHTTP

func (form *Form[Data]) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements serving the http form.

This works in two stages.

In the first stage, values for the form are parsed.

- If the request method is post, the form values are extracted using the [Values] method. - If the request method is get, and [Form.Skip] is defined, check if the form can be skipped. - If the request method is get, and the form cannot be skipped, we continue to the second stage below. - For all other cases, [ErrMethodNotAllowed] is rendered.

In the second stage, we either render the form template, or the success template.

- If form data was generated successfully (either via [Values] or [SkipForm]), we invoke [Form.RenderSuccess] with the appropriate data. - Otherwise, we render the Form Template with an appropriate error.

func (*Form[Data]) Values

func (form *Form[Data]) Values(r *http.Request) (map[string]string, Data, error)

Values validates values inside the given request, and returns parsed out form values from a post request.

Validation is performed using the [Form.Validate] function. Validation is passed the extracted field values.

- Only values corresponding to a form field in the request are used. - If multiple values are submitted for a specific field, only the first one is included. - If a value is missing, it is assigned the empty string.

If the parsed out values do not match, an error is returned instead.

Upon return, the map holds parsed out field values, Err indicates if an error occurred.

type FormContext

type FormContext struct {
	// Error is the underlying error (if any)
	Err error

	// AfterSuccess indicates if the form is rendered after a call to form.Success.
	// This typically means that validation passed, but an error occurred in the success function.
	AfterSuccess bool

	// Template is the underlying template rendered as html
	Form template.HTML
}

FormContext is passed to [Form.TemplateContext] when used

func (FormContext) Error

func (fc FormContext) Error() string

Error returns the underlying error string. If Err is nil, it returns an empty string.

func (FormContext) Unwrap

func (fc FormContext) Unwrap() error

Unwrap implements error unwrapping.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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