ラベル swagger の投稿を表示しています。 すべての投稿を表示
ラベル swagger の投稿を表示しています。 すべての投稿を表示

2022年5月27日金曜日

OpenAPI or Swagger ファイルを分割して管理する方法

OpenAPI or Swagger ファイルを分割して管理する方法

概要

今回は swagger-cli を使った方法を紹介します
どんなツールを使うにしろ最終的には分割した YAML ファイルを結合する処理が必要になります

環境

  • macOS 11.6.6
  • swagger-cli

swagger-cli のインストール

  • npm install -g @apidevtools/swagger-cli

分割前のファイル

共通部分などを各パートごとに分割します
以下は分割前のファイルになります

  • vim openapi.yaml
openapi: 3.0.0
info:
  title: split test
  version: 1.0.0
paths:
  /pets:
    get:
      summary: List all pets
      operationId: listPets
      tags:
        - pets
      parameters:
        - name: limit
          in: query
          description: How many items to return at one time (max 100)
          required: false
          schema:
            type: integer
            format: int32
      responses:
        '200':
          description: A paged array of pets
          content:
            application/json:
              schema:
                type: "array"
                items:
                  type: object
                  required:
                    - id
                    - name
                  properties:
                    id:
                      type: integer
                      format: int64
                    name:
                      type: string
                    tag:
                      type: string
  /pets/{petId}:
    get:
      summary: Info for a specific pet
      operationId: showPetById
      tags:
        - pets
      parameters:
        - name: petId
          in: path
          required: true
          description: The id of the pet to retrieve
          schema:
            type: string
      responses:
        '200':
          description: Expected response to a valid request
          content:
            application/json:
              schema:
                type: object
                required:
                  - id
                  - name
                properties:
                  id:
                    type: integer
                    format: int64
                  name:
                    type: string
                  tag:
                    type: string

schemas/pet.yaml の作成

レスポンスで使用するオブジェクトが共通なので分割します
schemas/pet.yaml として切り出します

  • vim schemas/pet.yaml
type: object
required:
  - id
  - name
properties:
id:
  type: integer
  format: int64
name:
  type: string
tag:
  type: string

あとはこれを ref で参照します

  • vim openapi.yaml
openapi: 3.0.0
info:
  title: split test
  version: 1.0.0
paths:
  /pets:
    get:
      summary: List all pets
      operationId: listPets
      tags:
        - pets
      parameters:
        - name: limit
          in: query
          description: How many items to return at one time (max 100)
          required: false
          schema:
            type: integer
            format: int32
      responses:
        '200':
          description: A paged array of pets
          content:
            application/json:
              schema:
                type: "array"
                items:
                  $ref: "./schemas/pet.yaml"
  /pets/{petId}:
    get:
      summary: Info for a specific pet
      operationId: showPetById
      tags:
        - pets
      parameters:
        - name: petId
          in: path
          required: true
          description: The id of the pet to retrieve
          schema:
            type: string
      responses:
        '200':
          description: Expected response to a valid request
          content:
            application/json:
              schema:
                $ref: "./schemas/pet.yaml"

parameters の分割

今度は少し応用テクニックを紹介します
parameters の役割で分割して管理しやすくしてみます
まず定義した parameters をすべて読み込むファイルを作成します

  • vim parameters/_index.yaml
petId:
  $ref: './petId.yaml'
limit:
  $ref: './limit.yaml'

パラメータはそれぞれ個別ファイルに定義します

  • vim parameters/petId.yaml
name: petId
in: path
required: true
description: The id of the pet to retrieve
schema:
  type: string
  • vim parameters/limit.yaml
name: limit
in: query
description: How many items to return at one time (max 100)
required: false
schema:
  type: integer
  format: int32

あとはそれぞれのファイルをメインのファイルから参照するように変更します

  • vim openapi.yaml
openapi: 3.0.0
info:
  title: split test
  version: 1.0.0
paths:
  /pets:
    get:
      summary: List all pets
      operationId: listPets
      tags:
        - pets
      parameters:
        - $ref: "./parameters/limit.yaml"
      responses:
        '200':
          description: A paged array of pets
          content:
            application/json:
              schema:
                type: "array"
                items:
                  $ref: "./schemas/pet.yaml"
  /pets/{petId}:
    get:
      summary: Info for a specific pet
      operationId: showPetById
      tags:
        - pets
      parameters:
        - $ref: "./parameters/petId.yaml"
      responses:
        '200':
          description: Expected response to a valid request
          content:
            application/json:
              schema:
                $ref: "./schemas/pet.yaml"
components:
  parameters:
    $ref: "./parameters/_index.yaml"

ファイルを結合して動作確認

ファイルの結合には swagger-cli を使います

  • swagger-cli bundle openapi.yaml --outfile _build/openapi.yaml --type yaml

これで _build/openapi.yaml が出来上がるので swagger ui などで確認するとちゃんと分割前と同じ情報が確認できると思います

できあたがったファイルを見ると ref 先をファイルに埋め込んでパスで無理やり参照しています

また今回の分割後のファイル構成は以下のようになっています

% tree .                     
.
├── _build
│   └── openapi.yaml
├── openapi.yaml
├── parameters
│   ├── _index.yaml
│   ├── limit.yaml
│   └── petId.yaml
└── schemas
    └── pet.yaml

3 directories, 6 file

最後に

基本は分割して ref で参照して結合するだけです
分割の仕方はいろいろありますが基本的には共通部分や役割ごとに分割すると良いと思います

参考サイトにあるように parameters, responses, schemas で分割してあとは paths ごとにもファイルを作成すると管理しやすそうです

参考サイト

2021年1月25日月曜日

APISpec の to_yaml で日本語が文字化けする場合の対処方法

概要

定義した spec 内で日本語を使っている場合は to_yaml を使わずに yaml.dump を使いましょう
to_yaml は内部的には yaml.dump を使っていますがオプションが使えないようになっています

環境

  • macOS 11.1
  • Python 3.8.7
    • apispec 4.0.0

文字化けするコード

with open(file_path, mode='w') as f:
    f.write(spec.to_yaml())

yaml.dump を使う

import yaml

with open(file_path, mode='w') as f:
    yaml.dump(spec.to_dict(), f, allow_unicode=True)


allow_unicode=True を忘れずに設定してください

おまけ: OrderedDict が入っている場合

to_dict した dict 内に OrderedDict が入っている場合以下の処理を追加しましょう

import yaml
from collections import OrderedDict

with open(file_path, mode='w') as f:
    represent_dict_order = lambda self, data:  self.represent_mapping("tag:yaml.org,2002:map", data.items())
    yaml.add_representer(OrderedDict, represent_dict_order)
    yaml.dump(spec.to_dict(), f, allow_unicode=True)

参考サイト

2021年1月24日日曜日

flask + apispec を使って OpenAPI3 の定義ファイルを自動生成する

概要

過去に flasgger を使って Swagger2.0 の定義ファイルを作成する方法を紹介しました
しかし flasgger では OpenAPI3 に完全に対応しておらず components.schame など自動で出力してくれません
今回は apispec を使って OpenAPI3 の定義ファイルを自動生成してみました

