はじめに
今回は,前回に続き外部パッケージのozzo-validationを使ってバリデーションの実装を行っていきます。
バリデーションの実装(タスク)
タスクのバリデーション処理を実装します。package validator
import (
"go-rest-api/model"
//外部パッケージなのでgo mod tidyコマンドを実行してダウンロードします。
validation "github.com/go-ozzo/ozzo-validation/v4"
)
type ITaskValidator interface {
//タスクのバリデーション処理を実行するメソッドです。
//バリデーション対象のタスクです。
//バリデーションエラーが発生した場合に返されるエラーです。
TaskValidate(task model.Task) error
}
//タスクバリデータの具体的な実装です。
type taskValidator struct{}
//この関数は、taskValidator 構造体の新しいインスタンスを生成して返します。
func NewTaskValidator() ITaskValidator {
return &taskValidator{}
}
//タスクのバリデーション処理を実行するメソッドの実装です。
func (tv *taskValidator) TaskValidate(task model.Task) error {
//validation.ValidateStruct 関数を使って、task 構造体のバリデーションを実行します。
//validation.Field は、バリデーション対象のフィールドを指定します。
return validation.ValidateStruct(&task,
validation.Field(
//task 構造体の Title フィールドをバリデーション対象として指定します。
&task.Title,
//Title フィールドが必須であることを指定します。エラーメッセージは "title is required" です。
validation.Required.Error("title is required"),
//Title フィールドの長さが1文字以上10文字以下であることを指定します。エラーメッセージは "limited max 10 char" です。
validation.RuneLength(1, 10).Error("limited max 10 char"),
),
)
}
バリデーションの実装(ユーザー)
ユーザーのバリデーション処理を実装します。package validator
import (
"go-rest-api/model"
validation "github.com/go-ozzo/ozzo-validation/v4"
//外部パッケージなのでgo mod tidyコマンドを実行してダウンロードします。
"github.com/go-ozzo/ozzo-validation/v4/is"
)
//ユーザーバリデータが実装すべきメソッドを定義するインターフェースです。
type IUserValidator interface {
//ユーザーのバリデーション処理を実行するメソッドです。
//user model.User: バリデーション対象のユーザーです。
//error: バリデーションエラーが発生した場合に返されるエラーです。
UserValidate(user model.User) error
}
//ユーザーバリデータの具体的な実装です。
type userValidator struct{}
//userValidator 構造体の新しいインスタンスを作成する関数です。
func NewUserValidator() IUserValidator {
//userValidator 構造体の新しいインスタンスを生成して返します。
return &userValidator{}
}
//ユーザーのバリデーション処理を実行するメソッドの実装です。
//validation.ValidateStruct 関数を使って、user 構造体のバリデーションを実行します。
func (uv *userValidator) UserValidate(user model.User) error {
return validation.ValidateStruct(&user,
//バリデーション対象のフィールドを指定します。
validation.Field(
//user 構造体の Email フィールドをバリデーション対象として指定します。
&user.Email,
//Email フィールドが必須であることを指定します。エラーメッセージは "email is required" です。
validation.Required.Error("email is required"),
//Email フィールドが有効なメールアドレス形式であることを指定します。エラーメッセージは "is not valid email format" です。
is.Email.Error("is not valid email format"),
),
validation.Field(
//user 構造体の Password フィールドをバリデーション対象として指定します。
&user.Password,
//Password フィールドが必須であることを指定します。エラーメッセージは "password is required" です。
validation.Required.Error("password is required"),
//Password フィールドの長さが6文字以上30文字以下であることを指定します。エラーメッセージは "limited min 6 max 30char" です。
validation.RuneLength(6, 30).Error("limited min 6 max 30char"),
),
)
}
タスクユースケースの実装
タスクユースケースの方にもバリデーションの機能を実装していきます。package usecase
import (
"go-rest-api/model"
"go-rest-api/repository"
"go-rest-api/validator"
)
type ITaskUsecase interface {
GetAllTasks(userId uint) ([]model.TaskResponse, error)
GetTaskById(userId uint, taskId uint) (model.TaskResponse, error)
CreateTask(task model.Task) (model.TaskResponse, error)
UpdateTask(task model.Task, userId uint, taskId uint) (model.TaskResponse, error)
DeleteTask(userId uint, taskId uint) error
}
type taskUsecase struct {
tr repository.ITaskRepository
//タスクバリデータへの依存関係を保持します。(新たに追加したコード)
tv validator.ITaskValidator
}
//タスクバリデーターが注入できるよう、引数に`tv validator.ITaskValidator`を追加
func NewTaskUsecase(tr repository.ITaskRepository, tv validator.ITaskValidator) ITaskUsecase {
//タスクユースケースをインスタンス化するフィールドに`tv`を追加
return &taskUsecase{tr, tv}
}
func (tu *taskUsecase) GetAllTasks(userId uint) ([]model.TaskResponse, error) {
tasks := []model.Task{}
if err := tu.tr.GetAllTasks(&tasks, userId); err != nil {
return nil, err
}
resTasks := []model.TaskResponse{}
for _, v := range tasks {
t := model.TaskResponse{
ID: v.ID,
Title: v.Title,
CreatedAt: v.CreatedAt,
UpdatedAt: v.UpdatedAt,
}
resTasks = append(resTasks, t)
}
return resTasks, nil
}
func (tu *taskUsecase) GetTaskById(userId uint, taskId uint) (model.TaskResponse, error) {
task := model.Task{}
if err := tu.tr.GetTaskById(&task, userId, taskId); err != nil {
return model.TaskResponse{}, err
}
resTask := model.TaskResponse{
ID: task.ID,
Title: task.Title,
CreatedAt: task.CreatedAt,
UpdatedAt: task.UpdatedAt,
}
return resTask, nil
}
func (tu *taskUsecase) CreateTask(task model.Task) (model.TaskResponse, error) {
//タスクバリデータの TaskValidate メソッドを呼び出して、受け取ったタスクのデータが妥当かどうか検証します。
//バリデーション処理中にエラーが発生した場合、エラーを返します。(新たに追加したコード)
if err := tu.tv.TaskValidate(task); err != nil {
return model.TaskResponse{}, err
}
if err := tu.tr.CreateTask(&task); err != nil {
return model.TaskResponse{}, err
}
resTask := model.TaskResponse{
ID: task.ID,
Title: task.Title,
CreatedAt: task.CreatedAt,
UpdatedAt: task.UpdatedAt,
}
return resTask, nil
}
func (tu *taskUsecase) UpdateTask(task model.Task, userId uint, taskId uint) (model.TaskResponse, error) {
//タスクバリデータの TaskValidate メソッドを呼び出して、受け取ったタスクのデータが妥当かどうか検証します。
//バリデーション処理中にエラーが発生した場合、エラーを返します。
if err := tu.tv.TaskValidate(task); err != nil {
return model.TaskResponse{}, err
}
if err := tu.tr.UpdateTask(&task, userId, taskId); err != nil {
return model.TaskResponse{}, err
}
resTask := model.TaskResponse{
ID: task.ID,
Title: task.Title,
CreatedAt: task.CreatedAt,
UpdatedAt: task.UpdatedAt,
}
return resTask, nil
}
func (tu *taskUsecase) DeleteTask(userId uint, taskId uint) error {
if err := tu.tr.DeleteTask(userId, taskId); err != nil {
return err
}
return nil
}
ユーザーユースケースの実装
ユーザーユースケースの方にもバリデーションの機能を実装していきます。
package usecase
import (
"go-rest-api/model"
"go-rest-api/repository"
"go-rest-api/validator"
"os"
"time"
"github.com/golang-jwt/jwt/v4"
"golang.org/x/crypto/bcrypt"
)
type IUserUsecase interface {
SignUp(user model.User) (model.UserResponse, error)
Login(user model.User) (string, error)
}
type userUsecase struct {
ur repository.IUserRepository
//ユーザーバリデータへの依存関係を保持します。(新たに追加したコード)
uv validator.IUserValidator
}
//ユーザーバリデーターが注入できるよう、引数に`uv validator.IUserValidator`を追加
func NewUserUsecase(ur repository.IUserRepository, uv validator.IUserValidator) IUserUsecase {
//ユーザーユースケースをインスタンス化するフィールドに`uv`を追加
return &userUsecase{ur, uv}
}
func (uu *userUsecase) SignUp(user model.User) (model.UserResponse, error) {
//ユーザーバリデータの UserValidate メソッドを呼び出して、受け取ったタスクのデータが妥当かどうか検証します。(新たに追加したコード)
//バリデーション処理中にエラーが発生した場合、エラーを返します。
if err := uu.uv.UserValidate(user); err != nil {
return model.UserResponse{}, err
}
hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), 10)
if err != nil {
return model.UserResponse{}, err
}
newUser := model.User{Email: user.Email, Password: string(hash)}
if err := uu.ur.CreateUser(&newUser); err != nil {
return model.UserResponse{}, err
}
resUser := model.UserResponse{
ID: newUser.ID,
Email: newUser.Email,
}
return resUser, nil
}
func (uu *userUsecase) Login(user model.User) (string, error) {
//ユーザーバリデータの UserValidate メソッドを呼び出して、受け取ったタスクのデータが妥当かどうか検証します。(新たに追加したコード)
//バリデーション処理中にエラーが発生した場合、エラーを返します。
if err := uu.uv.UserValidate(user); err != nil {
return "", err
}
storedUser := model.User{}
if err := uu.ur.GetUserByEmail(&storedUser, user.Email); err != nil {
return "", err
}
err := bcrypt.CompareHashAndPassword([]byte(storedUser.Password), []byte(user.Password))
if err != nil {
return "", err
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"user_id": storedUser.ID,
"exp": time.Now().Add(time.Hour * 12).Unix(),
})
tokenString, err := token.SignedString([]byte(os.Getenv("SECRET")))
if err != nil {
return "", err
}
return tokenString, nil
}
main.goの実装
バリデータをインスタンス化してからユースケースにDIしていきます。package main
import (
"go-rest-api/controller"
"go-rest-api/db"
"go-rest-api/repository"
"go-rest-api/router"
"go-rest-api/usecase"
"go-rest-api/validator"
)
func main() {
db := db.NewDB()
//ユーザーバリデータの新しいインスタンスを作成します。
userValidator := validator.NewUserValidator()
//タスクバリデータの新しいインスタンスを作成します。
taskValidator := validator.NewTaskValidator()
userRepository := repository.NewUserRepository(db)
taskRepository := repository.NewTaskRepository(db)
//作成したインスタンスをユースケースのコンストラクターに渡すため、引数に`userValidator`を追加
userUsecase := usecase.NewUserUsecase(userRepository, userValidator)
//作成したインスタンスをユースケースのコンストラクターに渡すため、引数に`taskValidator`を追加
taskUsecase := usecase.NewTaskUsecase(taskRepository, taskValidator)
userController := controller.NewUserController(userUsecase)
taskController := controller.NewTaskController(taskUsecase)
e := router.NewRouter(userController, taskController)
e.Logger.Fatal(e.Start(":8080"))
}
動作確認
下記のコマンドでプログラムを起動していきます。 `GO_ENV=dev go run main.go`起動できました!
パスワードを5文字にして6文字より少ない場合、バリデーションが表示されるか確認します。
POSTメソッドで、エンドポイントにhttp://localhost:8080/login
を入力してSendボタンを押下します。
そうすると500 Internal Server Error
とValitadorで設定したpassword: limited min 6 max 30char.
が返ってきます。
次はtasksの新規作成時のバリデーションを確認していきます。
ログイン後、titleが無い状態でPOSTメソッドで、エンドポイントにhttp://localhost:8080/tasks
を入力してSendボタンを押下します。
そうすると500 Internal Server Error
とValitadorで設定した"title: title is required."
が返ってきます。
次はtasksのtitleが10文字以上でPOSTメソッドで、エンドポイントにhttp://localhost:8080/tasks
を入力してSendボタンを押下します。
そうすると500 Internal Server Error
とValitadorで設定した"title: limited max 10 char."
が返ってきます。
※ユーザーのバリデーションも同じような内容なので割愛