Skip to content

companyinfo/go-reqbind

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Go-Reqbind

Go Reference License

Go-Reqbind is a Go library that automatically unmarshals HTTP requests into structured Go types and validates them using go-playground/validator. It supports extracting data from headers, query parameters, path parameters, and request bodies -- all driven by struct tags.

Table of Contents

Features

  • 🔄 Automatic Unmarshaling: Extract data from headers, query parameters, path parameters, and request bodies into Go structs
  • âś… Built-in Validation: Integrated with go-playground/validator for robust validation
  • 🎯 Custom Validations: Support for custom validation functions
  • 📝 Nested Structures: Handle complex nested query parameters and JSON bodies
  • 🔍 Detailed Error Messages: Get clear, structured validation error messages
  • 🏷️ Flexible Tagging: Use struct tags to map request data to struct fields

Installation

go get go.companyinfo.dev/go-reqbind

Quick Start

package main

import (
    "net/http"
    "go.companyinfo.dev/go-reqbind"
)

type CreateUserRequest struct {
    Body struct {
        Name  string `json:"name" validate:"required,min=3"`
        Email string `json:"email" validate:"required,email"`
    } `goReq:"body"`
}

func handler(w http.ResponseWriter, r *http.Request) {
    req := &CreateUserRequest{}
    
    validationErrors, err := reqbind.UnmarshalAndValidate(*r, req, nil)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    if validationErrors != nil {
        // Handle validation errors
        return
    }
    
    // Use req.Body.Name and req.Body.Email
}

Usage

Basic Concepts

Go-Reqbind uses struct tags to identify which parts of the HTTP request should be unmarshaled:

  • goReq:"header" - Extract from HTTP headers
  • goReq:"query" - Extract from query parameters
  • goReq:"path" - Extract from path parameters (requires URL template)
  • goReq:"body" - Extract from request body (JSON)

1. Query Parameters

Go-Reqbind supports nested query parameters using bracket notation (e.g., filter[user][name]=value).

Example 1: Simple Query Parameters

type SearchRequest struct {
    QueryParams struct {
        Name   string
        Page   int
        Limit  int
    } `goReq:"query"`
}

// URL: /search?name=john&page=1&limit=10

Example 2: Nested Query Parameters

type FilterRequest struct {
    QueryParams struct {
        Filter struct {
            User struct {
                Name  string
                Email string
            }
        }
        Page struct {
            Size   int
            Number int
        }
    } `goReq:"query"`
}

// URL: /users?filter[user][name]=John&filter[user][email]=john@example.com&page[size]=20&page[number]=1

Example 3: Using Custom Field Names with Tags

You can use the goReq tag to map query parameters to different struct field names:

type MyRequest struct {
    QueryParams struct {
        Name   string `goReq:"name"`
        Filter struct {
            User struct {
                Id        string `goReq:"userID"`
                Profile struct {
                    Age struct {
                        Min uint8 `goReq:"min"`
                        Max uint8 `goReq:"max"`
                    } `goReq:"ageRange"`
                } `goReq:"profile"`
            }
        }
    } `goReq:"query"`
}

// URL: /users?name=john&filter[user][userID]=123&filter[user][profile][age_range][min]=18&filter[user][profile][age_range][max]=65

Example 4: Arrays in Query Parameters

type SearchRequest struct {
    QueryParams struct {
        Filter struct {
            User struct {
                Interests []string
                Tags      []string
            }
        }
    } `goReq:"query"`
}

// URL: /search?filter[user][interests]=coding,reading,traveling&filter[user][tags]=developer,go

Example 5: Pointers for Optional Values

type FilterRequest struct {
    QueryParams struct {
        Filter struct {
            Year struct {
                Min *int
                Max *int
            }
        }
    } `goReq:"query"`
}

// URL: /search?filter[year][min]=2021
// Min will be set to 2021, Max will be nil

Example 6: Maps in Query Parameters

type SearchRequest struct {
    QueryParams struct {
        Filter struct {
            Metadata map[string]any
        }
    } `goReq:"query"`
}

// URL: /search?filter[metadata][reportType]=general-report&filter[metadata][domain]=nl

2. Headers

Extract HTTP headers into struct fields. Header matching is case-insensitive.

type MyRequest struct {
    Header struct {
        UserID         string   `goReq:"X-User-Id" validate:"required"`
        ContentType    string   `goReq:"Content-Type"`
        AcceptEncoding []string `goReq:"Accept-Encoding"`
    } `goReq:"header"`
}