環境

  • macOS 11.1
  • Python 3.8.7
    • apispec 4.0.0
    • apispec-webframeworks 0.5.2
    • flask 1.1.2
    • marshmallow 3.10.0

インストール

  • pipenv install apispec apispec-webframeworks flask marshmallow

サンプルコード

  • vim app.py
import uuid

from apispec import APISpec
from apispec.ext.marshmallow import MarshmallowPlugin
from apispec_webframeworks.flask import FlaskPlugin
from flask import Flask
from marshmallow import Schema, fields


spec = APISpec(
    title="Swagger Petstore",
    version="1.0.0",
    openapi_version="3.0.2",
    plugins=[FlaskPlugin(), MarshmallowPlugin()],
)

class CategorySchema(Schema):
    id = fields.Int()
    name = fields.Str(required=True)


class PetSchema(Schema):
    categories = fields.List(fields.Nested(CategorySchema))
    name = fields.Str()


api_key_scheme = {"type": "apiKey", "in": "header", "name": "X-API-Key"}
spec.components.security_scheme("ApiKeyAuth", api_key_scheme)


app = Flask(__name__)


@app.route("/random")
def random_pet():
    """A cute furry animal endpoint.
    ---
    get:
      description: Get a random pet
      security:
        - ApiKeyAuth: []
      responses:
        200:
          description: Return a pet
          content:
            application/json:
              schema: PetSchema
    """
    pet_data = {
        "name": "sample_pet_" + str(uuid.uuid1()),
        "categories": [{"id": 1, "name": "sample_category"}],
    }
    return PetSchema().dump(pet_data)


with app.test_request_context():
    spec.path(view=random_pet)


if __name__ == "__main__":
    import json
    print(json.dumps(spec.to_dict(), indent=2))
    print(print(spec.to_yaml()))
    app.run(debug=True)
  • pipenv run python app.py

or

  • FLASK_APP=app.py pipenv run flask run

説明

基本は APISpec を作成してこれに必要な属性を追加していく感じです
今回は Flask + Marshmallow と連携するので plugins でそれぞれのプラグインを追加しています
components.schemas はデフォルトで表示してくれますが components.securitySchemes は表示してくれないので spec.components.security_scheme で追加しています

Flask のルーティングのコメント部分に各パスのパラメータやレスポンスの定義を直接記載します

MethodView を使う

MethodView を使うとルーティングをクラスとして管理することができます
こちらの方が管理しやすくなると思います

  • vim app.py
import uuid

from apispec import APISpec
from apispec.ext.marshmallow import MarshmallowPlugin
from apispec_webframeworks.flask import FlaskPlugin
from flask import Flask
from flask.views import MethodView
from marshmallow import Schema, fields


spec = APISpec(
    title="Swagger Petstore",
    version="1.0.0",
    openapi_version="3.0.2",
    plugins=[FlaskPlugin(), MarshmallowPlugin()],
)

class CategorySchema(Schema):
    id = fields.Int()
    name = fields.Str(required=True)


class PetSchema(Schema):
    categories = fields.List(fields.Nested(CategorySchema))
    name = fields.Str()


api_key_scheme = {"type": "apiKey", "in": "header", "name": "X-API-Key"}
spec.components.security_scheme("ApiKeyAuth", api_key_scheme)


app = Flask(__name__)


class RandomPet(MethodView):
    def get(self):
        """A cute furry animal endpoint.
        ---
        description: Get a random pet
        security:
        - ApiKeyAuth: []
        responses:
          200:
            description: Return a pet
            content:
              application/json:
                schema: PetSchema
        """
        pet_data = {
            "name": "sample_pet_" + str(uuid.uuid1()),
            "categories": [{"id": 1, "name": "sample_category"}],
            }
        return PetSchema().dump(pet_data)


random_pet_view = RandomPet.as_view("random")
app.add_url_rule(
    "/random",
    view_func=random_pet_view
)
with app.test_request_context():
    spec.path(view=random_pet_view)


if __name__ == "__main__":
    import json
    print(json.dumps(spec.to_dict(), indent=2))
    print(print(spec.to_yaml()))
    app.run(debug=True)
  • pipenv run python app.py

最後に

apispec と flask を組み合わせて OpenAPI3 のドキュメントを flask 内で定義し自動生成する方法を紹介しました
今回はすべて 1 つのファイルに記載しましたが MVC は分割して管理したほうが良いかなと思います
OpenAPI3 の定義ファイルも今回は標準出力に出しているだけなので、別のメインファイルを作成してそちらで spec の情報をファイルに出力するようにしても良いかなと思います

参考サイト

2018年6月14日木曜日

swagger-codegen を使って Python のコードを生成してみよう

概要

前回 swagger-codegen を使って Ruby のコードを生成してみました
今回は Python のコードを生成しました
しかも今回は生成されたコードを修正して動くところまで実装してみます
Python2 で動作するコードも生成できますが今回は Python3 で動作するコードを生成します

環境

  • macOS 10.13.5
  • docker 18.03.1-ce
  • Python 3.6.5

サーバコードの生成

前回同様、使用する swagger.json は PetShop の JSON を使います
Python の場合 flask ベースのサーバコードを生成することができます

docker run --rm -v $(pwd):/local swaggerapi/swagger-codegen-cli generate -i http://petstore.swagger.io/v2/swagger.json -l python-flask -o /local/out/python_server

生成されたコードは以下の通り

  • ls -1 out/python_server/
Dockerfile
README.md
git_push.sh
requirements.txt
setup.py
swagger_server/
test-requirements.txt
tox.ini

Ruby の時とはだいぶことなっており Dockerfile もあります
requirements.txt があるのでそれを使って依存ライブラリをインストールします

  • pip3 install -r requirements.txt

グローバルインストールになるので必要であれば pipenv などを使って仮想環境を作ってください

クライアントコードの生成

クライアント側を生成するときは少し工夫が必要です
というのも Python の場合アクセスするホスト情報がハードコードされており、その元情報は swagger.json にある host になっています
なので一旦 swagger.json を手元にダウンロードしてから必要な部分を書き換えてコードを生成します

  • wget 'http://petstore.swagger.io/v2/swagger.json'
  • sed -i '.org' 's/petstore.swagger.io/localhost:8080/g' swagger.json
  • docker run --rm -v $(pwd):/local swaggerapi/swagger-codegen-cli generate -i /local/swagger.json -l python -o /local/out/python_client

こんな感じです
生成されたクライアントコードは以下の通りです

  • ls -1 out/python_client/
README.md
docs
git_push.sh
requirements.txt
sample.py
sample2.py
setup.py
swagger_client
test
test-requirements.txt
tox.ini

サーバの起動

  • cd out/python_server
  • python3 -m swagger_server

で OK です
localhost:8080 で起動します
localhost:8080/v2/ui で swagger ui が表示されます

サンプルコードの作成

ステータスを元にペットの情報を取得する API をコールしてみます

from __future__ import print_function
import swagger_client
from swagger_client.rest import ApiException

api_instance = swagger_client.PetApi()
status = ['available']

try:
    res = api_instance.find_pets_by_status(status)
    print(res)
