bind

package module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Aug 10, 2025 License: MIT Imports: 11 Imported by: 0

README

bind (English)

Go Report Card Go Reference

Inspired by go-chi/render.

A simple, powerful, and highly performant Go package for binding request data (JSON, XML, Form) to structs. It features recursive binding, detailed error pathing, file upload support, and is optimized for high-performance scenarios.

Features

  • Multiple Content-Types: Natively supports application/json, application/xml, application/x-www-form-urlencoded, and multipart/form-data.
  • Recursive Binding: Automatically calls the Bind method on nested fields that implement the Binder interface. The binding order is bottom-up, from the innermost field to the outermost struct.
  • File Uploads: Natively binds single (*multipart.FileHeader) and multiple ([]*multipart.FileHeader) file uploads from multipart/form-data requests.
  • Configurable Memory: The maximum memory for multipart form parsing can be easily configured via bind.SetMaxMultipartMemory().
  • Detailed Error Reporting: Errors are wrapped in a BindError type that includes the full field path (e.g., Parent.Child.Field), making debugging significantly easier.
  • Security: Includes a configurable recursion depth limit to prevent stack overflow attacks from malicious or malformed requests.
  • Extensible: Easily register new decoders for custom content types.
  • Performance-Optimized: Caches struct analysis results using sync.Map to minimize reflection overhead in hot paths, making it suitable for high-traffic services.

Installation

go get github.com/DevNewbie1826/bind

Basic Usage

See the examples below for basic JSON and file upload binding.


Advanced Usage

1. Custom Decoders

You can extend bind to support custom content types by registering a new decoder.

package main

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

	"github.com/DevNewbie1826/bind"
)

// 1. Define your custom Content-Type (if not already standard)
const MyCustomContentType bind.ContentType = 100

// 2. Create a custom decoder function
func decodeYAML(r *http.Request, v any) error {
	// In a real implementation, you would use a YAML library.
	// This is a simplified example.
	body, err := io.ReadAll(r.Body)
	if err != nil {
		return err
	}
	if strings.Contains(string(body), "name: John") {
		// Simulate decoding into a struct
		if p, ok := v.(*bind.TestPayload); ok {
			p.Name = "John"
			p.Value = 30
		}
		return nil
	}
	return fmt.Errorf("could not decode YAML")
}

func main() {
	// 3. Register the new decoder
	bind.RegisterDecoder(MyCustomContentType, decodeYAML)

	// You also need a way to map the "application/x-yaml" header to your custom type.
	// This part is left to the application logic, as GetContentType can also be customized.
	
	// ... server setup
}
2. Error Handling Best Practices

bind.Action returns a bind.BindError. You can use errors.As to inspect it and get detailed context about the failure.

package main

import (
	"errors"
	"fmt"
	"log"
	"net/http"

	"github.com/DevNewbie1826/bind"
)

type Address struct {
	City string `json:"city"`
}

func (a *Address) Bind(r *http.Request) error {
	if a.City == "" {
		return fmt.Errorf("city is a required field")
	}
	return nil
}

type User struct {
	Address *Address `json:"address"`
}

func (u *User) Bind(r *http.Request) error { return nil }

func handler(w http.ResponseWriter, r *http.Request) {
	var user User
	// Important: Initialize nested pointers
	user.Address = &Address{}

	if err := bind.Action(r, &user); err != nil {
		var bindErr bind.BindError
		// Use errors.As to check if the error is a BindError
		if errors.As(err, &bindErr) {
			// Now you can access the specific field and the underlying error
			log.Printf("Binding failed on field '%s': %v", bindErr.Field, bindErr.Unwrap())
			http.Error(w, fmt.Sprintf("Bad Request in field '%s'", bindErr.Field), http.StatusBadRequest)
		} else {
			// Handle other types of errors (e.g., malformed JSON)
			http.Error(w, err.Error(), http.StatusBadRequest)
		}
		return
	}
	fmt.Fprintf(w, "Success!")
}

