inreq

package module
v0.20.0 Latest Latest
Warning

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

Go to latest
Published: Oct 16, 2023 License: MIT Imports: 15 Imported by: 0

README

InReq - Golang http request to struct

GoDoc

InReq is a Golang library to extract information from *http.Request into structs. It does this using struct tags and/or a configuration map.

It is highly configurable:

  • configurations can be entirely in maps without requiring struct changes
  • custom decoders can be created in addition to the built-in query, header, form, path and body
  • struct field configurations can be overriden on specific calls
  • configurable field name mapper and body unmarshaler
  • custom type resolvers (or the entire type resolving logic can be replaced)
  • the HTTP body can be parsed into a specific field

Examples

import (
    "fmt"
    "net/http"
    "strings"

    "github.com/rrgmc/inreq"
)

type InputBody struct {
    DeviceID string `json:"device_id"`
    Name     string `json:"name"`
}

type Input struct {
    AuthToken      string    `inreq:"header,name=X-Auth-Token"`
    DeviceID       string    `inreq:"path"`
    WithDetails    bool      `inreq:"query,name=with_details"`
    Page           int       `inreq:"query"`
    Body           InputBody `inreq:"body"`
    FormDeviceName string    `inreq:"form,name=devicename"`
}

func main() {
    r, err := http.NewRequest(http.MethodPost, "/device/12345?with_details=true&page=2",
        strings.NewReader(`{"device_id":"12345","name":"Device for testing"}`))
    if err != nil {
        panic(err)
    }
    err = r.ParseForm()
    if err != nil {
        panic(err)
    }
    r.Header.Add("Content-Type", "application/json")
    r.Header.Add("X-Auth-Token", "auth-token-value")
    r.Form.Add("devicename", "form-device-name")

    data := &Input{}

    err = inreq.Decode(r, data,
        // usually this will be a framework-specific implementation, like "github.com/rrgmc/inreq-path/gorillamux".
        inreq.WithPathValue(inreq.PathValueFunc(func(r *http.Request, name string) (found bool, value any, err error) {
            if name == "deviceid" {
                return true, "12345", err
            }
            return false, nil, nil
        })))
    if err != nil {
        panic(err)
    }

    fmt.Printf("Auth Token: %s\n", data.AuthToken)
    fmt.Printf("Device ID: %s\n", data.DeviceID)
    fmt.Printf("With details: %t\n", data.WithDetails)
    fmt.Printf("Page: %d\n", data.Page)
    fmt.Printf("Body Device ID: %s\n", data.Body.DeviceID)
    fmt.Printf("Body Name: %s\n", data.Body.Name)
    fmt.Printf("Form Device Name: %s\n", data.FormDeviceName)

    // Output: Auth Token: auth-token-value
    // Device ID: 12345
    // With details: true
    // Page: 2
    // Body Device ID: 12345
    // Body Name: Device for testing
    // Form Device Name: form-device-name
}

Using generics:

import (
    "fmt"
    "net/http"
    "strings"

    "github.com/rrgmc/inreq"
)

type InputTypeBody struct {
    DeviceID string `json:"device_id"`
    Name     string `json:"name"`
}

type InputType struct {
    AuthToken      string        `inreq:"header,name=X-Auth-Token"`
    DeviceID       string        `inreq:"path"`
    WithDetails    bool          `inreq:"query,name=with_details"`
    Page           int           `inreq:"query"`
    Body           InputTypeBody `inreq:"body"`
    FormDeviceName string        `inreq:"form,name=devicename"`
}

