配列が null だと Azure サーバがエラーを返す問題


# 概要
[Go から Microsoft Azure を利用する](http://qiita.com/jkawamoto/items/e0761cbe7a4c42305cc0)の方法でソースコードを生成すると，スライス型の構造体メンバに omitempty が付かず API 呼び出しがエラーになる場合があった．
その原因ととりあえずの対策をまとめる．

# go-swagger が生成する構造体
例えば，

```sh
$ swagger generate client \
	-f https://raw.githubusercontent.com/Azure/azure-rest-api-specs/master/batch/2016-07-01.3.1/swagger/BatchService.json \
	-t batch
```

上記のコマンドで [Batch API](https://docs.microsoft.com/en-us/rest/api/batchservice/) のバインディングを作った場合，
ジョブプールの追加に必要なパラメータを表す構造体として，下記のような PoolAddParameter が生成される．

```go
type PoolAddParameter struct {

	// The list of application packages to be installed on each compute node in the pool.
	//
	// This property is currently not supported on pools created using
	// the virtualMachineConfiguration (IaaS) property.
	ApplicationPackageReferences []*ApplicationPackageReference `json:"applicationPackageReferences"`
	
	AutoScaleEvaluationInterval strfmt.Duration `json:"autoScaleEvaluationInterval,omitempty"`
	
	AutoScaleFormula string `json:"autoScaleFormula,omitempty"`

	// 以下略
}
```

ここで，ApplicationPackageReferences のタグに omitempty が付いていないため，
この構造体インスタンスを Marshal すると，ApplicationPackageReferences はデフォルトで null が書き出されてしまう．

Azure は null が含まれているリクエストを無条件にリジェクトするので，
null が書き出されないように要素数 0 のスライスを設定する必要がある．

たいていの場合は，この空のスライスを設定しておけば問題ないのだが，上記の PoolAddParameter は注釈に
virtualMachineConfiguration (IaaS) property とは競合すると書かれていて，
実際 virtualMachineConfiguration に値が設定してあると，空のスライスであってもエラーになる．
（どちらか片方だけ設定するように言われる）

go-swagger 的には未設定と空を区別するために omitempty をあえて付けないでいるようなので，
(参考: [can't distinguish between an empty array and null array for non-required array fields](https://github.com/go-swagger/go-swagger/issues/707))
Azure サーバの方で null を認めるかプログラムの方でなんとかするしかない．


# とりあえずの対策
自動生成されたソースコードを調べて omitempty をつけて回るか，
リフレクションや [ast](https://golang.org/pkg/go/ast/) を使って動的に対処すれば良いのだが，
[OAuthアクセストークンを使ってMicrosoft Azureにアクセスする](http://qiita.com/jkawamoto/items/6600d2d4ee4855fbfb87)で扱ったクライアントの Transport に，
リクエストの構造体をテキストメッセージ（この場合は JSON 文書）に変換する Producer を登録できるので，
この Producer を書き換えて対応することにする．

Producer は，[go-openapi/runtime](https://godoc.org/github.com/go-openapi/runtime) パッケージにて，

```go
type Producer interface {
	// Produce writes to the http response
	Produce(io.Writer, interface{}) error
}
```

と定義されている．

今回は，とりあえず動けば良いと思って下記のような Producer を用意した．

```go
type MinimalJSONProducer struct {
	regexp *regexp.Regexp
	blank  []byte
}

func NewMinimalJSONProducer() *MinimalJSONProducer {
	return &MinimalJSONProducer{
		regexp: regexp.MustCompile("(\"[^\"]+?\":null,?|,\"[^\"]+\":null)"),
		blank:  []byte(""),
	}
}

func (p *MinimalJSONProducer) Produce(out io.Writer, msg interface{}) (err error) {
	data, err := json.Marshal(msg)
	if err != nil {
		return
	}
	data = p.regexp.ReplaceAllLiteral(data, p.blank)
	
	_, err = out.Write(data)
	return
}
```

Producer の登録は，[OAuthアクセストークンを使ってMicrosoft Azureにアクセスする](http://qiita.com/jkawamoto/items/6600d2d4ee4855fbfb87)の時と同様にクライアントを *httptransport.Runtime にキャストして登録する．
API 呼び出しの通信で使われているリクエストのコンテンツタイプは `application/json; odata=minimalmetadata` なので，
このコンテンツタイプ用の Producer として先ほど定義した MinimalJSONProducer を与える．

```go
import(
	httptransport "github.com/go-openapi/runtime/client"
	"github.com/go-openapi/strfmt"
	// go-swagger が生成したパッケージ
	"github.com/jkawamoto/roadie/cloud/azure/batch/client"
)
```

```go
cli := client.NewHTTPClient(strfmt.NewFormats())

switch transport := cli.Transport.(type) {
case *httptransport.Runtime:
	transport.Producers["application/json; odata=minimalmetadata"] = NewMinimalJSONProducer()
	// 子クライアントに登録
	cli.Accounts.SetTransport(transport)
	cli.Jobs.SetTransport(transport)
}
```

Transport をいじったら，各子クライアントに設定するのを忘れないように．