Validator 검증 라이브러리
문서 주소: validator/README.md at master · go-playground/validator (github.com)
공식 예제: validator/_examples at master · go-playground/validator (github.com)
소개
go-playground/validator는 구조체 태그 기반의 값 검증기를 구현하며 다음과 같은 독특한 특징이 있습니다.
검증 태그와 사용자 정의 검증기를 사용하여 교차 필드 및 교차 구조체 검증 가능
슬라이스, 배열, map 또는 모든 다차원 필드 검증 가능
map 의 key 와 value 를 깊이 있게 검증 가능
기본 타입을 통해 처리 방법 결정
사용자 정의 필드 타입 처리 가능
별칭 태그 지원로 여러 검증을 단일 태그에 매핑하여 구조체 검증 정의 용이
사용자 정의 필드명 추출 가능 (예: JSON 이름 추출하여 오류 메시지 표시)
다국어 오류 메시지 지원
gin프레임워크의 표준 기본 검증 컴포넌트
설치
go get github.com/go-playground/validator/v10가져오기
import "github.com/go-playground/validator/v10"태그
검증기에는 매우 많은 기본 검증 태그가 있으며 모든 태그에 해당하는 검증 함수는 baked_in.go 파일에서 찾을 수 있습니다. 검증기의 구조체 Tag 는 validate입니다.
예를 들어
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_addr | Unix 도메인 소켓 엔드포인트 주소 |
uri | 통합 리소스 식별자 |
url | 통합 리소스 로케이터 |
url_encoded | 통합 리소스 식별자 인코딩 |
urn_rfc2141 | RFC 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 | 설명 |
|---|---|
base64 | Base64 문자열 |
base64url | Base64URL 문자열 |
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_alpha2 | iso 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 | 지정된 모든 필드가 존재하지 않을 때 필드가 존재하지 않거나 비어있을 수 있음 |
unique | 각 arr, map, slice 값이 고유한지 검증 |
별칭
| Tag | 설명 |
|---|---|
iscolor | hexcolor|rgb|rgba|hsl|hsla |
country_code | iso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric |
연산자
| Tag | 설명 | Hex |
|---|---|---|
, | AND 연산, 여러 검증 태그 사용, 모든 조건 만족 필요, 쉼표 사이 공백 불가 | 0x2c |
| | OR 연산, 여러 검증 태그 사용, 그 중 하나만 만족하면 됨 | 0x7c |
- | 해당 필드 검증 건너뜀 | 0x2d |
= | 파라미터 매칭 기호 | 0x3d |
TIP
검증 필드에서 연산자를 매칭하려면 utf8 16 진수 표현 형식으로 교체해야 합니다. 예를 들어
filed string `validate:"contains=0x2c"`사용법
다음은 Validator의 기본 사용법과 몇 가지 코드 예제를 소개합니다.
싱글톤
var validate *validator.Validate사용 시 공식은 전체 프로그램 수명 주기 동안 검증기 인스턴스를 하나만 유지하는 것을 권장하며 이는 데이터 캐싱에 유리합니다.
검증기 생성
Validator 를 단독으로 사용하고 다른 프레임워크와 통합하지 않는 경우 수동으로 검증기를 생성해야 합니다.
validate = validator.New()구조체 검증
func (v *Validate) Struct(s interface{}) errorStruct 메서드는 구조체의 모든 공개된 필드를 검증하며 기본적으로 중첩 구조체 검증을 자동으로 수행합니다. 잘못된 값이나 nil 값을 전달하면 InvalidValidationError 를 반환하고 검증 실패 오류는 ValidationErrors 를 반환합니다.
예제
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' tagmap 검증
func (v *Validate) ValidateMap(data map[string]interface{}, rules map[string]interface{}) map[string]interface{}map 태그를 통해 키 - 값 쌍을 검증합니다.
예제
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 를 사용합니다.
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슬라이스의 각 사용자에 대한 구조체 검증
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
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
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' tagTIP
Var 메서드는 구조체, 변수, 슬라이스, map 을 검증할 수 있으며 dive 태그와 합리적으로 결합하여 사용해야 합니다.
필드 검증
필드 검증의 파라미터는 기본 타입이 아닌 구조체 필드명이며 자체 필드명이나 중첩 구조체의 필드명일 수 있습니다.
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 파라미터로 사용된 필드나 구조체가 존재하지 않으면 검증 실패로 간주됩니다. 예를 들어:
type Password struct {
FirstPassword string `validate:"eqfield=SeconddPaswod"` // SeconddPaswod != SecondPassword
SecondPassword string
}이러한 철자 오류는 발견하기 어렵고 검증 시에도 검증 실패 형태로만 표시되므로 매우 주의해야 합니다.
고급
다음으로 고급 사용 기술과 더 많은 사용자 정의 작업을 설명합니다.
사용자 정의 별칭
때때로 필드에 매우 많은 검증 태그가 있는 경우 다른 필드에서 재사용하려면 직접 복사하여 붙여넣을 수 있지만 이것이 최선의 방법은 아닙니다. 더 좋은 방법은 별칭을 등록하여 재사용성을 높이는 것입니다. 다음 예제를 보십시오:
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)
}출력
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 를 제공합니다. 다음 예제를 보십시오:
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입니다. 출력은 다음과 같습니다.
<nil>
Key: 'Example.Name' Error:Field validation for 'Name' failed on the 'is666' tagTIP
등록된 Tag 가 이미 존재하면 기존 것으로 덮어쓰여지므로 기본 Tag 검증 로직을 "재작성"할 수 있습니다.
사용자 정의 타입 검증 함수
타입 검증 함수는 특정 타입 전용으로 일반적으로 비기본 타입에 사용되며 기본 타입의 기본 검증을 덮어쓸 수도 있습니다. 다음 예제를 보십시오:
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
}출력
Key: 'Example.Address' Error:Field validation for 'Address' failed on the 'required' tag
<nil>TIP
여러 타입을 하나의 함수에 등록하는 것도 같은 원리입니다.
사용자 정의 구조체 레벨 검증 함수
구조체 레벨 검증 함수의 차이점은 다른 함수의 파라미터가 필드인 반면 이 함수의 파라미터는 구조체라는 점입니다. 다음 예제를 보십시오:
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검증기의 기본 언어는 영어이지만 프로젝트 개발 시 한 가지 언어만 사용하지는 않습니다. 이때 국제화 다국어 컴포넌트가 필요합니다. 다음 예제를 보십시오:
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'을 포함해야 함]각 오류를 개별적으로 번역할 수도 있습니다.
for _, fieldError := range err.(validator.ValidationErrors) {
fmt.Println(fieldError.Translate(trans))
}출력
Name 은 텍스트'jack'을 포함해야 함
Age 는 18 이상이어야 함
Address 는 텍스트'시'로 끝나야 함반환 값이 map 이며 기본 오류 메시지 번역은 되었지만 사용에는 아직 부족합니다. 고객이나 프론트엔드와 더 잘 연동할 수 있도록 오류 메시지를 더 다듬어야 합니다.
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 는 다음과 같습니다:
// 사용자 정의 태그를 추가했으며 이는 구조체 필드의 중국어 이름을 위해 사용되며 원래 필드 이름을 대체합니다
func CustomTagNameFunc(field reflect.StructField) string {
label := field.Tag.Get("label")
if len(label) == 0 {
return field.Name
}
return label
}마지막으로 등록합니다.
validate.RegisterTagNameFunc(CustomTagNameFunc)다시 실행하여 출력
이름은 텍스트'jack'을 포함해야 함
나이는 18 이상이어야 함
주소는 텍스트'시'로 끝나야 함하지만 이것만으로는 프론트엔드에 반환할 오류 정보로 충분하지 않습니다. 정보를 json 이나 메시지 전송에 적합한 형식으로 포맷팅해야 합니다. map 을 json 으로 직렬화하는 방법을 생각할 수 있지만 다음과 같은 결과를 얻을 수 있습니다:
{
"User.주소": "주소는 텍스트'시'로 끝나야 함",
"User.이름": "이름은 텍스트'back'을 포함해야 함",
"User.나이": "나이는 18 이상이어야 함",
"User.성별": "성별은 필수 필드입니다"
}map 의 key 값을 처리하여 다음과 같은 결과를 얻습니다:
{
"주소": "주소는 텍스트'시'로 끝나야 함",
"이름": "이름은 텍스트'back'을 포함해야 함",
"나이": "나이는 18 이상이어야 함",
"성별": "성별은 필수 필드입니다"
}하지만 위와 같은 정보를 프론트엔드에 반환하는 것은 권장하지 않습니다. 문자열로 처리하여 정보로 반환할 수 있습니다.
이름은 텍스트'back'을 포함해야 함, 나이는 18 이상이어야 함, 주소는 텍스트'시'로 끝나야 함, 성별은 필수 필드입니다,전체 코드
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 를 예로 듭니다:
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
}마지막으로 등록합니다.
validate.RegisterTranslation("required", trans, requiredOverrideRegister, requiredOverrideTranslation)결과
이름은 텍스트'back'을 포함해야 함, 나이는 18 이상이어야 함, 주소는 텍스트'시'로 끝나야 함, 성별은 필수 입력 필드입니다,언어 파일
실제로 코드로 하나씩 등록하는 것은 매우 번거롭습니다. universal-translator 는 JSON 구성 파일을 통해 번역을 제공하는 방법을 제공합니다: universal-translator/examples/full-with-files at master · go-playground/universal-translator (github.com)
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 파일
[
{
"locale": "zh",
"key": "required",
"trans": "이는 매우 중요한 필드{0}입니다. 반드시 입력해야 합니다",
"override": true
}
]출력
map[Gopher.Language:이는 매우 중요한 필드 Language 입니다. 반드시 입력해야 합니다]TIP
universal-translator 사용 시 많은 함정이 있습니다. 기존 Tag 를 덮어쓰려면 type 과 rule 를 모두 비워둘 수 있습니다. 기존 구성에도 없기 때문입니다. 일관성을 유지하는 것이 가장 좋습니다. 어떤 type 을 입력하면 해당 map 에 구성이 추가되며 Cardinal 이나 다른 type 이고 rule 에 one 등을 구성했다면 로컬에서 해당 구성을 해야 정상적으로 사용할 수 있으며 그렇지 않으면 오류가 발생합니다.