func main() {
    r, err := http.NewRequest(http.MethodPost, "/device/12345?with_details=true&page=2",
        strings.NewReader(`{"device_id":"12345","name":"Device for testing"}`))
    if err != nil {
        panic(err)
    }
    err = r.ParseForm()
    if err != nil {
        panic(err)
    }
    r.Header.Add("Content-Type", "application/json")
    r.Header.Add("X-Auth-Token", "auth-token-value")
    r.Form.Add("devicename", "form-device-name")

    data, err := inreq.DecodeType[InputType](r,
        // usually this will be a framework-specific implementation, like "github.com/rrgmc/inreq-path/gorillamux".
        inreq.WithPathValue(inreq.PathValueFunc(func(r *http.Request, name string) (found bool, value any, err error) {
            if name == "deviceid" {
                return true, "12345", err
            }
            return false, nil, nil
        })))
    if err != nil {
        panic(err)
    }

    fmt.Printf("Auth Token: %s\n", data.AuthToken)
    fmt.Printf("Device ID: %s\n", data.DeviceID)
    fmt.Printf("With details: %t\n", data.WithDetails)
    fmt.Printf("Page: %d\n", data.Page)
    fmt.Printf("Body Device ID: %s\n", data.Body.DeviceID)
    fmt.Printf("Body Name: %s\n", data.Body.Name)
    fmt.Printf("Form Device Name: %s\n", data.FormDeviceName)

    // Output: Auth Token: auth-token-value
    // Device ID: 12345
    // With details: true
    // Page: 2
    // Body Device ID: 12345
    // Body Name: Device for testing
    // Form Device Name: form-device-name
}

Default operations

query

inreq:"query,name=<query-param-name>,required=true,explode=false,explodesep=,"

  • name: the query parameter name to get from req.URL.Query().Get(). Default uses FieldNameMapper, which by default uses strings.ToLower.
  • required: whether the query parameter is required to exist. Default is true.
  • explode: whether to use strings.Split on the query string if the target struct field is a slice. Default is false.
  • explodesep: the separator to use when exploding the string.
header

inreq:"header,name=<header-name>,required=true"

  • name: the header name to get from req.Header.Values(). Default uses FieldNameMapper, which by default uses strings.ToLower.
  • required: whether the header is required to exist. Default is true.
form

inreq:"form,name=<form-field-name>,required=true"

  • name: the form field name to get from req.Form.Get() or req.MultipartForm.Value. Default uses FieldNameMapper, which by default uses strings.ToLower.
  • required: whether the form field is required to exist. Default is true.
path

inreq:"path,name=<path-var-name>,required=true"

A path isn't an HTTP concept, but usually http frameworks have a concept of routes which can contain path variables, a framework-specific function should be set using WithPathValue. Some of these are available a https://github.com/rrgmc/inreq-path.

  • name: the path var name to get from PathValue.GetRequestPath. Default uses FieldNameMapper, which by default uses strings.ToLower.
  • required: whether the path var is required to exist. Default is true.
body

inreq:"body,required=true,type=json"

Body unmarshals data into the struct field, usually JSON or XML.

  • required: whether an HTTP body required to exist. Default is true.
  • type: type of body to decode. If blank, will use the Content-Type header. Should be only a type name ("json", "xml").
recurse

inreq:"recurse"

This tag is available for fields of struct type only. Usually structs are not recursed into (otherwise we could recurse inside time.Time), using this tag the inner struct will be transversed.

ignore

inreq:"-"

This tag makes the field be ignored.

Author

The code is based on my other library, InStruct, a generic library for mapping any data into structs.

Rangel Reale (rangelreale@gmail.com)

Documentation

Overview

Package inreq parses http requests to structs.

Index

Examples

Constants

View Source
const (
	OperationIgnore  string = instruct.OperationIgnore
	OperationRecurse        = instruct.OperationRecurse
)
View Source
const (
	OperationQuery  string = "query"
	OperationPath          = "path"
	OperationHeader        = "header"
	OperationForm          = "form"
	OperationBody          = "body"
)

Default operations.

View Source
const (
	DefaultTagName = "inreq"
)
View Source
const StructOptionMapTag = instruct.StructOptionMapTag

StructOptionMapTag corresponds to StructOption in a MapTags.

Variables

View Source
var (
	ErrCoerceInvalid     = types.ErrCoerceInvalid
	ErrCoerceOverflow    = types.ErrCoerceOverflow
	ErrCoerceUnsupported = types.ErrCoerceUnsupported
	ErrCoerceUnknown     = types.ErrCoerceUnknown
)
View Source
var IgnoreDecodeValue = instruct.IgnoreDecodeValue

IgnoreDecodeValue can be returned from [DecodeOperation.Decode] to signal that the value should not be set on the struct field. This is used for example in the "body" decoder.

Functions

func CustomDecode

func CustomDecode(r *http.Request, data any, options ...AnyOption) error

CustomDecode decodes the http request to the struct passed in "data" using NewCustomDecoder. Any map tags set using WithMapTags will be considered as "default" map tags. (see WithDefaultMapTags for details).

