Rego 语言
7 分钟阅读
简要概述
这里做 Rego 的简要语法说明。
基础类型
Scalar Values
标量值是 Rego 中最简单的类型,可以是字符串、数字、布尔值或 null,如:
greeting := "Hello"
max_height := 42
pi := 3.14159
allowed := true
location := null
docs := [greeting, max_height, pi, allowed, location]
Strings
字符串支持两种声明方式,第一种通过双引号包含,如果有特殊字符需通过反斜杠转译,如:
"[a-zA-Z_]\\w*"
第二种声明方式是通过反引号(`),对特殊字符既所见即所得,如:
`[a-zA-Z_]\w*`
这其实和 Go 语言字符串有相同申明方式。
Composite Values
组合值包含两种类型:Objects(对象)、Sets(集合),在简单情况下,组合值可以像标量值一样被视为常量:
cube := {"width": 3, "height": 4, "depth": 5}
docs := [greeting, max_height, pi, allowed, location]
Objects(对象,也包含数组)键值可以是任意值:
ips_by_port := {
80: ["1.1.1.1", "1.1.1.2"],
443: ["2.2.2.1"],
"80": ["1.1.1.3"],
}
这里 ips_by_port[80]
与 ips_by_port["80"]
获取是不一样的结果,当需要把 ips_by_port
输出 JSON 结构时,原先以 80
为键名的内容会被 "80"
所覆盖,因为 JSON 本身不支持以数字为属性,所以我们在定义 rego 变量是要避免使用数字类型键值,如 ips_by_port
输出 JSON 结果集为:
{
"443": [
"2.2.2.1"
],
"80": [
"1.1.1.3"
]
}
Sets(集合)是无序的唯一值的集合,如:
{1,2,3} == {3,1,2}
定义变量
变量在 Rego 中与大部分编程语言不同,可同时是输入和输出。如果为变量提供了一个值,那么该变量是一个输入型,如果没有为变量提供值,那么该变量是一个输出型。
作为输入
m := [10,6,9,8,15,3,12]
sites := [
{"name": "prod"},
{"name": "smoke1"},
{"name": "dev"}
]
以上变量 m
与 sites
均作为输入型变量,这同其他语言定义。
作为输出
> m := [10,6,9,8,15,3,12]
Rule 'm' defined in package repl. Type 'show' to see rules.
> m
[
10,
6,
9,
8,
15,
3,
12
]
>
> n
1 error occurred: 1:1: rego_unsafe_var_error: var n is unsafe
>
> m[n] == 3
+---+
| n |
+---+
| 5 |
+---+
>
定义一个数组 m
,此时变量 n
未定义,通过查询语句 m[n] == 3
查找数组 m
中值为 “3” 的结果并写入到变量 n
中,此时变量 n
作为输出型,在看另外一个示例:
import future.keywords.if
import future.keywords.in
import future.keywords.contains
sites := [
{"name": "prod"},
{"name": "smoke1"},
{"name": "dev"}
]
q contains name if {
some site in sites
name := site.name
}
此时变量 q
的结果为:
[
"dev",
"prod",
"smoke1"
]
在使用一个未定义过的变量 x
来评估 q
,此时 x
为输出,如 q[x]
结果为:
+----------+----------+
| x | q[x] |
+----------+----------+
| "dev" | "dev" |
| "prod" | "prod" |
| "smoke1" | "smoke1" |
+----------+----------+
这里由于 q
是一个集合,所以它们结果相同。
再比如 sites[i].name
,这样在遍历数组时会自动把变量 i
赋值输出:
+---+---------------+
| i | sites[i].name |
+---+---------------+
| 0 | "prod" |
| 1 | "smoke1" |
| 2 | "dev" |
+---+---------------+
如果以上变量 i
在下文中不会使用,我们可以用下划线代替 sites[_].name
,下划线在 Rego 中为特殊含义表示永不重复变量。
推导式
推导式,提供了一种从子查询构建复合值的简洁方式,主要有三种形式,这里通过以下示例说明:
apps := [
{
"id": "001",
"name": "web",
"servers": [
"web-0",
"web-1",
"web-1000",
"web-1001",
"web-dev"
]
},
{
"id": "002",
"name": "mysql",
"servers": [
"db-0",
"db-1000"
]
},
{
"id": "003",
"name": "web",
"servers": [
"web-3",
"web-prod"
]
},
{
"id": "004",
"name": "mongodb",
"servers": [
"db-dev"
]
}
]
数组推导式
Array Comprehensions
[ <term> | <body> ]
> comp := [ server | apps[i].name == "web"; server := apps[i].servers[0] ]
> comp
[
"web-0",
"web-3"
]
>
对象推导式
Object Comprehensions
{ <key>: <term> | <body> }
> comp := { key: server | key := apps[i].id; server := apps[i].servers[0] }
> comp
{
"001": "web-0",
"002": "db-0",
"003": "web-3",
"004": "db-dev"
}
>
集合推导式
Set Comprehensions
{ <term> | <body> }
> comp := { name | name = apps[x].name }
> comp
[
"mongodb",
"mysql",
"web"
]
>
根据已有数据生成变量
在编写规则时,使用到的一些关键字需要自己引入下,如 if
contains
等:
import future.keywords.if
import futere.keywords.contains
通过规则生成数组
comp[name] {
name := apps[_].servers[0]
}
> comp
[
"db-0",
"db-dev",
"web-0",
"web-3"
]
>
通过规则生成对象
comp[id] := server {
some i
id := apps[i].id
server := apps[i].servers
}
> comp
{
"001": [
"web-0",
"web-1",
"web-1000",
"web-1001",
"web-dev"
],
"002": [
"db-0",
"db-1000"
],
"003": [
"web-3",
"web-prod"
],
"004": [
"db-dev"
]
}
>
这里使用示例数据 apps
生成一组对象 comp
以 “id” 为键,以 “server” 为值,这个值来自后面的 {} 判断。
对已有数组的累加
使用相同的变量 comp
在通过多次规则时,它们的结果集是做增量,如下面两次叠加:
> comp[name] { name := apps[_].servers[0] }
>
> comp
[
"db-0",
"db-dev",
"web-0",
"web-3"
]
>
>
> comp[name] { name := apps[_].name }
> comp
[
"db-0",
"db-dev",
"mongodb",
"mysql",
"web",
"web-0",
"web-3"
]
>
对变量的完整定义
除了部分定义集合和对象的规则外,还支持任何类型文档的完整定义,通常用于常量:
pi := 3.14159
由具有完整定义的规则生成的文档一次只能具有一个值,如果评估为同一文档生成多个值,将返回错误。
package repl
import future.keywords.if
# 定义一个用户 "bob"
user := "bob"
# 定义两个用户组,分布表示管理员与受限用户组
power_users := {"alice", "bob", "fred"}
restricted_users := {"bob", "kim"}
# 管理员可拥有 32GB 内存
max_memory := 32 if power_users[user]
# 受限用户仅可有 4GB 内存
max_memory := 4 if restricted_users[user]
TODO;
这里注意下,官方文档是说无法更改,但实际测试中可以被变更:
Rule 'max_memory' re-defined in package repl. Type 'show' to see rules.
需具体检查下原因。
用户自定义函数
支持用户自定定义函数,可以与内置函数相同的语义进行调用,它们可以访问数据文档和输入文档。
trim_and_split(s) := x {
t := trim(s, " ")
x := split(t, ".")
}
> trim_and_split(" foo.bar ")
[
"foo",
"bar"
]
>
foo([x, {"bar": y}]) := z {
z := {x: y}
}
> foo([10, {"bar": 30}])
{
"10": 30
}
>
模块、包与内置函数
模块组成
一个 Rego 的模块,由以下三部分组成:
- 固定使用一个
package
关键字申明包名称; - 零个或多个
import
关键字语句导入外部模块; - 零个或多个规则定义。
包名称
包名称仅能使用字符串,如需分割多个层级,则通过点号 “.",如:
package foo.bar.baz
pi := 3.14159
以上规则定义好后,会自动导出到 opa,可通过以下 api 查询:
GET /v1/data/foo/bar/baz/pi
属于相同的包名称下的模块,可以分布在不同的目录。
导入模块
如需复用第三方模块,使用关键字 import
导入,这样即可在当前模块中引用该文档导出的标识符,如需使用关键字 if
与 contains
则:
package foo.bar.baz
import future.keywords.if
import future.keywords.contains
http_servers contains server if {
some server in servers
"http" in server.protocols
}
TODO; 关键字 data
与 input
备注注解
同其他语言一样,通过 “#” 实现注解。
内置函数
OPA 本身提供了很多常用的函数,可直接调用,如:
> m := [6,9,8]
Rule 'm' defined in package repl. Type 'show' to see rules.
> max(m)
9
> min(m)
6
>
具体列表查看官方文档。
内置关键字
some
用于遍历数据或对象,显式声明键索引与键值局部变量,这些变量的作用范围仅限于该规则内,类似 go 语言的 for 循环,如:
> import future.keywords.in
>
> m := [10,6,9,8,15,3,12]
Rule 'm' defined in package repl. Type 'show' to see rules.
>
> some k, v in m
+---+----+
| k | v |
+---+----+
| 0 | 10 |
| 1 | 6 |
| 2 | 9 |
| 3 | 8 |
| 4 | 15 |
| 5 | 3 |
| 6 | 12 |
+---+----+
>
with
<expr> with <target-1> as <value-1> [with <target-2> as <value-2> [...]]
TODO;
default
default <name> := <term>
为变量定义默认值,因为在判断规则不成立时,变量会被设置为 undefined
,这样在使用过程中不友好,如:
> import future.keywords.if
>
> m if 3 > 10
Rule 'm' defined in package repl. Type 'show' to see rules.
> m
undefined
>
> default m := false
Rule 'm' re-defined in package repl. Type 'show' to see rules.
>
> m
false
>
> m if 3 > 10
Rule 'm' defined in package repl. Type 'show' to see rules.
>
> m
false
>
else
基本的控制流构造,对一组规则按照顺序进行评估,一旦找到匹配项,就不会继续对后续规则评估,如:
import future.keywords.if
x := 10
y := 10
name := "user1" if {
x > y
} else := "user2" {
x == y
} else := "user3" {
x < y
}
future
包含 in
every
if
contains
在使用过程中可一次性导入 import futer.keyworks
不过建议按需,如:
import future.keywords.in
import future.keywords.every
import future.keywords.if
import future.keywords.contains
future.keywords.in
对查找的内容是否在给定的对象或数组中,如:
> import future.keywords.in
>
> m := [10,6,9,8,15,3,12]
Rule 'm' defined in package repl. Type 'show' to see rules.
> m
[
10,
6,
9,
8,
15,
3,
12
]
>
> n := 3
Rule 'n' defined in package repl. Type 'show' to see rules.
>
> n
3
>
> n in m
true
>
future.keywords.every
对遍历的对象或数组,在规则内必须每个都成立,此时结果才为真,如:
> import future.keywords.in
> import future.keywords.every
>
> m := [10,6,9,8,15,3,12]
Rule 'm' defined in package repl. Type 'show' to see rules.
>
> n := 3
Rule 'n' defined in package repl. Type 'show' to see rules.
>
> n in m
true
>
> every x in m {
| x >= n
| }
|
true
>
future.keywords.if
如果规则判断为真,对前变量赋值,如:
> import future.keywords.if
>
> m := [10,6,9,8,15,3,12]
Rule 'm' defined in package repl. Type 'show' to see rules.
>
> n := 3
Rule 'n' defined in package repl. Type 'show' to see rules.
>
> deny if n > m[0]
Rule 'deny' defined in package repl. Type 'show' to see rules.
>
> deny
undefined
>
> deny if n < m[0]
Rule 'deny' defined in package repl. Type 'show' to see rules.
>
> deny
true
>
future.keywords.contains
TODO;
运算符
等号运算符
等号存在三种运算:赋值(:=)、相等(==)和对比赋值(=)。建议尽量使用赋值(:=)和比较(==),以便编写和阅读更容易理解的策略。
赋值运算 “:=” 是将值分配给变量
- 在规则内分配变量的作用域仅限于该规则内,并且会覆盖全局变量
import future.keywords.if
x := 100
p if {
# 这里变量 "x" 仅在该规则内有效,并忽略上面的变量 "x"
x := 1
x != 100
}
- 以下均是错误的,变量赋值过不可重复
import future.keywords.if
x := 100
p if {
# 这里变量 "x" 为以上定义的全局变量,不能在重复赋值,所以出错
x != 100
x := 1
}
p if {
x := 1
x := 2
}
- 从数组中提取值并将其赋值给变量
import future.keywords.if
address := ["3 Abbey Road", "NW8 9AY", "London", "England"]
in_london if {
[_, _, city, country] := address
city == "London"
country == "England"
}
相等运算 “==” 比较在规则内检查两个值是否相等
- 比较两个变量值是否相等
import future.keywords.if
p if {
x := 100
x == 100
}
- 两边对比的变量必须已经定义否则出错
import future.keywords.if
p if {
x == 100
}
对比赋值 “=” 用于比较两边规则如果为真则分别对其赋值
[x, "world"] = ["hello", y]
- 与单独赋值(:=)区别在他们规则内无需按照顺序
import future.keywords.if
s if {
# 这个正常执行
x > y
y = 41
x = 42
}
import future.keywords.if
s if {
# 这个会出错
x > y
y := 41
x := 42
}
对比运算符
也就平常的大于、小于、等于等,如:
a == b # `a` is equal to `b`.
a != b # `a` is not equal to `b`.
a < b # `a` is less than `b`.
a <= b # `a` is less than or equal to `b`.
a > b # `a` is greater than `b`.
a >= b # `a` is greater than or equal to `b`.