except ApiException as e:
    print("Exception when calling PetApi->add_pet: %s\n" % e)

これで実行すると以下のようなエラーになります (一部省略)

ValueError: Invalid value for `name`, must not be `None`

原因はサーバサイドのコードが swagger.json の記載してある通りのレスポンスを返していないためです
今回はこれがちゃんと動くようにサーバ側のコードを修正してみたいと思います

サーバコードの修正

修正するコードは swagger_server/controllers/pet_controller.py になります
ここに find_pets_by_status(status) という関数があるのでこれを修正します

  • vim swagger_server/controllers/pet_controller.py
def find_pets_by_status(status):
    return [Pet(name='taro', photo_urls=['https://www.min-inuzukan.com/images/detailMain_pomeranian.png'])]

コメントなど関係ない部分はすべて削除しています
swagger.json を見るとわかりますが本来は Pet クラスの配列が返ってくるのが正しいです
なのでその通りになるようにレスポンスを返却します

これで再度サーバを起動してクライアントのサンプルコードを実行してみましょう
すると今度はエラーとならず正常にレスポンスが表示されると思います

[{'category': None,
 'id': None,
 'name': 'taro',
 'photo_urls': ['https://www.min-inuzukan.com/images/detailMain_pomeranian.png'],
 'status': None,
 'tags': None}]

少し解説

クライアントコード側の内部的な処理ですが、ざっくり説明するとサーバから取得した情報と swagger.json にある情報を元に必要な model or dictionary or Array or String etc… を生成します
find_pets_by_status の場合、swagger.json を見ると Pet の配列が返ってくることを想定しています
なので、クライアント側もサーバからのレスポンスを元に Pet モデルにバインドしようとします
Pet には namephoto_urls が必須パラメータとして定義されているためそれを含めた情報をサーバが返却する必要があります
また配列であることも想定しているのでたとえ要素が 1 つしかなくても配列で返却する必要があります

内部的には deserialize という関数がありそこでごにょごにょやっているので興味があれば見てください (swagger_client/api_client.py)
よくあるメタプログラミングを使っています

最後に

swagger-codegen を使って Python のコードをサーバ/クライアント側で生成してみました
また、実際にサーバを動作させてクライアントコードから問題なくコールできることを確認しました

Ruby のときもそうだったのですが、swagger-codegen は生成したコードを使って localhost で動作させるのに少し工夫が必要です
そもそも使用している swagger.json が外部のものなので localhost にアクセスするのを想定していないと言えばそれまでですが、テストなどではまずは localhost で動かしたくなります

今回は場合はクライアントコードの向き先とサーバコードのレスポンスの修正を行いました
実際は DB なども絡むので更に複雑になると思いますが最終的には swagger.json に定義されたレスポンス形式に落とし込む必要があるという点はしっかり抑えておきたい点かなと思います

2018年6月13日水曜日

swagger-codegen を試してみた

概要

swagger-codegen は 1 つの swagger ファイルから複数の言語のクライアントツールを生成することができるツールです
コマンドラインと と docker で使えるので試してみました
今回は Ruby のコードを生成します

環境

  • macOS 10.13.5
  • docker 18.03.1-ce

docker で生成する

docker run --rm -v $(pwd):/local swaggerapi/swagger-codegen-cli generate -i http://petstore.swagger.io/v2/swagger.json -l ruby -o /local/out/ruby
  • ls -1 out/ruby/
Gemfile
README.md
Rakefile
docs
git_push.sh
lib
spec
swagger_client.gemspec

こんな感じで生成されました

homebrew でバイナリをインストールして生成

Mac であれば homebrew で swagger-codegen コマンドをインストールして使うことができます
ただし Java のインストールも必要です

  • brew cask install java
  • brew install swagger-codegen

Java8 が必要だと言われて怒られた場合は

  • brew cask install homebrew/cask-versions/java8

を実行してください

これで swagger-codegen というコマンドがローカルで使えるようになります
先程の docker も内部で同じコマンドを実行しています

生成するコマンドを実行してみます

swagger-codegen generate -i http://petstore.swagger.io/v2/swagger.json -l ruby -o ./out/ruby

ほぼ同じです
パスの部分が若干違うだけです
生成されるファイルは全く同じなので割愛します

と思ったのですが docker と homebrew だと swagger-codegen のバージョンが異なるようです
また生成されるファイルも若干違っていました
docker 版には .rubocup.yml がありましたが、homebrew 側にはありませんでした
docker 側は 2.4.0-SNAPSHOT で homebrew が 2.3.1 なので docker 側の最新イメージには開発中の最新版が入っているようです

サーバ側のコードを生成する

上記のコマンドはクライアント側のツールを生成するだけです
とりあえずリクエストを投げてみたいのでサーバ側のコードも生成してみましょう
Ruby の場合 Rails5 or Sinatra が選択できるようです
個人的に Sinatra のほうが好きなので Sinatra を選択します

docker run --rm -v $(pwd):/local swaggerapi/swagger-codegen-cli generate -i http://petstore.swagger.io/v2/swagger.json -l sinatra -o /local/out/ruby_server

当然ですが swagger.json は同じものを指定してください
docker を使っていますが、ローカルでコールする場合もほぼ同じです

生成されるファイルは以下の通りです

  • ls -1
Gemfile
README.md
api
config.ru
lib
my_app.rb
swagger.yaml

使ってみる

このままでは絵に書いた餅です
実際に使ってみます
今回は docker で生成したコードを使います

サーバを立てる

先ほど生成したサーバ用のコードを使います
とりあえず Gemfile があるのでなすがままに bundle install してみましょう

  • bundle install --path vendor

いろいろとインストールされます
config.ru があるので、それを使って起動してみます

  • bundle exec rackup config.ru

これで localhost:9292 で起動します
もしバインドする IP を指定したい場合は -o 0.0.0.0 という感じで指定できます

クライアントからコールしてみる

サーバが起動したのでクライアントツールを使ってコールしてみます
こちらも Gemfile があるのでとりあえず bundle install しましょう

  • bundle install --path vendor

こちらもいろいろインストールされます
作成されたファイルを見ると swagger_client.gemspecRakefile があるのでどうやら専用の gem が作成できそうです

とりあえずそのまま作成してみましょう (ただ bundler/gem_tasks を使っていないので rake build することはできません)

  • bundle exec gem build swagger_client.gemspec

これで swagger_client-1.0.0.gem という gem ができあがります
あとはインストールして使いましょう

  • gem install swagger_client-1.0.0.gem

ethontyphoeus に依存していました
HTTP クライアントとして使っているので内部では libcurl を使っているっぽいです

サンプルコード

ではサーバ側のコードをコールしてみましょう
コール先は localhost:9292 なので SwaggerClient.configure で変更します
と言ってもサーバ側で何も実装していないので何も返ってきません

  • vim sample.rb
require 'swagger_client'

SwaggerClient.configure do |config|
  config.host = 'localhost:9292'
end

api_instance = SwaggerClient::PetApi.new
status = ['sample_status']

begin
  p api_instance.find_pets_by_status status
rescue SwaggerClient::ApiError => e
  puts "Exception when calling PetApi->add_pet: #{e}"