func CustomDecodeType

func CustomDecodeType[T any](r *http.Request, options ...AnyTypeOption) (T, error)

CustomDecodeType decodes the http request to the struct passed in "data" using NewCustomDecoder. Any map tags set using WithMapTags will be considered as "default" map tags. (see WithDefaultMapTags for details).

func Decode

func Decode(r *http.Request, data any, options ...AnyOption) error

Decode decodes the http request to the struct passed in "data" using NewDecoder. Any map tags set using WithMapTags will be considered as "default" map tags. (see WithDefaultMapTags for details).

Example
package main

import (
	"fmt"
	"net/http"
	"strings"

	"github.com/rrgmc/inreq"
)

type InputBody struct {
	DeviceID string `json:"device_id"`
	Name     string `json:"name"`
}

type Input struct {
	AuthToken      string    `inreq:"header,name=X-Auth-Token"`
	DeviceID       string    `inreq:"path"`
	WithDetails    bool      `inreq:"query,name=with_details"`
	Page           int       `inreq:"query"`
	Body           InputBody `inreq:"body"`
	FormDeviceName string    `inreq:"form,name=devicename"`
}

func main() {
	r, err := http.NewRequest(http.MethodPost, "/device/12345?with_details=true&page=2",
		strings.NewReader(`{"device_id":"12345","name":"Device for testing"}`))
	if err != nil {
		panic(err)
	}
	err = r.ParseForm()
	if err != nil {
		panic(err)
	}
	r.Header.Add("Content-Type", "application/json")
	r.Header.Add("X-Auth-Token", "auth-token-value")
	r.Form.Add("devicename", "form-device-name")

	data := &Input{}

	err = inreq.Decode(r, data,
		// usually this will be a framework-specific implementation, like "github.com/rrgmc/inreq-path/gorillamux".
		inreq.WithPathValue(inreq.PathValueFunc(func(r *http.Request, name string) (found bool, value any, err error) {
			if name == "deviceid" {
				return true, "12345", err
			}
			return false, nil, nil
		})))
	if err != nil {
		panic(err)
	}

	fmt.Printf("Auth Token: %s\n", data.AuthToken)
	fmt.Printf("Device ID: %s\n", data.DeviceID)
	fmt.Printf("With details: %t\n", data.WithDetails)
	fmt.Printf("Page: %d\n", data.Page)
	fmt.Printf("Body Device ID: %s\n", data.Body.DeviceID)
	fmt.Printf("Body Name: %s\n", data.Body.Name)
	fmt.Printf("Form Device Name: %s\n", data.FormDeviceName)

}
Output:

Auth Token: auth-token-value
Device ID: 12345
With details: true
Page: 2
Body Device ID: 12345
Body Name: Device for testing
Form Device Name: form-device-name

func DecodeType

func DecodeType[T any](r *http.Request, options ...AnyTypeOption) (T, error)

DecodeType decodes the http request to the struct passed in "data" using NewDecoder. Any map tags set using WithMapTags will be considered as "default" map tags. (see WithDefaultMapTags for details).

Example
package main

import (
	"fmt"
	"net/http"
	"strings"

	"github.com/rrgmc/inreq"
)

type InputTypeBody struct {
	DeviceID string `json:"device_id"`
	Name     string `json:"name"`
}

type InputType struct {
	AuthToken      string        `inreq:"header,name=X-Auth-Token"`
	DeviceID       string        `inreq:"path"`
	WithDetails    bool          `inreq:"query,name=with_details"`
	Page           int           `inreq:"query"`
	Body           InputTypeBody `inreq:"body"`
	FormDeviceName string        `inreq:"form,name=devicename"`
}

