Skip to content

Validator 검증 라이브러리

공식 주소: go-playground/validator: 💯Go Struct and Field validation, including Cross Field, Cross Struct, Map, Slice and Array diving (github.com)

문서 주소: validator/README.md at master · go-playground/validator (github.com)

공식 예제: validator/_examples at master · go-playground/validator (github.com)

벤치마크: go-playground/validator: 💯Go Struct and Field validation, including Cross Field, Cross Struct, Map, Slice and Array diving (github.com)

소개

go-playground/validator는 구조체 태그 기반의 값 검증기를 구현하며 다음과 같은 독특한 특징이 있습니다.

  • 검증 태그와 사용자 정의 검증기를 사용하여 교차 필드 및 교차 구조체 검증 가능

  • 슬라이스, 배열, map 또는 모든 다차원 필드 검증 가능

  • map 의 key 와 value 를 깊이 있게 검증 가능

  • 기본 타입을 통해 처리 방법 결정

  • 사용자 정의 필드 타입 처리 가능

  • 별칭 태그 지원로 여러 검증을 단일 태그에 매핑하여 구조체 검증 정의 용이

  • 사용자 정의 필드명 추출 가능 (예: JSON 이름 추출하여 오류 메시지 표시)

  • 다국어 오류 메시지 지원

  • gin 프레임워크의 표준 기본 검증 컴포넌트

설치

powershell
go get github.com/go-playground/validator/v10

가져오기

go
import "github.com/go-playground/validator/v10"

태그

검증기에는 매우 많은 기본 검증 태그가 있으며 모든 태그에 해당하는 검증 함수는 baked_in.go 파일에서 찾을 수 있습니다. 검증기의 구조체 Tag 는 validate입니다.

예를 들어

go
type User {
  age int `validate:"gte=18"` // 18 세 이상 표시
}

setTagName 메서드를 통해 기본 Tag 를 수정할 수도 있습니다.

필드

Tag설명
eqcsfield별도 구조체에서 현재 필드 값이 param 값으로 지정된 필드와 같은지 검증
eqfield현재 필드 값이 파라미터 값으로 지정된 필드와 같은지 검증
fieldcontains현재 필드 값이 파라미터 값으로 지정된 필드를 포함하는지 검증
fieldexcludes현재 필드 값이 파라미터 값으로 지정된 필드를 포함하지 않는지 검증
gtcsfield별도 구조체에서 현재 필드 값이 파라미터 값으로 지정된 필드보다 큰지 검증
gtecsfield별도 구조체에서 현재 필드 값이 파라미터 값으로 지정된 필드보다 크거나 같은지 검증
gtefield현재 필드 값이 파라미터 값으로 지정된 필드보다 크거나 같은지 검증
gtfield현재 필드 값이 파라미터 값으로 지정된 필드보다 큰지 검증
ltcsfield별도 구조체에서 현재 필드 값이 파라미터 값으로 지정된 필드보다 작은지 검증
ltecsfield별도 구조체에서 현재 필드 값이 파라미터 값으로 지정된 필드보다 작거나 같은지 검증
ltefield현재 필드 값이 파라미터 값으로 지정된 필드보다 작거나 같은지 검증
ltfield현재 필드 값이 파라미터 값으로 지정된 필드보다 작은지 검증
necsfield현재 필드 값이 파라미터 값으로 지정된 별도 구조체의 필드와 같지 않은지 검증
nefield현재 필드 값이 파라미터 값으로 지정된 필드와 같지 않은지 검증

네트워크

Tag설명
cidr무클래스 도메인 간 라우팅 CIDR
cidrv4무클래스 도메인 간 라우팅 CIDRv4
cidrv6무클래스 도메인 간 라우팅 CIDRv6
datauri데이터 통합 리소스 로케이터
fqdn정규화된 도메인 이름 (FQDN)
hostname호스트명 RFC 952
hostname_port일반적으로 소켓 주소에 사용되는 필드 검증 <dns>:<port> 조합
hostname_rfc1123호스트명 RFC 952
ip인터넷 프로토콜 주소 IP
ip4_addr인터넷 프로토콜 주소 IPv4
ip6_addr인터넷 프로토콜 주소 IPv6
ip_addr인터넷 프로토콜 주소 IP
ipv4인터넷 프로토콜 주소 IPv4
ipv6인터넷 프로토콜 주소 IPv6
mac미디어 액세스 제어 주소, 로컬 네트워크 주소
tcp4_addr전송 제어 프로토콜 주소 TCP4
tcp6_addr전송 제어 프로토콜 주소 TCPv6
tcp_addr전송 제어 프로토콜 주소 TCP
udp4_addr사용자 데이터그램 프로토콜 주소 UDPv4
udp6_addr사용자 데이터그램 프로토콜 주소 UDPv6
udp_addr사용자 데이터그램 프로토콜 주소 UDP
unix_addrUnix 도메인 소켓 엔드포인트 주소
uri통합 리소스 식별자
url통합 리소스 로케이터
url_encoded통합 리소스 식별자 인코딩
urn_rfc2141RFC 2141 통합 리소스 이름