end

こんな感じでクライアントからコールすることができます
クライアント側のコードに README.md が出来ておりリファレンスも生成されているのでそれを参考にすると良いと思います

ちなみに localhost:9292/swagger.yml で定義ファイルを確認できます

最後に

swagger-codegen を使って Ruby のコードを生成し試してみました
正直まだまだ絶賛開発中な感じはします

また生成したコード (特にサーバ側) は本当に簡単なものだけなので、実際にサービス化するときはコーディングが必要になります

なので、生成されるコードをしっかりと読み解く時間も必要になるのは注意が必要です

参考サイト

2017年5月30日火曜日

go-swagger で XML なレスポンスを返却する方法

概要

デフォルトだと application/json としてレスポンスが返却されます
レスポンスを XML として返却できないか試してみたので紹介します

環境

  • CentOS 7.3.1611
  • golang 1.8
  • swagger 2.0
  • go-swagger 0.10.0

swagger.yml

  • vim swagger.yml
consumes:
- application/json
info:
  description: The product of a tutorial on goswagger.io
  title: A To Do list application
  version: 1.0.0
produces:
- application/xml
schemes:
- http
swagger: "2.0"
basePath: /v1
definitions:
  item:
    type: object
    required:
      - description
    properties:
      id:
        type: integer
        format: int64
        readOnly: true
      description:
        type: string
        minLength: 1
      completed:
        type: boolean
  error:
    type: object
    required:
      - message
    properties:
      code:
        type: integer
        format: int64
      message:
        type: string
paths:
  /:
    get:
      tags:
        - todos
      operationId: findTodos
      parameters:
        - name: since
          in: query
          type: integer
          format: int64
        - name: limit
          in: query
          type: integer
          format: int32
          default: 20
      responses:
        200:
          description: list the todo operations
          schema:
            type: array
            items:
              $ref: "#/definitions/item"
        default:
          description: generic error response
          schema:
            $ref: "#/definitions/error"

ポイントは produces の部分でここでレスポンス時の Content-Type を application/xml に固定します

produces:
- application/xml

コード生成

  • swagger generate server -f swagger.yml

メイン部分修正

  • vim configure_a_to_do_list_application.go

一部抜粋です
適当に値を返す処理を追加します

api.TodosFindTodosHandler = todos.FindTodosHandlerFunc(func(params todos.FindTodosParams) middleware.Responder {
        result := make([]*models.Item, 0)
        item := new(models.Item)
        item.Completed = true
        s := "hoge"
        item.Description = &s
        result = append(result, item)
        return todos.NewFindTodosOK().WithPayload(result)
})

アプリ起動

  • go install ./cmd/a-to-do-list-application-server/
  • a-to-do-list-application-server --host 0.0.0.0 --port 18080

動作確認

  • curl localhost:18080/v1 | xmllint --format -

とすると

<?xml version="1.0"?>
<Item>
  <Completed>true</Completed>
  <Description>hoge</Description>
  <ID>0</ID>
</Item>

という感じで XML が返ってきます
-v で詳細を見ると Content-Type も application/xml となっているのが確認できると思います

追加調査

とりあえずこれで自分が実装する API の部分に関しては XML で返却することができます
ただ、go-swagger がデフォルトで実装しているエラーハンドリングの部分に関しては json で返ってきてしまいます
例えば今回であれば「/」にアクセスした場合、Not Found のエラーになるのですが、それが

{"code":404,"message":"path / was not found"}

となってしまいます
他には Method Not Allowed なども json になってしまいます

{"code":405,"message":"method POST is not allowed, but [GET] are"}

このあたりのデフォルトエラーに関しても XML にしたいと思います
デフォルトエラーのカスタマイズ方法についてはまだ調査できていないので別途調査が必要かなと思います

最後に

go-swagger で XML のレスポンスを返却する方法を紹介しました
基本は consumes の設定を変更するだけで XML にすることができました

2017年4月28日金曜日

go-swagger をアップデートしたときに「Your local changes to the following files would be overwritten by merge」が発生

概要

go get で go-swagger をアップデートしたときに最新版をうまくマージできない現象が発生しました
対象方法を紹介します

環境

  • Ubuntu 16.04
  • golang 1.8.1
  • go-swagger 0.8.0 -> 0.10.0

エラー詳細

アップデート時に使用したコマンドは以下の通り

  • go get -u github.com/go-swagger/go-swagger/cmd/swagger

そして以下のエラーが発生

go get -u github.com/go-swagger/go-swagger/cmd/swagger
# cd /root/go/src/github.com/go-swagger/go-swagger; git pull --ff-only
error: Your local changes to the following files would be overwritten by merge:
        generator/bindata.go
        generator/client.go
        generator/shared.go
        scan/path.go
        scan/responses.go
Please, commit your changes or stash them before you can merge.
Aborting
Updating 00f4a1f..e62bb82
package github.com/go-swagger/go-swagger/cmd/swagger: exit status 1

単純にマージできないだけだったので、対象のディレクトリをごそっと削除して再度インストールしてあげます
その前に clean で swagger コマンド自体を削除します

  • go clean -i github.com/go-swagger/go-swagger/cmd/swagger
  • cd /root/go/src/github.com/go-swagger/go-swagger
  • cd ../ && rm -rf go-swagger
  • go get -u github.com/go-swagger/go-swagger/cmd/swagger

でエラーなく最新版をインストールすることができました

コードも実は修正する必要あり

go-swagger が生成してくれる main 的なソース「restapi/configure_your_project_name.go」ですがこいつも修正しないと最新版ではエラーが発生すると思います
configureServer の引数が 1 つ増えているので以下のように最後に一つ引数を追加してあげましょう

func configureServer(s *graceful.Server, scheme, addr string) {
...
}

最後に

go-swagger をバージョンアップした際に対応したことを紹介しました
オープンソースだしまだメジャーバージョンアップでもないので何とも言えませんが、後方互換性がないアップデートだとちょっと焦ります

2017年4月1日土曜日

go-swagger で作成したアプリにテストを付けてみた

概要

過去 に go-swagger をつかって TODO アプリを作成しました
今回は swagger client という機能を使ってアプリのテストコードを作成してみました

環境

  • Mac OS X 10.12.3
  • golang 1.8
  • go-swagger dev

クライアントコードの生成

  • cd /path/to/go/src/github.com/hawksnowlog/todo-list/client
  • swagger generate client -f swagger.yml
  • tree -a client
client/
├── a_to_do_list_application_client.go
└── todos
    ├── add_one_parameters.go
    ├── add_one_responses.go
    ├── destroy_one_parameters.go
    ├── destroy_one_responses.go
    ├── find_todos_parameters.go
    ├── find_todos_responses.go
    ├── todos_client.go
    ├── update_one_parameters.go
    └── update_one_responses.go

client というディレクトリ配下にクライアントコードが作成できます

テストコードの作成

では、生成したクライアントコードを使ってテストコードを作成します

  • cd /path/to/go/src/github.com/hawksnowlog/todo-list/
  • mkdir test
  • vim todo_list_test.go
package test