func main() {
	r, err := http.NewRequest(http.MethodPost, "/device/12345?with_details=true&page=2",
		strings.NewReader(`{"device_id":"12345","name":"Device for testing"}`))
	if err != nil {
		panic(err)
	}
	err = r.ParseForm()
	if err != nil {
		panic(err)
	}
	r.Header.Add("Content-Type", "application/json")
	r.Header.Add("X-Auth-Token", "auth-token-value")
	r.Form.Add("devicename", "form-device-name")

	data, err := inreq.DecodeType[InputType](r,
		// usually this will be a framework-specific implementation, like "github.com/rrgmc/inreq-path/gorillamux".
		inreq.WithPathValue(inreq.PathValueFunc(func(r *http.Request, name string) (found bool, value any, err error) {
			if name == "deviceid" {
				return true, "12345", err
			}
			return false, nil, nil
		})))
	if err != nil {
		panic(err)
	}

	fmt.Printf("Auth Token: %s\n", data.AuthToken)
	fmt.Printf("Device ID: %s\n", data.DeviceID)
	fmt.Printf("With details: %t\n", data.WithDetails)
	fmt.Printf("Page: %d\n", data.Page)
	fmt.Printf("Body Device ID: %s\n", data.Body.DeviceID)
	fmt.Printf("Body Name: %s\n", data.Body.Name)
	fmt.Printf("Form Device Name: %s\n", data.FormDeviceName)

}
Output:

Auth Token: auth-token-value
Device ID: 12345
With details: true
Page: 2
Body Device ID: 12345
Body Name: Device for testing
Form Device Name: form-device-name

Types

type BodyDecoder

type BodyDecoder interface {
	// Unmarshal should unmarshal r.Body into data. If r.Body is read, "ctx.DecodedBody()" MUST be called
	// even if there are read errors.
	Unmarshal(ctx DecodeContext, typeParam string, r *http.Request, data any) (bool, any, error)
}

BodyDecoder should unmarshal the body into "data". The default one supports JSON and XML.

func NewDefaultBodyDecoder

func NewDefaultBodyDecoder() BodyDecoder

type CoerceError

type CoerceError = types.CoerceError

type DecodeContext

type DecodeContext interface {
	instruct.DecodeContext
	// PathValue is the function used to extract the path from the request.
	PathValue() PathValue
	// BodyDecoder is the interface used to parse body data into structs.
	BodyDecoder() BodyDecoder
	// IsBodyDecoded returns whether the body was already decoded.
	IsBodyDecoded() bool
	// DecodedBody signals that the body was decoded.
	DecodedBody()
	// SliceSplitSeparator returns the string used for string-to-array conversions. The default is ",".
	SliceSplitSeparator() string
	// AllowReadBody returns whether the user gave permission to read the request body.
	AllowReadBody() bool
	// EnsureAllQueryUsed returns whether to check if all query parameters were used.
	EnsureAllQueryUsed() bool
	// EnsureAllFormUsed returns whether to check if all form parameters were used.
	EnsureAllFormUsed() bool
}

DecodeContext is the context sent to DecodeOperation.

type DecodeOperation

type DecodeOperation = instruct.DecodeOperation[*http.Request, DecodeContext]

DecodeOperation is the interface for the http request-to-struct decoders.

type DecodeOperationBody

type DecodeOperationBody struct {
}

DecodeOperationBody is a DecodeOperation that reads values from the request body.

func (*DecodeOperationBody) Decode

func (d *DecodeOperationBody) Decode(ctx DecodeContext, r *http.Request, isList bool, field reflect.Value,
	tag *Tag) (bool, any, error)

type DecodeOperationForm

type DecodeOperationForm struct {
}

DecodeOperationForm is a DecodeOperation that gets values from HTTP forms.

func (*DecodeOperationForm) Decode

func (d *DecodeOperationForm) Decode(ctx DecodeContext, r *http.Request, isList bool, field reflect.Value,
	tag *Tag) (bool, any, error)

func (*DecodeOperationForm) Validate

func (d *DecodeOperationForm) Validate(ctx DecodeContext, r *http.Request) error

type DecodeOperationHeader

type DecodeOperationHeader struct {
}

DecodeOperationHeader is a DecodeOperation that gets values from HTTP headers.

func (*DecodeOperationHeader) Decode

func (d *DecodeOperationHeader) Decode(ctx DecodeContext, r *http.Request, isList bool, field reflect.Value,
	tag *Tag) (bool, any, error)

type DecodeOperationPath

type DecodeOperationPath struct {
}

DecodeOperationPath is a DecodeOperation that gets values from HTTP paths (or routes). This is always framework-specific.

func (*DecodeOperationPath) Decode

func (d *DecodeOperationPath) Decode(ctx DecodeContext, r *http.Request, isList bool, field reflect.Value,
	tag *Tag) (bool, any, error)

type DecodeOperationQuery