문자열

Tag설명
alpha현재 필드 값이 유효한 알파벳인지 검증
alphanum현재 필드 값이 유효한 영숫자인지 검증
alphanumunicode현재 필드 값이 유효한 영숫자 unicode 값인지 검증
alphaunicode현재 필드 값이 유효한 알파벳 unicode 값인지 검증
ascii필드 값이 유효한 ASCII 문자인지 검증
boolean현재 필드 값이 유효한 불리언 값이거나 불리언으로 안전하게 변환 가능한지 검증
contains필드 값이 파라미터에서 지정된 텍스트를 포함하는지 검증
containsany필드 값이 파라미터에서 지정된 문자를 포함하는지 검증
containsrune필드 값이 파라미터에서 지정된 룬을 포함하는지 검증
endsnotwith필드 값이 파라미터에서 지정된 텍스트로 끝나지 않는지 검증
endswith필드 값이 파라미터에서 지정된 텍스트로 끝나는지 검증
excludes필드 값이 파라미터에서 지정된 텍스트를 포함하지 않는지 검증
excludesall필드 값이 파라미터에서 지정된 문자를 포함하지 않는지 검증
excludesrune필드 값이 파라미터에서 지정된 문자를 포함하지 않는지 검증
lowercase현재 필드 값이 소문자 문자열인지 검증
multibyte필드 값이 멀티바이트 문자를 가지는지 검증
number현재 필드 값이 유효한 숫자인지 검증
numeric현재 필드 값이 유효한 수치인지 검증
printascii필드 값이 유효한 인쇄 가능한 ASCII 문자인지 검증
startsnotwith필드 값이 파라미터에서 지정된 텍스트로 시작하지 않는지 검증
startswith필드 값이 파라미터에서 지정된 텍스트로 시작하는지 검증
uppercase현재 필드 값이 대문자 문자열인지 검증

포맷