import (
    "log"
    "testing"
    "time"

    "github.com/go-openapi/swag"

    apiclient "github.com/kakakikikeke/todo-list/client"
    "github.com/kakakikikeke/todo-list/client/todos"
    "github.com/kakakikikeke/todo-list/models"
)

var tc = apiclient.TransportConfig{
    "localhost:8080",
    apiclient.DefaultBasePath,
    apiclient.DefaultSchemes,
}

func TestAddOne(t *testing.T) {
    d := "test1"
    i := models.Item{
        Completed:   false,
        Description: &d,
    }
    p := todos.NewAddOneParams().WithBody(&i)
    resp, err := apiclient.NewHTTPClientWithConfig(nil, &tc).Todos.AddOne(p.WithTimeout(10 * time.Second))
    if err != nil {
        log.Fatal(err)
    }
    log.Println(resp)
}

func TestFindTodos(t *testing.T) {
    p := todos.NewFindTodosParams()
    p.Since = swag.Int64(0)
    resp, err := apiclient.NewHTTPClientWithConfig(nil, &tc).Todos.FindTodos(p.WithTimeout(10 * time.Second))
    if err != nil {
        log.Fatal(err)
    }
    if len(resp.Payload) <= 0 {
        log.Fatal("No records")
    }
    for _, p := range resp.Payload {
        log.Println(p.ID)
        log.Println(*p.Description)
        log.Println(p.Completed)
    }
}

func TestUpdateOne(t *testing.T) {
    d := "test2"
    i := models.Item{
        Completed:   true,
        Description: &d,
    }
    p := todos.NewUpdateOneParams().WithBody(&i)
    p.ID = 1
    resp, err := apiclient.NewHTTPClientWithConfig(nil, &tc).Todos.UpdateOne(p.WithTimeout(10 * time.Second))
    if err != nil {
        log.Fatal(err)
    }
    log.Println(resp)
}

func TestDestroyOne(t *testing.T) {
    p := todos.NewDestroyOneParams()
    p.ID = 1
    resp, err := apiclient.NewHTTPClientWithConfig(nil, &tc).Todos.DestroyOne(p.WithTimeout(10 * time.Second))
    if err != nil {
        log.Fatal(err)
    }
    log.Println(resp)
}

各機能ごとにテスト用のメソッドを作成しています
流れとしてはパラメータを作成して apiclient を使って API をコールし、その結果を評価しています

デフォルトでアクセスするホストは localhost でポートは 80 番です
もしそれ以外にアクセスする場合は TransportConfig で設定できます

実行

  • go fmt github.com/hawksnowlog/todo-list/test
  • go test -v github.com/hawksnowlog/todo-list/test

で、テストを実行できます
fmt や log を使っている場合は「-v」オプションを付与することで出力することができます

このテストは実際に API をコールします
なのでテストを実行する前にはアプリも実行しておく必要があります

最後に

swagger client を使ってテストコードを作成してみました
ユニットテストというよりかはインテグレーションテストになるかと思います

swagger を使えばインテグレーションテストも簡単に書けるので便利です
あとは UI や CLI ツールを作成するときにも client のコードは使えるかなと思います

2017年3月5日日曜日

swagger-editor を構築する

概要

Swagger-Editor は swagger.yml を作成することでツールです
UI を使って行えるので直感的に API インタフェースを定義することができます
今回はローカルの Mac に構築して swagger.yml を編集してみました

環境

  • Mac OS X 10.12.3
  • Docker 1.13.1
  • swagger editor 2.10.4

インストール

Docker を使います
他にも Swagger が提供する Web 版を使ったり nodejs を使って npm install でインストールしたりする方法があります

  • docker pull swaggerapi/swagger-editor
  • docker run -p 80:8080 swaggerapi/swagger-editor

80 番ポートが使わている場合は別のホストポートにバインドしてください

起動したら http://localhost にブラウザでアクセスすれば Swagger Editor を使うことができます

使い方

swagger.yml および swagger.json が Web 経由で取得できる場合は

  • File -> Import URL

で URL を指定すれば YAML ファイルを開くことができます
ローカルからはアクセスできなかったり CORS を設定していない場合などは

  • File -> Import File

でローカルにある swagger.yml を指定することもできます

作成が完了したら

  • File -> Download YAML

でファイルをダウンロードすることができます
あとはダウンロードしたファイルをサーバにアップロードすれば完了です
swagger generate serverswagger generate client を各言語ごとに作成する機能もあります

最後に

Swagger Editor をローカルマシンに構築してみました
Swagger UI 同様に swagger で作成したアプリが動作しているサーバじゃなくても OK です

swagger.yml or swagger.json だけを渡せれば動作させられるのは Swagger の良いところかなと思います

ちなみに停止する場合は docker run しているターミナルで Ctrl + c すれば OK です

2017年3月1日水曜日

go-swagger でエラーハンドリングする方法

概要

使用するアプリケーションはこれまでに作成した TODO アプリを使用します

環境

  • CentOS 6.7 64bit
  • go-swagger dev
  • golang 1.6

swagger.yml にエラーの定義を追加

  • paths -> / -> post -> responses -> 400

に以下を定義します

400:
  description: BadRequest
  schema:
    $ref: "#/definitions/error"

そしてこれで再度コードを生成し直します

  • swagger validate swagger.yml
  • swagger generate server -A TodoList -f swagger.yml

ソースコード修正

restapi/configure_todo_list.go を編集します
POST の部分を以下のように修正することで定義した 400 用の関数を使うことができます

api.TodosAddOneHandler = todos.AddOneHandlerFunc(func(params todos.AddOneParams) middleware.Responder {
        if err := addItem(params.Body); err != nil {
                return todos.NewAddOneBadRequest().WithPayload(&models.Error{Code: 400, Message: swag.String(err.Error())})
        }
        testQueue.Put("testpayload")
        return todos.NewAddOneCreated().WithPayload(params.Body)
})

ポイントは 400 の定義を swagger.yml に追加することで restapi/operations/todos/add_one_responses.go に NewAddOneBadRequest というメソッドが追加されていることです
これを使うことでステータスコードが既に設定されたレスポンスを返却することができます

WithPayload でレスポンスのボディを設定することができます
今回はモデルにそれっぽい値を設定してレスポンスボディとしています

  • &models.Error{Code: 400, Message: swag.String(err.Error())}

ちょっとおまけ

ここで思うのがいちいち Code: 400 という感じでステータスコードを決め打ちするのは面倒ということです
addItem は github.com/go-openapi/errors というパッケージの Error オブジェクトを返します
このオブジェクトはステータスコードも持っています

なので addItem 関数の返り値を errors.Error のように修正してモデルからエラーオブジェクトを生成するときに以下のように修正します

  • addItem 関数
func addItem(item *models.Item) errors.Error {
        if item == nil {
                return errors.New(400, "item must be present")
        }

        itemsLock.Lock()
        defer itemsLock.Unlock()

        newID := newItemID()
        item.ID = newID
        items[newID] = item

        return nil
}
  • api.TodosAddOneHandler 関数
&models.Error{Code: err.Code(), Message: swag.String(err.Error())}

err.Code() で返ってくる型が int32 なので swagger.yml の設定も以下のように変更する必要があります

