swiftでprotoのCustomOptionsを拾う

本記事はSwiftで[CustomOptions](https://developers.google.com/protocol-buffers/docs/proto#customoptions)(以下、option)を解釈できるprotocを書けるようになろうという趣旨です

## 背景

- [Golang ProtoBuf Validator Compiler - https://github.com/mwitkow/go-proto-validators](https://github.com/mwitkow/go-proto-validators)
    - これのSwift版がほしい。でもGoは書けない...だったらSwiftで書けばいいだろ！
- proto=>swiftの [swift-protobuf](https://github.com/apple/swift-protobuf) はSwiftで書かれているが **swift-protobufではoptionは必要ないからキャプチャ/公開しない** とのこと
    - [Use of undeclared type 'Google_Protobuf_FieldOptions'](https://github.com/apple/swift-protobuf/issues/740)
    - [Add support for "option" field in protobuf message](https://github.com/apple/swift-protobuf/issues/761)
    - [Support proto options](https://github.com/apple/swift-protobuf/issues/815)
    - [Custom FieldOptions going to unknownFields instead of extensionFieldValues](https://github.com/apple/swift-protobuf/issues/742)
- とはいえ最後のIssueのとおりUnknownFieldsにちゃんと入っていました。ツンデレか

## 実装

### optionを付ける

[Proto3 Language Guide（和訳） #カスタムオプション](https://qiita.com/CyLomw/items/9aa4551bd6bb9c0818b6#%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0%E3%82%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3)

[Language Guide #customoptions](https://developers.google.com/protocol-buffers/docs/proto#customoptions)

descriptorを継承してフィールドやメッセージに情報を付与するもので、proto表現外の情報を定義することができます

optionは `FileOptions, MessageOptions, FieldOptions, EnumOptions, EnumValueOptions, ServiceOptions, MethodOptions` の7種類ありますが、今回はFieldOptionsで説明します

なお公開する際には下のタグナンバー `50002` を https://github.com/protocolbuffers/protobuf/blob/master/docs/options.md にプルリクをして他protocと衝突しないようにする必要があるみたいです


```myoptions.proto
syntax = "proto3";

import "google/protobuf/descriptor.proto";

extend google.protobuf.FieldOptions {
  MyField my_field_option = 50002;
}
message MyField {
  float foo = 1;
}
```

```api.proto
syntax = "proto3";

import "myoptions.proto";

message APIMessage {
  int32 foo = 1 [(my_field_option) = {foo:4.5}];
  string bar = 2;
}
```

### option定義をSwiftに変換

```sh
git clone git@github.com:apple/swift-protobuf.git
cd swift-protobuf
swift build
vi myoptions.proto
protoc --plugin=protoc-gen-swift=.build/debug/protoc-gen-swift --swift_out=./ -I./Protos -I. ./myoptions.proto
# generate myoptions.pb.swift
```

### ジェネレートしたpb.swiftをProjectに含める

今回は本丸に含めます

```sh
cp myoptions.pb.swift Sources/protoc-gen-swift/
```

### [補足]ProtocolBuffersのバイナリ

参考: 
[165行で実装するProtocol Buffersデコーダ（ミニマム版）](https://cipepser.hatenablog.com/entry/go-protobuf-decoder)
[Protocol Buffers のエンコーディング仕様の解説](https://qiita.com/aiueo4u/items/38195248a29e9ff719c7)
[公式エンコーディング解説](https://developers.google.com/protocol-buffers/docs/encoding)

簡単にまとめると

- ProtocolBuffersのtypeは次のとおり

|Type|Meaning|Used For|
|:--:|:--|:--|
| 0 | Varint | int32, int64, uint32, uint64, sint32, sint64, bool, enum |
| 1 | 64-bit | fixed64, sfixed64, double |
| 2 | Length-delimited | string, bytes, embedded messages, packed repeated fields |
| 3 | Start group | groups (deprecated) |
| 4 | End group | groups (deprecated) |
| 5 | 32-bit | fixed32, sfixed32, float |

- 各タイプの構成は次のとおり。 **TagとPayloadLengthの導出にもdecodeVarintが必要** になります。

|Type|タグナンバー*8+タイプ値|PayloadLength|Payload|
|:--:|:--:|:--:|:--|
|0|Varint<<3&#124;0|-|Varint|
|1|Varint<<3&#124;1|-|8byte|
|2|Varint<<3&#124;2|Varint|PayloadLength分の何か|
|5|Varint<<3&#124;5|-|4byte|

### UnknownFields切り分け+デシリアライズ

UnknownFieldsには他optionもシリアライズされて入っているので、[公式ソースコード](https://github.com/apple/swift-protobuf/blob/master/Sources/SwiftProtobuf/BinaryDecoder.swift#L1376-L1407) を参考に対象タグナンバーのPayloadを切り出す必要があります。(タグナンバーはmyoptions.protoで定義した `50002`)

optionのデシリアライズは対象Messageのinitに渡すだけです

```swift

diff --git a/Sources/protoc-gen-swift/FieldGenerator.swift b/Sources/protoc-gen-swift/FieldGenerator.swift
index 8b2177c2..15ba3505 100644
--- a/Sources/protoc-gen-swift/FieldGenerator.swift
+++ b/Sources/protoc-gen-swift/FieldGenerator.swift
@@ -88,9 +88,98 @@ class FieldGeneratorBase {
     }
   }
 
+  var myoptions: MyField?
+
   init(descriptor: FieldDescriptor) {
     precondition(!descriptor.isExtension)
     number = Int(descriptor.number)
     fieldDescriptor = descriptor
+    // UnknownFields
+    myoptions = MyField.init(unknownFieldData: fieldDescriptor.options.unknownFields.data)
+    // 確認用
+Stderr.print("\(myoptions?.foo ?? 0.0)")
+  }
+}
+
+extension MyField {
+  init?(unknownFieldData: Data) {
+    if unknownFieldData.count == 0 {
+      return nil
+    }
+    // TODO: change tagNumber
+    let extensionsTagNumber = 50002
+    guard let data = getFieldData(data: unknownFieldData, tagNumber: extensionsTagNumber) else {
+      return nil
+    }
+    // デシリアライズ
+    guard let v = try? MyField(serializedData: data) else {
+      return nil
+    }
+    self = v
+  }
+}
+
+// https://developers.google.com/protocol-buffers/docs/encoding
+func getFieldData(data: Data, tagNumber: Int) -> Data? {
+  var index = 0
+  // タグナンバー*8+タイプ値 の形にしておく。my_field_optionはType2なので0x02
+  let targetKey = tagNumber << 3 | 0x02
+  while index < data.count {
+    let wireFormat = data[index] & 0x07
+    switch wireFormat {
+    case 0x02: // Length-delimited	string, bytes, embedded messages, packed repeated fields
+      guard let key = decodeVarint(data: data, index: &index) else {
+        return nil
+      }
+      guard let length = decodeVarint(data: data, index: &index) else {
+        return nil
+      }
+      if key == targetKey {
+        // Target Option
+        return data.subdata(in: index..<index+Int(length))
+      }
+      index += Int(length)
+      break
+    case 0x00: // Varint	int32, int64, uint32, uint64, sint32, sint64, bool, enum
+      // key
+      guard let _ = decodeVarint(data: data, index: &index) else {
+        return nil
+      }
+      // value
+      guard let _ = decodeVarint(data: data, index: &index) else {
+        return nil
+      }
+      break
+    case 0x01: // 64-bit	fixed64, sfixed64, double
+      // key
+      guard let _ = decodeVarint(data: data, index: &index) else {
+        return nil
+      }
+      index += 8
+      break
+    case 0x05: // 32-bit	fixed32, sfixed32, float
+      // key
+      guard let _ = decodeVarint(data: data, index: &index) else {
+        return nil
+      }
+      index += 4
+      break
+    default:
+      return nil
+    }
+  }
+  return nil
+}
+func decodeVarint(data: Data, index i: inout Int) -> UInt64? {
+  var value = UInt64(0)
+  var shift = UInt64(0)
+
+  while i < data.count {
+    let c = data[i]
+    i += 1
+    value |= UInt64(c & 0x7f) << shift
+    shift += 7
+    let msb = (c & 0x80) >> 7
+    if msb == 0 {
+      return value
+    }
   }
+  return nil
 }
```

```sh
swift build
protoc --plugin=protoc-gen-swift=.build/debug/protoc-gen-swift --swift_out=./ -I./Protos -I. ./api.proto
# protoc-gen-swift: 4.5 // int32 foo = 1 のoption
# protoc-gen-swift: 0.0 // string bar = 2 のoption は無いので0.0
```

## 所感

protoはとてもシンプルにインターフェイスを書き表せるツールで、今後protocの需要も多少出てくるかなということで勉強がてらやってみました
swift-protobufの実装がすこし癖があり時間がかかりましたが、久しぶりにバイナリに触れていい刺激になりました