// Headers:
// X-User-Id: user-123
// Content-Type: application/json
// Accept-Encoding: gzip, deflate, br

Note:

  • Header names are matched case-insensitively
  • Multiple values for the same header are joined with commas for strings, or stored as a slice for slice fields
  • Use goReq:"-" to skip a field during unmarshaling but still validate it

3. Path Parameters

Path parameters require a URL template to extract values from the URL path.

type GetUserProfileRequest struct {
    PathParams struct {
        UserID string `goReq:"userID" validate:"uuid"`
        Year   string `goReq:"year"`
    } `goReq:"path"`
}

// URL: /api/users/6f3c1b3e-9d42-4f7a-8b8a-3b8d2e2e7c91/profile/2024
// Template: /api/users/:userID/profile/:year

options := &reqbind.Options{
    URLTemplate: "/api/users/:userID/profile/:year",
}
validationErrors, err := reqbind.UnmarshalAndValidate(*r, req, options)

4. Request Body

Extract JSON request body into struct fields. Uses standard json tags.

type CreateUserProfileRequest struct {
    Body struct {
        FirstName string `json:"firstName" validate:"required,min=2"`
        LastName  string `json:"lastName" validate:"required,min=2"`
        Email     string `json:"email" validate:"required,email"`
        Phone     string `json:"phone,omitempty"`
        Address   struct {
            Street   string `json:"street,omitempty"`
            City     string `json:"city,omitempty"`
            Postcode string `json:"postcode,omitempty"`
            Country  string `json:"country,omitempty"`
        } `json:"address,omitempty"`
    } `goReq:"body"`
}

// Request Body:
// {
//   "firstName": "John",
//   "lastName": "Doe",
//   "email": "john@example.com",
//   "phone": "+1234567890",
//   "address": {
//     "street": "123 Main St",
//     "city": "New York",
//     "postcode": "10001",
//     "country": "USA"
//   }
// }

5. Complete Example: Combining All Request Parts

type GetUserRequest struct {
    Header struct {
        UserID         string   `goReq:"X-User-Id" validate:"required"`
        ContentType    string   `goReq:"Content-Type"`
        AcceptEncoding []string `goReq:"Accept-Encoding"`
    } `goReq:"header"`
    
    PathParams struct {
        UserID string `goReq:"userID" validate:"uuid"`
    } `goReq:"path"`
    
    QueryParams struct {
        Filter struct {
            User struct {
                Name    string `validate:"required"`
                Profile struct {
                    FirstName string `validate:"required,min=2,max=50"`
                    LastName  string `validate:"required,min=2,max=50"`
                    Email     string `validate:"required,email"`
                    Age       uint8  `validate:"required,numeric,gte=18,lte=120"`
                    Gender    string `validate:"omitempty,oneof=male female prefer_not_to"`
                }
                Address struct {
                    Postcode string `validate:"omitempty,len=5"`
                }
            }
        }
        Page struct {
            Size   int
            Number int
        }
    } `goReq:"query"`
}

func handler(w http.ResponseWriter, r *http.Request) {
    req := &GetUserRequest{}
    
    options := &reqbind.Options{
        URLTemplate: "/api/users/:userID",
    }
    
    validationErrors, err := reqbind.UnmarshalAndValidate(*r, req, options)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    if validationErrors != nil {
        // Return validation errors
        w.Header().Set("Content-Type", "application/json")
        w.WriteHeader(http.StatusBadRequest)
        json.NewEncoder(w).Encode(validationErrors)
        return
    }
    
    // Process the request
    // Access: req.Header.UserID, req.PathParams.UserID, req.QueryParams.Filter.User.Name, etc.
}

Validation

Go-Reqbind integrates with go-playground/validator for validation. Use the validate tag on struct fields.

Common Validation Tags

  • required - Field must be present and not empty
  • email - Must be a valid email address
  • min=3 - Minimum length/value
  • max=100 - Maximum length/value
  • numeric - Must be numeric
  • uuid - Must be a valid UUID
  • oneof=value1 value2 - Must be one of the specified values
  • omitempty - Validate only if field is not empty
  • dive - Validate nested structs/slices

Validation Error Response

Validation errors are returned as a slice of ValidationError structs:

