LoginSignup
4
2

More than 1 year has passed since last update.

【Go】パスパラメータを使用するときの注意点やエラーハンドリングなど

Last updated at Posted at 2022-03-21

はじめに

前記事からの続きで、パスパラメータのidでcustomerの個別データを取得するAPIを作成しました。
エラーハンドリングなど、以上のケースにおけるAPI作成にあたっての注意点をメモとしてまとめました。
Go初心者の方の参考になれば幸いです。

数値以外のパスパラメータをはじく

idとして渡すパスパラメータが数値のみとなるように、正規表現を使用します。

app.go
router.HandleFunc("/customers/{customer_id:[0-9]+}", ch.getCustomer).Methods(http.MethodGet)

試しにパラメータに数値以外dddを入力してみると、404 page not foundが表示されます。

JSONオブジェクトを返す

Handlerとして、getCustomerを新しく作成します。
取得したパラメータを元にch.service.GetCustomer(id)でデータを取得し、errの有無に関わらずにwriteResponse()を返すようにします。
writeResponse()では、Content-Typeの設定、ヘッダーへのステータスコードの付加、json形式へのデータのエンコードを行います。

customerHandlers.go
func (ch *CustomerHandlers) getCustomer(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	id := vars["customer_id"]

	customer, err := ch.service.GetCustomer(id)
	if err != nil {
		writeResponse(w, err.Code, err.AsMessage())
	} else {
		writeResponse(w, http.StatusOK, customer)
	}
}

func writeResponse(w http.ResponseWriter, code int, data interface{}) {
	w.Header().Add("Content-Type", "application/json")
	w.WriteHeader(code)
	if err := json.NewEncoder(w).Encode(data); err != nil {
		panic(err)
	}
}

指定したidのデータがないときに404を返す

前項のcustomer, err := ch.service.GetCustomer(id)errに渡す中身を考えます。
リポジトリパターンのため、処理内容はServiceではなくRepositoryに記載されます。

なお、(*domain.Customer, *errs.AppError)のようにGetCustomer()の返り値がポインタ型となっていますが、データがなかった場合にnilを返すためにこのようにしています。
初期値をnilにするときなどにもポインタ型は使われます。

customerService.go
func (s DefaultCustomerService) GetCustomer(id string) (*domain.Customer, *errs.AppError) {
	return s.repo.ById(id)
}

話を戻しますが、customerRepositoryDbのメソッドById()を以下のように記述します。

domain/customerRepositoryDb.go
func (d CustomerRepositoryDb) ById(id string) (*Customer, *errs.AppError) {
	customerSql := "select customer_id, name, city, zipcode, date_of_birth, status from customers where customer_id = ?"

	row := d.client.QueryRow(customerSql, id)
	var c Customer
	err := row.Scan(&c.Id, &c.Name, &c.City, &c.Zipcode, &c.DateofBirth, &c.Status)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, errs.NewNotFoundError("customer not found")
		} else {
			log.Println("Error while scanning customer " + err.Error())
			return nil, errs.NewUnexpectedError("unexpected database error")
		}
	}
	return &c, nil
}

row.Scan()errが返ってきたときのエラーハンドリングで、さらにif err == sql.ErrNoRowsでデータが空だったときの分岐を行います。
errs.NewNotFoundError("customer not found")のようなエラーを返していますが、この中身はMessageとCodeを含んだAppErrorという構造体となります。

errs/errors.go
package errs

import "net/http"

type AppError struct {
	Code int	`json:",omitempty"`
	Message string	`json:"message"`
}

func (e AppError) AsMessage() *AppError {
	return &AppError{
		Message: e.Message,
	}
}

func NewNotFoundError(message string) *AppError {
	return &AppError {
		Message: message,
		Code: http.StatusNotFound, // 404
	}
}

JSONにCodeを加えない場合に、AsMessage()というメソッドも用意しています。

想定外のエラーの際に500をメッセージとあわせて返す

ById()のエラーハンドリングで、データが空じゃない場合のエラー(想定外のエラー)として、errs.NewUnexpectedError("unexpected database error")を返します。

こちらについては、500のCodeとMessageを含んだ構造体を返すようにします。

errs/errors.go
func NewUnexpectedError(message string) *AppError {
	return &AppError {
		Message: message,
		Code: http.StatusInternalServerError,
	}
}

参考資料

4
2
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
4
2