Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/scripts/golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ linters:
- musttag
- nilnil
- noctx
- paralleltest
- perfsprint
- prealloc
- predeclared
Expand Down
123 changes: 71 additions & 52 deletions forms.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,21 @@ var (
ErrParseFailure = errors.New("could not parse value")
)

func Parse(data url.Values, schema Schema) error {
// Parse uses the given Schema to parse the HTTP form values in the given HTTP
// Request. If the values of the form do not match the schema, or required values
// are missing, an error is returned.
func Parse(r *http.Request, schema Schema) error {
if err := r.ParseForm(); err != nil {
return err
}

return ParseValues(r.Form, schema)
}

// ParseValues uses the given Schema to parse the values in the given url.Values.
// If the values do not match the schema, or required values are missing, an
// error is returned.
func ParseValues(data url.Values, schema Schema) error {
for name, parser := range schema {
values := data[name]
if err := parser.Parse(values); err != nil {
Expand All @@ -32,14 +46,6 @@ func Parse(data url.Values, schema Schema) error {
return nil
}

func ParseForm(r *http.Request, schema Schema) error {
if err := r.ParseForm(); err != nil {
return err
}

return Parse(r.Form, schema)
}

// A Schema describes how a set of url.Values should be parsed.
// Typically these are coming from an http.Request.Form from inside an
// http.Handler responding to an inbound request.
Expand All @@ -54,31 +60,18 @@ type Parser interface {
Parse([]string) error
}

// String is used to extract a form data value into a Go string. If the value
// is not a string or is missing then an error is returned during parsing.
func String(s *string) Parser {
return &stringParser{
required: true,
destination: s,
}
// StringType represents any type compatible with the Go string built-in type,
// to be used as a destination for writing the value of an environment variable.
type StringType interface {
~string
}

// StringOr is used to extract a form data value into a Go string. If the value
// is missing, then the alt value is used instead.
func StringOr(s *string, alt string) Parser {
*s = alt
return &stringParser{
required: false,
destination: s,
}
}

type stringParser struct {
type stringParser[T StringType] struct {
required bool
destination *string
destination *T
}

func (p *stringParser) Parse(values []string) error {
func (p *stringParser[T]) Parse(values []string) error {
switch {
case len(values) > 1:
return ErrMulitpleValues
Expand All @@ -87,11 +80,30 @@ func (p *stringParser) Parse(values []string) error {
case len(values) == 0:
return nil
default:
*p.destination = values[0]
*p.destination = T(values[0])
}
return nil
}

// String is used to extract a form data value into a Go string. If the value
// is not a string or is missing then an error is returned during parsing.
func String[T StringType](s *T) Parser {
return &stringParser[T]{
required: true,
destination: s,
}
}

// StringOr is used to extract a form data value into a Go string. If the value
// is missing, then the alt value is used instead.
func StringOr[T StringType](s *T, alt T) Parser {
*s = alt
return &stringParser[T]{
required: false,
destination: s,
}
}

// Secret is used to extract a form data value into a Go conceal.Text. If the
// value is missing then an error is returned during parsing.
func Secret(s **conceal.Text) Parser {
Expand Down Expand Up @@ -121,31 +133,19 @@ func (p *secretParser) Parse(values []string) error {
return nil
}

type intParser struct {
required bool
destination *int
}

// Int is used to extract a form data value into a Go int. If the value is not
// an int or is missing then an error is returned during parsing.
func Int(i *int) Parser {
return &intParser{
required: true,
destination: i,
}
// IntType represents any type compatible with the Go integer built-in types,
// to be used as a destination for writing the value of a form value.
type IntType interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}

// IntOr is used to extract a form data value into a Go int. If the value is
// missing, then the alt value is used instead.
func IntOr(i *int, alt int) Parser {
*i = alt
return &intParser{
required: false,
destination: i,
}
type intParser[T IntType] struct {
required bool
destination *T
}

func (p *intParser) Parse(values []string) error {
func (p *intParser[T]) Parse(values []string) error {
switch {
case len(values) > 1:
return ErrMulitpleValues
Expand All @@ -160,10 +160,29 @@ func (p *intParser) Parse(values []string) error {
return err
}

*p.destination = i
*p.destination = T(i)
return nil
}

// Int is used to extract a form data value into a Go int. If the value is not
// an int or is missing then an error is returned during parsing.
func Int[T IntType](i *T) Parser {
return &intParser[T]{
required: true,
destination: i,
}
}

// IntOr is used to extract a form data value into a Go int. If the value is
// missing, then the alt value is used instead.
func IntOr[T IntType](i *T, alt T) Parser {
*i = alt
return &intParser[T]{
required: false,
destination: i,
}
}

type floatParser struct {
required bool
destination *float64
Expand Down
Loading