Tag설명
base64Base64 문자열
base64urlBase64URL 문자열
bic현재 필드 값이 ISO 9362 에 정의된 유효한 BIC 코드 (SWIFT 코드) 인지 검증
bcp47_language_tag현재 필드 값이 BCP47 규격의 언어 태그인지 검증
btc_addr필드 값이 유효한 BTC 주소인지 검증
btc_addr_bech32필드 값이 유효한 bech32 BTC 주소인지 검증
credit_card현재 필드 값이 유효한 신용카드 번호인지 검증
datetime현재 필드 값이 유효한 날짜 시간 문자열인지 검증
e164현재 필드 값이 유효한 e.164 형식의 전화번호인지 검증
email현재 필드 값이 유효한 이메일 주소인지 검증
eth_addr필드 값이 유효한 이더리움 주소인지 검증
hexadecimal현재 필드 값이 유효한 16 진수인지 검증
hexcolor현재 필드 값이 유효한 16 진수 색상인지 검증
hsl현재 필드 값이 유효한 HSL 색상인지 검증
hsla현재 필드 값이 유효한 HSLA 색상인지 검증
html현재 필드 값이 유효한 HTML 인지 검증
html_encoded현재 필드 값이 유효한 HTML 인코딩인지 검증
isbn필드 값이 유효한 v10 또는 v13 ISBN(국제 표준 도서 번호) 인지 검증
isbn10필드 값이 유효한 v10 ISBN(국제 표준 도서 번호) 인지 검증
isbn13필드 값이 유효한 v13 ISBN(국제 표준 도서 번호) 인지 검증
iso3166_1_alpha2현재 필드 값이 유효한 iso3166-1 alpha-2 국가 코드인지 검증
iso3166_1_alpha3현재 필드 값이 유효한 iso3166-1 alpha-3 국가 코드인지 검증
iso3166_1_alpha_numeric현재 필드 값이 유효한 iso3166-1 영숫자 국가 코드인지 검증
iso3166_2현재 필드 값이 유효한 국가 지역 코드 (ISO 3166-2) 인지 검증
iso4217현재 필드 값이 유효한 통화 코드 (ISO 4217) 인지 검증
json현재 필드 값이 유효한 json 문자열인지 검증
jwt현재 필드 값이 유효한 JWT 문자열인지 검증
latitude필드 값이 유효한 위도 좌표인지 검증
longitude필드 값이 유효한 경도 좌표인지 검증
postcode_iso3166_alpha2iso 3166 alpha 2 국가 코드 값에 따라 검증
postcode_iso3166_alpha2_field필드를 통해 검증하며 이 필드는 iso 3166 alpha 2 의 국가 코드 값을 나타냄
rgb현재 필드 값이 유효한 RGB 색상인지 검증
rgba현재 필드 값이 유효한 RGBA 색상인지 검증
ssn필드 값이 유효한 SSN 인지 검증
timezone현재 필드 값이 유효한 시간대 문자열인지 검증
uuid필드 값이 모든 버전의 유효한 UUID 인지 검증
uuid3필드 값이 유효한 UUID v3 인지 검증
uuid3_rfc4122필드 값이 유효한 RFC4122 v3 UUID 인지 검증
uuid4필드 값이 유효한 v4 UUID 인지 검증
uuid4_rfc4122필드 값이 유효한 RFC4122 v4 UUID 인지 검증
uuid5필드 값이 유효한 v5 UUID 인지 검증
uuid5_rfc4122필드 값이 유효한 RFC4122 v5 UUID 인지 검증
uuid_rfc4122필드 값이 모든 버전의 유효한 RFC4122 UUID 인지 검증
md4필드 값이 유효한 MD4 인지 검증
md5필드 값이 유효한 MD5 인지 검증
sha256필드 값이 유효한 SHA256 인지 검증
sha384필드 값이 유효한 SHA384 인지 검증
sha512필드 값이 유효한 SHA512 인지 검증
ripemd128필드 값이 유효한 RIPEMD128 인지 검증
ripemd160필드 값이 유효한 RIPEMD160 인지 검증
tiger128필드 값이 유효한 TIGER128 인지 검증
tiger160필드 값이 유효한 TIGER160 인지 검증
tiger192필드 값이 유효한 TIGER192 인지 검증
semver현재 필드 값이 시맨틱 버전 2.0.0 에 정의된 유효한 semver 버전인지 검증
ulid필드 값이 유효한 ULID 인지 검증

비교

Tag설명
eq같음
gt초과
gte이상
lt미만
lte이하
ne다름

기타

Tag설명
dir파일 디렉토리
file파일 경로
isdefault현재 필드 값이 기본 정적 값인지 검증
len필드 길이
max최대값
min최소값
oneof열거값 중 하나인지
omitempty필드가 설정되지 않은 경우 해당 필드 무시
required필수 값
required_if다른 지정된 필드가 지정된 값과 같을 때만 필드가 존재하고 비어있지 않은지 검증
required_unless다른 지정된 필드가 지정된 값과 같지 않은 경우 필드가 존재하고 비어있지 않은지 검증
required_with지정된 필드 중 하나가 존재할 때 필드가 존재하고 비어있지 않은지 검증
required_with_all지정된 모든 필드가 존재할 때 필드가 존재하고 비어있지 않은지 검증
required_without지정된 필드 중 하나가 존재하지 않을 때 필드가 존재하고 비어있지 않은지 검증
required_without_all지정된 모든 필드가 존재하지 않을 때 필드가 존재하고 비어있지 않은지 검증
excluded_if다른 지정된 필드가 지정된 값과 같을 때만 필드가 존재하지 않거나 비어있을 수 있음
excluded_unless다른 지정된 필드가 지정된 값과 같지 않은 경우 필드가 존재하지 않거나 비어있을 수 있음
excluded_with지정된 필드 중 하나가 존재할 때 필드가 존재하지 않거나 비어있을 수 있음
excluded_with_all지정된 모든 필드가 존재할 때 필드가 존재하지 않거나 비어있을 수 있음
excluded_without지정된 필드 중 하나가 존재하지 않을 때 필드가 존재하지 않거나 비어있을 수 있음
excluded_without_all지정된 모든 필드가 존재하지 않을 때 필드가 존재하지 않거나 비어있을 수 있음
uniquearr, map, slice 값이 고유한지 검증

별칭

Tag설명
iscolorhexcolor|rgb|rgba|hsl|hsla
country_codeiso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric

연산자

Tag설명Hex
,AND 연산, 여러 검증 태그 사용, 모든 조건 만족 필요, 쉼표 사이 공백 불가0x2c
|OR 연산, 여러 검증 태그 사용, 그 중 하나만 만족하면 됨0x7c
-해당 필드 검증 건너뜀0x2d
=파라미터 매칭 기호0x3d