swagger.yml の definitions -> error の format を int32 に変更する必要があります

error:
  type: object
  required:
    - message
  properties:
    code:
      type: integer
      format: int32
    message:
      type: string

swagger.yml を修正しているのでビルドするときは validate -> generate してください
これでロジック側で生成したエラーオブジェクトを configureAPI 側で返すだけなのでキレイなコードになると思います

最後に

go-swagger でエラーハンドリングする方法を紹介しました
基本的には swagger.yml で定義したステータスコード用の関数ができるのでちゃんとそれを使うようにするだけです

エラーを生成するのはあくまでもロジック側のコード (今回だと addItem) で、それを configure_todo_list.go の configureAPI 側で受け取って返却する流れがきれいな流れかなと思います

2017年2月28日火曜日

swagger ui のスタイルを変更してみた

概要

swagger ui にスタイルを当ててみました
使用するアプリケーションはこれまでに作成した TODO アプリを使用します

環境

  • CentOS 6.7 64bit
  • go-swagger dev
  • golang 1.6
  • swagger ui v2.2.10

事前準備

Apache Httpd をインストールする

  • yum -y install httpd
  • service httpd start

TODO アプリの CORS を有効にする

  • go get github.com/rs/cors
  • cd $GOPATH/src/github.com/hawksnowlog/todo-list
  • emacs restapi/configure_todo_list.go
import "github.com/rs/cors"
func setupGlobalMiddleware(handler http.Handler) http.Handler {
        handleCORS := cors.Default().Handler
        return handleCORS(handler)
}

修正できたらビルドしてアプリを起動します

  • go fmt restapi/configure_todo_list.go && go install ./cmd/todo-list-server/
  • todo-list-server –host=0.0.0.0 –port=18080

swagger ui のインストールと起動

swagger ui の dist 配下に index.html やら必要な静的ファイルがあるのでこれを Apache の DocumentRoot 配下に配置するだけです
配置できたら http://xxx.xxx.xxx.xxx/dist/ にアクセスしてみましょう

すると、swagger ui が表示されるので、ヘッダにあるフィールドに http://xxx.xxx.xxx.xxx:18080/swagger.json を入力し「Explore」を選択します
でとりあえず自分のアプリでデフォルトの swagger ui が表示されると思います
swagger_ui1.png

スタイルの設定

今回はすでに公開されているスタイルを使ってみたいと思います

<link href="css/theme-feeling-blue.css" media="screen" rel="stylesheet" id="sut" type="text/css">

を head タグの <!-- Some basic translations --> の直下にコピペします
そして再度 http://xxx.xxx.xxx.xxx/dist/ にアクセスするとスタイルが変わっていると思います

最後に

swagger ui でスタイルを変更する方法を紹介しました
公式の swagger ui は go-swagger で表示させた ui のスタイルとは全く違うデザインになっていました

今回はすでに公開されているスタイルを使いましたが、直接 css や html を変更しても問題ないと思います

参考サイト

2017年2月27日月曜日

go-swagger でリクエスト情報をロギングしてみた

概要

どんな Web アプリでもリクエストされた情報はロギングしたいと思います
今回は go-swagger でリクエスト情報をロギングしてみたいと思います
ロギングする情報はリクエストメソッド、パス、ボディになります
使用するアプリケーションはこれまでに作成した TODO アプリを使用します

環境

  • CentOS 6.7 64bit
  • go-swagger dev
  • golang 1.6
  • swagger ui v2.2.10

ソース修正

  • cd $GOPATH/src/github.com/hawksnowlog/todo-list
  • vim restapi/configure_todo_list.go

import の追加

import (
        "bytes"
        "io/ioutil"
        "log"
)

既存の import に追記してください

ロギング用ハンドラメソッドの追加

func addLogging(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                buf, _ := ioutil.ReadAll(r.Body)
                rdr1 := ioutil.NopCloser(bytes.NewBuffer(buf))
                rdr2 := ioutil.NopCloser(bytes.NewBuffer(buf))
                r.Body = rdr2
                bufbody := new(bytes.Buffer)
                bufbody.ReadFrom(rdr1)
                body := bufbody.String()
                log.Println("received request:", r.Method, r.URL, body)
                next.ServeHTTP(w, r)
        })
}

ここでポイントですが、r.Body の情報を 2 つのバッファに分割しています
これは r.Body をそのまま ReadFrom にかけてしまうと r.Body の情報が失われてしまい、この後の go-swagger 側の処理で body がないと言われエラーになってしまうからです
なので、一度分割して使っていない方を再度 r.Body に設定しています

ロギング処理のコール

func setupGlobalMiddleware(handler http.Handler) http.Handler {
        handleCORS := cors.Default().Handler
        return handleCORS(addLogging(handler))
}

swagger ui に対応するために CORS の処理を入れたので更にその処理にロギング処理を追加します
こんな感じで go-swagger はハンドラを何個も挟むことで、別の処理を追加することができます

動作確認

  • cd $GOPATH/src/github.com/hawksnowlog/todo-list
  • go fmt restapi/configure_todo_list.go
  • go install ./cmd/todo-list-server/
  • todo-list-server --host=0.0.0.0 --port=18080

でアプリを起動して

  • curl -XPOST -H "Content-Type: application/io.goswagger.examples.todo-list.v1+json" "http://127.0.0.1:18080/v1/" -d '{"description":"test2", "completed":true}'

でアクセスすると

2017/02/23 14:57:10 received request: POST /v1/ {"description":"test2", "completed":true}

こんな感じのログが出力されると思います
ログファイルに出力したい場合はコード内でファイルに出力するようにしても良いですし todo-list-server --host=0.0.0.0 --port=18080 >> log 2>&1 こんな感じで出力をリダイレクトしても良いと思います

参考サイト

2017年2月26日日曜日

go-swagger + redismq を試してみた

概要

go-swagger + redismq を試してみました
イメージとしてはある API をコールしたらバックエンドの redis のキューに値を入れる感じです
go-swagger のアプリケーションは前回 までに作成している TODO アプリを使っています

環境

  • CentOS 6.7 64bit
  • go-swagger dev
  • golang 1.8
  • redis-server 3.0.2

go 1.8 のインストール

go version go1.8 linux/amd64

インストール

  • go get “github.com/adjust/redismq”

コーディング

  • cd $GOPATH/src/github.com/hawksnowlog/todo-list
  • vim restapi/configure_todo_list.go
import "github.com/adjust/redismq"
var testQueue = redismq.CreateQueue("localhost", "6379", "", 9, "clicks")
api.TodosAddOneHandler = todos.AddOneHandlerFunc(func(params todos.AddOneParams) middleware.Responder {
        fmt.Println("TodosAddOneHandler")
        if err := addItem(params.Body); err != nil {
                return todos.NewAddOneDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
        }
        testQueue.Put("testpayload") // -> ここを追記
        return todos.NewAddOneCreated().WithPayload(params.Body)
})

という感じです
やっている処理はヒジョに簡単です

ビルド

  • go fmt restapi/configure_todo_list.go
  • go install ./cmd/todo-list-server/
  • todo-list-server –host=0.0.0.0 –port=18080

