by ChatGPT 😀

Configurable and Extensible Data Validation in Go

Furkan Güngör
4 min read5 days ago

--

Web uygulamaları geliştirirken, dışarıdan gelen verilerin doğruluğunu ve tutarlılığını sağlamak kritik bir gerekliliktir. Go ekosisteminde bu ihtiyacı karşılamak için çeşitli doğrulama kütüphaneleri bulunuyor. Bunlardan biri de, esnek kurallar ve okunabilir bir API sunan ozzo-validation paketi. Bu makalede, ozzo-validation’ın sunduğu özellikleri detaylıca inceleyerek, veri doğrulama süreçlerini nasıl daha etkili hale getirebileceğimizi keşfedeceğiz.

Ne öneriyor?

ozzo-validation aşağıdaki özelliklere sahiptir;

  • Verilerin doğrulanması için struct tags yerine kod yazmayı önerir.
  • Hemen hemen bütün türleri destekler.
  • Validatable interface’i uygulandığı sürece özel veri türlerini destekler.
  • sql.Valuer interface’ini uygulayan tüm veri türlerini doğrulayabilir. Örneğin sql.NullString
  • Hata kodu desteği.

ozzo-validation paketi temelde bir adet doğrulama kümesi ve iki adet doğrulama yöntemi içerir. Bir değerin nasıl geçerli kabul edileceğini tanımlamak için doğrulama kurallarını kullanacağız ve değeri doğrulamak için validation.Validate() veya validation.ValidateStruct() kullanabiliriz.

How to use?

go get github.com/go-ozzo/ozzo-validation

Basit bir örnek ile başlayalım.

package main

import (
"fmt"
validation "github.com/go-ozzo/ozzo-validation"
"github.com/go-ozzo/ozzo-validation/is"
)

func main() {
data := "example"
err := validation.Validate(data,
validation.Required,
validation.Length(5, 100),
is.URL)
fmt.Println(err)
}
$ go run main.go

must be a valid URL

validation.Validate() kuralları listelendikleri sırayla çalıştırır. Bir kural doğrulamayı geçemezse ona karşılık gelen hatayı döndürür ve kuralların geri kalanını atlar. Değer tüm doğrulama kurallarını geçerse nil döndürür.

Validate Struct

Bir struct içerisinde farklı veri tiplerinden çok sayıda field içerebilir. Genellikle bir Rest endpoint geliştirirken payload değerini bir data transfer object (dto) olarak kurgularız. validateStruct metodu ile bu nesneleri doğrulayacak kurallar yazabiliriz.

Bir veya birden fazla alan tanımlanan kurallara uymuyorsa, kurallara uymayan değerlerin hangi sebeple geçersiz olarak kabul edildiğini açıkca görmek isteriz.

package model

import (
validation "github.com/go-ozzo/ozzo-validation"
"github.com/go-ozzo/ozzo-validation/is"
)

type User struct {
Username string
Email string
Password string
}

func (u User) Validate() error {
return validation.ValidateStruct(&u,
validation.Field(&u.Username, validation.Required, validation.Length(5, 100)),
validation.Field(&u.Email, validation.Required, validation.Length(5, 100), is.Email),
validation.Field(&u.Password, validation.Required, validation.Length(5, 100)))
}

User isimli bir struct tipinde nesnemiz var. Bu nesne içerisinde userName email ve password için validation rule yazabilirsiniz.

Şimdi bu user nesnesini kullanan bir endpoint geliştirelim.

go get github.com/gofiber/fiber/v2
package model

import (
validation "github.com/go-ozzo/ozzo-validation"
"github.com/go-ozzo/ozzo-validation/is"
)

type User struct {
Username string `json:"username"`
Email string `json:"email"`
Password string `json:"password"`
}

func (u User) Validate() error {
return validation.ValidateStruct(&u,
validation.Field(&u.Username, validation.Required, validation.Length(5, 100)),
validation.Field(&u.Email, validation.Required, validation.Length(5, 100), is.Email),
validation.Field(&u.Password, validation.Required, validation.Length(5, 100)))
}

handlers/user_handler.go

package handlers

import (
"github.com/gofiber/fiber/v2"
"ozzo-validation/model"
)

func RegisterUser(c *fiber.Ctx) error {
var user model.User

if err := c.BodyParser(&user); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid JSON format",
})
}

if err := user.Validate(); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": err.Error(),
})
}

return c.Status(fiber.StatusCreated).JSON(fiber.Map{
"message": "Successfully registered",
"user": user,
})
}

main.go

package main

import (
"github.com/gofiber/fiber/v2"
"log"
"ozzo-validation/handlers"
)

