🙆

prismでmockサーバを立ててみる

2023/04/09に公開

背景

こちらの続き。
prismでmockサーバを立てて、外部通信の部分を実装してみたい。

イメージ図

環境

M1 Mac
docker desktop 4.17.0

ファイル構成

.
├── Dockerfile
├── README.md
├── animal
│   └── animal.go
├── docker-compose.yml
├── go.mod
├── go.sum
├── mock
│   └── openapi.yml
├── openyml
├── server.go
├── tmp
│   ├── air.log
│   └── main
└── user
    └── user.go

実装

Goを書き換え

・server.go
/animal/dogと/animal/catのエンドポイントを追加

package main

import (
	"echo-practice/animal"
	"echo-practice/user"
	"net/http"

	"github.com/labstack/echo/v4"
)

func main() {
	e := echo.New()
	e.GET("/", func(c echo.Context) error {
		return c.String(http.StatusOK, "Hello, World!")
	})

	u := user.New(1, "testUser", 20)

	e.GET("/user", u.Get)
	e.POST("/user", user.Post)

	dog := animal.New("dog")

	e.GET("/animal/dog", dog.Get)
	e.POST("/animal/dog", dog.Post)

	cat := animal.New("cat")

	e.GET("/animal/cat", cat.Get)
	e.POST("/animal/cat", cat.Post)

	e.Logger.Fatal(e.Start(":1323"))
}

prismのモックサーバと通信する。
モックサーバのホスト名とポートは環境変数から取得。
とりあえず、動くものを実装。
リファクタリングは別の機会にやれたら・・・

package animal

import (
	"bytes"
	"encoding/json"
	"io"
	"log"
	"net/http"
	"net/url"
	"os"

	"github.com/labstack/echo/v4"
)

type Animal struct {
	Name string `json:"name"`
}

type RequestBody struct {
	Name string `json:"name"`
}

type Response struct {
	Message     string `json:"message"`
	Explanation string `json:"explanation"`
}

func New(name string) *Animal {
	return &Animal{Name: name}
}

func (a *Animal) Get(c echo.Context) error {
	var res Response

	animalRes, err := http.Get("http://" + os.Getenv("ANIMAL_API_HOST") + ":" + os.Getenv("ANIMAL_API_PORT") + "/animal/" + a.Name)

	if err != nil {
		return c.JSON(http.StatusInternalServerError, res)
	}

	defer animalRes.Body.Close()

	body, _ := io.ReadAll(animalRes.Body)

	log.Printf("Response:%s", string(body))

	return c.String(http.StatusOK, string(body))
}

func (a *Animal) Post(c echo.Context) error {
	rbody := &RequestBody{
		Name: a.Name,
	}

	animalData, err := json.Marshal(rbody)
	if err != nil {
		return echo.NewHTTPError(400, "failed to bind request")
	}

	log.Printf("Animal:%s", string(animalData))

	base, _ := url.Parse("http://" + os.Getenv("ANIMAL_API_HOST") + ":" + os.Getenv("ANIMAL_API_PORT"))

	reference, _ := url.Parse("animal/register")

	endpoint := base.ResolveReference(reference).String()

	req, _ := http.NewRequest("POST", endpoint, bytes.NewBuffer(animalData))

	req.Header.Set("Content-Type", "application/json")

	q := req.URL.Query()

	req.URL.RawQuery = q.Encode()

	var client *http.Client = &http.Client{}

	resp, err := client.Do(req)
	if err != nil {
		log.Fatalln(err)
	}

	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)

	log.Printf("Response:%s", string(body))

	jsonData, _ := json.Marshal(struct {
		Message string `json:"message"`
	}{
		Message: "ok",
	})
	return c.String(http.StatusCreated, string(jsonData))
}


・openapi.yml
これをprismに渡せばモックサーバ立ててくれる。
書き方はググりながら、とりあえず動くように最低限を記述。

openapi: 3.0.0
info:
  title: Animal API
  description: This is Sample API.
  termsOfService: http://animal-api/
  contact:
    name: API support
    url: http://XXXXX/support
    email: support@XXXX.com
  version: 1.0.0
servers:
  - url: "http://localhost:4010"
    description: Development server
paths:
  /animal/register:
    post:
      tags: 
        - animal
      summary: Create a new Animal
      description: Create a new Animal
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
      responses:
        '201':
          description: status
          content:
            application/json:
              schema:
                type: "object"
                properties:
                  message:
                    type: "string"
                    example: "ok"
  /animal/dog:
    get:
      tags:
        - animal
      summary: Get Dog.
      description: Get Dog.
      responses:
        '200':
          description: A single Animal model
          content:
            application/json:
              schema:
                type: "object"
                properties:
                  message:
                    type: "string"
                    example: "ok"
                  explanation:
                    type: "string"
                    example: "Dog is very cool"
  /animal/cat:
    get:
      tags:
        - animal
      summary: Get Cat.
      description: Get Cat.
      responses:
        '200':
          description: A single Animal model
          content:
            application/json:
              schema:
                type: "object"
                properties:
                  message:
                    type: "string"
                    example: "ok"
                  explanation:
                    type: "string"
                    example: "Cat is very cute"

・docker-compose.yml
prism用のコンテナ追加。
4010ポートを開けておく。

version: '3.8'
services:
  app:
    build:
      context: .
      target: dev
      dockerfile: Dockerfile
    env_file: .env
    tty: true
    container_name: echo-practice
    volumes:
      - ./:/go/src/app
    ports:
      - 1323:1323
  mock:
    image: node:19-alpine3.16
    tty: true
    container_name: animal-api
    working_dir: /tmp
    #restart: always
    command: >
      sh -c '
        npm install -g @stoplight/prism-cli
        prism mock -h 0.0.0.0 "/tmp/openapi.yml"
      '
    volumes:
      - data-mock:/var/lib/mock/data
      - ./mock:/tmp
    ports:
      - 4010:4010
volumes:
  data-mock:

コンテナ起動

docker-compose up -d

動作確認

openapi.ymlで定義したレスポンス返ってくること確認

Discussion