TIP

검증 필드에서 연산자를 매칭하려면 utf8 16 진수 표현 형식으로 교체해야 합니다. 예를 들어

go
filed string `validate:"contains=0x2c"`

사용법

다음은 Validator의 기본 사용법과 몇 가지 코드 예제를 소개합니다.

싱글톤

go
var validate *validator.Validate

사용 시 공식은 전체 프로그램 수명 주기 동안 검증기 인스턴스를 하나만 유지하는 것을 권장하며 이는 데이터 캐싱에 유리합니다.

검증기 생성

Validator 를 단독으로 사용하고 다른 프레임워크와 통합하지 않는 경우 수동으로 검증기를 생성해야 합니다.

go
validate = validator.New()

구조체 검증

go
func (v *Validate) Struct(s interface{}) error

Struct 메서드는 구조체의 모든 공개된 필드를 검증하며 기본적으로 중첩 구조체 검증을 자동으로 수행합니다. 잘못된 값이나 nil 값을 전달하면 InvalidValidationError 를 반환하고 검증 실패 오류는 ValidationErrors 를 반환합니다.

예제

go
package validate

import (
  "fmt"
  "github.com/go-playground/validator/v10"
  "testing"
)

type User struct {
  Name    string `validate:"contains=jack"` // 이름에 jack 포함
  Age     int    `validate:"gte=18"`        // 17 세 이상
  Address string `validate:"endswith=시"`    // 시로 끝남
}

func TestStruct(t *testing.T) {
  validate := validator.New()
  user := User{
    Name:    "jacklove",
    Age:     17,
    Address: "토포시",
  }
  err := validate.Struct(user)
  for _, err := range err.(validator.ValidationErrors) {
    fmt.Println(err.Namespace()) // 네임스페이스
    fmt.Println(err.Field())
    fmt.Println(err.StructNamespace())
    fmt.Println(err.StructField())
    fmt.Println(err.Tag())
    fmt.Println(err.ActualTag())
    fmt.Println(err.Kind())
    fmt.Println(err.Type())
    fmt.Println(err.Value())
    fmt.Println(err.Param())
    fmt.Println()
  }
  fmt.Println(err)
}

출력

User.Age
Age
User.Age
Age
gte
gte
int
int
17
18

Key: 'User.Age' Error:Field validation for 'Age' failed on the 'gte' tag

map 검증

go
func (v *Validate) ValidateMap(data map[string]interface{}, rules map[string]interface{}) map[string]interface{}

map 태그를 통해 키 - 값 쌍을 검증합니다.

예제

go
func TestMap(t *testing.T) {
   user := map[string]interface{}{
      "name":    "jak",
      "age":     17,
      "address": "토포시",
   }
   rules := map[string]interface{}{
      "name":    "contains=jacklove",
      "age":     "gte=18",
      "address": "endswith=시",
   }

   validate := validator.New()

   validateMap := validate.ValidateMap(user, rules)
   fmt.Println(validateMap)
}

출력

map[age:Key: '' Error:Field validation for '' failed on the 'gte' tag name:Key: '' Error:Field validation for '' failed on the 'contains' tag]

슬라이스 검증

문자열 슬라이스 검증에서 dive 앞의 태그는 슬라이스 검증용이며 dive 뒤의 태그는 슬라이스 내 값 검증용입니다. 중첩 슬라이스도 같은 원리로 차원 수만큼 dive 를 사용합니다.

go
func TestSlice1(t *testing.T) {
  list := []string{"jack", "mike", "lisa", "golang"}
  err := validator.New().Var(list, "max=5,dive,contains=a,min=5") // 슬라이스 길이 최대 5, 요소는 문자 a 포함, 최소 길이 5
  fmt.Println(err)
}

출력

Key: '[0]' Error:Field validation for '[0]' failed on the 'min' tag
Key: '[1]' Error:Field validation for '[1]' failed on the 'contains' tag
Key: '[2]' Error:Field validation for '[2]' failed on the 'min' tag

슬라이스의 각 사용자에 대한 구조체 검증

go
func TestSlice(t *testing.T) {
   userList := make([]User, 0)
   user := User{
      Name:    "jacklove",
      Age:     17,
      Address: "토포시",
   }
   userList = append(userList, user)
   err := validator.New().Var(userList, "dive") // "dive" 는 심층 검증을 의미하며 요소가 구조체일 때 자동으로 구조체 검증 수행
   fmt.Println(err)
}

출력

