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 يُنفذ أداة تحقق قيمية تعتمد على وسوم الهيكل، ولها الخصائص الفريدة التالية:

  • يمكن استخدام وسوم التحقق والمحققات المخصصة للتحقق عبر الحقول وعبر الهياكل

  • الشرائح، والمصفوفات، والخرائط، أو أي حقول متعددة الأبعاد يمكن التحقق منها

  • يمكن التحقق بشكل عميق من مفاتيح وقيم الخرائط

  • قبل التحقق، يتم تحديد كيفية المعالجة بناءً على النوع الأساسي

  • يمكن التعامل مع أنواع الحقول المخصصة

  • يدعم وسوم الأسماء المستعارة، والتي تسمح بتعيين عدة تحقيقات لوسم واحد لتسهيل تعريف التحقق للهياكل

  • يمكن استخراج أسماء الحقول المخصصة، مثل استخراج اسم JSON أثناء التحقق لعرضه في رسائل الخطأ

  • رسائل خطأ متعددة اللغات مخصصة

  • مكون التحقق القياسي الافتراضي لإطار عمل gin

التثبيت

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

الاستيراد

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

الوسوم

للمحقق عدد كبير جداً من وسوم التحقق الأساسية، يمكن العثور على جميع الدوال المقابلة للوسوم في ملف baked_in.go، ووسم الهيكل للمحقق هو validate،

مثال

go
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 فريدة

الأسماء المستعارة

الوسمالوصف
iscolorhexcolor|rgb|rgba|hsl|hsla
country_codeiso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric

العوامل

الوسمالوصفHex
,عملية AND، استخدام عدة وسوم تحقق، يجب استيفاء جميع الشروط، لا توجد مسافات بين الفواصل0x2c
|عملية OR، استخدام عدة وسوم تحقق، لكن يكفي استيفاء واحد منها0x7c
-تخطي التحقق من هذا الحقل0x2d
=رمز مطابقة المعامل0x3d

TIP

عند التحقق من الحقل وتريد مطابقة العوامل، يجب استخدام الصيغة السداسية العشرية utf8 للاستبدال، مثال

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"`        // أكبر من أو يساوي 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

التحقق من الخريطة

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

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 يمكنها التحقق من الأنواع بما في ذلك الهياكل، والمتغيرات، والشرائح، والخرائط، يجب استخدامها بحكمة مع وسم 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

عند استخدام التحقق من الحقل، عندما يكون الحقل أو الهيكل المحدد كمعامل في الوسم غير موجود، سيتم الحكم مباشرة بأن التحقق قد فشل، مثال:

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"، والوسم المقابل لها هو is666، الخرج كالتالي

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

TIP

إذا كان الوسم المسجل موجوداً بالفعل، فسيتم استبداله بالحالي، أي يمكن "إعادة كتابة" منطق التحقق الافتراضي للوسم.

دالة تحقق نوع مخصصة

دالة التحقق من النوع مخصصة لنوع معين، تُستخدم عادة للأنواع غير الأساسية، ويمكن أيضاً استبدال التحقق الافتراضي للأنواع الأساسية، انظر المثال التالي:

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"`        // أكبر من أو يساوي 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']

يمكن أيضاً ترجمة كل خطأ بشكل منفصل

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

الخرج

Name必须包含文本'jack'
Age必须大于或等于18
Address必须以文本'市'结尾

كما نرى القيمة المرجعة هي خريطة، الترجمة الأساسية لرسائل الخطأ تمت، لكنها لا تزال غير كافية للاستخدام، نحتاج لتحسين رسائل الخطأ للتواصل بشكل أفضل مع العميل أو الواجهة الأمامية.

go
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 كالتالي:

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 أو أي تنسيق مناسب لنقل الرسائل، قد تفكر في تسلسل الخريطة مباشرة إلى JSON، هذا حل، لكن قد تحصل على النتيجة التالية:

json
{
  "User.地址": "地址必须以文本'市'结尾",
  "User.姓名": "姓名必须包含文本'back'",
  "User.年龄": "年龄必须大于或等于18",
  "User.性别": "性别为必填字段"
}

من خلال معالجة مفتاح الخريطة نحصل على:

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:"年龄"`        // أكبر من أو يساوي 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:

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()) // يمكن أن تكون هناك عدة معاملات، تعتمد على عدد العناصر النائبة في قالب الوسم المقابل
  return t
}

أخيراً نسجلها

go
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)

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") // يُنصح بالاستيراد بعد التسجيل، ليتم استبدال الوسوم الأصلية
   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 الأصلي، يمكن ترك type و rule فارغين، لأن التكوين الأصلي لم يملأهما أيضاً، من الأفضل الحفاظ على الاتساق. ما تملؤه في type سيُضاف إلى الخريطة المقابلة، إذا كان Cardinal أو أنواع أخرى و rule مُعد بـ one أو ما شابه، ستحتاج لعمل التكوين المناسب محلياً ليعمل بشكل صحيح، وإلا سيظهر خطأ.

Golang تم تحريره بواسطة www.golangdev.cn