Validator Validation Library
ที่อยู่ทางการ: 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 ดำเนินการตัวตรวจสอบค่าตามแท็กโครงสร้าง体 มีคุณสมบัติที่ไม่เหมือนใครดังนี้:
สามารถใช้แท็กตรวจสอบและตัวตรวจสอบที่กำหนดเองสำหรับการตรวจสอบข้ามฟิลด์และข้ามโครงสร้าง体
สไลซ์, อาร์เรย์, map หรือฟิลด์หลายมิติใดๆ สามารถตรวจสอบได้
สามารถตรวจสอบ key และ value ของ map ได้
กำหนดวิธีการจัดการตามประเภทพื้นฐานก่อนการตรวจสอบ
สามารถจัดการฟิลด์ประเภทที่กำหนดเองได้
รองรับแท็กนามธรรม ซึ่งอนุญาตให้แมปการตรวจสอบหลายรายการกับแท็กเดียว เพื่อให้ง่ายต่อการกำหนดการตรวจสอบสำหรับโครงสร้าง体
สามารถแยกชื่อฟิลด์ที่กำหนดเอง เช่น สามารถแยกชื่อ JSON ในการตรวจสอบเพื่อแสดงในข้อความข้อผิดพลาด
ข้อผิดพลาดหลายภาษาที่กำหนดเอง
คอมโพเนนต์ตรวจสอบเริ่มต้นมาตรฐานของเฟรมเวิร์ก
gin
การติดตั้ง
go get github.com/go-playground/validator/v10การนำเข้า
import "github.com/go-playground/validator/v10"แท็ก
ตัวตรวจสอบมีแท็กตรวจสอบพื้นฐานมากมาย ฟังก์ชันตรวจสอบที่สอดคล้องกับแท็กทั้งหมดสามารถพบได้ในไฟล์ baked_in.go แท็กโครงสร้าง体ของตัวตรวจสอบคือ validate
เช่น
type User {
age int `validate:"gte=18"` //表示大于等于18岁
}ยังสามารถแก้ไขแท็กเริ่มต้นผ่านเมธอด setTagName
ฟิลด์
| Tag | คำอธิบาย |
|---|---|
eqcsfield | ในโครงสร้างที่แยกต่างหาก ตรวจสอบว่าค่าของฟิลด์ปัจจุบันเท่ากับฟิลด์ที่ระบุโดยค่าของพารามิเตอร์ |
eqfield | ตรวจสอบว่าค่าของฟิลด์ปัจจุบันเท่ากับฟิลด์ที่ระบุโดยค่าของพารามิเตอร์ |
fieldcontains | ตรวจสอบว่าค่าของฟิลด์ปัจจุบันมีฟิลด์ที่ระบุโดยค่าของพารามิเตอร์ |
fieldexcludes | ตรวจสอบว่าค่าของฟิลด์ปัจจุบันไม่มีฟิลด์ที่ระบุโดยค่าของพารามิเตอร์ |
gtcsfield | ในโครงสร้างที่แยกต่างหาก ตรวจสอบว่าค่าของฟิลด์ปัจจุบันมากกว่าฟิลด์ที่ระบุโดยค่าของพารามิเตอร์ |
gtecsfield | ในโครงสร้างที่แยกต่างหาก ตรวจสอบว่าค่าของฟิลด์ปัจจุบันมากกว่าหรือเท่ากับฟิลด์ที่ระบุโดยค่าของพารามิเตอร์ |
gtefield | ตรวจสอบว่าค่าของฟิลด์ปัจจุบันมากกว่าหรือเท่ากับฟิลด์ที่ระบุโดยค่าของพารามิเตอร์ |
gtfield | ตรวจสอบว่าค่าของฟิลด์ปัจจุบันมากกว่าฟิลด์ที่ระบุโดยค่าของพารามิเตอร์ |
ltcsfield | ในโครงสร้างที่แยกต่างหาก ตรวจสอบว่าค่าของฟิลด์ปัจจุบันน้อยกว่าฟิลด์ที่ระบุโดยค่าของพารามิเตอร์ |
ltecsfield | ในโครงสร้างที่แยกต่างหาก ตรวจสอบว่าค่าของฟิลด์ปัจจุบันน้อยกว่าหรือเท่ากับฟิลด์ที่ระบุโดยค่าของพารามิเตอร์ |
ltefield | ตรวจสอบว่าค่าของฟิลด์ปัจจุบันน้อยกว่าหรือเท่ากับฟิลด์ที่ระบุโดยค่าของพารามิเตอร์ |
ltfield | ตรวจสอบว่าค่าของฟิลด์ปัจจุบันน้อยกว่าฟิลด์ที่ระบุโดยค่าของพารามิเตอร์ |
necsfield | ตรวจสอบว่าค่าของฟิลด์ปัจจุบันไม่เท่ากับฟิลด์ในโครงสร้างที่แยกต่างหากที่ระบุโดยค่าของพารามิเตอร์ |
nefield | ตรวจสอบว่าค่าของฟิลด์ปัจจุบันไม่เท่ากับฟิลด์ที่ระบุโดยค่าของพารามิเตอร์ |
Network
| Tag | คำอธิบาย |
|---|---|
cidr | Classless Inter-Domain Routing CIDR |
cidrv4 | Classless Inter-Domain Routing CIDRv4 |
cidrv6 | Classless Inter-Domain Routing CIDRv6 |
datauri | Data Uniform Resource Locator |
fqdn | Fully Qualified Domain Name (FQDN) |
hostname | Hostname RFC 952 |
hostname_port | การตรวจสอบฟิลด์สำหรับชุดค่าผสม<dns>:<port> ที่มักใช้สำหรับที่อยู่ซ็อกเก็ต |
hostname_rfc1123 | Hostname RFC 952 |
ip | Internet Protocol Address IP |
ip4_addr | Internet Protocol Address IPv4 |
ip6_addr | Internet Protocol Address IPv6 |
ip_addr | Internet Protocol Address IP |
ipv4 | Internet Protocol Address IPv4 |
ipv6 | Internet Protocol Address IPv6 |
mac | Media Access Control Address หรือที่เรียกว่าที่อยู่ LAN |
tcp4_addr | Transmission Control Protocol Address TCP4 |
tcp6_addr | Transmission Control Protocol Address TCPv6 |
tcp_addr | Transmission Control Protocol Address TCP |
udp4_addr | User Datagram Protocol Address UDPv4 |
udp6_addr | User Datagram Protocol Address UDPv6 |
udp_addr | User Datagram Protocol Address UDP |
unix_addr | Unix Domain Socket Endpoint Address |
uri | Uniform Resource Identifier |
url | Uniform Resource Locator |
url_encoded | Uniform Resource Identifier Encoding |
urn_rfc2141 | RFC 2141 Uniform Resource Name |
String
| Tag | คำอธิบาย |
|---|---|
alpha | ตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นตัวอักษรที่ถูกต้อง |
alphanum | ตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นตัวอักษรตัวเลขที่ถูกต้อง |
alphanumunicode | ตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นค่าตัวอักษรตัวเลข unicode ที่ถูกต้อง |
alphaunicode | ตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นค่าตัวอักษร unicode ที่ถูกต้อง |
ascii | ตรวจสอบว่าค่าของฟิลด์เป็นอักขระ ASCII ที่ถูกต้อง |
boolean | ตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นค่าบูลีนที่ถูกต้องหรือสามารถแปลงเป็นบูลีนได้อย่างปลอดภัย |
contains | ตรวจสอบว่าค่าของฟิลด์มีข้อความที่ระบุในพารามิเตอร์ |
containsany | ตรวจสอบว่าค่าของฟิลด์มีอักขระใดๆ ที่ระบุในพารามิเตอร์ |
containsrune | ตรวจสอบว่าค่าของฟิลด์มี rune ที่ระบุในพารามิเตอร์ |
endsnotwith | ตรวจสอบว่าค่าของฟิลด์ไม่ลงท้ายด้วยข้อความที่ระบุในพารามิเตอร์ |
endswith | ตรวจสอบว่าค่าของฟิลด์ลงท้ายด้วยข้อความที่ระบุในพารามิเตอร์ |
excludes | ตรวจสอบว่าค่าของฟิลด์ไม่มีข้อความที่ระบุในพารามิเตอร์ |
excludesall | ตรวจสอบว่าค่าของฟิลด์ไม่มีอักขระใดๆ ที่ระบุในพารามิเตอร์ |
excludesrune | ตรวจสอบว่าค่าของฟิลด์ไม่มีอักขระที่ระบุในพารามิเตอร์ |
lowercase | ตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นสตริงตัวพิมพ์เล็ก |
multibyte | ตรวจสอบว่าค่าของฟิลด์มีอักขระหลายไบต์ |
number | ตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นตัวเลขที่ถูกต้อง |
numeric | ตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นค่าตัวเลขที่ถูกต้อง |
printascii | ตรวจสอบว่าค่าของฟิลด์เป็นอักขระ ASCII ที่พิมพ์ได้ |
startsnotwith | ตรวจสอบว่าค่าของฟิลด์ไม่เริ่มต้นด้วยข้อความที่ระบุในพารามิเตอร์ |
startswith | ตรวจสอบว่าค่าของฟิลด์เริ่มต้นด้วยข้อความที่ระบุในพารามิเตอร์ |
uppercase | ตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นสตริงตัวพิมพ์ใหญ่ |
Format
| Tag | คำอธิบาย |
|---|---|
base64 | สตริง Base64 |
base64url | สตริง Base64URL |
bic | ตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นรหัส BIC ที่ถูกต้องตาม ISO 9362 (รหัส SWIFT) |
bcp47_language_tag | ตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นแท็กภาษาตามข้อกำหนด BCP47 |
btc_addr | ตรวจสอบว่าค่าของฟิลด์เป็นที่อยู่ BTC ที่ถูกต้อง |
btc_addr_bech32 | ตรวจสอบว่าค่าของฟิลด์เป็นที่อยู่ bech32 BTC ที่ถูกต้อง |
credit_card | ตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นหมายเลขบัตรเครดิตที่ถูกต้อง |
datetime | ตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นสตริงวันที่เวลาที่ถูกต้อง |
e164 | ตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นหมายเลขโทรศัพท์รูปแบบ e.164 ที่ถูกต้อง |
email | ตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นที่อยู่อีเมลที่ถูกต้อง |
eth_addr | ตรวจสอบว่าค่าของฟิลด์เป็นที่อยู่ Ethereum ที่ถูกต้อง |
hexadecimal | ตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นเลขฐานสิบหกที่ถูกต้อง |
hexcolor | ตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นสีฐานสิบหกที่ถูกต้อง |
hsl | ตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นสี HSL ที่ถูกต้อง |
hsla | ตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นสี HSLA ที่ถูกต้อง |
html | ตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็น HTML ที่ถูกต้อง |
html_encoded | ตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็น HTML encoding ที่ถูกต้อง |
isbn | ตรวจสอบว่าค่าของฟิลด์เป็น ISBN v10 หรือ v13 ที่ถูกต้อง (หมายเลขมาตรฐานสากลของหนังสือ) |
isbn10 | ตรวจสอบว่าค่าของฟิลด์เป็น ISBN v10 ที่ถูกต้อง (หมายเลขมาตรฐานสากลของหนังสือ) |
isbn13 | ตรวจสอบว่าค่าของฟิลด์เป็น ISBN v13 ที่ถูกต้อง (หมายเลขมาตรฐานสากลของหนังสือ) |
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 | ตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นเวอร์ชัน semver ที่ถูกต้องตาม Semantic Versioning 2.0.0 |
ulid | ตรวจสอบว่าค่าของฟิลด์เป็น ULID ที่ถูกต้อง |
Comparison
| Tag | คำอธิบาย |
|---|---|
eq | เท่ากับ |
gt | มากกว่า |
gte | มากกว่าหรือเท่ากับ |
lt | น้อยกว่า |
lte | น้อยกว่าหรือเท่ากับ |
ne | ไม่เท่ากับ |
Other
| 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 เป็นเอกลักษณ์ |
Alias
| Tag | คำอธิบาย |
|---|---|
iscolor | hexcolor|rgb|rgba|hsl|hsla |
country_code | iso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric |
Operators
| Tag | คำอธิบาย | Hex |
|---|---|---|
, | AND operation ใช้แท็กตรวจสอบหลายรายการ ต้องเป็นไปตามเงื่อนไขทั้งหมด ไม่มีช่องว่างระหว่างเครื่องหมายจุลภาค | 0x2c |
| | OR operation ใช้แท็กตรวจสอบหลายรายการ แต่ต้องเป็นไปตามเงื่อนไขใดเงื่อนไขหนึ่ง | 0x7c |
- | ข้ามการตรวจสอบฟิลด์นี้ | 0x2d |
= | สัญลักษณ์การจับคู่พารามิเตอร์ | 0x3d |
TIP
เมื่อต้องการจับคู่ตัวดำเนินการในการตรวจสอบฟิลด์ ต้องใช้การแทนที่ด้วย hex สิบหก utf8 เช่น
filed string `validate:"contains=0x2c"`การใช้งาน
ต่อไปนี้จะแนะนำการใช้งานพื้นฐานของ Validator พร้อมตัวอย่างโค้ด
Singleton
var validate *validator.Validateในการใช้งาน ทางทางการแนะนำให้ มีอินสแตนซ์ตัวตรวจสอบเพียงหนึ่งตัวตลอดวงจรชีวิตของโปรแกรม ซึ่งจะเป็นประโยชน์ต่อการแคชข้อมูล
สร้างตัวตรวจสอบ
เมื่อใช้ Validator แบบเดี่ยวโดยไม่รวมเฟรมเวิร์กอื่น ต้องสร้างตัวตรวจสอบด้วยตนเอง
validate = validator.New()การตรวจสอบโครงสร้าง体
func (v *Validate) Struct(s interface{}) errorเมธอด Struct ใช้สำหรับตรวจสอบฟิลด์สาธารณะทั้งหมดของโครงสร้าง体 โดยค่าเริ่มต้นจะทำการตรวจสอบโครงสร้าง体แบบซ้อนโดยอัตโนมัติ เมื่อส่งค่าที่ไม่ถูกต้องหรือค่าเป็น 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"` //มากกว่าหรือเท่ากับ18ปี
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()) //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
func (v *Validate) ValidateMap(data map[string]interface{}, rules map[string]interface{}) map[string]interface{}ตรวจสอบ key-value ผ่านแท็ก 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]การตรวจสอบ slice
ตรวจสอบ slice สตริง ก่อน dive คือ tag สำหรับตรวจสอบ slice หลัง dive คือ tag สำหรับตรวจสอบค่าใน slice slice ซ้อนก็เช่นเดียวกัน มีกี่มิติก็ใช้ dive กี่ตัว
func TestSlice1(t *testing.T) {
list := []string{"jack", "mike", "lisa", "golang"}
err := validator.New().Var(list, "max=5,dive,contains=a,min=5") //ความยาว slice สูงสุด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ตรวจสอบโครงสร้าง体สำหรับผู้ใช้แต่ละคนใน slice
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 สามารถตรวจสอบประเภทได้แก่ โครงสร้าง体, ตัวแปร, slice, 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
หากแท็กที่ลงทะเบียนมีอยู่แล้ว จะถูกแทนที่ด้วยค่าใหม่ กล่าวคือ สามารถ "เขียนทับ" ตรรกะการตรวจสอบแท็กเริ่มต้นได้
ฟังก์ชันตรวจสอบประเภทที่กำหนดเอง
ฟังก์ชันตรวจสอบประเภทมีไว้สำหรับประเภทใดประเภทหนึ่งโดยเฉพาะ มักใช้สำหรับประเภทที่ไม่ใช่ประเภทพื้นฐาน และสามารถทับการตรวจสอบประเภทพื้นฐานเริ่มต้นได้ ดูตัวอย่างด้านล่าง:
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"` //มากกว่าหรือเท่ากับ18ปี
Address string `validate:"endswith=市"` //ลงท้ายด้วย市
}
var (
uni *ut.UniversalTranslator
validate *validator.Validate
)
func TestTranslate(t *testing.T) {
zh := zh.New()
//ตัวแรกคือตัวสำรอง ต่อมาคือภาษาที่รองรับ สามารถมีได้หลายภาษา
uni = ut.New(zh, zh)
//ภาษาที่นี่โดยปกติสามารถรับจาก Accept-Language ในส่วนหัวของการร้องขอ http
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 เห็นได้ว่าการแปลข้อความข้อผิดพลาดพื้นฐานทำได้แล้ว แต่ยังไม่เพียงพอที่จะนำไปใช้ ต้องตกแต่งข้อความข้อผิดพลาดเพื่อให้ติดต่อกับลูกค้าหรือ frontend ได้ดีขึ้น
type User struct {
Name string `validate:"contains=jack" label:"姓名"` //ชื่อต้องมี jack
Age int `validate:"gte=18" label:"年龄"` //มากกว่าหรือเท่ากับ18ปี
Address string `validate:"endswith=市" label:"地址"` //ลงท้ายด้วย市
Sex string `validate:"required" label:"性别"`
}ก่อนอื่นกำหนดแท็ก label เอง ค่าของมันคือชื่อภาษาจีนของฟิลด์ จากนั้นลงทะเบียน TagNameFunc ผ่านตัวตรวจสอบ หน้าที่ของมันคือรับชื่อฟิลด์หรือแทนที่ชื่อเดิม ในไฟล์ errors.go คำอธิบายประกอบบนเมธอด Filed() 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
地址必须以文本'市'结尾แต่ยังไม่เพียงพอ ยังไม่เพียงพอที่จะส่งคืนเป็นข้อความข้อผิดพลาดให้ frontend คุณอาจคิดที่จะ serialize map เป็น json นี่เป็นวิธีแก้ปัญหา แต่คุณอาจได้ผลลัพธ์ดังนี้:
{
"User.地址": "地址必须以文本'市'结尾",
"User.姓名": "姓名必须包含文本'back'",
"User.年龄": "年龄必须大于或等于18",
"User.性别": "性别为必填字段"
}โดยการประมวลผล key ของ map ได้ผลลัพธ์ดังนี้:
{
"地址": "地址必须以文本'市'结尾",
"姓名": "姓名必须包含文本'back'",
"年龄": "年龄必须大于或等于18",
"性别": "性别为必填字段"
}แต่ไม่แนะนำให้ส่งคืนข้อมูลข้างต้นให้ frontend เราสามารถประมวลผลเป็นสตริงเพื่อส่งคืนเป็นข้อความ
姓名必须包含文本'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:"年龄"` //มากกว่าหรือเท่ากับ18ปี
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)
//ภาษาที่นี่โดยปกติสามารถรับจาก Accept-Language ในส่วนหัวของการร้องขอ http
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 และต้องใช้ฟังก์ชันสองประเภท ประเภทแรกคือ RegisterTranslationsFunc รับผิดชอบการลงทะเบียนเทมเพลตการแปลสำหรับ Tag ที่สอดคล้องกัน อีกประเภทคือ TranslationFunc รับผิดชอบการประมวลผลเทมเพลตเพื่อให้ได้การแปลสุดท้าย ที่นี่ใช้ required เป็นตัวอย่าง:
func requiredOverrideRegister(ut ut.Translator) error { //หน้าที่ของฟังก์ชันนี้คือการลงทะเบียนเทมเพลตการแปล
return ut.Add("required", "{}เป็นฟิลด์ที่ต้องกรอก", true) // {} เป็น placeholder true หมายถึงการเขียนทับเทมเพลตที่มีอยู่
}
func requiredOverrideTranslation(ut ut.Translator, fe validator.FieldError) string { // หน้าที่ของฟังก์ชันนี้คือรับผิดชอบการแปลเนื้อหา
t, _ := ut.T("required", fe.Field()) //พารามิเตอร์สามารถมีได้หลายตัว ขึ้นอยู่กับจำนวน placeholder ในเทมเพลต 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") //แนะนำให้导入หลังลงทะเบียน เพื่อทับแท็กเดิม
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 หรืออื่นๆ ต้องทำการกำหนดค่าที่สอดคล้องกันในท้องถิ่นจึงจะใช้งานได้ตามปกติ มิฉะนั้นจะเกิดข้อผิดพลาด