動作確認

まずはリクエストを送ります

  • curl -XPOST -H "Content-Type: application/io.goswagger.examples.todo-list.v1+json" "http://127.0.0.1:18080/v1/" -d '{"description":"test", "completed":false}'

今回は redis-cli で確認します
DB は 9 を選択しているので SELECT コマンドで切り替えてから操作します
redis-cli するときに -n 9 でも OK です

  • redis-cli
  • redis> SELECT 9
  • redis> keys *
1) "redismq::clicks::failed::size::1487755650"
2) "redismq::clicks"
3) "redismq::clicks::size::1487755650"
4) "redismq::clicks::rate::1487755084"

で、こんな key が入っていれば OK です
それぞれタイプは上から string, list, string, string なのでデータにアクセスする場合は

  • redis> GET redismq::clicks::failed::size::1487755650
  • redis> LRANGE redismq::clicks 0 -1 LRANGE redismq::clicks 0 -1
  • redis> GET redismq::clicks::size::1487755650
  • redis> GET redismq::clicks::rate::1487755084

になります
実際に送信したペイロード情報は redismq::clicks に入っています

Consumer を作成してみる

  • vim consumer.go
package main

import (
        "fmt"
        "github.com/adjust/redismq"
)

func main() {
        testQueue := redismq.CreateQueue("localhost", "6379", "", 9, "clicks")
        consumer, err := testQueue.AddConsumer("testconsumer")
        if err != nil {
                panic(err)
        }
        for {
                p, err := consumer.Get()
                if err != nil {
                        fmt.Println(err)
                        continue
                }
                //fmt.Println(p.CreatedAt)
                fmt.Println(p)
                err = p.Ack()
                if err != nil {
                        fmt.Println(err)
                }
        }
}
  • go run consumer.go

で待ち状態になるので、これで curl を実行してみると値が表示されるのが確認できると思います

最後に

go-swagger で redismq というライブラリを試してみました
所謂 Message Queue なので Producker と Consumer が登場します
また、pubsub モデルっぽいですが内部では redis の PUBLISH と SUBSCRIBE を使っているわけではないです
https://redis.io/topics/pubsub

内部では紹介したとおり list を使っていてかつ list にあるメタ情報を別の key で管理している感じです
list には ttl がないですが各 key には ttl があるので勝手に消えますが、ttl が過ぎるまで key が大量に増えるのは少し嫌かもしれません
デフォルトだと 7200 秒 (2時間) で設定されているようです

参考サイト

2017年2月25日土曜日

go-swagger で swagger ui を使ってみた

概要

前回 go-swagger を使って単純な TODO アプリを作ってみました
今回は swagger の特徴の一つである swagger ui を試してみました
go-swagger でも swagger ui が実装されているので使えます

環境

  • CentOS 6.7 64bit
  • go-swagger dev
  • golang 1.6

事前準備

事前に TODO アプリを起動しておきましょう

  • todo-list-server --port=18080

swagger ui の起動

ローカルで立ち上げます
同一ローカル上でアプリが動いていることが前提です

  • swagger serve "http://127.0.0.1:18080/swagger.json" --port=28080

動作確認

ブラウザで http://xxx.xxx.xxx.xxx:28080/docs にアクセスすると以下のような UI が表示されると思います
go-swagger-ui.png

最後に

go-swagger で実装されている swagger ui を試してみました
ポイントは /swagger.json を明示的に指定する必要がある点でした

おそらく go-swagger で実装されている ui のデザインは変更することができないと思います
プライベートで使う分にはそんなに変なデザインではないので問題かなと思います

とは言えスタイルを当てたいというケースは多いと思うのでちょっと方法を考えてみたいと思います
たぶん標準の swagger ui を使ってかつテーマを当てればできるんだと思います
http://stackoverflow.com/questions/28033075/why-there-are-no-themes-for-swagger-ui

2017年2月24日金曜日

go-swagger を使って生成したコードに独自のロジックを実装してみた

概要

前回 go-swagger のインストールと簡単なサーバの生成と起動まで実施しました
今回は生成されたコードを修正し実際のロジックまで作成してみました

環境

  • CentOS 6.7 64bit
  • go-swagger dev
  • golang 1.6

swagger.yml 編集

TODO リストに必要な REST API を追加します
前回の swagger.yml から追記する必要がある差分は以下の通りです
基本的には paths の「/」に post 命令を追加するのと新規の paths「/{id}」に対して put と delete の定義を追加しています
TODO アプリに必要な CRUD 機能を追加してる感じです

65,118d65
<     post:
<       tags:
<         - todos
<       operationId: addOne
<       parameters:
<         - name: body
<           in: body
<           schema:
<             $ref: "#/definitions/item"
<       responses:
<         201:
<           description: Created
<           schema:
<             $ref: "#/definitions/item"
<         default:
<           description: error
<           schema:
<             $ref: "#/definitions/error"
<   /{id}:
<     parameters:
<       - type: integer
<         format: int64
<         name: id
<         in: path
<         required: true
<     put:
<       tags:
<         - todos
<       operationId: updateOne
<       parameters:
<         - name: body
<           in: body
<           schema:
<             $ref: "#/definitions/item"
<       responses:
<         200:
<           description: OK
<           schema:
<             $ref: "#/definitions/item"
<         default:
<           description: error
<           schema:
<             $ref: "#/definitions/error"
<     delete:
<       tags:
<         - todos
<       operationId: destroyOne
<       responses:
<         204:
<           description: Deleted
<         default:
<           description: error
<           schema:
<             $ref: "#/definitions/error"

追記できたら validation して再生成します

  • swagger validate swagger.yml
  • swagger generate server -A TodoList -f swagger.yml

で再度 .go ファイルが生成されます

restapi/configure_todo_list.go 編集

では、実際に TODO アプリに必要な機能を実装してみます
編集する箇所がやや多いのでポイントごとに紹介します

import

import (
        "crypto/tls"
        "fmt"
        "net/http"
        "sync"
        "sync/atomic"

        errors "github.com/go-openapi/errors"
        runtime "github.com/go-openapi/runtime"
        middleware "github.com/go-openapi/runtime/middleware"
        "github.com/go-openapi/swag"
        graceful "github.com/tylerb/graceful"

        "github.com/hawksnowlog/todo-list/models"
        "github.com/hawksnowlog/todo-list/restapi/operations"
        "github.com/hawksnowlog/todo-list/restapi/operations/todos"
)

既存 import にいくつかライブラリを追加しています
足りない部分を追加すれば基本は OK です
sync や swag, モデルを管理するための models が追加になっていると思います

ロジック

ちょっと長いです
が、これが TODO アプリのコアの機能の部分になっています

var items = make(map[int64]*models.Item)
var lastID int64

var itemsLock = &sync.Mutex{}

func newItemID() int64 {
        return atomic.AddInt64(&lastID, 1)
}

まずは TODO を保存するを定義します
TODO にはインクリメントな ID が振られるため、それを生成するための関数を定義します
次に各 CRUD 処理のメインとなる関数をそれぞれ準備します