func main() {
	http.HandleFunc("/", handler)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Test with curl:

# Send a request with a missing "city" field
curl -X POST http://localhost:8080 -d '{"address":{}}' -H "Content-Type: application/json"

# Server will log:
# Binding failed on field 'Address': city is a required field

# Client will receive:
# Bad Request in field 'Address'
3. Configuring Multipart Memory

For services that handle large file uploads, it's crucial to control memory usage. You can set the maximum memory for multipart form parsing globally.

It's best to do this once during application startup using an init function.

package main

import (
	"github.com/DevNewbie1826/bind"
	// ... other imports
)

func init() {
    // Set max memory for multipart forms to 64MB
    bind.SetMaxMultipartMemory(64 << 20)
}

// ... your application code

bind (한국어)

go-chi/render에서 영감을 받아 제작되었습니다.

요청 데이터(JSON, XML, Form)를 Go 구조체에 바인딩하는 간단하고, 강력하며, 성능이 뛰어난 패키지입니다. 재귀적 바인딩, 상세한 오류 경로 추적, 파일 업로드 기능을 지원하며 고성능 시나리오에 최적화되어 있습니다.

주요 특징

  • 다양한 Content-Type 지원: application/json, application/xml, application/x-www-form-urlencoded, multipart/form-data를 기본 지원합니다.
  • 재귀적 바인딩: Binder 인터페이스를 구현하는 중첩 필드의 Bind 메서드를 가장 안쪽(bottom-up)부터 순서대로 자동 호출합니다.
  • 파일 업로드: multipart/form-data 요청으로부터 단일(*multipart.FileHeader) 및 다중([]*multipart.FileHeader) 파일 업로드를 자동으로 바인딩합니다.
  • 메모리 설정 가능: bind.SetMaxMultipartMemory() 함수를 통해 멀티파트 폼 파싱 시 최대 메모리를 쉽게 설정할 수 있습니다.
  • 상세한 오류 리포팅: 오류 발생 시 전체 필드 경로(예: Parent.Child.Field)를 포함하는 BindError 타입으로 래핑하여 디버깅을 크게 용이하게 합니다.
  • 보안: 설정 가능한 재귀 깊이 제한을 두어 악의적이거나 잘못된 형식의 요청으로 인한 스택 오버플로우 공격을 방지합니다.
  • 확장성: 커스텀 Content-Type을 위한 새로운 디코더를 쉽게 등록할 수 있습니다.
  • 성능 최적화: sync.Map을 사용하여 구조체 분석 결과를 캐싱함으로써, 트래픽이 많은 서비스에 적합하도록 리플렉션 오버헤드를 최소화합니다.

설치

go get github.com/DevNewbie1826/bind

기본 사용법

기본적인 JSON 및 파일 업로드 바인딩은 아래 예제를 참고하세요.


고급 사용법

1. 커스텀 디코더 등록

bind를 확장하여 커스텀 Content-Type을 지원하도록 새로운 디코더를 등록할 수 있습니다.

package main

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

	"github.com/DevNewbie1826/bind"
)

// 1. 커스텀 Content-Type 정의 (표준이 아닌 경우)
const MyCustomContentType bind.ContentType = 100

// 2. 커스텀 디코더 함수 생성
func decodeYAML(r *http.Request, v any) error {
	// 실제 구현에서는 YAML 라이브러리를 사용해야 합니다.
	// 여기서는 간단한 예시입니다.
	body, err := io.ReadAll(r.Body)
	if err != nil {
		return err
	}
	if strings.Contains(string(body), "name: John") {
		// 구조체로 디코딩하는 것을 시뮬레이션
		if p, ok := v.(*bind.TestPayload); ok {
			p.Name = "John"
			p.Value = 30
		}
		return nil
	}
	return fmt.Errorf("YAML을 디코딩할 수 없습니다")
}

