0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

cueで複数のprotobufファイルからschemaを読み込む

Posted at

cueで複数のprotobufファイルからschemaを読み込む

grpc-testing ではprotobufファイルからcueを自動生成する機能があり、protobufのschemaに従ってリクエスト内容を記述することができます。

しかし実はprotobufのimportで他ファイルから定義を引っ張ってくる場合、素直にcueを生成するだけではcueのコンパイルが通りません。

syntax = "proto3";

package user;

option go_package = "github.com/ryoya-fujimoto/device;device";

import "proto/user/resource.proto"; // <--これ

message Device {
  uint64 id = 1;
  user.User user = 2;
}

こんな感じのprotobufから生成したcueのコンパイルを目指します。
今回作ったサンプルファイルなどは https://github.com/ryoya-fujimoto/cue-module-sample に公開してあります。

公式のドキュメント

cue + protobufなドキュメントは https://cuelang.org/docs/integrations/protobuf/ にあります。その中の

Extract CUE from multiple interdependent .proto files

という項目に説明があります。ざっくりいうと「ちょっとめんどくさいけどoption go_packageに従ってcueのmoduleを配置するとできるよ」みたいなことが書いてあります。

ではcueのmoduleとはなんぞやというと、https://cuelang.org/docs/concepts/packages/ にドキュメントがあります。なるほど、この通りにやってやれば行けそうです。

まずは素直にcueを生成してみる。

サンプル用のprotobufファイルを以下のように配置します。

.
├── README.md
└── proto
    ├── device
    │   └── resource.proto
    └── user
        └── resource.proto

それぞれのファイルは user/resource.proto, device/resource.proto のようになっており、device/resource.proto からuser packageをimportしています。

これらのprotobufファイルからcue importコマンドでcueファイルを生成します。

cue import -I ./ proto/device/resource.proto
cue import -I ./ proto/user/resource.proto

すると以下のようなファイルが生成されます。

# proto/device/resource.proto.cue
package device

import "github.com/ryoya-fujimoto/user"

Device: {
	id?:   uint64      @protobuf(1)
	user?: _user_.User @protobuf(2,type=user.User)
}
_user_ = user

# proto/user/resource.proto.cue
package user

User: {
	email?: string @protobuf(1)
	name?:  string @protobuf(2)
}

しかしこれをcueでコンパイル(json出力)してみようとすると

$ cue export proto/device/resource.proto.cue
cannot find package "github.com/ryoya-fujimoto/user"

と怒られます。user packageが解決できていません。同じく生成したuserのcueファイルを含めてみても

$ cue export proto/user/resource.proto.cue proto/device/resource.proto.cue
found packages user (resource.proto.cue) and device (resource.proto.cue) in /Users/ryoya/github/cue-module-sample

ダメそうです。ちなみにuserだけだとうまくいきます。

$ cue export proto/user/resource.proto.cue
{
    "User": {}
}

moduleに配置する

ということで、ドキュメント の通りにファイルを配置していきます。

まずはcue mod initでcueのモジュールを作成します。この辺りはgo mod initと思想が似ています。

cue mod init github.com/ryoya-fujimoto

するとcud.modという ディレクトリ が生成され、module.cueにモジュール名が記述されます。

$ cue mod init github.com/ryoya-fujimoto
$ tree
.
├── README.md
├── cue.mod
│   ├── module.cue
│   ├── pkg
│   └── usr
└── proto
    ├── device
    │   ├── resource.proto
    │   └── resource.proto.cue
    └── user
        ├── resource.proto
        └── resource.proto.cue

6 directories, 6 files
$ cat cue.mod/module.cue
module: "github.com/ryoya-fujimoto"

そしてuser/resource.protooption go_packageの通りにディレクトリを作成し、その下にcueファイルを置きます。このときディレクトリはmodule.cueに記載されたモジュール名から下のパスで生成します。

$ mkdir user
$ cp proto/user/resource.proto.cue user
$ tree
.
├── README.md
├── cue.mod
│   ├── module.cue
│   ├── pkg
│   └── usr
├── proto
│   ├── device
│   │   ├── resource.proto
│   │   └── resource.proto.cue
│   └── user
│       ├── resource.proto
│       └── resource.proto.cue
└── user  <- 新規作成
    └── resource.proto.cue  <- コピー

7 directories, 7 files

すると無事userpackageが解決され、device/resource.proto.cueがコンパイルできるようになります。

$ cue export proto/device/resource.proto.cue
{
    "Device": {}
}

せっかくなので、device packageもimportするようにして色々なデータを入れてみましょう。

$ mkdir device
$ cp proto/device/resource.proto.cue device
$ cat << EOS > device.cue
import "github.com/ryoya-fujimoto/device"

someDevice: device.Device & {
  id: 1
  user: {
    email: "user@example.com"
    name: "user1"
  }
}
EOS

$ cue export device.cue
{
    "someDevice": {
        "id": 1,
        "user": {
            "name": "user1",
            "email": "user@example.com"
        }
    }
}

user packageが読み込まれているので、型に違反するとちゃんとエラーになります。

$ cat << EOS > device.cue
import "github.com/ryoya-fujimoto/device"

someDevice: device.Device & {
  id: 1
  user: {
    email: "user@example.com"
    name: 1
  }
}
EOS
$ cue export device.cue
someDevice.user.name: conflicting values string and 1 (mismatched types string and int):
    ./device.cue:3:13
    ./device.cue:7:11
    ./user/resource.proto.cue:5:10

まとめ

公式ドキュメントの通りに進めるだけですが、初見だとだいぶハマりそうなのでドキュメントにしました。cueのmoduleやpackage周りの思想はまだよく分かってないところが多いですが、goのライブラリをそのまま読み込めるみたいなので、その辺も試すと面白いかもしれません。

これを自動生成? で、できらぁ・・・

0
2
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
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?