클라이언트의 정보를 저장하는 API에서
요청 시,
이메일, 비밀번호,휴대폰 번호 등 다양한 데이터를 받는다.
요청받은 데이터를 데이터베이스에 저장되며,
사용자의 요청이 있을 때 다시 데이터를 반환해준다.
예를들어 사용자의 휴대폰번호를 데이터를 반환해줄 때,
누구는 010-xxxx-xxxx 형태고,
누구는 010xxxxxxxx
누구는 +8210xxxxxxxx 형태면
미적으로 보기 좋지 않다.
이는 프로그램의 품질과 관계되며,
품질저하는 사용자의 신뢰를 낮추는 주요한 요인이다.
또다른 예로,
사용자의 보안을 위해 비밀번호 정책을 세운다고 해보자.
"사용자의 비밀번호에는 특수문자를 꼭 포함시키세요"하는 정책이 있을 때 (SQL Inject의 위험이 되는 특수문자는 제거해줘야 한다.)
데이터베이스에 비밀번호를 저장하기 전에, 특수문자를 포함했는지 검사를 해야 한다.
"클라이언트에서 검사를 하면 되지 않나요?" 라는 질문이 있을 수 있다.
서버 URL로 직접 호출하는 경우는 막을 수 없기 때문에, 완벽한 검사가 될 수는 없다.
그러므로 데이터베이스에 저장하기 전에 각 정책에 맞게 데이터를 규격화할 필요가 있다.
다양한 라이브러리가 존재하지만,
gin framework에서 사용하는
go-playground/validator의 사용법을 알아보자.
https://github.com/go-playground/validator
~참고 : binding하는 방법~
gin 공식 문서에 나와있는 validation 방법이다.
구조체 안에 binding:"" 따옴표 안에 validation할 것을 적어주면 된다.
뭘 넣을 지는 상황에 따라 선택해서 넣어주면 되고, validator공식문서를 참고하면 되지만,
예시 몇 가지를 들어 이해를 돕고 싶다.
요청 시 없으면 에러를 발생시키는 태그는 required다.
type Login struct {
User string `form:"user" json:"user" binding:"required"`
Password string `form:"password" json:"password" binding:"required"`
}
min max라는 태그를 사용한다.
최소 최대 둘 다 적용하고 싶다면, 순서관계 없이 ,로 구분하여 작성하면 된다.
type Login struct {
User string `form:"user" json:"user" binding:"required,max=8"`
Password string `form:"password" json:"password" binding:"required,min=8,max=20"`
}
oneof라는 태그를 사용하며,
공백으로 구분하여 문자열을 적어주면 된다.
type Login struct {
Type string `form:"type" json:"type" binding:"required,oneof=user admin superadmin"`
}
이외에도 숫자만, 알파뱃숫자만, 소문자만 받는 등 다양한 경우의 Validation 기능이 존재하니,
자세하게 알기 위해서는 go-playground/validator를 참고하길 바란다.
만약 내가 Validation을 하고 싶은 게 공식문서에 없으면 어떡할까?
Custom Validation을 만들면 된다.
휴대폰 Validator를 한다고 가정해보자.
핵심은 서버에
v.RegisterValidation을 해줘야 한다.
첫 번째 파라미터는 구조체 binding 안에서 작동할 문자열을 넣어주면 되고,
두 번째 파라미터에는 검사할 코드를 넣어주면 된다.
func main(){
const phoneRegex = `^01([0|1|6|7|8|9])([0-9]{3,4})([0-9]{4})$`
func ValidateRegex(regex, value string) bool {
reg := regexp.MustCompile(regex)
return reg.Match([]byte(value))
}
func Phone() validator.Func {
return func(fl validator.FieldLevel) bool {
if value, ok := fl.Field().Interface().(string); ok {
return ValidateRegex(phoneRegex, value)
}
return true
}
}
server := gin.Default()
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("customPhone", Phone())
}
type User struct {
Phone string `json:"phone" binding:"required,customPhone"`
}
server.GET("/", func(c *gin.Context) {
var json User
if err := c.shouldBindJson(&json); err != nil {
c.JSON(200, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"status": "ok"})
})
}
원리는 customPhone을 적어놓은 필드에 값이 들어오면,
Phone()함수를 거친다.
필드의 값이 정규식 상황에 따라 true, false를 반환한다.
validator 소스는 validate:""안에 넣어주면 된다.
validate를 생성하고, 구조체를 검사해주면 된다.
type User struct {
phone string `validate:"required,min=5,max=10"`
}
validate := validator.New()
s := User{phone: "01022234213"}
if err := validate.Struct(s); err != nil {
//에러처리
}
이상.