func main() {
	// 3. 새로운 디코더 등록
	bind.RegisterDecoder(MyCustomContentType, decodeYAML)

	// "application/x-yaml" 헤더를 커스텀 타입에 매핑하는 로직도 필요합니다.
	// GetContentType 또한 커스터마이징이 가능하므로, 이 부분은 애플리케이션 로직에 맡겨집니다.
	
	// ... 서버 설정
}
2. 에러 처리 베스트 프랙티스

bind.Actionbind.BindError를 반환합니다. errors.As를 사용하여 에러를 검사하고 실패에 대한 상세한 컨텍스트를 얻을 수 있습니다.

package main

import (
	"errors"
	"fmt"
	"log"
	"net/http"

	"github.com/DevNewbie1826/bind"
)

type Address struct {
	City string `json:"city"`
}

func (a *Address) Bind(r *http.Request) error {
	if a.City == "" {
		return fmt.Errorf("city 필드는 필수입니다")
	}
	return nil
}

type User struct {
	Address *Address `json:"address"`
}

func (u *User) Bind(r *http.Request) error { return nil }

func handler(w http.ResponseWriter, r *http.Request) {
	var user User
	// 중요: 중첩된 포인터 초기화
	user.Address = &Address{}

	if err := bind.Action(r, &user); err != nil {
		var bindErr bind.BindError
		// errors.As를 사용하여 BindError인지 확인
		if errors.As(err, &bindErr) {
			// 특정 필드와 원본 에러에 접근 가능
			log.Printf("필드 '%s'에서 바인딩 실패: %v", bindErr.Field, bindErr.Unwrap())
			http.Error(w, fmt.Sprintf("필드 '%s'에 잘못된 요청", bindErr.Field), http.StatusBadRequest)
		} else {
			// 다른 종류의 에러 처리 (예: 잘못된 JSON 형식)
			http.Error(w, err.Error(), http.StatusBadRequest)
		}
		return
	}
	fmt.Fprintf(w, "성공!")
}

func main() {
	http.HandleFunc("/", handler)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

curl로 테스트하기:

# "city" 필드가 없는 요청 보내기
curl -X POST http://localhost:8080 -d '{"address":{}}' -H "Content-Type: application/json"

# 서버 로그:
# 필드 'Address'에서 바인딩 실패: city 필드는 필수입니다

# 클라이언트가 받는 응답:
# 필드 'Address'에 잘못된 요청
3. 멀티파트 메모리 설정

대용량 파일 업로드를 처리하는 서비스에서는 메모리 사용량을 제어하는 것이 중요합니다. 멀티파트 폼 파싱을 위한 최대 메모리를 전역적으로 설정할 수 있습니다.

애플리케이션 시작 시 init 함수를 사용하여 한 번만 설정하는 것이 가장 좋습니다.

package main

import (
	"github.com/DevNewbie1826/bind"
	// ... 다른 임포트
)

func init() {
    // 멀티파트 폼을 위한 최대 메모리를 64MB로 설정
    bind.SetMaxMultipartMemory(64 << 20)
}

// ... 애플리케이션 코드

License

This project is licensed under the MIT License.


라이선스

이 프로젝트는 MIT 라이선스를 따릅니다.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	MaxMultipartMemory int64 = 32 << 20
)

Functions

func Action

func Action(r *http.Request, v Binder) error

Action - 요청 바인딩 실행 함수 1. 등록된 디코더를 사용하여 요청 본문을 'v'에 디코딩합니다. 2. 'v' 내부의 모든 Binder 필드를 재귀적으로 바인딩합니다. (바텀업 순서) 3. 마지막으로 'v' 자체의 Bind 메서드를 호출합니다. Action - Executes the request binding. 1. Decodes the request body into 'v' using the registered decoder. 2. Recursively binds all Binder fields within 'v' (in bottom-up order). 3. Finally, calls the Bind method on 'v' itself.

func DefaultDecoder

func DefaultDecoder(r *http.Request, v any) error

func ErrorToJSON

func ErrorToJSON(src error) []byte