Key: '[0].Age' Error:Field validation for 'Age' failed on the 'gte' tag

변수 검증

비교 간단하므로过多的인 설명은 생략합니다.

예제 1

go
func TestVar(t *testing.T) {
   name := "jack"
   err := validator.New().Var(name, "max=5,contains=a,min=1,endswith=l") // 최대 길이 5, 최소 길이 1, 문자 a 포함, 문자 l 로 끝남
   fmt.Println(err)
}

출력

Key: '' Error:Field validation for '' failed on the 'endswith' tag

예제 2

go
func TestVar1(t *testing.T) {
   age := 18
   err := validator.New().Var(age, "gte=19")
   fmt.Println(err)
}

출력

Key: '' Error:Field validation for '' failed on the 'gte' tag

TIP

Var 메서드는 구조체, 변수, 슬라이스, map 을 검증할 수 있으며 dive 태그와 합리적으로 결합하여 사용해야 합니다.

필드 검증

필드 검증의 파라미터는 기본 타입이 아닌 구조체 필드명이며 자체 필드명이나 중첩 구조체의 필드명일 수 있습니다.

go
type Password struct {
   FirstPassword  string `validate:"eqfield=SecondPassword"` // 두 번 입력한 비밀번호가 같은지 검증
   SecondPassword string
}

type RegisterUser struct {
   Username string `validate:"necsfield=Password.FirstPassword"` // 등록 시 보안을 위해 비밀번호와 사용자명이 같지 않아야 함
   Password Password
}

func TestCrossStructFieldValidate(t *testing.T) {
   validate = validator.New()
   // 실패
   fmt.Println(validate.Struct(RegisterUser{
      Username: "gopher",
      Password: Password{
         FirstPassword:  "gopher",
         SecondPassword: "gophers",
      },
   }))
   // 성공
   fmt.Println(validate.Struct(RegisterUser{
      Username: "gophers",
      Password: Password{
         FirstPassword:  "gopher",
         SecondPassword: "gopher",
      },
   }))
}

출력

Key: 'RegisterUser.Username' Error:Field validation for 'Username' failed on the 'necsfield' tag
Key: 'RegisterUser.Password.FirstPassword' Error:Field validation for 'FirstPassword' failed on the 'eqfield' tag
<nil>

WARNING

필드 검증 사용 시 Tag 파라미터로 사용된 필드나 구조체가 존재하지 않으면 검증 실패로 간주됩니다. 예를 들어:

go
type Password struct {
   FirstPassword  string `validate:"eqfield=SeconddPaswod"` // SeconddPaswod != SecondPassword
   SecondPassword string
}

이러한 철자 오류는 발견하기 어렵고 검증 시에도 검증 실패 형태로만 표시되므로 매우 주의해야 합니다.

고급

다음으로 고급 사용 기술과 더 많은 사용자 정의 작업을 설명합니다.

사용자 정의 별칭

때때로 필드에 매우 많은 검증 태그가 있는 경우 다른 필드에서 재사용하려면 직접 복사하여 붙여넣을 수 있지만 이것이 최선의 방법은 아닙니다. 더 좋은 방법은 별칭을 등록하여 재사용성을 높이는 것입니다. 다음 예제를 보십시오:

go
var validate *validator.Validate

const PERSON_NAME_RULES = "max=10,min=1,contains=jack"

func TestAlias(t *testing.T) {
  validate = validator.New()
    // 별칭 등록
  validate.RegisterAlias("namerules", PERSON_NAME_RULES)
  type person struct {
    FirstName string `validate:"namerules"` // 별칭 사용
    LastName  string `validate:"namerules"`
  }

  err := validate.Struct(person{
    FirstName: "",
    LastName:  "",
  })

  fmt.Println(err)
}

출력

go
Key: 'person.FirstName' Error:Field validation for 'FirstName' failed on the 'namerules' tag
Key: 'person.LastName' Error:Field validation for 'LastName' failed on the 'namerules' tag

사용자 정의 검증 함수

컴포넌트 자체의 검증 태그로 기본적인 상황은 충분하지만 때로는 특수한 요구 사항에 대해 직접 로직을 정의해야 할 때가 있습니다. Validator 는 사용자 정의 검증 함수를 위한 관련 API 를 제공합니다. 다음 예제를 보십시오:

go
func TestCustomValidate(t *testing.T) {
   validate = validator.New()
   fmt.Println(validate.RegisterValidation("is666", is666))
   type Example struct {
      Name string `validate:"is666"`
   }
   fmt.Println(validate.Struct(Example{Name: "777"}))
   fmt.Println(validate.Struct(Example{Name: "666"}))
}