type DecodeOperationQuery struct {
}

DecodeOperationQuery is a DecodeOperation that gets values from HTTP query parameters.

func (*DecodeOperationQuery) Decode

func (d *DecodeOperationQuery) Decode(ctx DecodeContext, r *http.Request, isList bool, field reflect.Value,
	tag *Tag) (bool, any, error)

func (*DecodeOperationQuery) Validate

func (d *DecodeOperationQuery) Validate(ctx DecodeContext, r *http.Request) error

type DecodeOption

type DecodeOption = options.DecodeOption[*http.Request, DecodeContext, decodeOptions]

type Decoder

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

Decoder decodes http requests to structs.

func NewCustomDecoder

func NewCustomDecoder(options ...DefaultOption) *Decoder

NewCustomDecoder creates a Decoder instance without any decode operations. At least one must be added for decoding to work.

func NewDecoder

func NewDecoder(options ...DefaultOption) *Decoder

NewDecoder creates a Decoder instance with the default decode operations (query, path, header, form, body).

Example
package main

import (
	"fmt"
	"net/http"
	"strings"

	"github.com/rrgmc/inreq"
)

type InputDecoderBody struct {
	DeviceID string `json:"device_id"`
	Name     string `json:"name"`
}

type InputDecoder struct {
	AuthToken      string           `inreq:"header,name=X-Auth-Token"`
	DeviceID       string           `inreq:"path"`
	WithDetails    bool             `inreq:"query,name=with_details"`
	Page           int              `inreq:"query"`
	Body           InputDecoderBody `inreq:"body"`
	FormDeviceName string           `inreq:"form,name=devicename"`
}

func main() {
	r, err := http.NewRequest(http.MethodPost, "/device/12345?with_details=true&page=2",
		strings.NewReader(`{"device_id":"12345","name":"Device for testing"}`))
	if err != nil {
		panic(err)
	}
	err = r.ParseForm()
	if err != nil {
		panic(err)
	}
	r.Header.Add("Content-Type", "application/json")
	r.Header.Add("X-Auth-Token", "auth-token-value")
	r.Form.Add("devicename", "form-device-name")

	data := &Input{}

	decoder := inreq.NewDecoder(
		// usually this will be a framework-specific implementation, like "github.com/rrgmc/inreq-path/gorillamux".
		inreq.WithPathValue(inreq.PathValueFunc(func(r *http.Request, name string) (found bool, value any, err error) {
			if name == "deviceid" {
				return true, "12345", err
			}
			return false, nil, nil
		})),
	)

	err = decoder.Decode(r, data)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Auth Token: %s\n", data.AuthToken)
	fmt.Printf("Device ID: %s\n", data.DeviceID)
	fmt.Printf("With details: %t\n", data.WithDetails)
	fmt.Printf("Page: %d\n", data.Page)
	fmt.Printf("Body Device ID: %s\n", data.Body.DeviceID)
	fmt.Printf("Body Name: %s\n", data.Body.Name)
	fmt.Printf("Form Device Name: %s\n", data.FormDeviceName)

}
Output:

Auth Token: auth-token-value
Device ID: 12345
With details: true
Page: 2
Body Device ID: 12345
Body Name: Device for testing
Form Device Name: form-device-name

func (*Decoder) Decode

func (d *Decoder) Decode(r *http.Request, data any, options ...DecodeOption) error

Decode decodes the http request to the struct passed in "data".

type DefaultAndDecodeOption

type DefaultAndDecodeOption = options.DefaultAndDecodeOption[*http.Request, DecodeContext, defaultOptions, decodeOptions]

type DefaultAndTypeDefaultDecodeOption

type DefaultAndTypeDefaultDecodeOption = options.DefaultAndTypeDefaultDecodeOption[*http.Request, DecodeContext, decodeOptions, decodeOptions]

type DefaultAndTypeDefaultOption

type DefaultAndTypeDefaultOption = options.DefaultAndTypeDefaultOption[*http.Request, DecodeContext, defaultOptions, typeDefaultOptions]

func WithBodyDecoder

func WithBodyDecoder(bodyDecoder BodyDecoder) DefaultAndTypeDefaultOption

WithBodyDecoder sets the interface used to parse body data into structs.

func WithDecodeOperation