ErrorToJSON - 에러를 JSON 형식으로 변환 에러 메시지를 {"error":"..."} 형식의 JSON 바이트 슬라이스로 변환합니다. src가 nil이면 빈 에러 메시지를 반환합니다. ErrorToJSON - Converts an error to JSON format. Converts an error message to a JSON byte slice of the form {"error":"..."}. Returns an empty error message if src is nil.

func ErrorToMap

func ErrorToMap(src error) map[string]string

ErrorToMap - 에러를 맵 형식으로 변환 에러 메시지를 map[string]string{"error":"..."} 형식으로 변환합니다. src가 nil이면 빈 에러 메시지를 반환합니다. ErrorToMap - Converts an error to a map format. Converts an error message to map[string]string{"error":"..."}. Returns an empty error message if src is nil.

func GetDecoder

func GetDecoder(ct ContentType) (func(*http.Request, any) error, bool)

GetDecoder - 지정된 Content-Type에 대한 디코더 함수를 반환합니다. 테스트 또는 동적 디코더 관리에 유용합니다. GetDecoder returns the decoder function for the given Content-Type. Useful for testing or dynamic decoder management.

func RegisterDecoder

func RegisterDecoder(ct ContentType, fn func(*http.Request, any) error)

func SetDecode

func SetDecode(fn func(*http.Request, any) error)

SetDecode - 전역 디코더 함수를 안전하게 설정 SetDecode - Safely sets the global decoder function.

func SetMaxMultipartMemory

func SetMaxMultipartMemory(size int64)

Types

type BindError

type BindError struct {
	Field string
	Err   error
}

BindError - 표준 바인딩 에러 구조체 바인딩 실패 시 어떤 필드에서 에러가 발생했는지에 대한 추가 정보를 포함할 수 있습니다. BindError - A standard binding error struct. Can include additional information about which field caused the binding failure.

func (BindError) Error

func (e BindError) Error() string

func (BindError) Unwrap

func (e BindError) Unwrap() error

type Binder

type Binder interface {
	Bind(r *http.Request) error
}

Binder - 바인딩 인터페이스 구조체 또는 필드가 요청(r)을 기반으로 추가적인 바인딩 로직을 수행할 수 있도록 합니다. Binder - The binding interface. Allows a struct or field to perform additional binding logic based on the request (r).

type ContentType

type ContentType int

ContentType - HTTP Content-Type을 나타내는 열거형 ContentType - An enumeration for HTTP Content-Types.

const (
	// ContentTypeUnknown - 알 수 없거나 지원하지 않는 Content-Type
	// ContentTypeUnknown - An unknown or unsupported Content-Type.
	ContentTypeUnknown ContentType = iota
	// ContentTypePlainText - "text/plain"
	// ContentTypePlainText - "text/plain".
	ContentTypePlainText
	// ContentTypeHTML - "text/html"
	// ContentTypeHTML - "text/html".
	ContentTypeHTML
	// ContentTypeJSON - "application/json"
	// ContentTypeJSON - "application/json".
	ContentTypeJSON
	// ContentTypeXML - "application/xml"
	// ContentTypeXML - "application/xml".
	ContentTypeXML
	// ContentTypeForm - "application/x-www-form-urlencoded"
	// ContentTypeForm - "application/x-www-form-urlencoded".
	ContentTypeForm
	// ContentTypeMultipart - "multipart/form-data"
	// ContentTypeMultipart - "multipart/form-data".
	ContentTypeMultipart
	// ContentTypeEventStream - "text/event-stream"
	// ContentTypeEventStream - "text/event-stream".
	ContentTypeEventStream
)

func GetContentType

func GetContentType(s string) ContentType

GetContentType - Content-Type 문자열을 파싱하여 ContentType 열거형 값으로 변환합니다. "; charset=..."과 같은 추가 파라미터는 무시합니다. GetContentType - Parses a Content-Type string and converts it to a ContentType enum value. It ignores additional parameters like "; charset=...".

Jump to

Keyboard shortcuts

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