Go の Gin と GORM で API を作る(2)

前回は repository を修正したので、今回はそれを利用して handler を修正していきたいと思います。handler/api/article.go というファイルを新規に作成し、下記のように記述しました。

package handler_api

import (
    "bbs/repository"
    "errors"
    "github.com/gin-gonic/gin"
    "net/http"
    "strconv"
)

// 1ページあたりいくつの記事を表示するか
const perPage = 20

// ArticleRequest 記事投稿リクエストを受ける struct
// バリデーションルールを binding に記述する
// 最大文字数のバリデーション max を追加
type ArticleRequest struct {
    Name string `json:"name" binding:"max=255"`
    Body string `json:"body" binding:"required,max=10000"`
}

// GetArticles 掲示板の記事を取得する
func GetArticles(c *gin.Context) {
    // クエリパラメータ取得
    paramLimit := c.Query("limit")
    paramOffset := c.Query("offset")
    limit, _ := strconv.Atoi(paramLimit)
    offset, _ := strconv.Atoi(paramOffset)
    if limit == 0 {
        limit = perPage
    }

    articles, _, err := repository.GetArticles(limit, offset)
    if err != nil {
        // このエラーハンドリングは次回以降に説明
        c.Error(err).SetType(gin.ErrorTypePublic)
        return
    }

    // レスポンスは json で返したいので下記のメソッドを利用する
    // レスポンスは repository.Article struct のスライス
    c.JSON(http.StatusOK, articles)
}

// CreateArticle 掲示板の記事を作成する
func CreateArticle(c *gin.Context) {
    // バリデーション&バインド
    var req ArticleRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.Error(err).SetType(gin.ErrorTypePublic)
        return
    }

    // DBに保存
    article, _, err := repository.CreateArticle(req.Name, req.Body)
    if err != nil {
        c.Error(err).SetType(gin.ErrorTypePublic)
        return
    }

    // レスポンスは repository.Article struct
    c.JSON(http.StatusCreated, article)
}

// UpdateArticle 掲示板の記事を更新する
func UpdateArticle(c *gin.Context) {
    // PathInfo のパラメータを取得
    // ルーティングは下記のように設定する想定 
    // r.PUT("/article/:articleId", handler_api.UpdateArticle)
    paramArticleId := c.Param("articleId")
    articleId, _ := strconv.Atoi(paramArticleId)
    if articleId == 0 {
        err := errors.New("invalid ArticleId")
        c.Error(err).SetType(gin.ErrorTypePublic)
        return
    }

    // バリデーション&バインド
    var req ArticleRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.Error(err).SetType(gin.ErrorTypePublic)
        return
    }

    // DBに保存
    article, _, err := repository.UpdateArticle(articleId, req.Name, req.Body)
    if err != nil {
        c.Error(err).SetType(gin.ErrorTypePublic)
        return
    }

    c.JSON(http.StatusOK, article)
}

// DeleteArticle 掲示板の記事を更新する
func DeleteArticle(c *gin.Context) {
    // PathInfo のパラメータを取得
    paramArticleId := c.Param("articleId")
    articleId, _ := strconv.Atoi(paramArticleId)
    if articleId == 0 {
        err := errors.New("invalid ArticleId")
        c.Error(err).SetType(gin.ErrorTypePublic)
        return
    }

    // DBから削除
    _, err := repository.DeleteArticle(articleId)
    if err != nil {
        c.Error(err).SetType(gin.ErrorTypePublic)
        return
    }

    c.JSON(http.StatusOK, nil)
}

説明が必要そうな箇所には、ソース内にコメントを付けました。これで、正常系は問題無く動作することが確認できています。が、異常系で動かしてみると、いろいろと問題がありそうです。具体的には下記のような問題があります。

  • クエリパラメータやPathInfoのパラメータが不正なパラメータのとき、期待通りの挙動にならない
  • 更新・削除をするとき、対象となるレコードが存在しなくても、エラーにならない
  • 更新するとき、キーの無い項目については更新しない、という仕様の方が良い?

次回は上記の問題点を解消していきます。

参考リンク