goのコードを解析してgoのコードを生成するpythonのコードを書いてみる

この記事は [WACUL Advent Calendar 2016](http://qiita.com/advent-calendar/2016/wacul) 14日目の記事です。
書いてる本人は今年の9月からWACULで働いていて、主にバックエンドでgoのコードを書いていたり時々pythonを書いていたりしています。

この記事は[前回の記事](http://qiita.com/podhmo/items/a12952b07648e42911b4)の続きです(前回の記事は無駄に長いので無理に読まなくても良いです)。

前回は[JSON-to-Go](https://mholt.github.io/json-to-go/)の真似をしてAPIのresponse例からgoのコードを生成するpythonのコードを書きました。今回はgoのコードを解析した結果を利用してgoのコードを生成したいと思います。

## はじめに

### 変換処理の自動生成

例えば、[go-swagger](https://github.com/go-swagger/go-swagger)など使うとresponse用のstructの定義が生成されます。これは内部のdatabaseに格納されるstructとは微妙に異なりますがだいたいのところ同じような構造のstructです。

実際のweb APIの作成時には、databaseに永続化されているstructから、このresponse用のstructへの変換が必要になる事があります。これがわりとつまらなくてだるいですね。この部分のコードを自動生成したいという話です。

### 概要

もう少し話を整理すると、欲しいものはmodel(src)からresponseオブジェクト(dst)への変換コードです。これを得るために変換元や変換先のstruct定義を解析して型の情報を取り出します。このあたりはgoはastパッケージなどを使って気合で作ります。作ったツールで型情報を抽出した後JSONとして出力します。このJSONを利用してsrcからdstへの変換コードを生成しようという企みです。

## sample: github api

テキトウな例を使ってコード生成の実行例を紹介したいと思います。

本来は、永続化層の表現をresponseの表現に変換する処理を生成しようという話になるところですが、面倒なので、少し恣意的ですが、以下の様な形で変換処理のコードの自動生成を試したいと思います。

1. テキトウなAPIのresponseの例からstructを生成([前回の記事で作成したコードを利用](http://qiita.com/podhmo/items/a12952b07648e42911b4))
2. テキトウなswaggerのyamlからstructを生成([go-swagger](https://github.com/go-swagger/go-swagger)を利用)

これらで生成された２つのstructのgoコードをそれぞれ永続化のためのmodelとapiのresponse表現のstructと見立てて、srcからdstに変換する処理を自動生成をしてみたいと思います。

自動生成自体は以下の様な手順で作成します。

1. 変換元のgoのstruct定義から情報を抽出 `extract :: go[src] => JSON[src]`
2. 変換先のgoのstruct定義から情報を抽出 `extract :: go[dst] => JSON[dst]`
3. 抽出した情報からgoのコードを生成 `convert :: JSON[src], json[dst] => go[convert]`

この2つを利用して変換元(src)のstructから変換先(dst)のstructへの変換処理のコードを生成します。

### goのコード(src)の生成

github のdocumentの以下の部分から認証済みユーザーを取得するapiのresponse例を取ってきます。特にこのAPIを選んだ理由はありません。なんとなく目についたからただそれだけです。

- [github user api get the authenticated user response](https://developer.github.com/v3/users/#get-the-authenticated-user)

github-get-authenticated-user.jsonという名前で保存しておきます。

```json
{
  "login": "octocat",
  "id": 1,
  "avatar_url": "https://github.com/images/error/octocat_happy.gif",
  "gravatar_id": "",
  "url": "https://api.github.com/users/octocat",
  "html_url": "https://github.com/octocat",
  "followers_url": "https://api.github.com/users/octocat/followers",
  "following_url": "https://api.github.com/users/octocat/following{/other_user}",
  "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
  "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
  "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
  "organizations_url": "https://api.github.com/users/octocat/orgs",
  "repos_url": "https://api.github.com/users/octocat/repos",
  "events_url": "https://api.github.com/users/octocat/events{/privacy}",
  "received_events_url": "https://api.github.com/users/octocat/received_events",
  "type": "User",
  "site_admin": false,
  "name": "monalisa octocat",
  "company": "GitHub",
  "blog": "https://github.com/blog",
  "location": "San Francisco",
  "email": "octocat@github.com",
  "hireable": false,
  "bio": "There once was...",
  "public_repos": 2,
  "public_gists": 1,
  "followers": 20,
  "following": 0,
  "created_at": "2008-01-14T04:33:35Z",
  "updated_at": "2008-01-14T04:33:35Z",
  "total_private_repos": 100,
  "owned_private_repos": 100,
  "private_gists": 81,
  "disk_usage": 10000,
  "collaborators": 8,
  "plan": {
    "name": "Medium",
    "space": 400,
    "private_repos": 20,
    "collaborators": 0
  }
}
```

ここからstructの定義をを生成します。[前回の記事で作ったスクリプト](https://github.com/podhmo/advent2016/blob/master/src/jsontogo/jsontogo.py)を使います。

```bash
$ swagger_src
mkdir -p src/github
python src/jsontogo/jsontogo.py json/github-get-authenticated-user.json --package github --name AuthenticatedUser | gofmt > src/github/authenticated_user.go
```

以下のようなstructの定義が手に入ります。(src/github/authenticated_user.goとして保存しておきます)

```go
package github

import (
	"time"
)

/* structure
AuthenticatedUser
	Plan
*/
// AuthenticatedUser : auto generated JSON container
type AuthenticatedUser struct {
	AvatarURL         string    `json:"avatar_url" example:"https://github.com/images/error/octocat_happy.gif"`
	Bio               string    `json:"bio" example:"There once was..."`
	Blog              string    `json:"blog" example:"https://github.com/blog"`
	Collaborators     int       `json:"collaborators" example:"8"`
	Company           string    `json:"company" example:"GitHub"`
	CreatedAt         time.Time `json:"created_at" example:"2008-01-14T04:33:35Z"`
	DiskUsage         int       `json:"disk_usage" example:"10000"`
	Email             string    `json:"email" example:"octocat@github.com"`
	EventsURL         string    `json:"events_url" example:"https://api.github.com/users/octocat/events{/privacy}"`
	Followers         int       `json:"followers" example:"20"`
	FollowersURL      string    `json:"followers_url" example:"https://api.github.com/users/octocat/followers"`
	Following         int       `json:"following" example:"0"`
	FollowingURL      string    `json:"following_url" example:"https://api.github.com/users/octocat/following{/other_user}"`
	GistsURL          string    `json:"gists_url" example:"https://api.github.com/users/octocat/gists{/gist_id}"`
	GravatarID        string    `json:"gravatar_id" example:""`
	HTMLURL           string    `json:"html_url" example:"https://github.com/octocat"`
	Hireable          bool      `json:"hireable" example:"False"`
	ID                int       `json:"id" example:"1"`
	Location          string    `json:"location" example:"San Francisco"`
	Login             string    `json:"login" example:"octocat"`
	Name              string    `json:"name" example:"monalisa octocat"`
	OrganizationsURL  string    `json:"organizations_url" example:"https://api.github.com/users/octocat/orgs"`
	OwnedPrivateRepos int       `json:"owned_private_repos" example:"100"`
	Plan              Plan      `json:"plan"`
	PrivateGists      int       `json:"private_gists" example:"81"`
	PublicGists       int       `json:"public_gists" example:"1"`
	PublicRepos       int       `json:"public_repos" example:"2"`
	ReceivedEventsURL string    `json:"received_events_url" example:"https://api.github.com/users/octocat/received_events"`
	ReposURL          string    `json:"repos_url" example:"https://api.github.com/users/octocat/repos"`
	SiteAdmin         bool      `json:"site_admin" example:"False"`
	StarredURL        string    `json:"starred_url" example:"https://api.github.com/users/octocat/starred{/owner}{/repo}"`
	SubscriptionsURL  string    `json:"subscriptions_url" example:"https://api.github.com/users/octocat/subscriptions"`
	TotalPrivateRepos int       `json:"total_private_repos" example:"100"`
	Type              string    `json:"type" example:"User"`
	URL               string    `json:"url" example:"https://api.github.com/users/octocat"`
	UpdatedAt         time.Time `json:"updated_at" example:"2008-01-14T04:33:35Z"`
}

// Plan : auto generated JSON container
type Plan struct {
	Collaborators int    `json:"collaborators" example:"0"`
	Name          string `json:"name" example:"Medium"`
	PrivateRepos  int    `json:"private_repos" example:"20"`
	Space         int    `json:"space" example:"400"`
}
```

これで変換元(src)の方のstruct定義が手に入りました。

### goのコード(dst)の生成

次は変換先(dst)の方のstruct定義を手に入れましょう。swaggerの定義からgo-swaggerを使ってのgoのstruct定義を手に入れます。

#### api.gru

[api.gru](https://apis.guru/)というサービスがあります。これは、世の中にあるwebサービスのAPI specを共有してくれているサービスです。多くはOpenAPI/Swagger2.0の形式のものが提供されています。ちなみにここで取得できるswagger.yamlを[ReDoc](https://rebilly.github.io/ReDoc/)などを使って見てみるとswaggerがどんなものかなんとなく分かって良いと思います。

![api.gru](https://raw.githubusercontent.com/podhmo/advent2016/master/img/api.gru-mini.png)

試しにgithubのAPIを例に自動生成を試してみましょう。以下のURLからswagger定義のyamlをダウンロードしてきます。

- https://api.apis.guru/v2/specs/github.com/v3/swagger.yaml

```bash
$ make swagger_fetch
mkdir -p yaml
wget https://api.apis.guru/v2/specs/github.com/v3/swagger.yaml -O yaml/github-swagger.yaml
gsed -i "s/type: *'null'/type: object/g; s/'+1':/'plus1':/g; s/'-1':/'minus1':/" yaml/github-swagger.yaml
```

[リンク先の様なyaml](https://github.com/podhmo/advent2016/blob/master/yaml/github-swagger.yaml)が手に入ります。wgetの後のsedは現状のgo-swaggerの問題や登録されているswagger.yamlの問題に対応するためのちょっとした修正です。


## struct定義から型情報抽出

こちらは普通にgo-swaggerを実行するだけです。以下の様にして実行します。今回はstructの変換だけなのでmodelのみを生成します。本筋とは関係ないのですがなぜか`"github.com/go-openapi/swag"`のimportが出力されないという問題があったためgoimportsで追加しています。

```bash
$ make swagger_gen
rm -rf dst/swagger/gen
mkdir -p dst/swagger/gen
swagger generate model -f yaml/github-swagger.yaml --target dst/swagger/gen --model-package def
goimports -w dst/swagger/gen/def/*.go
```

たくさんのコードが生成されますが今回気にするのはUser.goだけです。

```go
package def

// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command

import (
	strfmt "github.com/go-openapi/strfmt"
	"github.com/go-openapi/swag"

	"github.com/go-openapi/errors"
)

// User user
// swagger:model user
type User struct {

	// avatar url
	AvatarURL string `json:"avatar_url,omitempty"`

	// bio
	Bio string `json:"bio,omitempty"`

	// blog
	Blog string `json:"blog,omitempty"`

	// collaborators
	Collaborators int64 `json:"collaborators,omitempty"`

	// company
	Company string `json:"company,omitempty"`

	// ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ
	CreatedAt string `json:"created_at,omitempty"`

	// disk usage
	DiskUsage int64 `json:"disk_usage,omitempty"`

	// email
	Email string `json:"email,omitempty"`

	// followers
	Followers int64 `json:"followers,omitempty"`

	// following
	Following int64 `json:"following,omitempty"`

	// gravatar id
	GravatarID string `json:"gravatar_id,omitempty"`

	// hireable
	Hireable bool `json:"hireable,omitempty"`

	// html url
	HTMLURL string `json:"html_url,omitempty"`

	// id
	ID int64 `json:"id,omitempty"`

	// location
	Location string `json:"location,omitempty"`

	// login
	Login string `json:"login,omitempty"`

	// name
	Name string `json:"name,omitempty"`

	// owned private repos
	OwnedPrivateRepos int64 `json:"owned_private_repos,omitempty"`

	// plan
	Plan *UserPlan `json:"plan,omitempty"`

	// private gists
	PrivateGists int64 `json:"private_gists,omitempty"`

	// public gists
	PublicGists int64 `json:"public_gists,omitempty"`

	// public repos
	PublicRepos int64 `json:"public_repos,omitempty"`

	// total private repos
	TotalPrivateRepos int64 `json:"total_private_repos,omitempty"`

	// type
	Type string `json:"type,omitempty"`

	// url
	URL string `json:"url,omitempty"`
}

// Validate validates this user
func (m *User) Validate(formats strfmt.Registry) error {
	var res []error

	if err := m.validatePlan(formats); err != nil {
		// prop
		res = append(res, err)
	}

	if len(res) > 0 {
		return errors.CompositeValidationError(res...)
	}
	return nil
}

func (m *User) validatePlan(formats strfmt.Registry) error {

	if swag.IsZero(m.Plan) { // not required
		return nil
	}

	if m.Plan != nil {

		if err := m.Plan.Validate(formats); err != nil {
			return err
		}
	}

	return nil
}

// UserPlan user plan
// swagger:model UserPlan
type UserPlan struct {

	// collaborators
	Collaborators int64 `json:"collaborators,omitempty"`

	// name
	Name string `json:"name,omitempty"`

	// private repos
	PrivateRepos int64 `json:"private_repos,omitempty"`

	// space
	Space int64 `json:"space,omitempty"`
}

// Validate validates this user plan
func (m *UserPlan) Validate(formats strfmt.Registry) error {
	var res []error

	if len(res) > 0 {
		return errors.CompositeValidationError(res...)
	}
	return nil
}
```

### structからの型情報の抽出

なにはなくともstruct定義のgoコードから型情報を取り出さなくてはいけません。この辺は気合で頑張りましょう。astと気合があればどうにかなります。[go-structjson](https://github.com/podhmo/go-structjson)というコマンドを作りました。雑な作りなのであまり中のコードの品質には期待しないでください。

以下のようなコードから

```go
package alias

// Person :
type Person struct {
	Name string
}

type P *Person
type PS []Person
type PS2 []P
type PSP *[]P
```

以下のようなJSONを出力します。

```json
{
  "module": {
    "alias": {
      "file": {
        "GOPATH/src/github.com/podhmo/go-structjson/examples/alias/person.go": {
          "alias": {
            "P": {
              "candidates": null,
              "name": "P",
              "original": {
                "kind": "pointer",
                "value": {
                  "kind": "primitive",
                  "value": "Person"
                }
              }
            },
            "PS": {
              "candidates": null,
              "name": "PS",
              "original": {
                "kind": "array",
                "value": {
                  "kind": "primitive",
                  "value": "Person"
                }
              }
            },
            "PS2": {
              "candidates": null,
              "name": "PS2",
              "original": {
                "kind": "array",
                "value": {
                  "kind": "primitive",
                  "value": "P"
                }
              }
            },
            "PSP": {
              "candidates": null,
              "name": "PSP",
              "original": {
                "kind": "pointer",
                "value": {
                  "kind": "array",
                  "value": {
                    "kind": "primitive",
                    "value": "P"
                  }
                }
              }
            }
          },
          "name": "GOPATH/src/github.com/podhmo/go-structjson/examples/alias/person.go",
          "struct": {
            "Person": {
              "fields": {
                "Name": {
                  "embed": false,
                  "name": "Name",
                  "tags": {},
                  "type": {
                    "kind": "primitive",
                    "value": "string"
                  }
                }
              },
              "name": "Person"
            }
          }
        }
      },
      "fullname": "github.com/podhmo/go-structjson/examples/alias",
      "name": "alias"
    }
  }
}
```

aliasという名前で扱っているものはnewtypeという名前の方が適切だという話があったりもしましたが。ここでは`type MyInt int`などはtype aliasと呼んでいます。ごめんなさい。

それぞれのsrc,dstに対してJSONファイルを出力してやります。

```
$ make swagger_extract
mkdir -p dst/swagger/convert
mkdir -p json/extracted
go-structjson -target src/github > json/extracted/src.json
go-structjson -target dst/swagger/gen/def > json/extracted/dst.json
```

### 抽出された型情報のJSONから変換用のコードの生成

さて変換元と変換先のstructのコードは手に入れる事ができました。次は変換処理の自動生成です。この時のために[goconvert](https://github.com/podhmo/goconvert)というライブラリを作っています。作り途中のことがreadmeにtodoしか書かれていないところにも現れています。とりあえずこれを使います。実際にはこのライブラリを使って作った[convert.py](https://github.com/podhmo/advent2016/blob/master/src/convert.py)を使ってsrcからdstへの変換用のコードを生成します。

```bash
$make swagger_convert
mkdir -p dst/swagger/convert
python src/convert.py --logger=DEBUG --src json/extracted/src.json --dst json/extracted/dst.json --override json/extracted/convert.json > dst/swagger/convert/autogen.go || rm dst/swagger/convert/autogen.go
INFO:goconvert.builders:start register function AuthenticatedUserToUser: ['github.com/podhmo/advent2016/src/github.AuthenticatedUser'] -> ['github.com/podhmo/advent2016/dst/swagger/gen/def.User']
DEBUG:goconvert.builders:resolve: avatarurl ('string',) -> avatarurl ('string',)
DEBUG:goconvert.minicode:gencode: ('string',) -> ('string',)
...
NotImplementedError: mapping not found ('time.Time',) -> ('string',)
```

おや。上手くいかないですね。

`time.Time` から `string` のコードを生成できないようです。中を見てみるとCreatedAtの扱いで困っているようでした。

srcの方(api responseから生成)

```go
type AuthenticatedUser struct {
	AvatarURL         string    `json:"avatar_url" example:"https://github.com/images/error/octocat_happy.gif"`
...
	CreatedAt         time.Time `json:"created_at" example:"2008-01-14T04:33:35Z"`
```

dstの方(swagger.yamlから生成)

```go
// User user
// swagger:model user
type User struct {

	// avatar url
	AvatarURL string `json:"avatar_url,omitempty"`

...
	// ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ
	CreatedAt string `json:"created_at,omitempty"`
```

まぁたしかにこれはある意味当然で仕方がないことです。goの型の情報をどんなに辿ってみてもtime.Timeからstringの変換はどのように行えば良いかわからないですね。分かるのは開発者だけです。変換処理の出力先と同じのパッケージの中にprimitive.goとして以下のようなtime.Timeからstringへの変換処理を書いてあげます。

```go
package convert

import "time"

// TimeToString :
func TimeToString(t time.Time) string {
	return t.Format(time.RFC3339)
}
```

そしてJSONの出力をやり直します。今回はgo-funcjsonも追加しています(go-funcjsonで生成したJSONを渡すことで手書きした変換処理の関数はそのまま自動生成の時に使われる様になります)。

```bash
$ make swagger_extract
mkdir -p dst/swagger/convert
mkdir -p json/extracted
go-structjson -target src/github > json/extracted/src.json
go-structjson -target dst/swagger/gen/def > json/extracted/dst.json
go-funcjson -target dst/swagger/convert > json/extracted/convert.json || echo '{}' > json/extracted/convert.json
...
-- write: convert --
gofmt -w dst/swagger/convert/autogen.go
```

今回は成功しました以下の様なコードが生成されています(dst/swagger/convert/autogen.go)。

```go
package convert

import (
	def "github.com/podhmo/advent2016/dst/swagger/gen/def"
	github "github.com/podhmo/advent2016/src/github"
)

// AuthenticatedUserToUser :
func AuthenticatedUserToUser(from *github.AuthenticatedUser) *def.User {
	to := &def.User{}
	to.AvatarURL = from.AvatarURL
	to.Bio = from.Bio
	to.Blog = from.Blog
	tmp1 := (int64)(from.Collaborators)
	to.Collaborators = tmp1
	to.Company = from.Company
	to.CreatedAt = TimeToString(from.CreatedAt)
	tmp2 := (int64)(from.DiskUsage)
	to.DiskUsage = tmp2
	to.Email = from.Email
	tmp3 := (int64)(from.Followers)
	to.Followers = tmp3
	tmp4 := (int64)(from.Following)
	to.Following = tmp4
	to.GravatarID = from.GravatarID
	to.Hireable = from.Hireable
	to.HTMLURL = from.HTMLURL
	tmp5 := (int64)(from.ID)
	to.ID = tmp5
	to.Location = from.Location
	to.Login = from.Login
	to.Name = from.Name
	tmp6 := (int64)(from.OwnedPrivateRepos)
	to.OwnedPrivateRepos = tmp6
	to.Plan = PlanToUserPlan(&(from.Plan))
	tmp7 := (int64)(from.PrivateGists)
	to.PrivateGists = tmp7
	tmp8 := (int64)(from.PublicGists)
	to.PublicGists = tmp8
	tmp9 := (int64)(from.PublicRepos)
	to.PublicRepos = tmp9
	tmp10 := (int64)(from.TotalPrivateRepos)
	to.TotalPrivateRepos = tmp10
	to.Type = from.Type
	to.URL = from.URL
	return to
}

// PlanToUserPlan :
func PlanToUserPlan(from *github.Plan) *def.UserPlan {
	to := &def.UserPlan{}
	tmp11 := (int64)(from.Collaborators)
	to.Collaborators = tmp11
	to.Name = from.Name
	tmp12 := (int64)(from.PrivateRepos)
	to.PrivateRepos = tmp12
	tmp13 := (int64)(from.Space)
	to.Space = tmp13
	return to
}
```

primitive.goで追加した `TimeToString()` も `CreatedAt` の部分で使われてますね。コンパイルは通るでしょうか。

```bash
$ (cd dst/swagger/convert/; go install)
```

大丈夫そうです。

### 実際に変換処理を実行してみる

生成した変換処理のコードを実際に使ってみましょう。以下の様なコードを実行します。

```go
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"os"

	"github.com/davecgh/go-spew/spew"
	"github.com/podhmo/advent2016/dst/swagger/convert"
	"github.com/podhmo/advent2016/src/github"
)

func main() {
	decoder := json.NewDecoder(os.Stdin)
	var from github.AuthenticatedUser
	if err := decoder.Decode(&from); err != nil {
		log.Fatal(err)
	}

	{
		fmt.Println("------------------------------")
		fmt.Println("source:")
		fmt.Println("------------------------------")
		spew.Dump(from)
	}
	{
		to := convert.AuthenticatedUserToUser(&from)
		fmt.Println("------------------------------")
		fmt.Println("destination:")
		fmt.Println("------------------------------")
		spew.Dump(to)
	}
}
```

上手く変換処理のコードが生成できていればコンパイルは通りますし。実行できるはずです。面倒なので元々ダウンロードしたgithub apiのresponseを入力に渡してみましょう。

```bash
$ make swagger_run1
cat json/github-get-authenticated-user.json | go run dst/swagger/main/spew/*.go
------------------------------
source:
------------------------------
(github.AuthenticatedUser) {
 AvatarURL: (string) (len=49) "https://github.com/images/error/octocat_happy.gif",
 Bio: (string) (len=17) "There once was...",
 Blog: (string) (len=23) "https://github.com/blog",
 Collaborators: (int) 8,
 Company: (string) (len=6) "GitHub",
 CreatedAt: (time.Time) 2008-01-14 04:33:35 +0000 UTC,
 DiskUsage: (int) 10000,
 Email: (string) (len=18) "octocat@github.com",
 EventsURL: (string) (len=53) "https://api.github.com/users/octocat/events{/privacy}",
 Followers: (int) 20,
 FollowersURL: (string) (len=46) "https://api.github.com/users/octocat/followers",
 Following: (int) 0,
 FollowingURL: (string) (len=59) "https://api.github.com/users/octocat/following{/other_user}",
 GistsURL: (string) (len=52) "https://api.github.com/users/octocat/gists{/gist_id}",
 GravatarID: (string) "",
 HTMLURL: (string) (len=26) "https://github.com/octocat",
 Hireable: (bool) false,
 ID: (int) 1,
 Location: (string) (len=13) "San Francisco",
 Login: (string) (len=7) "octocat",
 Name: (string) (len=16) "monalisa octocat",
 OrganizationsURL: (string) (len=41) "https://api.github.com/users/octocat/orgs",
 OwnedPrivateRepos: (int) 100,
 Plan: (github.Plan) {
  Collaborators: (int) 0,
  Name: (string) (len=6) "Medium",
  PrivateRepos: (int) 20,
  Space: (int) 400
 },
 PrivateGists: (int) 81,
 PublicGists: (int) 1,
 PublicRepos: (int) 2,
 ReceivedEventsURL: (string) (len=52) "https://api.github.com/users/octocat/received_events",
 ReposURL: (string) (len=42) "https://api.github.com/users/octocat/repos",
 SiteAdmin: (bool) false,
 StarredURL: (string) (len=59) "https://api.github.com/users/octocat/starred{/owner}{/repo}",
 SubscriptionsURL: (string) (len=50) "https://api.github.com/users/octocat/subscriptions",
 TotalPrivateRepos: (int) 100,
 Type: (string) (len=4) "User",
 URL: (string) (len=36) "https://api.github.com/users/octocat",
 UpdatedAt: (time.Time) 2008-01-14 04:33:35 +0000 UTC
}
------------------------------
destination:
------------------------------
(*def.User)(0xc4200ae140)({
 AvatarURL: (string) (len=49) "https://github.com/images/error/octocat_happy.gif",
 Bio: (string) (len=17) "There once was...",
 Blog: (string) (len=23) "https://github.com/blog",
 Collaborators: (int64) 8,
 Company: (string) (len=6) "GitHub",
 CreatedAt: (string) (len=20) "2008-01-14T04:33:35Z",
 DiskUsage: (int64) 10000,
 Email: (string) (len=18) "octocat@github.com",
 Followers: (int64) 20,
 Following: (int64) 0,
 GravatarID: (string) "",
 Hireable: (bool) false,
 HTMLURL: (string) (len=26) "https://github.com/octocat",
 ID: (int64) 1,
 Location: (string) (len=13) "San Francisco",
 Login: (string) (len=7) "octocat",
 Name: (string) (len=16) "monalisa octocat",
 OwnedPrivateRepos: (int64) 100,
 Plan: (*def.UserPlan)(0xc4204da1b0)({
  Collaborators: (int64) 0,
  Name: (string) (len=6) "Medium",
  PrivateRepos: (int64) 20,
  Space: (int64) 400
 }),
 PrivateGists: (int64) 81,
 PublicGists: (int64) 1,
 PublicRepos: (int64) 2,
 TotalPrivateRepos: (int64) 100,
 Type: (string) (len=4) "User",
 URL: (string) (len=36) "https://api.github.com/users/octocat"
})
```

上手くいっていそうです。(ところで全く構造が同じものの場合は `json.Marshal` のあとの `json.Unmarshal` でJSONを経由して値を変換した方が簡単かもしれません)

変換に使ったコマンドなどのMakefileは[こちら](https://github.com/podhmo/advent2016/blob/master/Makefile)です。

## さらにつづき 既存の型が更新された場合の話

実はこれまでのコード変換で使ってきたコードのsrcの方は前回の記事と同様といっておきながら完全に同様ではありません。[前回のコードの](https://github.com/podhmo/advent2016/blob/master/src/jsontogo/jsontogo.py)以下の部分をコメントアウトしていたのでした。

```python
        # elif "://" in val:
        #     return "github.com/go-openapi/strfmt.Uri"
```

このコメントアウトを外して再度生成してみます。

```bash
$ make swagger_src
...
$ make swagger_extract
...
$ make swagger_convert
...
KeyError: 'Uri'
```

おや失敗してしまいました。実はこれは以前の記事を書いた時から使っていてたライブラリの [github.com/go-openapi/strfmt](https://github.com/go-openapi/strfmt)が更新されているせいでした。具体的には `strfmt.Uri` が `strfmt.URI` に変わったようです。

updateし直してから再度生成してみましょう。

```bash
$ go get -u github.com/go-openapi/strfmt
$ gsed -i 's/strfmt.Uri/strfmt.URI/' src/jsontogo/jsontogo.py
$ make swagger_src
$ make swagger_extract
$ make swagger_convert
```

今回は成功したようです。もちろん変換も実行できます。

```
$ make swagger_run1
cat json/github-get-authenticated-user.json | go run dst/swagger/main/spew/*.go
------------------------------
source:
------------------------------
(github.AuthenticatedUser) {
 AvatarURL: (strfmt.URI) (len=49) https://github.com/images/error/octocat_happy.gif,
 Bio: (string) (len=17) "There once was...",
 Blog: (strfmt.URI) (len=23) https://github.com/blog,
 Collaborators: (int) 8,
 Company: (string) (len=6) "GitHub",
 CreatedAt: (time.Time) 2008-01-14 04:33:35 +0000 UTC,
 DiskUsage: (int) 10000,
 Email: (string) (len=18) "octocat@github.com",
 EventsURL: (strfmt.URI) (len=53) https://api.github.com/users/octocat/events{/privacy},
 Followers: (int) 20,
 FollowersURL: (strfmt.URI) (len=46) https://api.github.com/users/octocat/followers,
 Following: (int) 0,
 FollowingURL: (strfmt.URI) (len=59) https://api.github.com/users/octocat/following{/other_user},
 GistsURL: (strfmt.URI) (len=52) https://api.github.com/users/octocat/gists{/gist_id},
 GravatarID: (string) "",
 HTMLURL: (strfmt.URI) (len=26) https://github.com/octocat,
 Hireable: (bool) false,
 ID: (int) 1,
 Location: (string) (len=13) "San Francisco",
 Login: (string) (len=7) "octocat",
 Name: (string) (len=16) "monalisa octocat",
 OrganizationsURL: (strfmt.URI) (len=41) https://api.github.com/users/octocat/orgs,
 OwnedPrivateRepos: (int) 100,
 Plan: (github.Plan) {
  Collaborators: (int) 0,
  Name: (string) (len=6) "Medium",
  PrivateRepos: (int) 20,
  Space: (int) 400
 },
 PrivateGists: (int) 81,
 PublicGists: (int) 1,
 PublicRepos: (int) 2,
 ReceivedEventsURL: (strfmt.URI) (len=52) https://api.github.com/users/octocat/received_events,
 ReposURL: (strfmt.URI) (len=42) https://api.github.com/users/octocat/repos,
 SiteAdmin: (bool) false,
 StarredURL: (strfmt.URI) (len=59) https://api.github.com/users/octocat/starred{/owner}{/repo},
 SubscriptionsURL: (strfmt.URI) (len=50) https://api.github.com/users/octocat/subscriptions,
 TotalPrivateRepos: (int) 100,
 Type: (string) (len=4) "User",
 URL: (strfmt.URI) (len=36) https://api.github.com/users/octocat,
 UpdatedAt: (time.Time) 2008-01-14 04:33:35 +0000 UTC
}
------------------------------
destination:
------------------------------
(*def.User)(0xc42015e280)({
 AvatarURL: (string) (len=49) "https://github.com/images/error/octocat_happy.gif",
 Bio: (string) (len=17) "There once was...",
 Blog: (string) (len=23) "https://github.com/blog",
 Collaborators: (int64) 8,
 Company: (string) (len=6) "GitHub",
 CreatedAt: (string) (len=20) "2008-01-14T04:33:35Z",
 DiskUsage: (int64) 10000,
 Email: (string) (len=18) "octocat@github.com",
 Followers: (int64) 20,
 Following: (int64) 0,
 GravatarID: (string) "",
 Hireable: (bool) false,
 HTMLURL: (string) (len=26) "https://github.com/octocat",
 ID: (int64) 1,
 Location: (string) (len=13) "San Francisco",
 Login: (string) (len=7) "octocat",
 Name: (string) (len=16) "monalisa octocat",
 OwnedPrivateRepos: (int64) 100,
 Plan: (*def.UserPlan)(0xc420330420)({
  Collaborators: (int64) 0,
  Name: (string) (len=6) "Medium",
  PrivateRepos: (int64) 20,
  Space: (int64) 400
 }),
 PrivateGists: (int64) 81,
 PublicGists: (int64) 1,
 PublicRepos: (int64) 2,
 TotalPrivateRepos: (int64) 100,
 Type: (string) (len=4) "User",
 URL: (string) (len=36) "https://api.github.com/users/octocat"
})
```

## おわりに

今回はgoのコードを解析して、特定のstructから別のstructへの変換のコードを生成してみました。ちなみに面倒だったので詳しい内部の話を書くのは止めました。コード生成にはpythonを使いましたがgoで完結した方が綺麗ですし便利かもしれません。ポインタの解析部分だとかalias(newtype)の扱いだとかslicesに対応したコードだとか変換の対応が見つからない場合にどうするかなど話す気力があれば話せるトピックが幾つかあるので気が向いたら書くかもしれません。

## おまけ

生成されたコードに勢いがないのでおまけを追加します。いくつかsrcの方の型定義をいじってpointerにするなど意地悪をしてみます。

```diff
diff --git a/src/github/authenticated_user.go b/src/github/authenticated_user.go
index 8893144..7e04142 100644
--- a/src/github/authenticated_user.go
+++ b/src/github/authenticated_user.go
@@ -11,8 +11,8 @@ AuthenticatedUser
 */
 // AuthenticatedUser : auto generated JSON container
 type AuthenticatedUser struct {
-	AvatarURL         strfmt.URI `json:"avatar_url" example:"https://github.com/images/error/octocat_happy.gif"`
-	Bio               string     `json:"bio" example:"There once was..."`
+	AvatarURL         *******strfmt.URI `json:"avatar_url" example:"https://github.com/images/error/octocat_happy.gif"`
+	Bio               *********string     `json:"bio" example:"There once was..."`
 	Blog              strfmt.URI `json:"blog" example:"https://github.com/blog"`
 	Collaborators     int        `json:"collaborators" example:"8"`
 	Company           string     `json:"company" example:"GitHub"`
@@ -35,10 +35,10 @@ type AuthenticatedUser struct {
 	OrganizationsURL  strfmt.URI `json:"organizations_url" example:"https://api.github.com/users/octocat/orgs"`
 	OwnedPrivateRepos int        `json:"owned_private_repos" example:"100"`
 	Plan              Plan       `json:"plan"`
-	PrivateGists      int        `json:"private_gists" example:"81"`
-	PublicGists       int        `json:"public_gists" example:"1"`
-	PublicRepos       int        `json:"public_repos" example:"2"`
-	ReceivedEventsURL strfmt.URI `json:"received_events_url" example:"https://api.github.com/users/octocat/received_events"`
+	PrivateGists      *int        `json:"private_gists" example:"81"`
+	PublicGists       **int        `json:"public_gists" example:"1"`
+	PublicRepos       ***int        `json:"public_repos" example:"2"`
+	ReceivedEventsURL ****strfmt.URI `json:"received_events_url" example:"https://api.github.com/users/octocat/received_events"`
 	ReposURL          strfmt.URI `json:"repos_url" example:"https://api.github.com/users/octocat/repos"`
 	SiteAdmin         bool       `json:"site_admin" example:"False"`
 	StarredURL        strfmt.URI `json:"starred_url" example:"https://api.github.com/users/octocat/starred{/owner}{/repo}"`
```

生成されたコードは以下の様になりますし。まともに動くようです。考えてみれば今回の例にはslicesもなかったですね。

```go
package convert

import (
	def "github.com/podhmo/advent2016/dst/swagger/gen/def"
	github "github.com/podhmo/advent2016/src/github"
)

// AuthenticatedUserToUser :
func AuthenticatedUserToUser(from *github.AuthenticatedUser) *def.User {
	to := &def.User{}
	if from.AvatarURL != nil {
		tmp1 := *(from.AvatarURL)
		if tmp1 != nil {
			tmp2 := *(tmp1)
			if tmp2 != nil {
				tmp3 := *(tmp2)
				if tmp3 != nil {
					tmp4 := *(tmp3)
					if tmp4 != nil {
						tmp5 := *(tmp4)
						if tmp5 != nil {
							tmp6 := *(tmp5)
							if tmp6 != nil {
								tmp7 := *(tmp6)
								tmp8 := (string)(tmp7)
								to.AvatarURL = tmp8
							}
						}
					}
				}
			}
		}
	}
	if from.Bio != nil {
		tmp9 := *(from.Bio)
		if tmp9 != nil {
			tmp10 := *(tmp9)
			if tmp10 != nil {
				tmp11 := *(tmp10)
				if tmp11 != nil {
					tmp12 := *(tmp11)
					if tmp12 != nil {
						tmp13 := *(tmp12)
						if tmp13 != nil {
							tmp14 := *(tmp13)
							if tmp14 != nil {
								tmp15 := *(tmp14)
								if tmp15 != nil {
									tmp16 := *(tmp15)
									if tmp16 != nil {
										tmp17 := *(tmp16)
										to.Bio = tmp17
									}
								}
							}
						}
					}
				}
			}
		}
	}
	tmp18 := (string)(from.Blog)
	to.Blog = tmp18
	tmp19 := (int64)(from.Collaborators)
	to.Collaborators = tmp19
	to.Company = from.Company
	to.CreatedAt = TimeToString(from.CreatedAt)
	tmp20 := (int64)(from.DiskUsage)
	to.DiskUsage = tmp20
	to.Email = from.Email
	tmp21 := (int64)(from.Followers)
	to.Followers = tmp21
	tmp22 := (int64)(from.Following)
	to.Following = tmp22
	to.GravatarID = from.GravatarID
	to.Hireable = from.Hireable
	tmp23 := (string)(from.HTMLURL)
	to.HTMLURL = tmp23
	tmp24 := (int64)(from.ID)
	to.ID = tmp24
	to.Location = from.Location
	to.Login = from.Login
	to.Name = from.Name
	tmp25 := (int64)(from.OwnedPrivateRepos)
	to.OwnedPrivateRepos = tmp25
	to.Plan = PlanToUserPlan(&(from.Plan))
	if from.PrivateGists != nil {
		tmp26 := *(from.PrivateGists)
		tmp27 := (int64)(tmp26)
		to.PrivateGists = tmp27
	}
	if from.PublicGists != nil {
		tmp28 := *(from.PublicGists)
		if tmp28 != nil {
			tmp29 := *(tmp28)
			tmp30 := (int64)(tmp29)
			to.PublicGists = tmp30
		}
	}
	if from.PublicRepos != nil {
		tmp31 := *(from.PublicRepos)
		if tmp31 != nil {
			tmp32 := *(tmp31)
			if tmp32 != nil {
				tmp33 := *(tmp32)
				tmp34 := (int64)(tmp33)
				to.PublicRepos = tmp34
			}
		}
	}
	tmp35 := (int64)(from.TotalPrivateRepos)
	to.TotalPrivateRepos = tmp35
	to.Type = from.Type
	tmp36 := (string)(from.URL)
	to.URL = tmp36
	return to
}

// PlanToUserPlan :
func PlanToUserPlan(from *github.Plan) *def.UserPlan {
	to := &def.UserPlan{}
	tmp37 := (int64)(from.Collaborators)
	to.Collaborators = tmp37
	to.Name = from.Name
	tmp38 := (int64)(from.PrivateRepos)
	to.PrivateRepos = tmp38
	tmp39 := (int64)(from.Space)
	to.Space = tmp39
	return to
}
```


<!-- ## 落ち葉拾い
 !-- 
 !-- misc
 !-- 
 !-- - 変換関数の一部を手書きしたい場合は？
 !-- - pointerの対応は？
 !-- - interfaceの対応は?
 !-- - slicesの対応は？
 !-- - type alias(new type)の対応は？
 !-- - embedの対応は？
 !-- - 変換不能なフィールドの対応は？
 !-- - 表記ゆれには対応しているの(Id,ID,...)
 !-- - 複数の引数の関数には対応しているの?
 !-- - 手書きする関数で新たに追加で引数が必要になった場合にはどうなるの？
 !-- - キャストの省略途中で自分で定義した関数使いたい場合に上手くいくの？ -->