テキトウな設定ファイル(yaml,json)から、swagger specを生成して、goのstructを定義してloadしてみる

github.com

テキトウな設定ファイル(yaml,json)から、swagger specを生成して、goのstructを定義してloadしてみることにしてみた。 生成されるswagger specとgoのstruct定義は雛形っぽい感じであんまり真面目に作っていない。とりあえず雰囲気だけでも分かるようにまとめておく。

手順

手順はMakefile

default:
  json2swagger --name conf ./config.yaml > swagger.yaml
  python gen.py swagger.yaml --package main --ref "#/definitions/conf" > conf.go
  dictknife concat config.yaml -f json | go run *.go

setup:
  pip install dictknife json2swagger
  go get -v github.com/k0kubun/pp
  1. json2swaggerで設定ファイルのyamlからswagger specを生成
  2. 作った gen.pyでswagger specからgoのstruct定義を生成
  3. 実際にloadして使ってみる

詳細

設定ファイルのサンプルはテキトウにmongodbのところから持ってきた

こういうyaml

config.yaml

# from: https://docs.mongodb.com/v3.2/reference/configuration-options/
systemLog:
   destination: file
   path: "/var/log/mongodb/mongod.log"
   logAppend: true
storage:
   journal:
      enabled: true
processManagement:
   fork: true
net:
   bindIp: 127.0.0.1
   port: 27017
setParameter:
   enableLocalhostAuthBypass: false

json2swaggerで設定ファイルのyamlからswagger specを生成

これをswagger specに変換すると以下の様な感じ。

swagger.yaml

definitions:
  systemLog:
    type: object
    properties:
      destination:
        type: string
        example: file
      path:
        type: string
        example: /var/log/mongodb/mongod.log
      logAppend:
        type: boolean
        example: true
    required:
    - destination
    - path
    - logAppend
  journal:
    type: object
    properties:
      enabled:
        type: boolean
        example: true
    required:
    - enabled
  storage:
    type: object
    properties:
      journal:
        $ref: '#/definitions/journal'
    required:
    - journal
  processManagement:
    type: object
    properties:
      fork:
        type: boolean
        example: true
    required:
    - fork
  net:
    type: object
    properties:
      bindIp:
        type: string
        example: 127.0.0.1
      port:
        type: integer
        example: 27017
    required:
    - bindIp
    - port
  setParameter:
    type: object
    properties:
      enableLocalhostAuthBypass:
        type: boolean
        example: false
    required:
    - enableLocalhostAuthBypass
  conf:
    type: object
    properties:
      systemLog:
        $ref: '#/definitions/systemLog'
      storage:
        $ref: '#/definitions/storage'
      processManagement:
        $ref: '#/definitions/processManagement'
      net:
        $ref: '#/definitions/net'
      setParameter:
        $ref: '#/definitions/setParameter'
    required:
    - systemLog
    - storage
    - processManagement
    - net
    - setParameter

作った gen.pyでswagger specからgoのstruct定義を生成

ここから以下のようなgoのstructを作る。生成されるstructはdefaultは値。pointerにしたければ x-nullable=true を追加する。このあたりの変換の基準はswaggerのそれとは合っていないので注意(真面目に対応しようとするとgo-swaggerと同様にほとんどがpointerになってしまう)。

package main

// Conf :
type Conf struct {
    Storage           Storage           `json:"storage" bson:"storage"`
    ProcessManagement ProcessManagement `json:"processManagement" bson:"processManagement"`
    Net               Net               `json:"net" bson:"net"`
    SystemLog         SystemLog         `json:"systemLog" bson:"systemLog"`
    SetParameter      SetParameter      `json:"setParameter" bson:"setParameter"`
}

// Storage :
type Storage struct {
    Journal Journal `json:"journal" bson:"journal"`
}

// Journal :
type Journal struct {
    Enabled bool `json:"enabled" bson:"enabled"`
}

// ProcessManagement :
type ProcessManagement struct {
    Fork bool `json:"fork" bson:"fork"`
}

// Net :
type Net struct {
    BindIP string `json:"bindIp" bson:"bindIp"`
    Port   int64  `json:"port" bson:"port"`
}