func addItem(item *models.Item) error {
        if item == nil {
                return errors.New(500, "item must be present")
        }

        itemsLock.Lock()
        defer itemsLock.Unlock()

        newID := newItemID()
        item.ID = newID
        items[newID] = item

        return nil
}

func updateItem(id int64, item *models.Item) error {
        if item == nil {
                return errors.New(500, "item must be present")
        }

        itemsLock.Lock()
        defer itemsLock.Unlock()

        _, exists := items[id]
        if !exists {
                return errors.NotFound("not found: item %d", id)
        }

        item.ID = id
        items[id] = item
        return nil
}

func deleteItem(id int64) error {
        itemsLock.Lock()
        defer itemsLock.Unlock()

        _, exists := items[id]
        if !exists {
                return errors.NotFound("not found: item %d", id)
        }

        delete(items, id)
        return nil
}

func allItems(since int64, limit int32) (result []*models.Item) {
        result = make([]*models.Item, 0)
        for id, item := range items {
                if len(result) >= int(limit) {
                        return
                }
                if since == 0 || id > since {
                        result = append(result, item)
                }
        }
        return
}

関数の名前の通りなのでそれほど読み解くのは難しくないと思います
先程定義した items という変数に対して値を追加したり削除したり更新したりする処理をそれぞれの関数で行っているだけです

またこのロジックは // This file is safe to edit. Once it exists it will not be overwritten というコメントがあるので、その直下に記載してください

ハンドラで各ロジックをコールする

実装したロジックをハンドラ側でコールします
configureAPI というメソッドがあるのでその中のハンドラを修正します

api.TodosAddOneHandler = todos.AddOneHandlerFunc(func(params todos.AddOneParams) middleware.Responder {
        fmt.Println("TodosAddOneHandler")
        if err := addItem(params.Body); err != nil {
                return todos.NewAddOneDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
        }
        return todos.NewAddOneCreated().WithPayload(params.Body)
})
api.TodosDestroyOneHandler = todos.DestroyOneHandlerFunc(func(params todos.DestroyOneParams) middleware.Responder {
        fmt.Println("TodosDestroyOneHandler")
        if err := deleteItem(params.ID); err != nil {
                return todos.NewDestroyOneDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
        }
        return todos.NewDestroyOneNoContent()
})
api.TodosFindTodosHandler = todos.FindTodosHandlerFunc(func(params todos.FindTodosParams) middleware.Responder {
        fmt.Println("TodosFindTodosHandler")
        mergedParams := todos.NewFindTodosParams()
        mergedParams.Since = swag.Int64(0)
        if params.Since != nil {
                mergedParams.Since = params.Since
        }
        if params.Limit != nil {
                mergedParams.Limit = params.Limit
        }
        return todos.NewFindTodosOK().WithPayload(allItems(*mergedParams.Since, *mergedParams.Limit))
})
api.TodosUpdateOneHandler = todos.UpdateOneHandlerFunc(func(params todos.UpdateOneParams) middleware.Responder {
        fmt.Println("TodosUpdateOneHandler")
        if err := updateItem(params.ID, params.Body); err != nil {
                return todos.NewUpdateOneDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
        }
        return todos.NewUpdateOneOK().WithPayload(params.Body)
})

デバッグ用に fmt していますが必須ではないので不要であれば削除してください
基本は先程定義したロジックをコールしてその結果を見て成功 or 失敗のレスポンス情報を返却してます
レスポンスを返却するようの関数はすでに swagger が生成してくれているのでそれを素直に使います

記載できたらフォーマットしてインストールしましょう
go install でビルドもされるのでバイナリが新規に作成されます

  • go fmt restapi/configure_todo_list.go && go install ./cmd/todo-list-server/

バイナリが生成できたら起動します

  • todo-list-server --host 0.0.0.0 --port=18080

P.S 20190206 解説追記

api.TodosFindTodosHandler で引数の params todos.FindTodosParams をそのまま参照せず、なんでわざわざ todos.NewFindTodosParams() し直しているかというと Since パラメータに default の定義がないからです
もしそのまま params.Since という感じでポインタ参照すると invalid memory address or nil pointer dereference になります
なので swagger.yml で

paths:
  /:
    get:
      tags:
        - todos
      operationId: findTodos
      parameters:
        - name: since
          in: query
          type: integer
          format: int64
          default: 0
        - name: limit
          in: query
          type: integer
          format: int32
          default: 20

という感じで since に default:0 を追加して swagger generate server -A TodoList -f swagger.yml し直してあげると以下のように直接 params を参照してもエラーになりません

api.TodosFindTodosHandler = todos.FindTodosHandlerFunc(func(params todos.FindTodosParams) middleware.Responder {
    return todos.NewFindTodosOK().WithPayload(allItems(*params.Since, *params.Limit))
})

go-swagger はこんな感じで引数やロジック側からのレスポンスをわざわざ正しい構造体に変換してから扱わなければいけない箇所が多いような気がします、、、

動作確認

それぞれ curl を叩けば OK です
なぞの Content-Type ヘッダがありますが、今回の swagger ファイルだとこの Content-Type が必須になります

  • curl -XPOST -H "Content-Type: application/io.goswagger.examples.todo-list.v1+json" "http://127.0.0.1:18080/v1/" -d '{"description":"test", "completed":false}'
{"description":"test","id":1}
  • curl -XGET "http://127.0.0.1:18080/v1"
[{"description":"test","id":1}]
  • curl -XPOST -H "Content-Type: application/io.goswagger.examples.todo-list.v1+json" "http://127.0.0.1:18080/v1/" -d '{"description":"test2", "completed":true}'
{"completed":true,"description":"test2","id":2}
  • curl -XGET "http://127.0.0.1:18080/v1"
[{"description":"test","id":1},{"completed":true,"description":"test2","id":2}]
  • curl -XPUT -H "Content-Type: application/io.goswagger.examples.todo-list.v1+json" "http://127.0.0.1:18080/v1/1" -d '{"description":"put test", "completed":true}'
{"completed":true,"description":"put test","id":1}
  • curl -XGET "http://127.0.0.1:18080/v1"
[{"completed":true,"description":"put test","id":1},{"completed":true,"description":"test2","id":2}]
  • curl -XDELETE -H "Content-Type: application/io.goswagger.examples.todo-list.v1+json" "http://127.0.0.1:18080/v1/1"

  • curl -XGET "http://127.0.0.1:18080/v1"

[{"completed":true,"description":"test2","id":2}]

こんな感じになれば OK です

最後に

go-swagger で実際にロジック部分を実装してみました
生成されるコードのほとんどは基本触れないでメインとなる部分だけいじればいいので簡単です
逆に言うと生成されたコードの部分は何しているさっぱりになるので、swagger の内容の理解を深めるためにコードを追ってみてもいいかもしれません

今回の実装したロジックは単純なオンメモリの情報なのでサーバを停止すると情報は消えてしまいます
なので、本来あれば DB を使ったりして実装します

その場合でも基本的な実装の流れは変わらないかなと思います

今回コードの紹介は全部だと長いので一部分とさせていただきました
基本は以下の参考サイトにあるコードを元にして作成しているので、以下を参考にするとコードの全容をイメージしやすくなるかなと思います

参考サイト