LoginSignup
1
1

More than 5 years have passed since last update.

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

Posted at

概要

Go から Microsoft Azure を利用するの方法でソースコードを生成すると,スライス型の構造体メンバに omitempty が付かず API 呼び出しがエラーになる場合があった.
その原因ととりあえずの対策をまとめる.

go-swagger が生成する構造体

例えば,

$ 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 のバインディングを作った場合,
ジョブプールの追加に必要なパラメータを表す構造体として,下記のような PoolAddParameter が生成される.

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)
Azure サーバの方で null を認めるかプログラムの方でなんとかするしかない.

とりあえずの対策

自動生成されたソースコードを調べて omitempty をつけて回るか,
リフレクションや ast を使って動的に対処すれば良いのだが,
OAuthアクセストークンを使ってMicrosoft Azureにアクセスするで扱ったクライアントの Transport に,
リクエストの構造体をテキストメッセージ(この場合は JSON 文書)に変換する Producer を登録できるので,
この Producer を書き換えて対応することにする.

Producer は,go-openapi/runtime パッケージにて,

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

と定義されている.

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

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にアクセスするの時と同様にクライアントを *httptransport.Runtime にキャストして登録する.
API 呼び出しの通信で使われているリクエストのコンテンツタイプは application/json; odata=minimalmetadata なので,
このコンテンツタイプ用の Producer として先ほど定義した MinimalJSONProducer を与える.

import(
    httptransport "github.com/go-openapi/runtime/client"
    "github.com/go-openapi/strfmt"
    // go-swagger が生成したパッケージ
    "github.com/jkawamoto/roadie/cloud/azure/batch/client"
)
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 をいじったら,各子クライアントに設定するのを忘れないように.

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1