func is666(fl validator.FieldLevel) bool {
   return fl.Field().String() == "666"
}

필드 값이 "666" 과 같은지 판단하는 함수를 생성했으며 해당 Tag 는 is666입니다. 출력은 다음과 같습니다.

go
<nil>
Key: 'Example.Name' Error:Field validation for 'Name' failed on the 'is666' tag

TIP

등록된 Tag 가 이미 존재하면 기존 것으로 덮어쓰여지므로 기본 Tag 검증 로직을 "재작성"할 수 있습니다.

사용자 정의 타입 검증 함수

타입 검증 함수는 특정 타입 전용으로 일반적으로 비기본 타입에 사용되며 기본 타입의 기본 검증을 덮어쓸 수도 있습니다. 다음 예제를 보십시오:

go
type Address struct {
  name string
}

func TestCustomTypeValidate(t *testing.T) {
  validate = validator.New()
  validate.RegisterCustomTypeFunc(ValidateAddress, Address{}) // 타입 검증 함수와 해당 타입 등록
  type Example struct {
    Address Address `validate:"required"`
  }
  fmt.Println(validate.Struct(Example{Address: Address{name: ""}}))
  fmt.Println(validate.Struct(Example{Address: Address{name: "cn"}}))
}

func ValidateAddress(value reflect.Value) interface{} {
  if address, ok := value.Interface().(Address); ok {
    // 오류 처리
    if address.name == "" {
      return address.name
    }

    return value // 필드 반환은 검증이 정확함을 의미
  }
  return nil
}

출력

go
Key: 'Example.Address' Error:Field validation for 'Address' failed on the 'required' tag
<nil>

TIP

여러 타입을 하나의 함수에 등록하는 것도 같은 원리입니다.

사용자 정의 구조체 레벨 검증 함수

구조체 레벨 검증 함수의 차이점은 다른 함수의 파라미터가 필드인 반면 이 함수의 파라미터는 구조체라는 점입니다. 다음 예제를 보십시오:

go
type People struct {
   FirstName string
   LastName  string
}

func TestCustomStructLevel(t *testing.T) {
   validate = validator.New()
   validate.RegisterStructValidation(PeopleValidate, People{}) // 타입 등록과 동일하게 전달할 수 있는 구조체도 하나 이상일 수 있음
   err := validate.Struct(People{
      FirstName: "",
      LastName:  "",
   })
   fmt.Println(err)
}

func PeopleValidate(sl validator.StructLevel) {
   people := sl.Current().Interface().(People)

   if people.FirstName == "" || people.LastName == "" {
      sl.ReportError(people.FirstName, "FirstName", "FirstName", "", "")
      sl.ReportError(people.FirstName, "LastName", "LastName", "", "")
   }
}

출력

Key: 'People.FirstName' Error:Field validation for 'FirstName' failed on the '' tag
Key: 'People.LastName' Error:Field validation for 'LastName' failed on the '' tag

다국어

번역기 컴포넌트

go get github.com/go-playground/universal-translator

지역 컴포넌트

go get github.com/go-playground/locales

검증기의 기본 언어는 영어이지만 프로젝트 개발 시 한 가지 언어만 사용하지는 않습니다. 이때 국제화 다국어 컴포넌트가 필요합니다. 다음 예제를 보십시오:

go
import (
  "fmt"
  "github.com/go-playground/locales/zh"
  ut "github.com/go-playground/universal-translator"
  "github.com/go-playground/validator/v10"
  zh_trans "github.com/go-playground/validator/v10/translations/zh"
  "reflect"
  "testing"
)

type User struct {
   Name    string `validate:"contains=jack"` // 이름에 jack 포함
   Age     int    `validate:"gte=18"`        // 17 세 이상
   Address string `validate:"endswith=시"`    // 시로 끝남
}

var (
   uni      *ut.UniversalTranslator
   validate *validator.Validate
)

func TestTranslate(t *testing.T) {
   zh := zh.New()
   // 첫 번째는 백업, 이후는 지원 언어로 여러 개 가능
   uni = ut.New(zh, zh)
   // 이 언어는 일반적으로 http 요청 헤더의 Accept-Language 에서 해당 언어를 가져올 수 있음
   trans, found := uni.GetTranslator(zh.Locale())
   validate = validator.New()
   if found {
      zh_trans.RegisterDefaultTranslations(validate, trans) // 기본 번역기 등록
   }
   err := validate.Struct(User{
      Name:    "",
      Age:     0,
      Address: "",
   })
   fmt.Println(err.(validator.ValidationErrors).Translate(trans))
}