type ValidationError struct {
    Detail string `json:"detail"`
    Source struct {
        Pointer   string `json:"pointer,omitempty"`   // For body errors (JSON pointer)
        Parameter string `json:"parameter,omitempty"` // For query/path errors
        Header    string `json:"header,omitempty"`    // For header errors
    } `json:"source,omitempty"`
}

Example: Handling Validation Errors

validationErrors, err := reqbind.UnmarshalAndValidate(*r, req, options)
if err != nil {
    // Handle unmarshaling error
    return
}

if validationErrors != nil {
    for _, ve := range validationErrors {
        fmt.Printf("Error in %s: %s\n", ve.Source.Parameter, ve.Detail)
    }
    return
}

Custom Validations

You can register custom validation functions:

// Define custom validation function
func isEven(fl validator.FieldLevel) bool {
    if num, ok := fl.Field().Interface().(int); ok {
        return num%2 == 0
    }
    return false
}

// Use in request struct
type MyRequest struct {
    Body struct {
        Age int `json:"age" validate:"is-even"`
    } `goReq:"body"`
}

// Register custom validation
options := &reqbind.Options{
    CustomValidations: map[string]validator.Func{
        "is-even": isEven,
    },
}

validationErrors, err := reqbind.UnmarshalAndValidate(*r, req, options)

Example: Validation with Nested Slices

type MyRequest struct {
    Body struct {
        Users []struct {
            Profiles []struct {
                Name string `json:"name" validate:"required,min=4"`
            } `json:"profiles" validate:"required,dive"`
        } `json:"users" validate:"required,dive"`
    } `goReq:"body"`
}

// Request Body:
// {
//   "users": [
//     {
//       "profiles": [
//         {"name": "John Doe"},
//         {"name": "Bob"}  // This will fail validation (min=4)
//       ]
//     }
//   ]
// }

API Reference

Functions

Unmarshal(request http.Request, target any, urlTemplate string) error

Unmarshals an HTTP request into the target struct without validation.

Parameters:

  • request: The HTTP request to unmarshal
  • target: Pointer to a struct that will be populated
  • urlTemplate: URL template for path parameter extraction (e.g., /users/:id)

Returns:

  • error: Error if unmarshaling fails

UnmarshalAndValidate(request http.Request, target any, options *Options) ([]ValidationError, error)

Unmarshals an HTTP request into the target struct and validates it.

Parameters:

  • request: The HTTP request to unmarshal
  • target: Pointer to a struct that will be populated
  • options: Optional configuration (can be nil)

Returns:

  • []ValidationError: Slice of validation errors (nil if validation passes)
  • error: Error if unmarshaling fails

Options Struct

type Options struct {
    URLTemplate       string                    // URL template for path parameters
    CustomValidations map[string]validator.Func // Custom validation functions
}

Field Name Resolution

Go-Reqbind uses the following priority to resolve field names:

  1. Struct tag (goReq tag) - If present, uses the tag value
  2. Field name - Converts the query parameter name to TitleCase and matches against struct field names

For example:

  • Query param name matches field Name or field with tag goReq:"name"
  • Query param filter[user][email] matches nested struct Filter.User.Email or field with tag goReq:"email"

Supported Types

Query Parameters

  • string
  • int, int8, int16, int32, int64
  • uint, uint8, uint16, uint32, uint64
  • float32, float64
  • []string (comma-separated values)
  • *int (pointers for optional values)
  • map[string]any

Headers

  • string
  • int
  • []string

Path Parameters

  • string
  • int

Request Body

  • Any type supported by encoding/json

Error Handling

Unmarshaling Errors

If unmarshaling fails (e.g., invalid JSON, malformed query string), Unmarshal and UnmarshalAndValidate return an error.

Validation Errors

If validation fails, UnmarshalAndValidate returns a slice of ValidationError structs with detailed information about each validation failure.

Examples

For complete, runnable examples covering all features, see the examples/ directory.

Contributing

Contributions are welcome! Here's how you can contribute:

  1. Fork the repository
  2. Create a new branch (git checkout -b feature/improvement)
  3. Make your changes
  4. Commit your changes (git commit -am 'Add new feature')
  5. Push to the branch (git push origin feature/improvement)
  6. Create a Pull Request

Please make sure to:

  • Follow the existing code style
  • Add tests if applicable
  • Update documentation as needed
  • Include a clear description of your changes in the PR

License

This project is licensed under the Apache License 2.0.

Acknowledgments

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages