CUE langでyamlを出力する

# 概要

CUE言語というのを最近知ったので、試しにCUEを使ってユーザーとグループのyamlファイルを作成してみます。CUE言語はjsonのスーパーセットという位置付けです。yamlも出力できますし、バリデーションも行うことができます。

## 公式サイトやチュートリアル
公式

https://cuelang.org/

Cuetorialsというチュートリアルサイトにいろいろテクニックが記述されています。基本的な使い方は公式サイトを見て、こちらでテクニカルな使い方を探すのが良いでしょう。
このサイトは非公式なのでしょうか。ちょっとわかりません。

https://cuetorials.com

# プログラム
## 構成

```bash
cue
├── groups.cue
└── users.cue
```

## ソース

```:users.cue
package directory

#prefectures_constraints: "北海道"|"青森県"|"岩手県"|"宮城県"|"秋田県"|"山形県"|"福島県"|"茨城県"|"栃木県"|"群馬県"|"埼玉県"|"千葉県"|"東京都"|
"神奈川県"|"新潟県"|"富山県"|"石川県"|"福井県"|"山梨県"|"長野県"|"岐阜県"|"静岡県"|"愛知県"|"三重県"|"滋賀県"|"京都府"|"大阪府"|"兵庫県"|"奈良県"|
"和歌山県"|"鳥取県"|"島根県"|"岡山県"|"広島県"|"山口県"|"徳島県"|"香川県"|"愛媛県"|"高知県"|"福岡県"|"佐賀県"|"長崎県"|"熊本県"|"大分県"|"宮崎県"|"鹿児島県"|"沖縄県"

#User: {
	first_name!:   string
	last_name!:    string
	name:          string | *"\(first_name) \(last_name)"
	email:         string
	date_of_birth: =~"[0-9]{8}"
	prefectures:   #prefectures_constraints
}

users: [...#User] & _data

_data: [
	{
		name:          "ウルシバタ"
		first_name:    "Shinya"
		last_name:     "Urushibata"
		email:         "urushibata@example.com"
		date_of_birth: "19000101"
		prefectures:   "東京都"
	},
	{
		name:          "開発 太郎"
		first_name:    "太郎"
		last_name:     "開発"
		email:         "t-kaihatu@exsample.com"
		date_of_birth: "19990919"
		prefectures:   "大阪府"
	},
	{
		name:          "営業 花子"
		first_name:    "花子"
		last_name:     "営業"
		email:         "h-eigyo@exsample.com"
		date_of_birth: "20000101"
		prefectures:   "北海道"
	},
	{
		first_name:    "Thomas"
		last_name:     "Platz"
		email:         "tom@exsample.com"
		date_of_birth: "19700801"
  		prefectures:   "バージニア州"
	},
]
```
```groups.cue
package directory

#Group: {
	name:         string
	description?: string
}

groups: [...#Group] & [
	{
		name: "営業部"
	},
	{
		name: "DX部"
	},
	{
		name: "開発部"
	},
]
```

### 説明
プログラムで使っている文法など説明します。

#### package
CUEでは通常1ファイル単位で扱われますが、`package`句を使うことで同じパッケージ名のファイルを1つのファイルとして扱うことができます。1つのディレクトリでパッケージ名を複数定義することはできません。例えば、`package directory`と`package directory2`を同じ階層のファイルに定義するとエラーとなります。

```bash:エラーの例
$ cue export
found packages "directory2" (group.cue) and directory (users.cue) in "/home/s-urushibata/work/terraform/cue"
```

#### Definitions
フィールドの先頭に`#`を付けると`Definitions`になります。`Definitions`で定義した部分はファイルに出力されません。オブジェクト指向言語で言うところのクラスのようなイメージです。

```
#User: {
	first_name!:   string
	last_name!:    string
 	name:          string | *"\(first_name) \(last_name)"
	email:         string
	date_of_birth: =~"[0-9]{8}"
	prefectures:   _#prefectures_constraints
}
```

usersフィールドは`#User`の配列といったように使用します。

```
users: [...#User]
```

#### Constraints
フィールドの型や許可する値の制約を指定できます。今回は使用していませんが、`int`などの数値型も指定できます。

```
//文字列で必須。フィールド名の後ろに"!"を付けると必須になります。
first_name!:   string
last_name!:    string

//文字列で未設定の場合、"first_name last_name"を自動設定します。
//"|"はORです。ORで複数定義した場合、"*"が付いたものをデフォルト値として指定できます。
name:          string | *"\(first_name) \(last_name)"

//=~で正規表現を使用できます。
date_of_birth: =~"[0-9]{8}"

//#Userの外で定義したをDefinitionsを指定できます。
prefectures:   #prefectures_constraints

//フィールドを省略できます（オプショナル）。フィールド名の後ろに"?"を付けます。
description?: string
```

#### hidden (definition)
`_data`のように先頭の文字をアンダーバーにするとhidden項目となり、ファイルに出力されなくなります。もしこれにアンダーバーを付けない場合、代入先の`users`と`data`で同じ内容が2つ出力されることになります。

```
users: [...#User] & _data

 _data: [
	{
        ...
 ```

## エクスポート
それでは出力してみます。デフォルトではjsonで出力されるので、`--out yaml`を指定します。

```
$ cue export --out yaml
users.3.prefectures: 47 errors in empty disjunction:
users.3.prefectures: conflicting values "三重県" and "バージニア州":
    ./users.cue:4:124
    ./users.cue:13:17
    ./users.cue:16:9
    ./users.cue:16:12
    ./users.cue:16:21
    ./users.cue:51:18
...
users.3.prefectures: conflicting values "鹿児島県" and "バージニア州":
    ./users.cue:5:196
    ./users.cue:13:17
    ./users.cue:16:9
    ./users.cue:16:12
    ./users.cue:16:21
    ./users.cue:51:18
```
4人目の都道府県がバージニア州となっているのでエラーとなりました。修正して再実行します。
```
$ cue export --out yaml
users:
  - name: ウルシバタ
    first_name: Shinya
    last_name: Urushibata
    email: urushibata@example.com
    date_of_birth: "19000101"
    prefectures: 東京都
  - name: 開発 太郎
    first_name: 太郎
    last_name: 開発
    email: t-kaihatu@exsample.com
    date_of_birth: "19990919"
    prefectures: 大阪府
  - name: 営業 花子
    first_name: 花子
    last_name: 営業
    email: h-eigyo@exsample.com
    date_of_birth: "20000101"
    prefectures: 北海道
  - first_name: Thomas
    last_name: Platz
    name: Thomas Platz
    email: tom@exsample.com
    date_of_birth: "19700801"
    prefectures: 鹿児島県
groups:
  - name: 営業部
  - name: DX部
  - name: 開発部
```
出力されました。

# 感想
都道府県のエラーですが、ORを使ってしまうとORの個数分（47件）がエラーになってしまうのはちょっと使いずらいかなと思いました。ORの個数が多い場合は、正規表現の方が良いかもしれません。
文法も簡単ですし、これは便利だと感じました。但し、まだバージョンが0.8なので本格導入するかは迷いますね。よく比較されるjsonnetを試してみた方がいいのかなぁと思いました。