출력

map[User.Address:Address 는 텍스트'시'로 끝나야 함 User.Age:Age 는 18 이상이어야 함 User.Name:Name 은 텍스트'jack'을 포함해야 함]

각 오류를 개별적으로 번역할 수도 있습니다.

go
for _, fieldError := range err.(validator.ValidationErrors) {
   fmt.Println(fieldError.Translate(trans))
}

출력

Name 은 텍스트'jack'을 포함해야 함
Age 는 18 이상이어야 함
Address 는 텍스트'시'로 끝나야 함

반환 값이 map 이며 기본 오류 메시지 번역은 되었지만 사용에는 아직 부족합니다. 고객이나 프론트엔드와 더 잘 연동할 수 있도록 오류 메시지를 더 다듬어야 합니다.

go
type User struct {

   Name    string `validate:"contains=jack" label:"이름"` // 이름에 jack 포함
   Age     int    `validate:"gte=18" label:"나이"`        // 17 세 이상
   Address string `validate:"endswith=시" label:"주소"`    // 시로 끝남
   Sex     string `validate:"required" label:"성별"`
}

먼저 사용자 정의 태그 label 을 정의하며 그 값은 필드의 중국어 이름으로 원래 필드 이름을 대체합니다. 이후 검증기를 통해 TagNameFunc 를 등록하며 그 역할은 필드 이름을 가져올 때 원래 이름을 대체하는 것입니다. errors.go 파일의 Field() string 메서드 주석에는 "레이블 이름이 있는 필드 이름이 실제 필드 이름보다 우선한다"고 나와 있으므로 이후 오류 발생 시 사용자 정의 중국어 이름을 사용하여 영어 단어를 대체할 수 있습니다. TagNameFunc 는 다음과 같습니다:

go
// 사용자 정의 태그를 추가했으며 이는 구조체 필드의 중국어 이름을 위해 사용되며 원래 필드 이름을 대체합니다
func CustomTagNameFunc(field reflect.StructField) string {
   label := field.Tag.Get("label")
   if len(label) == 0 {
      return field.Name
   }
   return label
}

마지막으로 등록합니다.

go
validate.RegisterTagNameFunc(CustomTagNameFunc)

다시 실행하여 출력

이름은 텍스트'jack'을 포함해야 함
나이는 18 이상이어야 함
주소는 텍스트'시'로 끝나야 함

하지만 이것만으로는 프론트엔드에 반환할 오류 정보로 충분하지 않습니다. 정보를 json 이나 메시지 전송에 적합한 형식으로 포맷팅해야 합니다. map 을 json 으로 직렬화하는 방법을 생각할 수 있지만 다음과 같은 결과를 얻을 수 있습니다:

json
{
  "User.주소": "주소는 텍스트'시'로 끝나야 함",
  "User.이름": "이름은 텍스트'back'을 포함해야 함",
  "User.나이": "나이는 18 이상이어야 함",
  "User.성별": "성별은 필수 필드입니다"
}

map 의 key 값을 처리하여 다음과 같은 결과를 얻습니다:

json
{
  "주소": "주소는 텍스트'시'로 끝나야 함",
  "이름": "이름은 텍스트'back'을 포함해야 함",
  "나이": "나이는 18 이상이어야 함",
  "성별": "성별은 필수 필드입니다"
}

하지만 위와 같은 정보를 프론트엔드에 반환하는 것은 권장하지 않습니다. 문자열로 처리하여 정보로 반환할 수 있습니다.

이름은 텍스트'back'을 포함해야 함, 나이는 18 이상이어야 함, 주소는 텍스트'시'로 끝나야 함, 성별은 필수 필드입니다,

전체 코드

go
import (
   "fmt"
   "github.com/go-playground/locales/zh"
   ut "github.com/go-playground/universal-translator"
   "github.com/go-playground/validator/v10"
   zh_trans "github.com/go-playground/validator/v10/translations/zh"
   "reflect"
   "strings"
   "testing"
)

type User struct {
   Name    string `validate:"contains=back" label:"이름"` // 이름에 jack 포함
   Age     int    `validate:"gte=18" label:"나이"`        // 17 세 이상
   Address string `validate:"endswith=시" label:"주소"`    // 시로 끝남
   Sex     string `validate:"required" label:"성별"`
}

var (
   uni      *ut.UniversalTranslator
   validate *validator.Validate
)