func main() {
app := fiber.New()

app.Post("register", handlers.RegisterUser)

log.Fatal(app.Listen(":3000"))
}
curl --location 'http://localhost:3000/register' \
--header 'Content-Type: application/json' \
--data '{
"email": "ali.veliexample.com",
"username": "aliveli",
"password": "Secret123"
}'
{"error":"email: must be a valid email address."}

validateStruct() gerçekleştirildiğinde, kurayla eklendikleri sırayla çalışır. Bir kural başarısız olursa, o field için hata kaydedilir ve doğrulama bir sonraki field ile devam eder.

Validate Map

Her zaman alanları belli veri yapıları ile çalışmayabiliriz. Bazen key-value olarak depolanan dinamik verilerle çalışmamız gerekebilir. Bu durumda validation.Map() metodunu kullanabiliriz. Birden fazla key için kurallar olabilir ve tek bir key için birden fazla kural ekleyebilirsiniz.

package main

import (
"fmt"
"github.com/go-ozzo/ozzo-validation/is"
validation "github.com/go-ozzo/ozzo-validation/v4"
"regexp"
)

func main() {
c := map[string]interface{}{
"Name": "Qiang Xue",
"Email": "q",
"Address": map[string]interface{}{
"Street": "123",
"City": "Unknown",
"State": "Virginia",
"Zip": "12345",
},
}

err := validation.Validate(c,
validation.Map(
// Name cannot be empty, and the length must be between 5 and 20.
validation.Key("Name", validation.Required, validation.Length(5, 20)),
// Email cannot be empty and should be in a valid email format.
validation.Key("Email", validation.Required, is.Email),
// Validate Address using its own validation rules
validation.Key("Address", validation.Map(
// Street cannot be empty, and the length must between 5 and 50
validation.Key("Street", validation.Required, validation.Length(5, 50)),
// City cannot be empty, and the length must between 5 and 50
validation.Key("City", validation.Required, validation.Length(5, 50)),
// State cannot be empty, and must be a string consisting of two letters in upper case
validation.Key("State", validation.Required, validation.Match(regexp.MustCompile("^[A-Z]{2}$"))),
// State cannot be empty, and must be a string consisting of five digits
validation.Key("Zip", validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))),
)),
),
)
fmt.Println(err)
// Output:
// Address: (State: must be in a valid format; Street: the length must be between 5 and 50.); Email: must be a valid email address.
}

Conditional Validation

Her zaman model içerisinde bulunan alanların doğruluğunu kontrol etmek istemeyebiliriz. Örneğin, kullanıcının kayıt tipi email ise email alanını kontrol etmek mantıklıdır. Ancak kullanıcı phoneNumber ile kayıt olmak istiyorsa o zaman email alanının doğruluğunu kontrol etmek gereksiz olabilir.

package model

import (
validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/go-ozzo/ozzo-validation/v4/is"
)

type RegisterDto struct {
Type string `json:"type"`
Email string `json:"email"`
Phone string `json:"phone"`
Password string `json:"password"`
}

func (rd RegisterDto) Validate() error {
return validation.ValidateStruct(&rd,
validation.Field(&rd.Type, validation.Required, validation.Length(5, 100)),
validation.Field(&rd.Email, validation.When(rd.Type == "email", validation.Required, validation.Length(5, 100), is.Email)),
validation.Field(&rd.Phone, validation.When(rd.Type == "phone", validation.Required, validation.Length(5, 100), is.Digit)))
}
func main() {
registerDto := model.RegisterDto{
Type: "phone",
Email: "213",
Phone: "123",
Password: "123",
}
err := registerDto.Validate()
fmt.Println(err)
}

Output : phone: the length must be between 5 and 100.

Yukarıdaki çıktıda gözüktüğü gibi type alanını phone olarak gönderdiğimiz için email alanı valid olmamasına rağmen o alan için herhangi bir kural çalışmadı.

Ozzo-validation ile Go’da esnek, okunabilir ve sürdürülebilir doğrulama kuralları oluşturmanın adımlarını aktarmaya çalıştım. Struct doğrulamadan dinamik kurallara, map desteğinden koşullu (conditional) validasyonlara kadar birçok farklı senaryoda kullanım örneklerini vermeye çalıştım. Eğer dikkatinizi çektiyse aşağıdaki linkten daha detaylı inceleyebilirsiniz.

Umarım faydalı olur. 👋

--

--

Furkan Güngör
Furkan Güngör

Written by Furkan Güngör

Solution Developer — I want to change the world, give me the source code.

No responses yet

Write a response