مكتبة 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 يُنفذ أداة تحقق قيمية تعتمد على وسوم الهيكل، ولها الخصائص الفريدة التالية:
يمكن استخدام وسوم التحقق والمحققات المخصصة للتحقق عبر الحقول وعبر الهياكل
الشرائح، والمصفوفات، والخرائط، أو أي حقول متعددة الأبعاد يمكن التحقق منها
يمكن التحقق بشكل عميق من مفاتيح وقيم الخرائط
قبل التحقق، يتم تحديد كيفية المعالجة بناءً على النوع الأساسي
يمكن التعامل مع أنواع الحقول المخصصة
يدعم وسوم الأسماء المستعارة، والتي تسمح بتعيين عدة تحقيقات لوسم واحد لتسهيل تعريف التحقق للهياكل
يمكن استخراج أسماء الحقول المخصصة، مثل استخراج اسم 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.
الحقول
| الوسم | الوصف |
|---|---|
eqcsfield | في هيكل منفصل، التحقق مما إذا كانت قيمة الحقل الحالي تساوي الحقل المحدد بواسطة قيمة المعامل |
eqfield | التحقق مما إذا كانت قيمة الحقل الحالي تساوي الحقل المحدد بقيمة المعامل |
fieldcontains | التحقق مما إذا كانت قيمة الحقل الحالي تحتوي على الحقل المحدد بقيمة المعامل |
fieldexcludes | التحقق مما إذا كانت قيمة الحقل الحالي لا تحتوي على الحقل المحدد بقيمة المعامل |
gtcsfield | في هيكل منفصل، التحقق مما إذا كانت قيمة الحقل الحالي أكبر من الحقل المحدد بقيمة المعامل |
gtecsfield | في هيكل منفصل، التحقق مما إذا كانت قيمة الحقل الحالي أكبر من أو تساوي الحقل المحدد بقيمة المعامل |
gtefield | التحقق مما إذا كانت قيمة الحقل الحالي أكبر من أو تساوي الحقل المحدد بقيمة المعامل |
gtfield | التحقق مما إذا كانت قيمة الحقل الحالي أكبر من الحقل المحدد بقيمة المعامل |
ltcsfield | في هيكل منفصل، التحقق مما إذا كانت قيمة الحقل الحالي أقل من الحقل المحدد بقيمة المعامل |
ltecsfield | في هيكل منفصل، التحقق مما إذا كانت قيمة الحقل الحالي أقل من أو تساوي الحقل المحدد بقيمة المعامل |
ltefield | التحقق مما إذا كانت قيمة الحقل الحالي أقل من أو تساوي الحقل المحدد بقيمة المعامل |
ltfield | التحقق مما إذا كانت قيمة الحقل الحالي أقل من الحقل المحدد بقيمة المعامل |
necsfield | التحقق مما إذا كانت قيمة الحقل الحالي لا تساوي الحقل في الهيكل المنفصل المحدد بقيمة المعامل |
nefield | التحقق مما إذا كانت قيمة الحقل الحالي لا تساوي الحقل المحدد بقيمة المعامل |
الشبكات
| الوسم | الوصف |
|---|---|
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 | عنوان نقطة نهاية مقبس يونكس |
uri | معرف المورد الموحد |
url | محدد موقع المورد الموحد |
url_encoded | ترميز معرف المورد الموحد |
urn_rfc2141 | اسم المورد الموحد RFC 2141 |
السلاسل النصية
| الوسم | الوصف |
|---|---|
alpha | التحقق مما إذا كانت قيمة الحقل الحالي أحرف صالحة |
alphanum | التحقق مما إذا كانت قيمة الحقل الحالي أحرف وأرقام صالحة |
alphanumunicode | التحقق مما إذا كانت قيمة الحقل الحالي أحرف وأرقام يونيكود صالحة |
alphaunicode | التحقق مما إذا كانت قيمة الحقل الحالي أحرف يونيكود صالحة |
ascii | التحقق مما إذا كانت قيمة الحقل أحرف ASCII صالحة |
boolean | التحقق مما إذا كانت قيمة الحقل الحالي قيمة بوليانية صالحة أو يمكن تحويلها بأمان إلى قيمة بوليانية |
contains | التحقق مما إذا كانت قيمة الحقل تحتوي على النص المحدد في المعامل |
containsany | التحقق مما إذا كانت قيمة الحقل تحتوي على أي من الأحرف المحددة في المعامل |
containsrune | التحقق مما إذا كانت قيمة الحقل تحتوي على الرمز المحدد في المعامل |
endsnotwith | التحقق مما إذا كانت قيمة الحقل لا تنتهي بالنص المحدد في المعامل |
endswith | التحقق مما إذا كانت قيمة الحقل تنتهي بالنص المحدد في المعامل |
excludes | التحقق مما إذا كانت قيمة الحقل لا تحتوي على النص المحدد في المعامل |
excludesall | التحقق مما إذا كانت قيمة الحقل لا تحتوي على أي من الأحرف المحددة في المعامل |
excludesrune | التحقق مما إذا كانت قيمة الحقل لا تحتوي على الحرف المحدد في المعامل |
lowercase | التحقق مما إذا كانت قيمة الحقل الحالي سلسلة أحرف صغيرة |
multibyte | التحقق مما إذا كانت قيمة الحقل تحتوي على أحرف متعددة البايت |
number | التحقق مما إذا كانت قيمة الحقل الحالي رقم صالح |
numeric | التحقق مما إذا كانت قيمة الحقل الحالي قيمة رقمية صالحة |
printascii | التحقق مما إذا كانت قيمة الحقل أحرف ASCII قابلة للطباعة |
startsnotwith | التحقق مما إذا كانت قيمة الحقل لا تبدأ بالنص المحدد في المعامل |
startswith | التحقق مما إذا كانت قيمة الحقل تبدأ بالنص المحدد في المعامل |
uppercase | التحقق مما إذا كانت قيمة الحقل الحالي سلسلة أحرف كبيرة |
التنسيق
| الوسم | الوصف |
|---|---|
base64 | سلسلة Base64 |
base64url | سلسلة Base64URL |
bic | التحقق مما إذا كانت قيمة الحقل الحالي رمز BIC صالح (رمز SWIFT) كما هو محدد في ISO 9362 |
bcp47_language_tag | التحقق مما إذا كانت قيمة الحقل الحالي علامة لغة صالحة وفق مواصفات BCP47 |
btc_addr | التحقق مما إذا كانت قيمة الحقل عنوان BTC صالح |
btc_addr_bech32 | التحقق مما إذا كانت قيمة الحقل عنوان bech32 BTC صالح |
credit_card | التحقق مما إذا كانت قيمة الحقل الحالي رقم بطاقة ائتمان صالح |
datetime | التحقق مما إذا كانت قيمة الحقل الحالي سلسلة تاريخ ووقت صالحة |
e164 | التحقق مما إذا كانت قيمة الحقل الحالي رقم هاتف بصيغة e.164 صالحة |
email | التحقق مما إذا كانت قيمة الحقل الحالي عنوان بريد إلكتروني صالح |
eth_addr | التحقق مما إذا كانت قيمة الحقل عنوان إيثريوم صالح |
hexadecimal | التحقق مما إذا كانت قيمة الحقل الحالي سداسي عشري صالح |
hexcolor | التحقق مما إذا كانت قيمة الحقل الحالي لون سداسي عشري صالح |
hsl | التحقق مما إذا كانت قيمة الحقل الحالي لون HSL صالح |
hsla | التحقق مما إذا كانت قيمة الحقل الحالي لون HSLA صالح |
html | التحقق مما إذا كانت قيمة الحقل الحالي HTML صالح |
html_encoded | التحقق مما إذا كانت قيمة الحقل الحالي ترميز HTML صالح |
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 | التحقق مما إذا كانت قيمة الحقل UUID v4 صالح |
uuid4_rfc4122 | التحقق مما إذا كانت قيمة الحقل RFC4122 v4 UUID صالح |
uuid5 | التحقق مما إذا كانت قيمة الحقل UUID v5 صالح |
uuid5_rfc4122 | التحقق مما إذا كانت قيمة الحقل RFC4122 v5 UUID صالح |
uuid_rfc4122 | التحقق مما إذا كانت قيمة الحقل RFC4122 UUID صالح لأي إصدار |
md4 | التحقق مما إذا كانت قيمة الحقل MD4 صالح |
md5 | التحقق مما إذا كانت قيمة الحقل MD5 صالح |
sha256 | التحقق مما إذا كانت قيمة الحقل SHA256 صالح |
sha384 | التحقق مما إذا كانت قيمة الحقل SHA384 صالح |
sha512 | التحقق مما إذا كانت قيمة الحقل SHA512 صالح |
ripemd128 | التحقق مما إذا كانت قيمة الحقل PIPEMD128 صالح |
ripemd160 | التحقق مما إذا كانت قيمة الحقل PIPEMD160 صالح |
tiger128 | التحقق مما إذا كانت قيمة الحقل TIGER128 صالح |
tiger160 | التحقق مما إذا كانت قيمة الحقل TIGER160 صالح |
tiger192 | التحقق مما إذا كانت قيمة الحقل TIGER192 صالح |
semver | التحقق مما إذا كانت قيمة الحقل الحالي إصدار semver صالح كما هو محدد في الإصدار الدلالي 2.0.0 |
ulid | التحقق مما إذا كانت قيمة الحقل ULID صالح |
المقارنة
| الوسم | الوصف |
|---|---|
eq | يساوي |
gt | أكبر من |
gte | أكبر من أو يساوي |
lt | أقل من |
lte | أقل من أو يساوي |
ne | لا يساوي |
أخرى
| الوسم | الوصف |
|---|---|
dir | دليل ملف |
file | مسار ملف |
isdefault | التحقق مما إذا كانت قيمة الحقل الحالي هي القيمة الافتراضية الثابتة |
len | طول الحقل |
max | القيمة القصوى |
min | القيمة الدنيا |
oneof | هل هو واحد من القيم المدرجة |
oimtempty | إذا لم يتم تعيين الحقل، تجاهل هذا الحقل |
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 فريدة |
الأسماء المستعارة
| الوسم | الوصف |
|---|---|
iscolor | hexcolor|rgb|rgba|hsl|hsla |
country_code | iso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric |
العوامل
| الوسم | الوصف | Hex |
|---|---|---|
, | عملية AND، استخدام عدة وسوم تحقق، يجب استيفاء جميع الشروط، لا توجد مسافات بين الفواصل | 0x2c |
| | عملية OR، استخدام عدة وسوم تحقق، لكن يكفي استيفاء واحد منها | 0x7c |
- | تخطي التحقق من هذا الحقل | 0x2d |
= | رمز مطابقة المعامل | 0x3d |
TIP
عند التحقق من الحقل وتريد مطابقة العوامل، يجب استخدام الصيغة السداسية العشرية utf8 للاستبدال، مثال
filed string `validate:"contains=0x2c"`الاستخدام
فيما يلي مقدمة للاستخدامات الأساسية لـ Validator وبعض أمثلة الكود.
النمط الفردي
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 `valiate:"endwith=市"` // ينتهي بـ "市"
}
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التحقق من الخريطة
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 يمكنها التحقق من الأنواع بما في ذلك الهياكل، والمتغيرات، والشرائح، والخرائط، يجب استخدامها بحكمة مع وسم 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
عند استخدام التحقق من الحقل، عندما يكون الحقل أو الهيكل المحدد كمعامل في الوسم غير موجود، سيتم الحكم مباشرة بأن التحقق قد فشل، مثال:
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"، والوسم المقابل لها هو 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)
// عادةً يمكن الحصول على اللغة من رأس 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必须以文本'市'结尾كما نرى القيمة المرجعة هي خريطة، الترجمة الأساسية لرسائل الخطأ تمت، لكنها لا تزال غير كافية للاستخدام، نحتاج لتحسين رسائل الخطأ للتواصل بشكل أفضل مع العميل أو الواجهة الأمامية.
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
地址必须以文本'市'结尾لكن هذا لا يزال غير كافٍ كرسالة خطأ للواجهة الأمامية، نحتاج لتنسيق المعلومات كـ JSON أو أي تنسيق مناسب لنقل الرسائل، قد تفكر في تسلسل الخريطة مباشرة إلى JSON، هذا حل، لكن قد تحصل على النتيجة التالية:
{
"User.地址": "地址必须以文本'市'结尾",
"User.姓名": "姓名必须包含文本'back'",
"User.年龄": "年龄必须大于或等于18",
"User.性别": "性别为必填字段"
}من خلال معالجة مفتاح الخريطة نحصل على:
{
"地址": "地址必须以文本'市'结尾",
"姓名": "姓名必须包含文本'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:"年龄"` // أكبر من أو يساوي 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)
// عادةً يمكن الحصول على اللغة من رأس 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، ويحتاج لنوعين من الدوال، الأولى RegisterTranslationsFunc مسؤولة عن تسجيل قالب الترجمة للوسم المقابل، والثانية 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()) // يمكن أن تكون هناك عدة معاملات، تعتمد على عدد العناصر النائبة في قالب الوسم المقابل
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 سيُضاف إلى الخريطة المقابلة، إذا كان Cardinal أو أنواع أخرى و rule مُعد بـ one أو ما شابه، ستحتاج لعمل التكوين المناسب محلياً ليعمل بشكل صحيح، وإلا سيظهر خطأ.
