1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ToDoアプリの作成(バリデーション編)

Posted at

はじめに

今回は,前回に続き外部パッケージのozzo-validationを使ってバリデーションの実装を行っていきます。

バリデーションの実装(タスク)

タスクのバリデーション処理を実装します。
validator/task_validator.go
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"),
		),
	)
}

バリデーションの実装(ユーザー)

ユーザーのバリデーション処理を実装します。
validator/user_validator.go
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"),
		),
	)
}

タスクユースケースの実装

タスクユースケースの方にもバリデーションの機能を実装していきます。
usecase/task_usecase.go
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
}

ユーザーユースケースの実装

ユーザーユースケースの方にもバリデーションの機能を実装していきます。
usecase/user_usecase.go

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していきます。
main.go
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`

起動できました!

スクリーンショット 2024-11-12 19.48.10.png

パスワードを5文字にして6文字より少ない場合、バリデーションが表示されるか確認します。
POSTメソッドで、エンドポイントにhttp://localhost:8080/loginを入力してSendボタンを押下します。

スクリーンショット 2024-11-12 19.53.49.png

そうすると500 Internal Server ErrorとValitadorで設定したpassword: limited min 6 max 30char.が返ってきます。

次はtasksの新規作成時のバリデーションを確認していきます。
ログイン後、titleが無い状態でPOSTメソッドで、エンドポイントにhttp://localhost:8080/tasksを入力してSendボタンを押下します。

スクリーンショット 2024-11-12 20.04.30.png

そうすると500 Internal Server ErrorとValitadorで設定した"title: title is required."が返ってきます。

次はtasksのtitleが10文字以上でPOSTメソッドで、エンドポイントにhttp://localhost:8080/tasksを入力してSendボタンを押下します。

スクリーンショット 2024-11-12 20.09.58.png

そうすると500 Internal Server ErrorとValitadorで設定した"title: limited max 10 char."が返ってきます。
※ユーザーのバリデーションも同じような内容なので割愛

まとめ

今回はバリデーションの機能を実装していきました。 次はCORSとCSRFのMiddlewareを追加していきます。
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?