### Install httpin Package Source: https://ggicci.github.io/httpin/index This snippet shows the command to install the httpin Go package using the go get command. It's the first step to start using the library in your Go projects. ```bash go get github.com/ggicci/httpin ``` -------------------------------- ### Advanced Decoding with Chi Router Source: https://ggicci.github.io/httpin/index This example showcases httpin integration with the go-chi/chi router. It defines a struct for decoding headers and query parameters, including a slice for repeated query parameters, and demonstrates how to use httpin.NewInput middleware. ```go package main import ( "fmt" "net/http" "net/http/httptest" "github.com/ggicci/httpin" "github.com/go-chi/chi/v5" ) type ListUsersInput struct { Token string `in:"header=Authorization" IsMember bool `in:"query=is_member" AgeRange []int `in:"query=age_range[],age_range" } func ListUsers(rw http.ResponseWriter, r *http.Request) { input := r.Context().Value(httpin.Input).(*ListUsersInput) fmt.Printf("input: %#v\n", input) } func main() { outer := chi.NewRouter() router.With( httpin.NewInput(ListUsersInput{}), ).Get("/users", ListUsers) r, _ := http.NewRequest("GET", "/users?is_member=1&age_range=18&age_range=60", nil) r.Header.Set("Authorization", "my-secret-here") rw := httptest.NewRecorder() outer.ServeHTTP(rw, r) } ``` -------------------------------- ### Basic Struct Decoding Example Source: https://ggicci.github.io/httpin/index Demonstrates how to define a Go struct with httpin tags to decode query parameters, path variables, headers, and form data from an HTTP request. It shows how to retrieve the decoded struct from the request context. ```go // Well define your data. type ListUsersInput struct { Page int `in:"query=page" PerPage int `in:"query=per_page" IsMember bool `in:"query=is_member" } func ListUsers(rw http.ResponseWriter, r *http.Request) { // Retrieve your data in one line of code! input := r.Context().Value(httpin.Input).(*ListUsersInput) // Do sth. } ``` -------------------------------- ### Go Client-Side File Upload Example Source: https://ggicci.github.io/httpin/advanced/upload-files Demonstrates preparing an HTTP request for file uploads. It shows how to create an `UpdateArticleInput` instance, use `httpin.UploadFile` to specify local file paths, and then generate the HTTP request using `httpin.NewRequest`. ```go updateArticleRequest := &UpdateArticleInput{ Title: "About Me", IsPrivate: false, Cover: httpin.UploadFile("/path/to/my/album/travel-selfie-no1.jpg"), Attachments: []*httpin.File{ httpin.UploadFile("/path/to/my/videos/vlog-sunset.mp4"), httpin.UploadFile("/path/to/my/videos/vlog-sea.mp4"), }, } req, err := httpin.NewRequest("POST", "/posts/about-me", updateArticleRequest) ``` -------------------------------- ### Default Directive Runable Example (Go) Source: https://ggicci.github.io/httpin/directives/default A complete Go program showcasing the 'default' directive's functionality. It sets up a chi router, defines a struct with default values, and demonstrates how a request without specific parameters is handled. ```Go package main import ( "fmt" "net/http" "net/http/httptest" "github.com/ggicci/httpin" "github.com/go-chi/chi/v5" ) type ListTasksQuery struct { Page int `in:"query=page;default=1" PerPage int `in:"query=per_page;default=20" StateList []string `in:"query=state;default=pending,running" } func ListTasks(rw http.ResponseWriter, r *http.Request) { input := r.Context().Value(httpin.Input).(*ListTasksQuery) fmt.Printf("input: %#v\n", input) } func main() { outer := chi.NewRouter() router.With( httpin.NewInput(ListTasksQuery{}), ).Get("/tasks", ListTasks) r, _ := http.NewRequest("GET", "/tasks?page=0", nil) rw := httptest.NewRecorder() router.ServeHTTP(rw, r) } ``` -------------------------------- ### Directive Structure and Execution Source: https://ggicci.github.io/httpin/advanced/concepts Illustrates the format of a directive ('name=argv') and how it maps to a directive executor function call. Shows examples of common directives and their execution. ```APIDOC Directive Format: name=argv Directive Executor Mapping: | Directive Executor | Directive | Execution | | ------------------ | -------------------------- | ----------------------------- | | query | `query=access_token,token` | `query(["access_token", "token"])` | | header | `header=x-api-token` | `header(["x-api-token"])` | | required | `required` | `required([])` | Execution Flow: Directives within a struct tag are executed in the order they appear. If a directive fails, subsequent directives for that field are not executed. ``` -------------------------------- ### Go Server-Side File Retrieval Example Source: https://ggicci.github.io/httpin/advanced/upload-files Illustrates how to retrieve uploaded file data on the server. It shows accessing the `httpin.File` object from the request context, and then using its methods like `Filename()`, `Size()`, and `ReadAll()` to get file metadata and content. ```go func UpdateArticle(rw http.ResponseWriter, r *http.Request) { input := r.Context().Value(httpin.Input).(*UpdateArticleInput) filename := input.Cover.Filename() filesize := input.Cover.Size() // Read content. fileBytes, err := input.Cover.ReadAll() // ... } ``` -------------------------------- ### httpin Body Directive Decoding Runable Example Source: https://ggicci.github.io/httpin/directives/body A complete Go example demonstrating the 'body' directive for decoding a JSON request body into a Go struct. It sets up a chi router, defines input structures, and handles the decoded data. ```go package main import ( "fmt" "io" "net/http" "net/http/httptest" "strings" "github.com/ggicci/httpin" httpin_integration "github.com/ggicci/httpin/integration" "github.com/go-chi/chi/v5" ) type UserPatch struct { Display string `json:"display"` Email string `json:"email"` IsAdmin bool `json:"is_admin"` } type UpdateUserInput struct { ID string `in:"path=id"` // NOTE: register a path directive before using Payload UserPatch `in:"body=json" } func UpdateUserHandler(rw http.ResponseWriter, r *http.Request) { input := r.Context().Value(httpin.Input).(*UpdateUserInput) fmt.Printf("input: %#v\n", input) } func init() { httpin_integration.UseGochiURLParam("path", chi.URLParam) } func main() { router := chi.NewRouter() router.With( httpin.NewInput(UpdateUserInput{}), ).Put("/users/{id}", UpdateUserHandler) r, _ := http.NewRequest("PUT", "/users/19911110", nil) r.Body = io.NopCloser(strings.NewReader(`{ "display": "Ggicci Never Cry", "email": "secret_@xxx.com", "is_admin": true }`)) rw := httptest.NewRecorder() router.ServeHTTP(rw, r) } ``` -------------------------------- ### httpin form directive usage example Source: https://ggicci.github.io/httpin/directives/form Demonstrates the usage of the 'form' directive with a Go struct. It shows how the directive maps form data and URL query parameters to struct fields, illustrating precedence rules with various request scenarios. ```go type Profile struct { Role string `in:"form=role"` Hireable bool `in:"form=hireable"` } ``` -------------------------------- ### httpin query directive decoding runnable example Source: https://ggicci.github.io/httpin/directives/query A complete Go program demonstrating the 'query' directive's decoding functionality. It sets up a chi router, defines an input struct, and processes a request to show how query parameters populate the struct. ```Go package main import ( "fmt" "net/http" "net/http/httptest" "github.com/ggicci/httpin" "github.com/go-chi/chi/v5" ) type ListUsersInput struct { IsMember bool `in:"query=is_member" AgeRange []int `in:"query=age_range[],age_range" } func ListUsers(rw http.ResponseWriter, r *http.Request) { input := r.Context().Value(httpin.Input).(*ListUsersInput) fmt.Printf("input: %#v\n", input) } func main() { router := chi.NewRouter() router.With( httpin.NewInput(ListUsersInput{}), ).Get("/users", ListUsers) r, _ := http.NewRequest("GET", "/users?is_member=1&age_range=18&age_range=60", nil) rw := httptest.NewRecorder() router.ServeHTTP(rw, r) } ``` -------------------------------- ### Runnable Example: httpin Coder Directive Source: https://ggicci.github.io/httpin/directives/coder A complete Go program showcasing the httpin library's 'coder' directive. It sets up an HTTP handler that uses a custom date coder for parsing a 'birthday' query parameter, demonstrating end-to-end functionality. ```go package main import ( "fmt" "net/http" "net/http/httptest" "time" "github.com/ggicci/httpin" httpin_core "github.com/ggicci/httpin/core" "github.com/justinas/alice" ) type MyDate time.Time // adapted time.Time to MyDate, MyDate must implement httpin_core.Stringable func (t MyDate) ToString() (string, error) { return time.Time(t).Format("2006-01-02"), nil } func (t *MyDate) FromString(value string) error { v, err := time.Parse("2006-01-02", value) if err != nil { return fmt.Errorf("invalid date: %w", err) } *t = MyDate(v) return nil } func init() { httpin_core.RegisterNamedCoder[time.Time]("date", func(t *time.Time) (httpin_core.Stringable, error) { return (*MyDate)(t), nil }) } type ListUsersInput struct { Gender string `in:"form=gender" // By default, the decoder is auto-selected by the field type, which is of `time.Time`. // When decoder directive is set, the specified named decoder "date" will be used instead. Birthday time.Time `in:"form=birthday;coder=date" } func ListUsers(rw http.ResponseWriter, r *http.Request) { input := r.Context().Value(httpin.Input).(*ListUsersInput) fmt.Printf("input: %#v\n", input) } func main() { mux := http.NewServeMux() mux.Handle("/users", alice.New( httpin.NewInput(ListUsersInput{}), ).ThenFunc(ListUsers)) r, _ := http.NewRequest("GET", "/users?gender=male&birthday=1991-11-10", nil) rw := httptest.NewRecorder() mux.ServeHTTP(rw, r) } ``` -------------------------------- ### Default Directive Decoding Example Table Source: https://ggicci.github.io/httpin/directives/default Illustrates the behavior of the 'default' directive by showing how different HTTP requests map to the ListTasksQuery struct, including cases where default values are applied. ```APIDOC | Request | ListTasksQuery | | --- | --- | | ``` GET /tasks?page=4&perPage=10&state=failed&state=succeeded ``` | ``` { Page: 4, PerPage: 10, StateList: []string{"failed", "succeeded"}, } ``` | | ``` GET /tasks ``` | ``` { Page: 1, PerPage: 20, StateList: []string{"pending", "running"}, } ``` | ``` -------------------------------- ### httpin query directive encoding example Source: https://ggicci.github.io/httpin/directives/query Illustrates how the 'query' directive encodes Go struct fields into URL querystring parameters. The directive uses the first specified key to construct the URL query string. ```Go package main import ( "fmt" "net/http/httputil" "github.com/ggicci/httpin" ) type ListUsersInput struct { IsMember bool `in:"query=is_member" AgeRange []int `in:"query=age_range[],age_range" } func main() { input := &ListUsersInput{ IsMember: true, AgeRange: []int{18, 60}, } r, _ := httpin.NewRequest("GET", "/users", input) data, _ := httputil.DumpRequest(r, false) fmt.Printf("%s\n", data) } ``` -------------------------------- ### Go: Custom Directive Example with httpin Source: https://ggicci.github.io/httpin/directives/custom This Go code snippet illustrates the creation of custom directives for the httpin library. It defines directive executors for transforming string values to lowercase and uppercase, registers them, and then applies them to struct fields via `in` tags to process query parameters. ```go package main import ( "errors" "fmt" "net/http" "net/http/httptest" "reflect" "strings" "github.com/ggicci/httpin" httpin_core "github.com/ggicci/httpin/core" "github.com/go-chi/chi/v5" ) type DirectiveCaseFormatter struct { Transform func(string) string } func (f *DirectiveCaseFormatter) Decode(rtm *httpin_core.DirectiveRuntime) error { if rtm.Value.Type().Elem().Kind() != reflect.String { return errors.New("not a string") } currentValue := rtm.Value.Elem().String() newValue := f.Transform(currentValue) rtm.Value.Elem().SetString(newValue) return nil } func (f *DirectiveCaseFormatter) Encode(rtm *httpin_core.DirectiveRuntime) error { if rtm.Value.Type().Kind() != reflect.String { return errors.New("not a string") } currentValue := rtm.Value.String() newValue := f.Transform(currentValue) rtm.Value.SetString(newValue) return nil } func init() { httpin_core.RegisterDirective("to_lowercase", &DirectiveCaseFormatter{ Transform: strings.ToLower, }) httpin_core.RegisterDirective("to_uppercase", &DirectiveCaseFormatter{ Transform: strings.ToUpper, }) } type ListUsersInput struct { Username string `in:"query=username;to_lowercase"` Gender string `in:"query=gender;to_uppercase"` } func ListUsersHandler(rw http.ResponseWriter, r *http.Request) { input := r.Context().Value(httpin.Input).(*ListUsersInput) fmt.Printf("input: %%#v\n", input) } func main() { router := chi.NewRouter() // Bind input struct with handler. router.With( httpin.NewInput(ListUsersInput{}), ).Get("/users", ListUsersHandler) r, _ := http.NewRequest("GET", "/users?username=Ggicci&gender=male", nil) rw := httptest.NewRecorder() router.ServeHTTP(rw, r) } ``` -------------------------------- ### Running the httpin Gin Demo Locally Source: https://ggicci.github.io/httpin/integrations/gin These bash commands outline the steps to set up and run the provided Go demo code on your local machine. It includes creating a directory, initializing a Go module, placing the code in `main.go`, tidying dependencies, and executing the program. ```bash mkdir /tmp/test && cd $_ touch main.go # then COPY & PASTE the above code to main.go go mod init test go mod tidy go run main.go ``` -------------------------------- ### httpin Body Directive Encoding Runable Example Source: https://ggicci.github.io/httpin/directives/body A Go example demonstrating the 'body' directive for encoding a Go struct into a JSON HTTP request body. It uses httpin.NewRequest to construct the request with the encoded payload. ```go package main import ( "fmt" "net/http/httputil" "github.com/ggicci/httpin" ) type UserPatch struct { Display string `json:"display"` Email string `json:"email"` IsAdmin bool `json:"is_admin"` } type UpdateUserInput struct { ID string `in:"path=id"` Payload UserPatch `in:"body=json" } func main() { input := &UpdateUserInput{ ID: "ggicci", Payload: UserPatch{ Display: "Ggicci", Email: "secret_@xxx.com", IsAdmin: true, }, } r, _ := httpin.NewRequest("PATCH", "/users/{id}", input) data, _ := httputil.DumpRequest(r, true) fmt.Printf("%s\n", data) } ``` -------------------------------- ### httpin API Documentation - Constants and Types Source: https://ggicci.github.io/httpin/github.com/ggicci/httpin Details important constants and types used within the httpin package, such as the context key for accessing decoded input and definitions for file uploads. ```APIDOC Package httpin API - Constants and Types: Constants: Input contextKey = iota - Represents the key used to store the decoded input object in the http.Request.Context(). - Usage Example: input := r.Context().Value(httpin.Input).(*InputStruct) Types: type File struct {} - Represents a file uploaded via multipart/form-data. Methods on File: func UploadFile(path string) *File - Creates a File struct representing a file at the given path. - Parameters: - path: The file system path to the file. - Returns: - A pointer to a File struct. func UploadStream(r io.ReadCloser) *File - Creates a File struct representing a file from an io.ReadCloser stream. - Parameters: - r: An io.ReadCloser providing the file content stream. - Returns: - A pointer to a File struct. ``` -------------------------------- ### Go net/http Integration with httpin Middleware Source: https://ggicci.github.io/httpin/integrations/http Demonstrates how to integrate httpin with Go's net/http package. It shows how to define input structures with 'in' tags for query parameters, bind them to handlers using httpin.NewInput, and chain middleware with justinas/alice. ```go package main import ( "fmt" "net/http" "net/http/httptest" "github.com/ggicci/httpin" "github.com/justinas/alice" ) type ListUsersInput struct { Gender string `in:"query=gender" AgeRange []int `in:"query=age_range" IsMember bool `in:"query=is_member" } func ListUsers(rw http.ResponseWriter, r *http.Request) { // Retrieve you data in one line of code! input := r.Context().Value(httpin.Input).(*ListUsersInput) fmt.Printf("input: %#v\n", input) } func init() { // Bind input struct with handler. http.Handle("/users", alice.New( httpin.NewInput(ListUsersInput{}), ).ThenFunc(ListUsers)) } func main() { r, _ := http.NewRequest("GET", "/users?gender=male&age_range=18&age_range=24&is_member=1", nil) rw := httptest.NewRecorder() http.DefaultServeMux.ServeHTTP(rw, r) } ``` -------------------------------- ### Integrate httpin with gorilla/mux Source: https://ggicci.github.io/httpin/integrations/gorilla Demonstrates how to register the 'path' directive with gorilla/mux to extract path variables into an httpin input struct. It shows setting up the router, defining an input struct with path parameters, and handling the request. ```go package main import ( "fmt" "net/http" "net/http/httptest" "github.com/ggicci/httpin" httpin_integration "github.com/ggicci/httpin/integration" "github.com/gorilla/mux" "github.com/justinas/alice" ) type ListUserReposInput struct { Username string `in:"path=username" Visibility string `in:"query=visibility" Fork bool `in:"query=fork" } func ListUserRepos(rw http.ResponseWriter, r *http.Request) { // Retrieve you data in one line of code! input := r.Context().Value(httpin.Input).(*ListUserReposInput) fmt.Printf("input: %#v\n", input) } func init() { // Register a directive named "path" to retrieve values from `mux.Vars`, // i.e. decode path variables. httpin_integration.UseGorillaMux("path", mux.Vars) } func main() { router := mux.NewRouter() // Bind input struct with handler. router.Handle("/users/{username}/repos", alice.New( httpin.NewInput(ListUserReposInput{}), // ).ThenFunc(ListUserRepos)).Methods("GET") r, _ := http.NewRequest("GET", "/users/ggicci/repos?visibility=public&fork=1", nil) rw := httptest.NewRecorder() router.ServeHTTP(rw, r) } ``` -------------------------------- ### Core Options Variable Source: https://ggicci.github.io/httpin/github.com/ggicci/httpin Defines a collection of options for creating a Core instance within the httpin package. These options configure the behavior of request decoding and encoding. ```Go var Option coreOptions = coreOptions{ WithErrorHandler: core.WithErrorHandler, WithMaxMemory: core.WithMaxMemory, WithNestedDirectivesEnabled: core.WithNestedDirectivesEnabled, } ``` -------------------------------- ### httpin API Documentation Source: https://ggicci.github.io/httpin/github.com/ggicci/httpin Comprehensive API documentation for the httpin Go package, covering core functions for decoding HTTP requests into Go structs and encoding Go structs into HTTP requests. It also details methods for handling file uploads. ```APIDOC httpin Package API: Core Decoding and Encoding Functions: Decode(req *http.Request, opts ...Option) (interface{}, error) - Decodes an HTTP request into a new Go struct instance. The struct type is determined by the first Option provided, or it decodes into a map[string]interface{} if no specific type is given. - Parameters: - req: The incoming http.Request object. - opts: Optional configuration settings (e.g., specifying the target struct type). - Returns: - interface{}: The decoded Go struct or map. - error: An error if decoding fails. DecodeTo(req *http.Request, input interface{}, opts ...Option) error - Decodes an HTTP request directly into a provided Go struct instance. - Parameters: - req: The incoming http.Request object. - input: A pointer to the Go struct instance to decode into. - opts: Optional configuration settings. - Returns: - error: An error if decoding fails. New(inputStruct interface{}, opts ...Option) (*Request, error) - Creates a new httpin Request object from a Go struct. This struct's fields, tagged with httpin directives, define the HTTP request to be built. - Parameters: - inputStruct: The Go struct containing data and directives. - opts: Optional configuration settings. - Returns: - *Request: A configured httpin Request object. - error: An error if the struct cannot be processed. NewInput(inputStruct interface{}, opts ...Option) (*Request, error) - Alias for New. Creates a new httpin Request object from a Go struct. - Parameters: - inputStruct: The Go struct containing data and directives. - opts: Optional configuration settings. - Returns: - *Request: A configured httpin Request object. - error: An error if the struct cannot be processed. NewRequest(method, url string, input interface{}, opts ...Option) (*http.Request, error) - Constructs a new http.Request from a Go struct. The struct fields are mapped to HTTP request components (path, query, body, headers) based on httpin tags. - Parameters: - method: The HTTP method (e.g., "GET", "POST"). - url: The target URL. - input: The Go struct to encode into the request. - opts: Optional configuration settings. - Returns: - *http.Request: The constructed HTTP request. - error: An error if request creation fails. NewRequestWithContext(ctx context.Context, method, url string, input interface{}, opts ...Option) (*http.Request, error) - Constructs a new http.Request with a context from a Go struct. - Parameters: - ctx: The request context. - method: The HTTP method. - url: The target URL. - input: The Go struct to encode into the request. - opts: Optional configuration settings. - Returns: - *http.Request: The constructed HTTP request. - error: An error if request creation fails. File Handling: File Type: Represents a file to be uploaded. UploadFile(path string) File - Creates a File object from a local file path. - Parameters: - path: The path to the local file. - Returns: - File: A File object ready for upload. UploadStream(r io.Reader) File - Creates a File object from an io.Reader (e.g., an uploaded file stream). - Parameters: - r: The io.Reader providing the file content. - Returns: - File: A File object ready for upload. Common Struct Tagging Directives: - `in:"query"`: Maps field to URL query parameter. - `in:"path"`: Maps field to URL path parameter. - `in:"header"`: Maps field to HTTP request header. - `in:"cookie"`: Maps field to HTTP cookie. - `in:"form"`: Maps field to form data. - `in:"json"`: Maps field to JSON request body. - `name:"fieldName"`: Specifies the name of the parameter/header/field. - `required:"true"`: Marks the field as mandatory. - `type:"file"`: Indicates the field is a file upload. Options (Example): - WithTargetStruct(target interface{}): Specifies the struct type for Decode. - WithFileName(name string): Sets the filename for file uploads. - WithContentType(contentType string): Sets the content type for file uploads. ``` -------------------------------- ### Manual HTTP Request Parsing (Comparison) Source: https://ggicci.github.io/httpin/github.com/ggicci/httpin Provides a comparison snippet showing the manual parsing logic required using the standard `net/http` package for similar functionality, highlighting the verbosity httpin aims to reduce. ```go import "net/http" import "strconv" func ListUsersManual(rw http.ResponseWriter, r *http.Request) { // Manual parsing for 'page' pageStr := r.FormValue("page") page, err := strconv.ParseInt(pageStr, 10, 64) if err != nil { // Handle invalid parameter: page return } // Manual parsing for 'per_page' perPageStr := r.FormValue("per_page") perPage, err := strconv.ParseInt(perPageStr, 10, 64) if err != nil { // Handle invalid parameter: per_page return } // Manual parsing for 'is_member' isMemberStr := r.FormValue("is_member") isMember, err := strconv.ParseBool(isMemberStr) if err != nil { // Handle invalid parameter: is_member return } // ... Do sth. with parsed values ... } ``` -------------------------------- ### httpin form directive request scenarios Source: https://ggicci.github.io/httpin/directives/form Illustrates how the 'form' directive handles different request types, including GET with query parameters and POST with URL-encoded bodies. It highlights how values from the request body override values from the URL query string. ```APIDOC Usage Scenarios: 1. GET /users?role=backend&hireable=true Profile: { Role: "backend", Hireable: true } // Works like query directive 2. POST /users HTTP/1.1 Host: foo.example Content-Type: application/x-www-form-urlencoded role=frontend&hireable=false Profile: { Role: "frontend", Hireable: false } // Body data used 3. POST /users?hireable=true HTTP/1.1 Host: foo.example Content-Type: application/x-www-form-urlencoded role=frontend&hireable=false Profile: { Role: "frontend", Hireable: false } // Body overrides URL query for 'hireable' 4. POST /users?hireable=true HTTP/1.1 Host: foo.example Content-Type: application/x-www-form-urlencoded role=frontend Profile: { Role: "frontend", Hireable: true } // 'hireable' from URL query used as body is missing it ``` -------------------------------- ### httpin UploadFile Helper Source: https://ggicci.github.io/httpin/github.com/ggicci/httpin A helper function to create a httpin File instance from a local file path. Useful for uploading files stored on the filesystem. ```go func UploadFile(path string) *File ``` -------------------------------- ### JSON Payload Request with patch.Field Source: https://ggicci.github.io/httpin/advanced/patch Demonstrates how to use `patch.Field` for JSON payload requests. It shows defining a struct with `patch.Field` for fields like `Username`, `Gender`, and `Age`. The example includes decoding the JSON body and checking the `Valid` field to determine if a field was present in the request. ```go import ( "encoding/json" "net/http" "github.com/ggicci/httpin/patch" ) type AccountPatchPayload struct { Username patch.Field[string] Gender patch.Field[string] Age patch.Field[int] } func PatchAccount(rw http.ResponseWriter, r *http.Request) { var payload AccountPatchPayload json.NewDecoder(r.Body).Decode(&payload) if !payload.Username.Valid { // field "Username" is missing (not found or null) } } ``` -------------------------------- ### Form Request with patch.Field Source: https://ggicci.github.io/httpin/advanced/patch Illustrates using `patch.Field` for form requests, including query strings, form data, and multipart-form data. It shows defining a struct with `in` tags to map fields and checking the `Valid` status. The example highlights that in form requests, a missing field is one that does not appear in the request at all. ```go import ( "net/http" "github.com/ggicci/httpin/patch" "github.com/ggicci/httpin" ) type AccountPatchForm struct { Username patch.Field[string] `in:"form=username"` Gender patch.Field[string] `in:"form=gender"` Age patch.Field[int] `in:"form=age"` } func PatchAccount(rw http.ResponseWriter, r *http.Request) { payload := r.Context().Value(httpin.Input).(*AccountPatchForm) if !payload.Username.Valid { // field "Username" is missing (not found or null) } } ``` -------------------------------- ### Create Core Instance Source: https://ggicci.github.io/httpin/github.com/ggicci/httpin Creates a new Core instance responsible for decoding HTTP requests into a specific input struct and encoding that struct into HTTP requests. The Core instance is bound to the provided struct type. ```Go func New(inputStruct any, opts ...core.Option) (*core.Core, error) ``` -------------------------------- ### httpin API Documentation - Core Functions Source: https://ggicci.github.io/httpin/github.com/ggicci/httpin Documents the primary functions provided by the httpin package for decoding and creating HTTP requests. This includes functions for decoding into generic types or specific input structures, and for generating new HTTP requests. ```APIDOC Package httpin API: func Decode[T any](req *http.Request, opts ...core.Option) (*T, error) - Decodes an HTTP request into a struct of type T. - Parameters: - req: The http.Request to decode. - opts: Optional configuration options for decoding. - Returns: - A pointer to the decoded struct of type T. - An error if decoding fails. func DecodeTo(req *http.Request, input any, opts ...core.Option) error - Decodes an HTTP request into a provided input struct instance. - Parameters: - req: The http.Request to decode. - input: A pointer to the Go struct instance to populate. - opts: Optional configuration options for decoding. - Returns: - An error if decoding fails. func NewRequest(method, url string, input any, opts ...core.Option) (*http.Request, error) - Creates a new http.Request from a Go struct instance. - Parameters: - method: The HTTP method (e.g., "GET", "POST"). - url: The request URL. - input: The Go struct instance to encode into the request body or parameters. - opts: Optional configuration options. - Returns: - A pointer to the created http.Request. - An error if request creation fails. func NewRequestWithContext(ctx context.Context, method, url string, input any, opts ...core.Option) (*http.Request, error) - Creates a new http.Request with a specified context from a Go struct instance. - Parameters: - ctx: The context for the new request. - method: The HTTP method. - url: The request URL. - input: The Go struct instance to encode. - opts: Optional configuration options. - Returns: - A pointer to the created http.Request. - An error if request creation fails. func NewInput(inputStruct any, opts ...core.Option) func(http.Handler) http.Handler - Creates an http.Handler middleware that decodes the request into the provided inputStruct and injects it into the request context. - Parameters: - inputStruct: A Go struct instance to decode into. - opts: Optional configuration options. - Returns: - An http.Handler middleware function. func New(inputStruct any, opts ...core.Option) (*core.Core, error) - Initializes the httpin core with a given input struct definition. - Parameters: - inputStruct: A Go struct instance defining the input structure. - opts: Optional configuration options. - Returns: - A pointer to the httpin core instance. - An error if initialization fails. ``` -------------------------------- ### httpin UploadStream Helper Source: https://ggicci.github.io/httpin/github.com/ggicci/httpin A helper function to create a httpin File instance from an io.ReadCloser. Ideal for uploading files from data streams. ```go func UploadStream(r io.ReadCloser) *File ``` -------------------------------- ### Go: Create HTTP Request with httpin Source: https://ggicci.github.io/httpin/directives/default This Go code snippet demonstrates how to use the httpin library to create an HTTP request. It defines a struct with tags to map fields to query parameters and then uses httpin.NewRequest to generate the request, which is then dumped for inspection. ```go package main import ( "fmt" "net/http/httputil" "github.com/ggicci/httpin" ) type ListTasksQuery struct { Page int `in:"query=page;default=1" PerPage int `in:"query=per_page;default=20" StateList []string `in:"query=state;default=pending,running" } func main() { input := &ListTasksQuery{Page: 3} r, _ := httpin.NewRequest("GET", "/tasks", input) data, _ := httputil.DumpRequest(r, true) fmt.Printf("%s\n", data) } ``` -------------------------------- ### Create HTTP Request from Struct Data Source: https://ggicci.github.io/httpin/github.com/ggicci/httpin Constructs an HTTP request from a given method, URL, and input struct. The struct's fields' 'in' tags dictate how data is mapped to the request. This function is a wrapper for NewRequestWithContext using context.Background(). ```Go func NewRequest(method, url string, input any, opts ...core.Option) (*http.Request, error) // Example usage: // addUserRequest, err := NewRequest("GET", "http://example.com", addUserPayload) ``` ```Go func NewRequestWithContext(ctx context.Context, method, url string, input any, opts ...core.Option) (*http.Request, error) // Example usage: // addUserRequest, err := NewRequestWithContext(context.Background(), "GET", "http://example.com", addUserPayload) ``` -------------------------------- ### Registering and Using a Custom Coder (Go) Source: https://ggicci.github.io/httpin/directives/coder Demonstrates how to define a custom type (MyDate) that implements httpin_core.Stringable, register it with a specific name ('date'), and then use the 'coder' directive in a struct to apply this custom logic. ```go import httpin_core "github.com/ggicci/httpin/core" type MyDate time.Time // adapted time.Time to MyDate, MyDate must implement httpin_core.Stringable func (t MyDate) ToString() (string, error) { return time.Time(t).Format("2006-01-02"), nil } func (t *MyDate) FromString(value string) error { v, err := time.Parse("2006-01-02", value) if err != nil { return &InvalidDate{Value: value, Err: err} } *t = MyDate(v) return nil } func init() { // Before using the named coder, you need to register it. httpin_core.RegisterNamedCoder[time.Time]("date", func(t *time.Time) (httpin_core.Stringable, error) { return (*MyDate)(t), nil }) } type ListUsersInput struct { Gender string `in:"form=gender" // By default, the decoder is auto-selected by the field type, which is of `time.Time`. // When coder directive is set, the specified named coder "date" will be used instead. Birthday time.Time `in:"form=birthday;coder=date" } ``` -------------------------------- ### Gin Middleware for httpin Input Binding Source: https://ggicci.github.io/httpin/integrations/gin This Go code defines a gin middleware function `BindInput` that creates an httpin engine for a given input struct. It decodes the request into the struct, handles potential errors like invalid fields, and injects the input into the request context. This allows handlers to easily access parsed input data. ```go package main import ( "context" "errors" "fmt" "net/http" "net/http/httptest" "github.com/ggicci/httpin" "github.com/gin-gonic/gin" ) // BindInput instances an httpin engine for an input struct as a gin middleware. func BindInput(inputStruct interface{}) gin.HandlerFunc { engine, err := httpin.New(inputStruct) if err != nil { panic(err) } return func(c *gin.Context) { input, err := engine.Decode(c.Request) if err != nil { var invalidFieldError *httpin.InvalidFieldError if errors.As(err, &invalidFieldError) { c.AbortWithStatusJSON(http.StatusBadRequest, invalidFieldError) return } c.AbortWithStatus(http.StatusInternalServerError) return } ctx := context.WithValue(c.Request.Context(), httpin.Input, input) c.Request = c.Request.WithContext(ctx) c.Next() } } type ListUsersInput struct { Gender string `in:"query=gender"` AgeRange []int `in:"query=age_range"` IsMember bool `in:"query=is_member"` } func ListUsers(c *gin.Context) { input := c.Request.Context().Value(httpin.Input).(*ListUsersInput) fmt.Printf("input: %#v\n", input) } func main() { router := gin.New() // Bind input struct with handler. router.GET("/users", BindInput(ListUsersInput{}), ListUsers) r, _ := http.NewRequest("GET", "/users?gender=male&age_range=18&age_range=24&is_member=1", nil) rw := httptest.NewRecorder() router.ServeHTTP(rw, r) } ``` -------------------------------- ### Struct Tagging for Data Binding Source: https://ggicci.github.io/httpin/advanced/concepts Demonstrates how to use the 'in' struct tag in Go to define directives for binding request data to struct fields. Multiple directives can be chained using semicolons. ```go type Authorization struct { Token string `in:"query=access_token,token;header=x-api-token;required"` } ``` -------------------------------- ### Create HTTP Middleware for Decoding Source: https://ggicci.github.io/httpin/github.com/ggicci/httpin Generates an HTTP middleware handler that decodes the HTTP request into a struct instance and places its pointer into the request's context. This middleware can be chained with other handlers. ```Go func NewInput(inputStruct any, opts ...core.Option) func(http.Handler) http.Handler // Example usage: // http.Handle("/users", alice.New(httpin.NewInput(&ListUsersRequest{})).ThenFunc(ListUsersHandler)) ``` -------------------------------- ### Configure httpin Core with Custom Error Handler Source: https://ggicci.github.io/httpin/advanced/error-handler Shows how to apply the `WithErrorHandler` option when creating a core httpin instance, followed by decoding a request using the configured instance. ```go co, err := httpin.New(Thing{}, WithErrorHandler(CustomErrorHandler)) input, err := co.Decode(req) ``` -------------------------------- ### Integrate httpin with go-chi/chi Router Source: https://ggicci.github.io/httpin/integrations/gochi Demonstrates how to use httpin middleware with go-chi/chi to bind request data into Go structs. It shows registering the 'path' directive to extract URL parameters and using httpin.NewInput middleware to process query parameters. ```go package main import ( "fmt" "net/http" "net/http/httptest" "github.com/ggicci/httpin" httpin_integration "github.com/ggicci/httpin/integration" "github.com/go-chi/chi/v5" ) type ListUserReposInput struct { Username string `in:"path=username" Visibility string `in:"query=visibility" Fork bool `in:"query=fork" } func ListUserRepos(rw http.ResponseWriter, r *http.Request) { // Retrieve you data in one line of code! input := r.Context().Value(httpin.Input).(*ListUserReposInput) fmt.Printf("input: %#v\n", input) } func init() { // Register a directive named "path" to retrieve values from `chi.URLParam`, // i.e. decode path variables. httpin_integration.UseGochiURLParam("path", chi.URLParam) } func main() { router := chi.NewRouter() // Bind input struct with handler. router.With( httpin.NewInput(ListUserReposInput{}), ).Get("/users/{username}/repos", ListUserRepos) r, _ := http.NewRequest("GET", "/users/ggicci/repos?visibility=public&fork=1", nil) rw := httptest.NewRecorder() router.ServeHTTP(rw, r) } ```