基础知识
JSON 超集
CUE 是 JSON 的超集,他包括 JSON 所有的特性并添加了额外的特性,
这意味着 CUE 可以读取并表示 JSON,但并不意味能把 CUE 理解为 JSON。
放心这是个好事,因为这让 CUE 更好理解而且能表示更多东西。
当用 CUE 来表示 JSON 的时候,区别是:
可以用 //
表示注释
JSON 中的 Objects 被称为 structs
Object 成员被称为 struct 字段
当 struct 字段不包含特殊字符时,可以省略引号 "
struct 字段后面不需要逗号 ,
在 list 中,最后一个元素后面可以添加逗号 ,
可以省略最外层的大括号
superset.cue
str: "hello world"
num: 42
flt: 3.14
// Special field name (and a comment)
"k8s.io/annotation" : "secure-me"
// lists can have different element types
list: [
"a" , "b" , "c" ,
1 ,
2 ,
3 ,
]
obj: {
foo: "bar"
// reuse another field?!
L: list
}
cue export superset.cue --out json
{
"str" : "hello world" ,
"num" : 42 ,
"flt" : 3.14 ,
"k8s.io/annotation" : "secure-me" ,
"list" : [
"a" ,
"b" ,
"c" ,
1 ,
2 ,
3
],
"obj" : {
"foo" : "bar" ,
"L" : [
"a" ,
"b" ,
"c" ,
1 ,
2 ,
3
]
}
}
格
任何实体都属于 CUE 的 格
(wikipedia )。
最通用的值是 “top
” 或者 “_
” ,可以匹配任意的实体,
最小范围的值是 “bottom
” 或者 “_|_
” ,来表示无效的实体,冲突或错误。
而其他的值都在这两者之间,而且部分有序。
现在可能会比较迷惑,但随着你更多地学习和使用 CUE 之后,将会比较明了。
它基于一些数学的概念,你可以从 CUE 理论 这章学习更多。
目前来说,将 top -> schema -> constraint -> data -> bottom
作为粗略指南会更容易理解。
类型和数据
JSONSchema 和 JSON 是完全不同的概念,一个定义了结构,另一个是数据,而在 CUE 中,它们将是一样的。
CUE 将 类型(types) 和 数据(values) 合并为同一个概念:格
。
这让我们能在同一个文件中定义类型、进行约束和生成数据,也意味着定义类型对人类思考然后实现它都将变得更容易。
Schema
album: {
title: string
year: int
live: bool
}
Constraints
album: {
title: string
year: > 1950
live: false
}
Data
album: {
title: "Houses of the Holy"
year: 1973
live: false
}
虽然你可能更熟悉类型(type
)和数据(data
),大多数语言不会把约束条件(constraint
)或者校验(validation
)作为首要概念。
但是 CUE 恰恰相反,用 约束条件(constraint
)来管理值的规则或限制,像其他所有的实体一样,它存在于结构和具体数值中间。
CUE 的最佳实践让我们从范围比较大的结构开始,然后逐渐限制,一直到具体的实体结束。
一开始都是从很小的结构定义,然后最终将它们改造成复杂的实体。
数值不能被改变
要理解 CUE 最重要的概念之一是 数值不能被概念
,在 CUE 中没有重载或重写的概念。
写代码或组织代码的方式都会影响值的变化,这样做的原因是为了维护性和可理解,同时这也是 CUE 的哲学所要求的。
当你在你的配置中找数值在哪里设置的时候,你就会发现其实这很有用。
CUE 不仅仅确保那个值是你设置的,而且会告诉你在哪里设置的,如果发生冲突,也会告诉你冲突的地方。
定义字段
CUE 允许字段被定义多次,只要保持一致的就没问题。
基本类型必须保持一致
你可以不断限制一个字段的范围,但不能放大
struct 的字段会合并,list 的元素必须完全匹配
规则是递归的
fields.cue
hello: "world"
hello: "world"
// set a type
s: {a: int }
// set some data
s: {a: 1 , b: 2 }
// set a nested field without curly braces
s: c: d: 3
// lists must have the same elements
// and cannot change length
l: [ "abc" , "123" ]
l: [
"abc" ,
"123" ,
]
cue eval fields.cue
hello: "world"
s: {
a: 1
c: {
d: 3
}
b: 2
}
l: [ "abc" , "123" ]
使用这些属性对于在一个地方定义结构,然后在另一个地方定义约束、构建配置
以及确保一个字段没有在不同地方错误地设置为不同的值很有用。
Definition
definition
是 CUE 定义结构的方法,它和 struct 有些不同的规则。
它们不会作为数据输出
它们可能不完整或者未全部指定
它们 指定
(close
) 一个 struct 包含的字段,防止 struct 包含未知或额外的字段
你可以用 #mydef:
定义一个结构,然后用 ...
放到最后以表明它可以扩展
definition.cue
#Album: {
artist: string
title: string
year: int
// ... // 2. uncomment to open and fix error, must be last
}
// This is a conjunction, it says "album" has to be "#Album"
album: #Album & {
artist: "Led Zeppelin"
title: "Led Zeppelin I"
year: 1969
// studio: true // 1. uncomment to trigger error
}
cue export definition.cue --out json
{
"album" : {
"artist" : "Led Zeppelin" ,
"title" : "Led Zeppelin I" ,
"year" : 1969
}
}
连接符
连接符可以 “连接”(“meet”")值, 合并他们的字段、规则、数据,就像 “and” 的功能,用 &
符号表示。
conjunction.cue
// conjunctions on a field
n: int & > 0 & < 100
n: 23
// conjunction of schemas
val: #Def1 & #Def2
val: {foo: "bar" , ans: 42 }
#Def1: {
foo: string
ans: int
}
#Def2: {
foo: =~ "[a-z]+"
ans: > 0
}
cue export conjunction.cue --out json
{
"n" : 23 ,
"val" : {
"foo" : "bar" ,
"ans" : 42
}
}
析取
析取符用于"合并"(“join”)可选项或备选方案,就像 or
的功能,用 |
表示。
disjunction.cue
// disjunction of values (like an enum)
hello: "world" | "bob" | "mary"
hello: "world"
// disjunction of types
port: string | int
port: 5432
// disjunction of schemas
val: #Def1 | #Def2
val: {foo: "bar" , ans: 42 }
#Def1: {
foo: string
ans: int
}
#Def2: {
name: string
port: int
}
cue export disjunction.cue --out json
{
"hello" : "world" ,
"port" : 5432 ,
"val" : {
"foo" : "bar" ,
"ans" : 42
}
}
析取符可以用于:
枚举值
多个类型(这些类型中任意一个)
空值结合运算(计算或者指定默认值)
默认值和可选值
CUE 支持设置值的默认值或指定字段是可选的。
default-optional.cue
s: {
// field with a default
hello: string | * "world" | "apple"
// an optional integer
count?: int
}
cue export default-optional.cue --out json
{
"s" : {
"hello" : "world"
}
}
不完全(Incomplete)和完全 (Concrete)
没有包含所有字段的数据,可以成为不完全
的值,CUE 将不会导出未完成的值,而会返回错误。
相反的,完全
的值是那些所有字段都被指定的数据。
开放(Open)和封闭 (Close)
开放表示 struct 可以被扩展,而封闭表示不能被扩展。
默认情况下,struct 是开放的,而 definition 是封闭的。
CUE 也支持让我们显式地做相反的声明。
open-closed.cue
// Closed struct
s: close ({
foo: "bar"
})
// Open definition
#d: {
foo: "bar"
... // must be last
}
更多相关讨论,请看 deep-dives/closedness .
逐步构建值
在 CUE 中,推荐从很小的定义开始逐渐构建值,让一些结构定义能重用,可以通过嵌套实现定义。
building-up.cue
#Base: {
name: string
kind: string
}
#Meta: {
// string and a semver regex
version: string & =~ "^v[0-9]+ \\ .[0-9]+ \\ .[0-9]+$"
// list of strings
labels: [... string ]
}
#Permissions: {
role: string
public: bool | * false
}
// Building up a schema using embeddings
#Schema: {
// embed other schemas
#Base
#Meta
#Permissions
// with no '...' this is final
}
value: #Schema & {
name: "app"
kind: "deploy"
version: "v1.0.42"
labels: [ "server" , "prod" ]
role: "backend"
// public: false (by default)
}
cue export building-up.cue --out json
{
"value" : {
"name" : "app" ,
"kind" : "deploy" ,
"version" : "v1.0.42" ,
"labels" : [
"server" ,
"prod"
],
"role" : "backend" ,
"public" : false
}
}
顺序并不重要
CUE 的合并系统涉及到值、结构和约束条件,但不会考虑顺序或者在哪个文件中。
order.cue
a: 3
b: "bb"
// notice the mixing
s: {x: 1 , y: int }
s: {x: int , y: 2 }
order-2.cue
// we can apply constraints after setting data
a: > 0 & < 10
// regex implies string
b: =~ "[a-z]+"
cue export order.cue order-2.cue --out json
{
"a" : 3 ,
"b" : "bb" ,
"s" : {
"x" : 1 ,
"y" : 2
}
}
简单的说,合并只是组合、交换,而且是幂等的。
图灵非完备
CUE 是图灵非完备的,意味着不像是通常意义的语言。你只提供值、类型、定义和约束条件,然后 CUE 会告诉你写的是不是正确。
这是有意为之的,而且是基于 Google 多年在管理配置的经验。
核心思想是:
用数据封装代码,而不是用代码封装数据
没有原始的递归或继承
最初的学习曲线值得长期维护
这些限制条件主要灵感是:
Difficulties with Borgcfg and GCL as complexity grew (i.e. object oriented and lambdas)
Lingo and Typed Feature Structure Grammars (managing massive configurations)
Logical and functional languages (various pieces like comprehensions in immutability)
随着 Borgcfg 和 GCL 复杂性不断增加遇到的困难(比如 面向对象和 lambdas)
术语和类型化结构性的语法(管理大规模配置)
逻辑和函数式编程语言(各种部分,像是对不变性的理解)
基于 Golang
CUE 开始于 Go 的一个分支,目的是为了简化作为新语言的启动,
Marcel 在 Google 也是 Go 团队的成员,有很多哲学思想也被延续了下来:
CUE 是用 Go 实现的
CLI 提供了丰富的工具链
这门语言提供丰富的 API
包含一个标准库