テキトウなyamlの値からswagger spec経由でgoのstruct定義のコードを生成してみる

テキトウなyamlの値からswagger spec経由でgoのstruct定義のコードを生成してみる。

使うのはdictknifegoaway

やること

  1. 素となるデータの例を書く(yaml)
  2. データからswagger specを生成
  3. swagger specからコードを生成

以下の様な形で変換が行われる。

config.yaml - json2swagger -> config-spec.yaml - swagger2go -> config/config.go

primitiveな状態

こういう感じ。

# need https://github.com/podhmo/{goaway,dictknife}

default:
  mkdir -p dst config
  swaggerknife json2swagger --name=config config.yaml --dst dst/config-spec.yaml
  swagger2go dst/config-spec.yaml --position=config --file=config.go --ref="#/definitions/config"
  goimports -w config/*.go

素となるデータは以下の様な形式

config.yaml

appconf:
  endpoint: http://foo.bar.jp/api
  key: hmm
  secret: hai

swagger specを生成

swagger specを生成する

$ swaggerknife json2swagger --name=config config.yaml --dst dst/config-spec.yaml

生成されたのは以下のようなもの。

config-spec.yaml

definitions:
  appconf:
    type: object
    properties:
      endpoint:
        type: string
        format: url
        example: http://foo.bar.jp/api
        x-go-type: net/url.URL
      key:
        type: string
        example: hmm
      secret:
        type: string
        example: hai
    required:
    - endpoint
    - key
    - secret
  config:
    type: object
    properties:
      appconf:
        $ref: '#/definitions/appconf'
    required:
    - appconf

goのコードの生成

以下の様な感じで実行(positionsなどを付けているのはGOPATH以下に生成しないようにするため)

$ swagger2go dst/config-spec.yaml --position=config --file=config.go --ref="#/definitions/config"

以下の様なコードが生成される。validationなどは含まれていない。

package main

// Config :
type Config struct {
    Appconf Appconf `json:"appconf"`
}

// Appconf :
type Appconf struct {
    Endpoint string `json:"endpoint"`
    Key      string `json:"key"`
    Secret   string `json:"secret"`
}

別の型(url.URL)に対応したい

別の型(url.URL)に対応したい。やろうと思えばできる。

  • json2swagger – swagger specの生成時に(format,x-go-typeを付加する)
  • swagger2go – goのコード生成時にx-go-typeを見て利用する型を変更する

swagger specの生成時に情報付加

json2swaggerの --emitter--detector というオプションで利用するオブジェクトを差し替えられる。なので以下の様なコードを用意してやる。

resolve.py

from dictknife.swaggerknife.json2swagger import Detector as _Detector
from dictknife.swaggerknife.json2swagger import Emitter as _Emitter


class Detector(_Detector):
    def resolve_type(self, value):
        if isinstance(value, str) and value.startswith(("http://", "https://")):
            return "string", "url"
        return super().resolve_type(value)


class Emitter(_Emitter):
    def make_primitive_schema(self, info):
        d = super().make_primitive_schema(info)
        if d.get("format") == "url":
            d["x-go-type"] = "net/url.URL"
        return d

そして実行時に --detector="./resolve.py:Detector" --emitter="./resolve.py:Emitter" を追加してあげる。 すると実行結果などは以下の様に変わる。

diff --git a/daily/20170617/Makefile b/daily/20170617/Makefile
index 652840b..ccd3240 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,8 @@
 # need https://github.com/podhmo/{goaway,dictknife}
+EXTRA = --detector="./resolve.py:Detector" --emitter="./resolve.py:Emitter"
 
 default:
    mkdir -p dst config
-  swaggerknife json2swagger --name=config config.yaml > dst/config-spec.yaml
+   swaggerknife json2swagger ${EXTRA} --name=config config.yaml --dst dst/config-spec.yaml
    swagger2go dst/config-spec.yaml --position=config --file=config.go --ref="#/definitions/config"
    goimports -w config/*.go
diff --git a/daily/20170617/dst/config-spec.yaml b/daily/20170617/dst/config-spec.yaml
index 112e1f3..e29fb54 100644
--- a/daily/20170617/dst/config-spec.yaml
+++ b/daily/20170617/dst/config-spec.yaml
@@ -4,7 +4,9 @@ definitions:
     properties:
       endpoint:
         type: string
+        format: url
         example: http://foo.bar.jp/api
+        x-go-type: net/url.URL
       key:
         type: string
         example: hmm
diff --git a/config/config.go b/config/config.go
index 9a00a57..f51bfe8 100644
--- a/config/config.go
+++ b/config/config.go
@@ -1,5 +1,9 @@
 package main
 
+import (
+   "net/url"
+)
+
 // Config :
 type Config struct {
    Appconf Appconf `json:"appconf"`
@@ -7,7 +11,7 @@ type Config struct {
 
 // Appconf :
 type Appconf struct {
-  Endpoint string `json:"endpoint"`
-  Key      string `json:"key"`
-  Secret   string `json:"secret"`
+   Endpoint url.URL `json:"endpoint"`
+   Key      string  `json:"key"`
+   Secret   string  `json:"secret"`
 }

swagger2goは x-go-type というkeyの情報が存在したらそちらを使うようになっている。なのでnet/urlのURLを使うようなコードが生成される。 おしまい。

goのコードを生成すること自体のは記事は昔書いていた