Google Cloud Go SDK の ForceSendFields について

2024/04/16に公開

要約

Google Cloud の Go SDK では、構造体の ForceSendFields フィールドにフィールド名を指定することで、 omitempty を回避してリクエストにゼロ値を強制的に含めることができる。

本題

Google Cloud の Go SDK(google.golang.org/api) では、定義された構造体のフィールドに一部の例外を除き json:"omitempty" タグがついています。これによって、あるフィールドがゼロ値であるとき、そのフィールドは省略されます。しかし、これは次のような問題を引き起こすことがあります。

Cloud SQL のインスタンスを作成する v1.instances.insert メソッドを例にとります(ドキュメント)。 このメソッドでは、パブリック IP アドレスの有効/無効はリクエストの DatabaseInstance.Settings.ipConfiguration.ipv4Enabled フィールドの値によって決まります。
Go SDK を使用する場合、上述の omitempty タグにより、このフィールドを false にした場合、フィールドは省略されて送信されます。しかし、Google Cloud の API は DatabaseInstance.Settings.ipConfiguration.ipv4Enabled フィールドが省略された場合デフォルト値である true を使用してしまいます(これに関して明言している公式ドキュメントは見つけられませんでした)。これによって、 DatabaseInstance.Settings.ipConfiguration.ipv4Enabledtrue にしても false にしても Cloud SQL インスタンスはパブリック IP アドレスが有効化された状態で作成されてしまいます。

対処

これを防ぐためには、フィールドがゼロ値であっても omitempty せずにそのままリクエストに含めて送信するように設定する必要があります。このために使うのが Go SDK の各構造体に定義されている ForceSendFields です。 ForceSendFields[]string 型のフィールドで、常にリクエストに明示するフィールドの名前を配列で指定します。このように指定したフィールドは省略されることなくリクエストに含めることができます。上記の Cloud SQL の例では、 IpConfiguration 構造体の ForceSendFieldsIpv4Enabled を設定すればいいです。

実装の説明

ForceSendFields がどうやって実装されているのか個人的に興味を持ったので調べてみました。Google Cloud Go SDK で使用されている MarsharlJSON は以下のように実装されています。

https://github.com/googleapis/google-api-go-client/blob/85b4e909e1368b10a713262dc4e416a4c7c95956/internal/gensupport/json.go#L14-L50

MarshalJSON では、 ForceSendFields フィールドをもとに schemaToMap 関数に mustInclude という map を渡して、 schemaToMap の返り値をそのまま JSON にエンコードしています。 schemaToMap 関数はリフレクションを使って各フィールドが ForceSendFields に含まれているかを判定し、ゼロ値のフィールドであって含まれていない場合は返却する map にフィールドを含めないようにしています。

https://github.com/googleapis/google-api-go-client/blob/85b4e909e1368b10a713262dc4e416a4c7c95956/internal/gensupport/json.go#L81-L83

https://github.com/googleapis/google-api-go-client/blob/85b4e909e1368b10a713262dc4e416a4c7c95956/internal/gensupport/json.go#L194-L213

Discussion