// 사용자 정의 태그를 추가했으며 이는 구조체 필드의 중국어 이름을 위해 사용되며 원래 필드 이름을 대체합니다
func CustomTagNameFunc(field reflect.StructField) string {
   label := field.Tag.Get("label")
   if len(label) == 0 {
      return field.Name
   }
   return label
}

func TestTranslate(t *testing.T) {
   zh := zh.New()
   uni = ut.New(zh, zh)
   // 이 언어는 일반적으로 http 요청 헤더의 Accept-Language 에서 해당 언어를 가져올 수 있음
   trans, found := uni.GetTranslator(zh.Locale())
   validate = validator.New()
   if found {
      zh_trans.RegisterDefaultTranslations(validate, trans) // 기본 번역기 등록
   }
   validate.RegisterTagNameFunc(CustomTagNameFunc)
   err := validate.Struct(User{
      Name:    "",
      Age:     0,
      Address: "",
   })
   translate := errInfoFormat(err.(validator.ValidationErrors), trans)
   fmt.Println(translate)
}

func errInfoFormat(errors validator.ValidationErrors, trans ut.Translator) string {
   builder := strings.Builder{}
   for _, err := range errors {
      builder.WriteString(err.Translate(trans))
      builder.WriteString(", ")
   }
   return builder.String()
}

마지막으로 오류 메시지가 너무 차갑고 더 인간적으로 만들려면 RegisterTranslation 메서드를 사용하여 지정된 tag 의 오류 메시지를 재작성할 수 있습니다. 동시에 두 가지 타입의 함수가 필요합니다. RegisterTranslationsFunc 는 해당 Tag 의 번역 템플릿 등록을 담당하고 TranslationFunc 는 템플릿을 처리하여 최종 번역 내용을 얻습니다. 여기서는 required 를 예로 듭니다:

go
func requiredOverrideRegister(ut ut.Translator) error { // 이 함수의 역할은 번역 템플릿 등록
  return ut.Add("required", "{}는 필수 입력 필드입니다", true) // {} 는 플레이스홀더, true 는 기존 템플릿 재작성 여부
}

func requiredOverrideTranslation(ut ut.Translator, fe validator.FieldError) string { // 이 함수의 역할은 번역 내용 처리
  t, _ := ut.T("required", fe.Field()) // 파라미터는 여러 개일 수 있으며 등록한 Tag 템플릿의 플레이스홀더 수에 따라 다름
  return t
}

마지막으로 등록합니다.

go
validate.RegisterTranslation("required", trans, requiredOverrideRegister, requiredOverrideTranslation)

결과

이름은 텍스트'back'을 포함해야 함, 나이는 18 이상이어야 함, 주소는 텍스트'시'로 끝나야 함, 성별은 필수 입력 필드입니다,

언어 파일

실제로 코드로 하나씩 등록하는 것은 매우 번거롭습니다. universal-translatorJSON 구성 파일을 통해 번역을 제공하는 방법을 제공합니다: universal-translator/examples/full-with-files at master · go-playground/universal-translator (github.com)

go
func TestFilei18n(t *testing.T) {
   validate = validator.New()
   zh := zh.New()
   universalTranslator := ut.New(zh, zh)
   translator, _ := universalTranslator.GetTranslator(zh.Locale())
   zh_trans.RegisterDefaultTranslations(validate, translator)
   er := universalTranslator.Import(ut.FormatJSON, "./zh.json") // 등록 후 가져오기를 권장하며 이렇게 해야 기존 Tag 를 덮어쓸 수 있음
   if er != nil {
      log.Fatal(er)
   }
   type Gopher struct {
      Language string `validate:"required"`
   }

   err := validate.Struct(Gopher{
      "",
   })
   fmt.Println(err.(validator.ValidationErrors).Translate(translator))
}

JSON 파일

json
[
  {
    "locale": "zh",
    "key": "required",
    "trans": "이는 매우 중요한 필드{0}입니다. 반드시 입력해야 합니다",
    "override": true
  }
]

출력

map[Gopher.Language:이는 매우 중요한 필드 Language 입니다. 반드시 입력해야 합니다]

TIP

universal-translator 사용 시 많은 함정이 있습니다. 기존 Tag 를 덮어쓰려면 typerule 를 모두 비워둘 수 있습니다. 기존 구성에도 없기 때문입니다. 일관성을 유지하는 것이 가장 좋습니다. 어떤 type 을 입력하면 해당 map 에 구성이 추가되며 Cardinal 이나 다른 type 이고 ruleone 등을 구성했다면 로컬에서 해당 구성을 해야 정상적으로 사용할 수 있으며 그렇지 않으면 오류가 발생합니다.

Golang by www.golangdev.cn edit