// SystemLog :
type SystemLog struct {
    Destination string `json:"destination" bson:"destination"`
    Path        string `json:"path" bson:"path"`
    LogAppend   bool   `json:"logAppend" bson:"logAppend"`
}

// SetParameter :
type SetParameter struct {
    EnableLocalhostAuthBypass bool `json:"enableLocalhostAuthBypass" bson:"enableLocalhostAuthBypass"`
}

dictknife concat config.yaml -f jsonyamljsonに変換するために使っている。作ったmain.goはjsonにしか対応していなかったので。

package main

import (
    "encoding/json"
    "log"
    "os"
    "github.com/k0kubun/pp"
)

func main() {
    decoder := json.NewDecoder(os.Stdin)
    var conf Conf
    if err := decoder.Decode(&conf); err != nil {
        log.Fatal(err)
    }
    pp.Print(conf)
}

実際にloadして使ってみる

実際上手く読み込めて以下のような出力を返す。

$ json2swagger --name conf ./config.yaml > swagger.yaml
$ python gen.py swagger.yaml --package main --ref "#/definitions/conf" > conf.go
$ dictknife concat config.yaml -f json | go run *.go
main.Conf{
  SetParameter: main.SetParameter{
    EnableLocalhostAuthBypass: false,
  },
  Net: main.Net{
    BindIP: "127.0.0.1",
    Port:   27017,
  },
  ProcessManagement: main.ProcessManagement{
    Fork: true,
  },
  SystemLog: main.SystemLog{
    LogAppend:   true,
    Path:        "/var/log/mongodb/mongod.log",
    Destination: "file",
  },
  Storage: main.Storage{
    Journal: main.Journal{
      Enabled: true,
    },
  },
}

swagger specを生成するときにちょっとした変更を追加

--annotations オプションにjson reference(ほとんどjson pointer)と対応する追加の設定を書くとswagger specに反映させられる。

nameはschemaの名前になる。それ以外は全て階層上の属性として追加される。配列への指定は末尾に[]をつける(e.g. #/definitions/ob/foo[])。

生のconfigの値から生成した場合にschemaの名前が期待した値にならない場合がある(例えば、personという名前のschemaであって欲しいfatherとmotherがfatherという名前のschemaとして定義されてしまうなど)。それを防ぐためにnameを指定できるようにしたけれど。そう言えばdescriptionなどを追加する機会もないということに気づいたのでそれらも追加出来るようにした。あんまり考えていないので安直な対応かもしれない。

"#/conf":
  description: my config file
"#/conf/systemLog":
  name: LogSetting
"#/conf/processManagement":
  name: ProcessSetting
"#/conf/net":
  name: NetSetting
"#/conf/storage":
  name: StorageSetting

そしてswagger.yamlを再生成

--- swagger.before.yaml  2017-05-03 17:17:41.000000000 +0900
+++ swagger.yaml  2017-05-03 17:17:06.000000000 +0900
@@ -1,5 +1,5 @@
 definitions:
-  systemLog:
+  LogSetting:
     type: object
     properties:
       destination:
@@ -23,14 +23,14 @@
         example: true
     required:
     - enabled
-  storage:
+  StorageSetting:
     type: object
     properties:
       journal:
         $ref: '#/definitions/journal'
     required:
     - journal
-  processManagement:
+  ProcessSetting:
     type: object
     properties:
       fork:
@@ -38,7 +38,7 @@
         example: true
     required:
     - fork
-  net:
+  NetSetting:
     type: object
     properties:
       bindIp:
@@ -62,13 +62,13 @@
     type: object
     properties:
       systemLog:
-        $ref: '#/definitions/systemLog'
+        $ref: '#/definitions/LogSetting'
       storage:
-        $ref: '#/definitions/storage'
+        $ref: '#/definitions/StorageSetting'
       processManagement:
-        $ref: '#/definitions/processManagement'
+        $ref: '#/definitions/ProcessSetting'
       net:
-        $ref: '#/definitions/net'
+        $ref: '#/definitions/NetSetting'
       setParameter:
         $ref: '#/definitions/setParameter'
     required:
@@ -77,3 +77,4 @@
     - processManagement
     - net
     - setParameter
+    description: my config file