func WithDecodeOperation(name string, operation DecodeOperation) DefaultAndTypeDefaultOption

WithDecodeOperation adds a decode operation.

func WithDefaultDecodeOperations

func WithDefaultDecodeOperations() DefaultAndTypeDefaultOption

WithDefaultDecodeOperations adds the default operations (query, path, header, form and body). If the non-"Custom" calls are used, this option is added by default.

func WithDefaultMapTags

func WithDefaultMapTags(dataForType any, tags MapTags) DefaultAndTypeDefaultOption

WithDefaultMapTags adds a "default" MapTags. The default one is checked alongside the tags, so the check for unused fields takes both in account. Passing a struct without any struct tags and using WithMapTags will result in "field configuration not found" errors (except in free-standing functions like Decode, CustomDecode, DecodeType and CustomDecodeType.

func WithDefaultMapTagsType

func WithDefaultMapTagsType(typ reflect.Type, tags MapTags) DefaultAndTypeDefaultOption

WithDefaultMapTagsType is the same as WithDefaultMapTags using a reflect.Type.

func WithDefaultRequired

func WithDefaultRequired(defaultRequired bool) DefaultAndTypeDefaultOption

WithDefaultRequired sets whether the default for fields should be "required" or "not required"

func WithFieldNameMapper

func WithFieldNameMapper(fieldNameMapper FieldNameMapper) DefaultAndTypeDefaultOption

WithFieldNameMapper sets the field name mapper. Default one uses strings.ToLower.

func WithPathValue

func WithPathValue(pathValue PathValue) DefaultAndTypeDefaultOption

WithPathValue sets the function used to extract the path from the request.

func WithResolver

func WithResolver(resolver Resolver) DefaultAndTypeDefaultOption

WithResolver sets the decode Resolver.

func WithSliceSplitSeparator

func WithSliceSplitSeparator(sep string) DefaultAndTypeDefaultOption

WithSliceSplitSeparator sets the string to be used as separator on string-to-array conversion. Default is ",".

func WithStructInfoCache

func WithStructInfoCache(cache bool) DefaultAndTypeDefaultOption

WithStructInfoCache sets whether to cache info for structs on parse. Default is false.

func WithTagName

func WithTagName(tagName string) DefaultAndTypeDefaultOption

WithTagName sets the tag name to check on structs. The default is "inreq".

type DefaultOption

type DefaultOption = options.DefaultOption[*http.Request, DecodeContext, defaultOptions]

type FieldNameMapper

type FieldNameMapper = instruct.FieldNameMapper

FieldNameMapper maps a struct field name to the header/query/form field name. The default one uses strings.ToLower.

type FullOption

type FullOption = options.FullOption[*http.Request, DecodeContext, defaultOptions, typeDefaultOptions, decodeOptions, decodeOptions]

func WithAllowReadBody

func WithAllowReadBody(allowReadBody bool) FullOption

WithAllowReadBody sets whether operations are allowed to read the request body. Default is false.

func WithEnsureAllFormUsed

func WithEnsureAllFormUsed(ensureAllFormUsed bool) FullOption

WithEnsureAllFormUsed sets whether to check if all form parameters were used.

func WithEnsureAllQueryUsed

func WithEnsureAllQueryUsed(ensureAllQueryUsed bool) FullOption

WithEnsureAllQueryUsed sets whether to check if all query parameters were used.

type InvalidDecodeError

type InvalidDecodeError = types.InvalidDecodeError

type MapTags

type MapTags = instruct.MapTags

MapTags is an alternative to struct tags, and can be used to override them.

type OperationNotSupportedError

type OperationNotSupportedError = types.OperationNotSupportedError

type PathValue

type PathValue interface {
	GetRequestPath(r *http.Request, name string) (found bool, value any, err error)
}

PathValue is used by the "path" operation to extract the path from the request. Usually this is stored in the context by libraries like "gorilla/mux".

type PathValueFunc

type PathValueFunc func(r *http.Request, name string) (found bool, value any, err error)

func (PathValueFunc) GetRequestPath

func (p PathValueFunc) GetRequestPath(r *http.Request, name string) (found bool, value any, err error)

type RequiredError

type RequiredError = types.RequiredError

type Resolver

type Resolver = instruct.Resolver

Resolver converts strings to the type of the struct field.

type StructOption

type StructOption = instruct.StructOption

StructOption can be used as a struct field to give options to the struct itself.

type Tag

type Tag = instruct.Tag

Tag contains the options parsed from the struct tags or MapTags.

type TypeDecodeOption

type TypeDecodeOption = options.TypeDecodeOption[*http.Request, DecodeContext, decodeOptions]

type TypeDecoder

type TypeDecoder[T any] struct {
	// contains filtered or unexported fields
}

TypeDecoder decodes http requests to structs.

func NewCustomTypeDecoder

func NewCustomTypeDecoder[T any](options ...TypeDefaultOption) *TypeDecoder[T]

NewCustomTypeDecoder creates a Decoder instance without any decode operations. At least one must be added for decoding to work.

func NewTypeDecoder

func NewTypeDecoder[T any](options ...TypeDefaultOption) *TypeDecoder[T]

NewTypeDecoder creates a Decoder instance with the default decode operations (query, path, header, form, body).

Example
package main

import (
	"fmt"
	"net/http"
	"strings"

	"github.com/rrgmc/inreq"
)

type InputTypeDecoderBody struct {
	DeviceID string `json:"device_id"`
	Name     string `json:"name"`
}

type InputTypeDecoder struct {
	AuthToken      string               `inreq:"header,name=X-Auth-Token"`
	DeviceID       string               `inreq:"path"`
	WithDetails    bool                 `inreq:"query,name=with_details"`
	Page           int                  `inreq:"query"`
	Body           InputTypeDecoderBody `inreq:"body"`
	FormDeviceName string               `inreq:"form,name=devicename"`
}

func main() {
	r, err := http.NewRequest(http.MethodPost, "/device/12345?with_details=true&page=2",
		strings.NewReader(`{"device_id":"12345","name":"Device for testing"}`))
	if err != nil {
		panic(err)
	}
	err = r.ParseForm()
	if err != nil {
		panic(err)
	}
	r.Header.Add("Content-Type", "application/json")
	r.Header.Add("X-Auth-Token", "auth-token-value")
	r.Form.Add("devicename", "form-device-name")

	decoder := inreq.NewTypeDecoder[InputTypeDecoder](
		// usually this will be a framework-specific implementation, like "github.com/rrgmc/inreq-path/gorillamux".
		inreq.WithPathValue(inreq.PathValueFunc(func(r *http.Request, name string) (found bool, value any, err error) {
			if name == "deviceid" {
				return true, "12345", err
			}
			return false, nil, nil
		})))

	data, err := decoder.Decode(r)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Auth Token: %s\n", data.AuthToken)
	fmt.Printf("Device ID: %s\n", data.DeviceID)
	fmt.Printf("With details: %t\n", data.WithDetails)
	fmt.Printf("Page: %d\n", data.Page)
	fmt.Printf("Body Device ID: %s\n", data.Body.DeviceID)
	fmt.Printf("Body Name: %s\n", data.Body.Name)
	fmt.Printf("Form Device Name: %s\n", data.FormDeviceName)

}
Output:

Auth Token: auth-token-value
Device ID: 12345
With details: true
Page: 2
Body Device ID: 12345
Body Name: Device for testing
Form Device Name: form-device-name

func (*TypeDecoder[T]) Decode

func (d *TypeDecoder[T]) Decode(r *http.Request, options ...TypeDecodeOption) (T, error)

Decode decodes the http request to the struct passed in "data".

type TypeDefaultAndDecodeOption

type TypeDefaultAndDecodeOption = options.TypeDefaultAndDecodeOption[*http.Request, DecodeContext, typeDefaultOptions, decodeOptions]

func WithMapTags

func WithMapTags(tags MapTags) TypeDefaultAndDecodeOption

WithMapTags sets decode-operation-specific MapTags. These override the default cached struct information but don't change the original one. This should be used to override configurations on each call.

type TypeDefaultAndTypeDecodeOption

type TypeDefaultAndTypeDecodeOption = options.TypeDefaultAndTypeDecodeOption[*http.Request, DecodeContext, typeDefaultOptions, decodeOptions]

type TypeDefaultOption

type TypeDefaultOption = options.TypeDefaultOption[*http.Request, DecodeContext, typeDefaultOptions]

type ValuesNotUsedError

type ValuesNotUsedError = types.ValuesNotUsedError

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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