types, ast, docs: Add support for named function arg declarations

This commit adds support for named argument declarations for built-in
functions as well as additional metadata/annotations on built-in
functions (e.g., descriptions, categories, etc.) This commit allows us
to generate a data file (builtin_metadata.json) that other tools can
consume to improve the Rego authoring experience.

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>

Co-authored-by: Torin Sandall <torinsandall@gmail.com>
This commit is contained in:
Stephan Renatus
2022-05-10 11:26:51 +02:00
committed by Torin Sandall
parent 953c2fb5c0
commit e8deba62bd
20 changed files with 14440 additions and 1206 deletions
+1106 -864
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -49,7 +49,8 @@ func CapabilitiesForThisVersion() *Capabilities {
f.WasmABIVersions = append(f.WasmABIVersions, WasmABIVersion{Version: vers[0], Minor: vers[1]})
}
f.Builtins = append(f.Builtins, Builtins...)
f.Builtins = make([]*Builtin, len(Builtins))
copy(f.Builtins, Builtins)
sort.Slice(f.Builtins, func(i, j int) bool {
return f.Builtins[i].Name < f.Builtins[j].Name
})
+6 -4
View File
@@ -314,17 +314,19 @@ func (tc *typeChecker) checkExprBuiltin(env *TypeEnv, expr *Expr) *Error {
}
fargs := ftpe.FuncArgs()
namedFargs := ftpe.NamedFuncArgs()
if ftpe.Result() != nil {
fargs.Args = append(fargs.Args, ftpe.Result())
namedFargs.Args = append(namedFargs.Args, ftpe.NamedResult())
}
if len(args) > len(fargs.Args) && fargs.Variadic == nil {
return newArgError(expr.Location, name, "too many arguments", pre, fargs)
return newArgError(expr.Location, name, "too many arguments", pre, namedFargs)
}
if len(args) < len(ftpe.FuncArgs().Args) {
return newArgError(expr.Location, name, "too few arguments", pre, fargs)
return newArgError(expr.Location, name, "too few arguments", pre, namedFargs)
}
for i := range args {
@@ -333,7 +335,7 @@ func (tc *typeChecker) checkExprBuiltin(env *TypeEnv, expr *Expr) *Error {
for i := range args {
post[i] = env.Get(args[i])
}
return newArgError(expr.Location, name, "invalid argument(s)", post, fargs)
return newArgError(expr.Location, name, "invalid argument(s)", post, namedFargs)
}
}
@@ -380,7 +382,7 @@ func (tc *typeChecker) checkExprWith(env *TypeEnv, expr *Expr, i int) *Error {
switch v := valueType.(type) {
case *types.Function: // ...by function
if !unifies(targetType, valueType) {
return newArgError(expr.With[i].Loc(), target.Value.(Ref), "arity mismatch", v.Args(), t.FuncArgs())
return newArgError(expr.With[i].Loc(), target.Value.(Ref), "arity mismatch", v.Args(), t.NamedFuncArgs())
}
default: // ... by value, nothing to check
}
+1 -1
View File
@@ -901,7 +901,7 @@ func arityMismatchError(env *TypeEnv, f Ref, expr *Expr, exp, act int) *Error {
for i, op := range expr.Operands() {
have[i] = env.Get(op)
}
return newArgError(expr.Loc(), f, "arity mismatch", have, want.FuncArgs())
return newArgError(expr.Loc(), f, "arity mismatch", have, want.NamedFuncArgs())
}
if act != 1 {
return NewError(TypeErr, expr.Loc(), "function %v has arity %d, got %d arguments", f, exp, act)
+5 -5
View File
@@ -4541,7 +4541,7 @@ func TestCompilerMockFunction(t *testing.T) {
http_send(_, _) = { "body": "nope" }
p { true with http.send as http_send }
`,
err: "rego_type_error: http.send: arity mismatch\n\thave: (any, any)\n\twant: (object[string: any])",
err: "rego_type_error: http.send: arity mismatch\n\thave: (any, any)\n\twant: (request: object[string: any])",
},
{
note: "invalid ref: arity mismatch (in call)",
@@ -4549,14 +4549,14 @@ func TestCompilerMockFunction(t *testing.T) {
http_send(_, _) = { "body": "nope" }
p { http.send({}) with http.send as http_send }
`,
err: "rego_type_error: http.send: arity mismatch\n\thave: (any, any)\n\twant: (object[string: any])",
err: "rego_type_error: http.send: arity mismatch\n\thave: (any, any)\n\twant: (request: object[string: any])",
},
{
note: "invalid ref: value another built-in with different type",
module: `package test
p { true with http.send as net.lookup_ip_addr }
`,
err: "rego_type_error: http.send: arity mismatch\n\thave: (string)\n\twant: (object[string: any])",
err: "rego_type_error: http.send: arity mismatch\n\thave: (string)\n\twant: (request: object[string: any])",
},
{
note: "ref: value another built-in with compatible type",
@@ -6462,7 +6462,7 @@ func TestQueryCompiler(t *testing.T) {
q: `startswith("x")`,
pkg: "",
imports: nil,
expected: fmt.Errorf("1 error occurred: 1:1: rego_type_error: startswith: arity mismatch\n\thave: (string)\n\twant: (string, string)"),
expected: fmt.Errorf("1 error occurred: 1:1: rego_type_error: startswith: arity mismatch\n\thave: (string)\n\twant: (base: string, search: string)"),
},
{
note: "built-in function arity mismatch (arity 0)",
@@ -6476,7 +6476,7 @@ func TestQueryCompiler(t *testing.T) {
q: "count(sum())",
pkg: "",
imports: nil,
expected: fmt.Errorf("1 error occurred: 1:7: rego_type_error: sum: arity mismatch\n\thave: (???)\n\twant: (any<array[number], set[number]>)"),
expected: fmt.Errorf("1 error occurred: 1:7: rego_type_error: sum: arity mismatch\n\thave: (???)\n\twant: (collection: any<array[number], set[number]>)"),
},
{
note: "check types",
+1
View File
@@ -4,6 +4,7 @@ EXCEPTIONS=(
"internal/compiler/wasm/opa/opa.go"
"internal/compiler/wasm/opa/opa.wasm"
"internal/compiler/wasm/opa/callgraph.csv"
"builtin_metadata.json"
)
STATUS=$(git status --porcelain)
+12816
View File
File diff suppressed because it is too large Load Diff
+9 -4
View File
@@ -8,7 +8,8 @@ DEPLOY_PRIME_URL ?= "http://localhost:8888"
.PHONY: clean
clean:
rm -rf ${CURDIR}/website/data/releases.yaml
rm -f $(CURDIR)/website/data/releases.yaml
rm -f $(CURDIR)/website/data/builtin_metadata.json
rm -rf $(CURDIR)/website/generated
rm -rf $(CURDIR)/website/public
rm -rf $(CURDIR)/website/resources
@@ -19,11 +20,15 @@ generate-cli-docs:
$(CURDIR)/../build/gen-cli-docs.sh "$(CURDIR)/content"
.PHONY: generate
generate: generate-cli-docs
generate: generate-cli-docs copy-builtin-metadata
$(CURDIR)/website/scripts/load-docs.sh
.PHONY: copy-builtin-metadata
copy-builtin-metadata:
cp -v $(CURDIR)/../builtin_metadata.json $(CURDIR)/website/data/builtin_metadata.json
.PHONY: dev-generate
dev-generate: generate-cli-docs
dev-generate: generate-cli-docs copy-builtin-metadata
DEV=true $(CURDIR)/website/scripts/load-docs.sh
# The website has some npm dependencies saved in ./website/node_modules
@@ -53,7 +58,7 @@ serve-remote: production-build
cd $(CURDIR)/.. && netlify deploy
.PHONY: dev-build
dev-build: clean dev-generate hugo-production-build live-blocks-inject
dev-build: clean dev-generate copy-builtin-metadata hugo-production-build live-blocks-inject
######################################################
#
@@ -32,16 +32,17 @@ The following example adds a simple built-in function, `repeat(string, int)`, th
In `ast/builtins.go`, we declare the structure of our built-in function with a `Builtin` struct instance:
```go
// Repeat returns, as a string, the given string repeated the given number of times.
var Repeat = &Builtin{
Name: "repeat", // The name of the function
Decl: types.NewFunction(
types.Args( // The built-in takes two arguments, where ..
types.S, // .. the first is a string, and ..
types.N, // .. the second is a number.
),
types.S, // The return type is a string.
),
Name: "repeat", // The name of the function
Description: "Returns, as a string, the given string repeated the given number of times.",
Decl: types.NewFunction(
types.Args( // The built-in takes two arguments, where ..
types.Named("str", types.S).Description("string to repeat"), // named string argument
types.Named("count", types.N).Description("how often to repeat `str`"), // named number argument
),
types.Named("output", types.S).Description("the repetitions"), // The return type is a string.
),
Categories: category("strings"), // the category the built-in belongs to
}
```
@@ -142,24 +143,16 @@ The above test cases can be run separate from all other tests through: `go test
See [test/cases/testdata/helloworld](https://github.com/open-policy-agent/opa/blob/main/test/cases/testdata/helloworld)
for a more detailed example of how to implement tests for your built-in functions.
> Note: We can manually test our new built-in function by [building](../contrib-development#getting-started)
> and running the `eval` command. E.g.: `$./opa_<OS>_<ARCH> eval 'repeat("Foo", 3)'`
{{< info >}}
Note: We can manually test our new built-in function by [building](../contrib-development#getting-started)
and running the `eval` command. E.g.: `$./opa_<OS>_<ARCH> eval 'repeat("Foo", 3)'`
{{< /info >}}
### Document
All built-in functions must be documented in `docs/content/policy-reference.md` under an appropriate subsection.
All built-in functions will automatically be documented in `docs/content/policy-reference.md` under an appropriate subsection.
For this example, we add an entry for our new function under the `Strings` section:
```markdown
### Strings
| Built-in | Description | Wasm Support |
| ------- |-------------|---------------|
...
| <span class="opa-keep-it-together">``output := repeat(string, count)``</span> | ``output`` is ``string`` repeated ``count``times | ``SDK-dependent`` |
...
```
For this example, we'll get an entry for our new function under the `Strings` section.
### Add a capability
+50 -280
View File
@@ -282,73 +282,13 @@ The built-in functions for the language provide basic operations to manipulate
scalar values (e.g. numbers and strings), and aggregate functions that summarize
complex types.
### Comparison
{{< builtin-table comparison >}}
{{< builtin-table numbers >}}
{{< builtin-table aggregates >}}
{{< builtin-table cat=array id=arrays-2 title=arrays >}}
{{< builtin-table cat=sets id=sets-2 >}}
| Built-in | Description | Wasm Support |
| ------- |-------------|---------------|
| <span class="opa-keep-it-together">``x == y``</span> | ``x`` is equal to ``y`` | ✅ |
| <span class="opa-keep-it-together">``x != y``</span> | ``x`` is not equal to ``y`` | ✅ |
| <span class="opa-keep-it-together">``x < y``</span> | ``x`` is less than ``y`` | ✅ |
| <span class="opa-keep-it-together">``x <= y``</span> | ``x`` is less than or equal to ``y`` | ✅ |
| <span class="opa-keep-it-together">``x > y``</span> | ``x`` is greater than ``y`` | ✅ |
| <span class="opa-keep-it-together">``x >= y``</span> | ``x`` is greater than or equal to ``y`` | ✅ |
### Numbers
| Built-in | Description | Wasm Support |
| ------- |-------------|---------------|
| <span class="opa-keep-it-together">``z := x + y``</span> | ``z`` is the sum of ``x`` and ``y`` | ✅ |
| <span class="opa-keep-it-together">``z := x - y``</span> | ``z`` is the difference of ``x`` and ``y`` | ✅ |
| <span class="opa-keep-it-together">``z := x * y``</span> | ``z`` is the product of ``x`` and ``y`` | ✅ |
| <span class="opa-keep-it-together">``z := x / y``</span> | ``z`` is the quotient of ``x`` and ``y`` | ✅ |
| <span class="opa-keep-it-together">``z := x % y``</span> | ``z`` is the remainder from the division of ``x`` and ``y`` | ✅ |
| <span class="opa-keep-it-together">``output := round(x)``</span> | ``output`` is ``x`` rounded to the nearest integer | ✅ |
| <span class="opa-keep-it-together">``output := ceil(x)``</span> | ``output`` is ``x`` rounded up to the nearest integer | ✅ |
| <span class="opa-keep-it-together">``output := floor(x)``</span> | ``output`` is ``x`` rounded down the nearest integer | ✅ |
| <span class="opa-keep-it-together">``output := abs(x)``</span> | ``output`` is the absolute value of ``x`` | ✅ |
| <span class="opa-keep-it-together">``output := numbers.range(a, b)``</span> | ``output`` is the range of integer numbers between ``a`` and ``b`` (inclusive). If ``a`` == ``b`` then ``output`` == ``[a]``. If ``a`` < ``b`` the range is in ascending order. If ``a`` > ``b`` the range is in descending order. | ✅ |
<span class="opa-keep-it-together">``output := rand.intn(str, n)``</span> | ``output`` is a ``number`` in the range [0, abs(``n``)). If ``n`` is 0, then ``output`` is 0. For any given (``str``, ``n``) pair the output will be consistent throughout a query evaluation. | SDK-dependent |
### Aggregates
| Built-in | Description | Wasm Support |
| ------- |-------------|---------------|
| <span class="opa-keep-it-together">``output := count(collection_or_string)``</span> | ``output`` is the length of the object, array, set, or string provided as input | ✅ |
| <span class="opa-keep-it-together">``output := sum(array_or_set)``</span> | ``output`` is the sum of the numbers in ``array_or_set`` | ✅ |
| <span class="opa-keep-it-together">``output := product(array_or_set)``</span> | ``output`` is the product of the numbers in ``array_or_set`` | ✅ |
| <span class="opa-keep-it-together">``output := max(array_or_set)``</span> | ``output`` is the maximum value in ``array_or_set`` | ✅ |
| <span class="opa-keep-it-together">``output := min(array_or_set)``</span> | ``output`` is the minimum value in ``array_or_set`` | ✅ |
| <span class="opa-keep-it-together">``output := sort(array_or_set)``</span> | ``output`` is the sorted ``array`` containing elements from ``array_or_set``. | ✅ |
### Arrays
| Built-in | Description | Wasm Support |
| ------- |-------------|---------------|
| <span class="opa-keep-it-together">``output := array.concat(array, array)``</span> | ``output`` is the result of concatenating the two input arrays together. | ✅ |
| <span class="opa-keep-it-together">``output := array.reverse(array)``</span> | ``output`` is the result of reversing the order of the elements in ``array``. | ✅ |
<span class="opa-keep-it-together">``output := array.slice(array, startIndex, stopIndex)``</span> | ``output`` is the part of the ``array`` from ``startIndex`` to ``stopIndex`` including the first but excluding the last. If `startIndex >= stopIndex` then `output == []`. If both `startIndex` and `stopIndex` are less than zero, `output == []`. Otherwise, `startIndex` and `stopIndex` are clamped to 0 and `count(array)` respectively. | ✅ |
### Sets
| Built-in | Description | Wasm Support |
| ------- |-------------|---------------|
| <span class="opa-keep-it-together">``s3 := s1 & s2``</span> | ``s3`` is the intersection of ``s1`` and ``s2``. | ✅ |
| <span class="opa-keep-it-together"><code>s3 := s1 &#124; s2</code></span> | ``s3`` is the union of ``s1`` and ``s2``. | ✅ |
| <span class="opa-keep-it-together">``s3 := s1 - s2``</span> | ``s3`` is the difference between ``s1`` and ``s2``, i.e., the elements in ``s1`` that are not in ``s2`` | ✅ |
| <span class="opa-keep-it-together">``output := intersection(set[set])``</span> | ``output`` is the intersection of the sets in the input set | ✅ |
| <span class="opa-keep-it-together">``output := union(set[set])``</span> | ``output`` is the union of the sets in the input set | ✅ |
### Objects
| Built-in | Description | Wasm Support |
| ------- |-------------|---------------|
| <span class="opa-keep-it-together">`value := object.get(object, key, default)`</span> | `value` is the value stored by the `object` at `key`. If no value is found, `default` is returned. If the supplied `key` is an `array`, then `object.get` will search through a nested object or array using each key in turn. For example: `object.get({"a": [{ "b": true }]}, ["a", 0, "b"], false)` results in `true` | ✅ |
| <span class="opa-keep-it-together">`output := object.remove(object, keys)`</span> | `output` is a new object which is the result of removing the specified `keys` from `object`. `keys` must be either an array, object, or set of keys. | ✅ |
| <span class="opa-keep-it-together">`output := object.union(objectA, objectB)`</span> | `output` is a new object which is the result of an asymmetric recursive union of two objects where conflicts are resolved by choosing the key from the right-hand object (`objectB`). For example: `object.union({"a": 1, "b": 2, "c": {"d": 3}}, {"a": 7, "c": {"d": 4, "e": 5}})` will result in `{"a": 7, "b": 2, "c": {"d": 4, "e": 5}}` | ✅ |
| <span class="opa-keep-it-together">`output := object.union_n(array)`</span> | `output` is a new object which is the result of an asymmetric recursive union of all objects in `array`, merged from left to right, where conflicts are resolved by choosing the key from the right-hand object. For example: `object.union_n([{"a": 1}, {"b": 2}, {"a": 3}])` will result in `{"b": 2, "a": 3}` | ``SDK-dependent`` |
| <span class="opa-keep-it-together">`filtered := object.filter(object, keys)`</span> | `filtered` is a new object with the remaining data from `object` with only keys specified in `keys` which is an array, object, or set of keys. For example: `object.filter({"a": {"b": "x", "c": "y"}, "d": "z"}, ["a"])` will result in `{"a": {"b": "x", "c": "y"}}`). | ✅ |
| <span class="opa-keep-it-together">`filtered := json.filter(object, paths)`</span> | `filtered` is the remaining data from `object` with only keys specified in `paths` which is an array or set of JSON string paths. For example: `json.filter({"a": {"b": "x", "c": "y"}}, ["a/b"])` will result in `{"a": {"b": "x"}}`). Paths are not filtered in-order and are deduplicated before being evaluated. | ✅ |
| <span class="opa-keep-it-together">`output := json.remove(object, paths)`</span> | `output` is a new object which is the result of removing all keys specified in `paths` which is an array or set of JSON string paths. For example: `json.remove({"a": {"b": "x", "c": "y"}}, ["a/b"])` will result in `{"a": {"c": "y"}}`. Paths are not removed in-order and are deduplicated before being evaluated. | ✅ |
| <span class="opa-keep-it-together">`output := json.patch(object, patches)`</span> | `output` is a the object obtained after consecutively applying all [JSON Patch](https://tools.ietf.org/html/rfc6902) operations in the array `patches`. For example: `json.patch({"a": {"foo": 1}}, [{"op": "add", "path": "/a/bar", "value": 2}])` results in `{"a": {"foo": 1, "bar": 2}`. The patches are applied atomically: if any of them fails, the result will be undefined. | ``SDK-dependent`` |
{{< builtin-table cat=object title=objects >}}
* When `keys` are provided as an object only the top level keys on the object will be used, values are ignored.
For example: `object.remove({"a": {"b": {"c": 2}}, "x": 123}, {"a": 1}) == {"x": 123}` regardless of the value
@@ -370,48 +310,10 @@ complex types.
the path `a/b/c` can be passed in as `["a", "b", "c"]`.
### Strings
{{< builtin-table strings >}}
{{< builtin-table regex >}}
| Built-in | Description | Wasm Support |
| ------- |-------------|---------------|
| <span class="opa-keep-it-together">``output := concat(delimiter, array_or_set)``</span> | ``output`` is the result of joining together the elements of ``array_or_set`` with the string ``delimiter`` | ✅ |
| <span class="opa-keep-it-together">``contains(string, search)``</span> | true if ``string`` contains ``search`` | ✅ |
| <span class="opa-keep-it-together">``endswith(string, search)``</span> | true if ``string`` ends with ``search`` | ✅ |
| <span class="opa-keep-it-together">``output := format_int(number, base)``</span> | ``output`` is string representation of ``number`` in the given ``base`` | ✅ |
| <span class="opa-keep-it-together">``output := indexof(string, search)``</span> | ``output`` is the index inside ``string`` where ``search`` first occurs, or -1 if ``search`` does not exist | ✅ |
| <span class="opa-keep-it-together">``output := indexof_n(string, search)``</span> | ``output`` is ``array[number]`` representing the indexes inside ``string`` where ``search`` occurs | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := lower(string)``</span> | ``output`` is ``string`` after converting to lower case | ✅ |
| <span class="opa-keep-it-together">``output := replace(string, old, new)``</span> | ``output`` is a ``string`` representing ``string`` with all instances of ``old`` replaced by ``new`` | ✅ |
| <span class="opa-keep-it-together">``output := strings.reverse(string)``</span> | ``output`` is ``string`` reversed | ✅ |
| <span class="opa-keep-it-together">``output := strings.replace_n(patterns, string)``</span> | ``patterns`` is an object with old, new string key value pairs (e.g. ``{"old1": "new1", "old2": "new2", ...}``). ``output`` is a ``string`` with all old strings inside ``patterns`` replaced by the new strings | ✅ |
| <span class="opa-keep-it-together">``output := split(string, delimiter)``</span> | ``output`` is ``array[string]`` representing elements of ``string`` separated by ``delimiter`` | ✅ |
| <span class="opa-keep-it-together">``output := sprintf(string, values)``</span> | ``output`` is a ``string`` representing ``string`` formatted by the values in the ``array`` ``values``. | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``startswith(string, search)``</span> | true if ``string`` begins with ``search`` | ✅ |
| <span class="opa-keep-it-together">``output := substring(string, start, length)``</span> | ``output`` is the portion of ``string`` from index ``start`` and having a length of ``length``. If ``length`` is less than zero, ``length`` is the remainder of the ``string``. If ``start`` is greater than the length of the string, ``output`` is empty. It is invalid to pass a negative offset to this function. | ✅ |
| <span class="opa-keep-it-together">``output := trim(string, cutset)``</span> | ``output`` is a ``string`` representing ``string`` with all leading and trailing instances of the characters in ``cutset`` removed. | ✅ |
| <span class="opa-keep-it-together">``output := trim_left(string, cutset)``</span> | ``output`` is a ``string`` representing ``string`` with all leading instances of the characters in ``cutset`` removed. | ✅ |
| <span class="opa-keep-it-together">``output := trim_prefix(string, prefix)``</span> | ``output`` is a ``string`` representing ``string`` with leading instance of ``prefix`` removed. If ``string`` doesn't start with prefix, ``string`` is returned unchanged.| ✅ |
| <span class="opa-keep-it-together">``output := trim_right(string, cutset)``</span> | ``output`` is a ``string`` representing ``string`` with all trailing instances of the characters in ``cutset`` removed. | ✅ |
| <span class="opa-keep-it-together">``output := trim_suffix(string, suffix)``</span> | ``output`` is a ``string`` representing ``string`` with trailing instance of ``suffix`` removed. If ``string`` doesn't end with suffix, ``string`` is returned unchanged.| ✅ |
| <span class="opa-keep-it-together">``output := trim_space(string)``</span> | ``output`` is a ``string`` representing ``string`` with all leading and trailing white space removed.| ✅ |
| <span class="opa-keep-it-together">``output := upper(string)``</span> | ``output`` is ``string`` after converting to upper case | ✅ |
### Regex
| Built-in | Description | Wasm Support |
| ------- |-------------|---------------|
| <span class="opa-keep-it-together">``output := regex.match(pattern, value)``</span> | ``output`` is a ``boolean`` that indicates if ``value`` matches the regex ``pattern``. | ✅ |
| <span class="opa-keep-it-together">``output := regex.is_valid(pattern)``</span> | ``output`` is a ``boolean`` that indicates if ``pattern`` is a valid regex pattern. The detailed syntax for regex patterns is defined by https://github.com/google/re2/wiki/Syntax. | ✅ |
| <span class="opa-keep-it-together">``output := regex.split(pattern, string)``</span> | ``output`` is ``array[string]`` representing elements of ``string`` separated by ``pattern`` | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``regex.globs_match(glob1, glob2)``</span> | true if the intersection of regex-style globs ``glob1`` and ``glob2`` matches a non-empty set of non-empty strings. The set of regex symbols is limited for this builtin: only ``.``, ``*``, ``+``, ``[``, ``-``, ``]`` and ``\`` are treated as special symbols. | ``SDK-dependent`` |
| <span class="opa-keep-it-normal">``output := regex.template_match(pattern, string, delimiter_start, delimiter_end)``</span> | ``output`` is true if ``string`` matches ``pattern``. ``pattern`` is a string containing ``0..n`` regular expressions delimited by ``delimiter_start`` and ``delimiter_end``. Example ``regex.template_match("urn:foo:{.*}", "urn:foo:bar:baz", "{", "}")`` returns ``true``. | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := regex.find_n(pattern, string, number)``</span> | ``output`` is an ``array[string]`` with the ``number`` of values matching the ``pattern``. A ``number`` of ``-1`` means all matches. | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := regex.find_all_string_submatch_n(pattern, string, number)``</span> | ``output`` is an ``array[array[string]]`` with the outer `array` including a ``number`` of matches which match the ``pattern``. A ``number`` of ``-1`` means all matches. | ✅ |
### Glob
| Built-in | Description | Wasm Support |
| ------- |-------------|---------------|
| <span class="opa-keep-it-together">``output := glob.match(pattern, delimiters, match)``</span> | ``output`` is true if ``match`` can be found in ``pattern`` which is separated by ``delimiters``. For valid patterns, check the table below. Argument ``delimiters`` is an array of single-characters (e.g. `[".", ":"]`). If ``delimiters`` is empty, it defaults to ``["."]``. | ✅ |
| <span class="opa-keep-it-together">``output := glob.quote_meta(pattern)``</span> | ``output`` is the escaped string of ``pattern``. Calling ``glob.quote_meta("*.github.com", output)`` returns ``\\*.github.com`` as ``output``. | ``SDK-dependent`` |
{{< builtin-table glob >}}
The following table shows examples of how ``glob.match`` works:
@@ -438,66 +340,13 @@ The following table shows examples of how ``glob.match`` works:
| ``output := glob.match("{cat,bat,[fr]at}", [], "rat")`` | ``true`` | A glob with pattern-alternatives matchers. |
| ``output := glob.match("{cat,bat,[fr]at}", [], "at")`` | ``false`` | A glob with pattern-alternatives matchers. |
### Bitwise
{{< builtin-table cat=bits title=bitwise >}}
{{< builtin-table conversions >}}
{{< builtin-table units >}}
{{< builtin-table types >}}
{{< builtin-table encoding >}}
| Built-in | Description | Wasm Support |
| ------- |-------------|---------------|
| <span class="opa-keep-it-together">``z := bits.or(x, y)``</span> | ``z`` is the bitwise or of integers ``x`` and ``y`` | ✅ |
| <span class="opa-keep-it-together">``z := bits.and(x, y)``</span> | ``z`` is the bitwise and of integers ``x`` and ``y`` | ✅ |
| <span class="opa-keep-it-together">``z := bits.negate(x)``</span> | ``z`` is the bitwise negation (flip) of integer ``x`` | ✅ |
| <span class="opa-keep-it-together">``z := bits.xor(x, y)``</span> | ``z`` is the bitwise exclusive-or of integers ``x`` and ``y`` | ✅ |
| <span class="opa-keep-it-together">``z := bits.lsh(x, s)``</span> | ``z`` is the bitshift of integer ``x`` by ``s`` bits to the left | ✅ |
| <span class="opa-keep-it-together">``z := bits.rsh(x, s)``</span> | ``z`` is the bitshift of integer ``x`` by ``s`` bits to the right | ✅ |
### Conversions
| Built-in | Description | Wasm Support |
| ------- |-------------|---------------|
| <span class="opa-keep-it-together">``output := to_number(x)``</span> | ``output`` is ``x`` converted to a number. `null` is converted to zero, `true` and `false` are converted to one and zero (respectively), `string` values are interpreted as base 10, and `numbers` are a no-op. Other types are not supported. | ✅ |
### Units
| Built-in | Description | Wasm Support |
| ------- |-------------|---------------|
| <span class="opa-keep-it-together">``output := units.parse(x)``</span> | ``output`` is ``x`` converted to a number with support for standard metric decimal and binary SI units (e.g., K, Ki, M, Mi, G, Gi etc.) m, K, M, G, T, P, and E are treated as decimal units and Ki, Mi, Gi, Ti, Pi, and Ei are treated as binary units. Note that 'm' and 'M' are case-sensitive, to allow distinguishing between "milli" and "mega" units respectively. Other units are case-insensitive. | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := units.parse_bytes(x)``</span> | ``output`` is ``x`` converted to a number with support for standard byte units (e.g., KB, KiB, etc.) KB, MB, GB, and TB are treated as decimal units and KiB, MiB, GiB, and TiB are treated as binary units. The bytes symbol (b/B) in the unit is optional and omitting it wil give the same result (e.g. Mi and MiB) | ``SDK-dependent`` |
### Types
| Built-in | Description | Wasm Support |
| ------- |-------------|---------------|
| <span class="opa-keep-it-together">``output := is_number(x)``</span> | ``output`` is ``true`` if ``x`` is a number; otherwise undefined| ✅ |
| <span class="opa-keep-it-together">``output := is_string(x)``</span> | ``output`` is ``true`` if ``x`` is a string; otherwise undefined | ✅ |
| <span class="opa-keep-it-together">``output := is_boolean(x)``</span> | ``output`` is ``true`` if ``x`` is a boolean; otherwise undefined | ✅ |
| <span class="opa-keep-it-together">``output := is_array(x)``</span> | ``output`` is ``true`` if ``x`` is an array; otherwise undefined | ✅ |
| <span class="opa-keep-it-together">``output := is_set(x)``</span> | ``output`` is ``true`` if ``x`` is a set; otherwise undefined | ✅ |
| <span class="opa-keep-it-together">``output := is_object(x)``</span> | ``output`` is ``true`` if ``x`` is an object; otherwise undefined | ✅ |
| <span class="opa-keep-it-together">``output := is_null(x)``</span> | ``output`` is ``true`` if ``x`` is null; otherwise undefined | ✅ |
| <span class="opa-keep-it-together">``output := type_name(x)``</span> | ``output`` is the type of ``x`` (e.g. ``"number"``, ``"boolean"``, ...) | ✅ |
### Encoding
| Built-in | Description | Wasm Support |
| ------- |-------------|---------------|
| <span class="opa-keep-it-together">``output := base64.encode(x)``</span> | ``output`` is ``x`` serialized to a base64 encoded string | ✅ |
| <span class="opa-keep-it-together">``output := base64.decode(string)``</span> | ``output`` is ``x`` deserialized from a base64 encoding string | ✅ |
| <span class="opa-keep-it-together">``output := base64url.encode(x)``</span> | ``output`` is ``x`` serialized to a base64url encoded string | ✅ |
| <span class="opa-keep-it-together">``output := base64url.encode_no_pad(x)``</span> | ``output`` is ``x`` serialized to a base64url encoded string without padding | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := base64url.decode(string)``</span> | ``output`` is ``string`` deserialized from a base64url encoded string with or without padding | ✅ |
| <span class="opa-keep-it-together">``output := urlquery.encode(string)``</span> | ``output`` is URL query parameter encoded ``string`` | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := urlquery.encode_object(object)``</span> | ``output`` is URL query parameter encoded ``object`` | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := urlquery.decode(string)``</span> | ``output`` is URL query parameter decoded ``string`` | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := urlquery.decode_object(string)``</span> | ``output`` is URL query parameter decoded ``string`` represented as an ``object`` | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := json.marshal(x)``</span> | ``output`` is ``x`` serialized to a JSON string | ✅ |
| <span class="opa-keep-it-together">``output := json.unmarshal(string)``</span> | ``output`` is ``string`` deserialized to a term from a JSON encoded string | ✅ |
| <span class="opa-keep-it-together">``output := json.is_valid(string)``</span> | ``output`` is a ``boolean`` that indicated whether ``string`` is a valid JSON document | ✅ |
| <span class="opa-keep-it-together">``output := yaml.marshal(x)``</span> | ``output`` is ``x`` serialized to a YAML string | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := yaml.unmarshal(string)``</span> | ``output`` is ``string`` deserialized to a term from YAML encoded string | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := yaml.is_valid(string)``</span> | ``output`` is a ``boolean`` that indicated whether ``string`` is a valid YAML document that can be decoded by `yaml.unmarshal` | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := hex.encode(x)``</span> | ``output`` is ``x`` serialized to a hex encoded string | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := hex.decode(string)``</span> | ``output`` is a ``string`` deserialized from a hex encoded string | ``SDK-dependent`` |
### Token Signing
{{< builtin-table cat=tokensign title="Token Signing" >}}
OPA provides two builtins that implement JSON Web Signature [RFC7515](https://tools.ietf.org/html/rfc7515) functionality.
@@ -509,10 +358,12 @@ OPA provides two builtins that implement JSON Web Signature [RFC7515](https://to
``io.jwt.encode_sign()`` takes three Rego Objects as parameters and returns their JWS Compact Serialization. This builtin
should be used by those that want to use rego objects for signing during policy evaluation.
> Note that with `io.jwt.encode_sign` the Rego objects are serialized to JSON with standard formatting applied
> whereas the `io.jwt.encode_sign_raw` built-in will **not** affect whitespace of the strings passed in.
> This will mean that the final encoded token may have different string values, but the decoded and parsed
> JSON will match.
{{< info >}}
Note that with `io.jwt.encode_sign` the Rego objects are serialized to JSON with standard formatting applied
whereas the `io.jwt.encode_sign_raw` built-in will **not** affect whitespace of the strings passed in.
This will mean that the final encoded token may have different string values, but the decoded and parsed
JSON will match.
{{< /info >}}
The following algorithms are supported:
@@ -529,15 +380,11 @@ The following algorithms are supported:
RS384 "RS384" // RSASSA-PKCS-v1.5 using SHA-384
RS512 "RS512" // RSASSA-PKCS-v1.5 using SHA-512
<br>
| Built-in | Description | Wasm Support |
| ------- |-------------|---------------|
| <span class="opa-keep-it-together">``output := io.jwt.encode_sign_raw(headers, payload, key)``</span> | ``headers``, ``payload`` and ``key`` as strings that represent the JWS Protected Header, JWS Payload and JSON Web Key ([RFC7517](https://tools.ietf.org/html/rfc7517)) respectively.| ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := io.jwt.encode_sign(headers, payload, key)``</span> | ``headers``, ``payload`` and ``key`` are JSON objects that represent the JWS Protected Header, JWS Payload and JSON Web Key ([RFC7517](https://tools.ietf.org/html/rfc7517)) respectively.| ``SDK-dependent`` |
> Note that the key's provided should be base64 encoded (without padding) as per the specification ([RFC7517](https://tools.ietf.org/html/rfc7517)).
> This differs from the plain text secrets provided with the algorithm specific verify built-ins described below.
{{< info >}}
Note that the key's provided should be base64 encoded (without padding) as per the specification ([RFC7517](https://tools.ietf.org/html/rfc7517)).
This differs from the plain text secrets provided with the algorithm specific verify built-ins described below.
{{< /info >}}
#### Token Signing Examples
@@ -628,27 +475,12 @@ io.jwt.encode_sign_raw(
```live:jwt/raw:output
```
### Token Verification
{{< builtin-table cat=tokens title="Token Verification" >}}
| Built-in | Description | Wasm Support |
| ------- |-------------|---------------|
| <span class="opa-keep-it-together">``output := io.jwt.verify_rs256(string, certificate)``</span> | ``output`` is ``true`` if the RS256 signature of the input token is valid. ``certificate`` is the PEM encoded certificate, PEM encoded public key, or the JWK key (set) used to verify the RS256 signature| ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := io.jwt.verify_rs384(string, certificate)``</span> | ``output`` is ``true`` if the RS384 signature of the input token is valid. ``certificate`` is the PEM encoded certificate, PEM encoded public key, or the JWK key (set) used to verify the RS384 signature| ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := io.jwt.verify_rs512(string, certificate)``</span> | ``output`` is ``true`` if the RS512 signature of the input token is valid. ``certificate`` is the PEM encoded certificate, PEM encoded public key, or the JWK key (set) used to verify the RS512 signature| ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := io.jwt.verify_ps256(string, certificate)``</span> | ``output`` is ``true`` if the PS256 signature of the input token is valid. ``certificate`` is the PEM encoded certificate, PEM encoded public key or the JWK key (set) used to verify the PS256 signature| ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := io.jwt.verify_ps384(string, certificate)``</span> | ``output`` is ``true`` if the PS384 signature of the input token is valid. ``certificate`` is the PEM encoded certificate, PEM encoded public key or the JWK key (set) used to verify the PS384 signature| ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := io.jwt.verify_ps512(string, certificate)``</span> | ``output`` is ``true`` if the PS512 signature of the input token is valid. ``certificate`` is the PEM encoded certificate, PEM encoded public key or the JWK key (set) used to verify the PS512 signature| ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := io.jwt.verify_es256(string, certificate)``</span> | ``output`` is ``true`` if the ES256 signature of the input token is valid. ``certificate`` is the PEM encoded certificate, PEM encoded public key or the JWK key (set) used to verify the ES256 signature| ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := io.jwt.verify_es384(string, certificate)``</span> | ``output`` is ``true`` if the ES384 signature of the input token is valid. ``certificate`` is the PEM encoded certificate, PEM encoded public key or the JWK key (set) used to verify the ES384 signature| ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := io.jwt.verify_es512(string, certificate)``</span> | ``output`` is ``true`` if the ES512 signature of the input token is valid. ``certificate`` is the PEM encoded certificate, PEM encoded public key or the JWK key (set) used to verify the ES512 signature| ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := io.jwt.verify_hs256(string, secret)``</span> | ``output`` is ``true`` if the Secret signature of the input token is valid. ``secret`` is a plain text secret used to verify the HS256 signature| ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := io.jwt.verify_hs384(string, secret)``</span> | ``output`` is ``true`` if the Secret signature of the input token is valid. ``secret`` is a plain text secret used to verify the HS384 signature| ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := io.jwt.verify_hs512(string, secret)``</span> | ``output`` is ``true`` if the Secret signature of the input token is valid. ``secret`` is a plain text secret used to verify the HS512 signature| ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := io.jwt.decode(string)``</span> | ``output`` is of the form ``[header, payload, sig]``. ``header`` and ``payload`` are ``object``. ``sig`` is the hexadecimal representation of the signature on the token. | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := io.jwt.decode_verify(string, constraints)``</span> | ``output`` is of the form ``[valid, header, payload]``. If the input token verifies and meets the requirements of ``constraints`` then ``valid`` is ``true`` and ``header`` and ``payload`` are objects containing the JOSE header and the JWT claim set. Otherwise, ``valid`` is ``false`` and ``header`` and ``payload`` are ``{}``. Supports the following algorithms: HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512, PS256, PS384 and PS512. | ``SDK-dependent`` |
> Note that the `io.jwt.verify_XX` built-in methods verify **only** the signature. They **do not** provide any validation for the JWT
> payload and any claims specified. The `io.jwt.decode_verify` built-in will verify the payload and **all** standard claims.
{{< info >}}
Note that the `io.jwt.verify_XX` built-in methods verify **only** the signature. They **do not** provide any validation for the JWT
payload and any claims specified. The `io.jwt.decode_verify` built-in will verify the payload and **all** standard claims.
{{< /info >}}
The input `string` is a JSON Web Token encoded with JWS Compact Serialization. JWE and JWS JSON Serialization are not supported. If nested signing was used, the ``header``, ``payload`` and ``signature`` will represent the most deeply nested token.
@@ -800,22 +632,12 @@ result_valid_hs256 := io.jwt.verify_hs256(result_hs256, "foo")
> `io.jwt.encode_sign_raw` does not change the whitespace of the strings passed
> in. The decoded and parsed JSON values are still the same.
### Time
{{< builtin-table time >}}
| Built-in | Description | Wasm Support |
| ------- |-------------|---------------|
| <span class="opa-keep-it-together">``output := time.now_ns()``</span> | ``output`` is a ``number`` representing the current time since epoch in nanoseconds. | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := time.parse_ns(layout, value)``</span> | ``output`` is a ``number`` representing the time ``value`` in nanoseconds since epoch; or ``undefined`` if outside the valid time range that can fit within an ``int64``. See the [Go `time` package documentation](https://golang.org/pkg/time/#Parse) for more details on ``layout``. | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := time.parse_rfc3339_ns(value)``</span> | ``output`` is a ``number`` representing the time ``value`` in nanoseconds since epoch; or ``undefined`` if outside the valid time range that can fit within an ``int64``. | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := time.parse_duration_ns(duration)``</span> | ``output`` is a ``number`` representing the duration ``duration`` in nanoseconds. See the [Go `time` package documentation](https://golang.org/pkg/time/#ParseDuration) for more details on ``duration``. | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := time.date(ns)``<br/>``output := time.date([ns, tz])``</span> | ``output`` is of the form ``[year, month, day]``, which includes the ``year``, ``month`` (0-12), and ``day`` (0-31) as ``number``s representing the date from the nanoseconds since epoch (``ns``) in the timezone (``tz``), if supplied, or as UTC.| ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := time.clock(ns)``<br/>``output := time.clock([ns, tz])``</span> | ``output`` is of the form ``[hour, minute, second]``, which outputs the ``hour``, ``minute`` (0-59), and ``second`` (0-59) as ``number``s representing the time of day for the nanoseconds since epoch (``ns``) in the timezone (``tz``), if supplied, or as UTC. | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``day := time.weekday(ns)``<br/>``day := time.weekday([ns, tz])``</span> | outputs the ``day`` as ``string`` representing the day of the week for the nanoseconds since epoch (``ns``) in the timezone (``tz``), if supplied, or as UTC. | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := time.add_date(ns, years, months, days)``</span> | ``output`` is a ``number`` representing the time since epoch in nanoseconds after adding the ``years``, ``months`` and ``days`` to ``ns``; or ``undefined`` if outside the valid time range that can fit within an ``int64``. See the [Go `time` package documentation](https://golang.org/pkg/time/#Time.AddDate) for more details on ``add_date``. | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := time.diff(ns1, ns2)``<br/>``output := time.diff([ns1, tz1], [ns2, tz2])``</span> | ``output`` is of the form ``[year(s), month(s), day(s), hour(s), minute(s), second(s)]``, which outputs ``year(s)``, ``month(s)`` (0-11), ``day(s)`` (0-30), ``hour(s)``(0-23), ``minute(s)``(0-59) and ``second(s)``(0-59) as ``number``s representing the difference between the the two timestamps in nanoseconds since epoch (``ns1`` and ``ns2``), in the timezones (``tz1`` and ``tz2``, respectively), if supplied, or as UTC. | ``SDK-dependent`` |
> Multiple calls to the `time.now_ns` built-in function within a single policy
{{< info >}}
Multiple calls to the `time.now_ns` built-in function within a single policy
evaluation query will always return the same value.
{{< /info >}}
Timezones can be specified as
@@ -823,31 +645,11 @@ Timezones can be specified as
* "UTC" or "", which are equivalent to not passing a timezone (i.e. will return as UTC)
* "Local", which will use the local timezone.
Note that the opa executable will need access to the timezone files in the environment it is running in (see the [Go time.LoadLocation()](https://golang.org/pkg/time/#LoadLocation) documentation for more information).
Note that the opa executable will need access to the timezone files in the environment it is running in (see the [Go `time.LoadLocation()`](https://pkg.go.dev/time#LoadLocation) documentation for more information).
### Cryptography
{{< builtin-table cat=crypto title=Cryptography >}}
| Built-in | Description | Wasm Support |
| ------- |-------------|---------------|
| <span class="opa-keep-it-together">``output := crypto.x509.parse_certificates(certs)``</span> | ``certs`` is base64 encoded DER or PEM data containing one or more certificates or a PEM string of one or more certificates. ``output`` is an array of X.509 certificates represented as JSON objects. | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := crypto.x509.parse_and_verify_certificates(certs)``</span> | ``certs`` is base64 encoded DER or PEM data containing two or more certificates where the first is a root CA, the last is a leaf certificate, and all others are intermediate CAs. ``output`` is of the form ``[valid, certs]``. If the input certificate chain could be verified then ``valid`` is ``true`` and ``certs`` is an array of X.509 certificates represented as JSON objects. If the input certificate chain could not be verified then ``valid`` is ``false`` and ``certs`` is ``[]``. | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := crypto.x509.parse_certificate_request(csr)``</span> | ``csr`` is a base64 string containing either a PEM encoded or DER CSR or a string containing a PEM CSR.``output`` is an X.509 CSR represented as a JSON object. | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := crypto.x509.parse_rsa_private_key(pem)``</span> | ``pem`` is a base64 string containing a PEM encoded RSA private key.``output`` is a JWK as a JSON object. | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := crypto.md5(string)``</span> | ``output`` is ``string`` md5 hashed. | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := crypto.sha1(string)``</span> | ``output`` is ``string`` sha1 hashed. | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := crypto.sha256(string)``</span> | ``output`` is ``string`` sha256 hashed. | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := crypto.hmac.md5(string, key)``</span> | ``output`` is HMAC-MD5 of ``string`` using ``key`` | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := crypto.hmac.sha1(string, key)``</span> | ``output`` is HMAC-SHA-1 of ``string`` using ``key`` | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := crypto.hmac.sha256(string, key)``</span> | ``output`` is HMAC-SHA-256 of ``string`` using ``key`` | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := crypto.hmac.sha512(string, key)``</span> | ``output`` is HMAC-SHA-512 of ``string`` using ``key`` | ``SDK-dependent`` |
### Graphs
| Built-in | Description | Wasm Support |
| ------- |-------------|--------------|
| <span class="opa-keep-it-together">``walk(x, [path, value])``</span> | ``walk`` is a relation that produces ``path`` and ``value`` pairs for documents under ``x``. ``path`` is ``array`` representing a pointer to ``value`` in ``x``. Queries can use ``walk`` to traverse documents nested under ``x`` (recursively). | ✅ |
| <span class="opa-keep-it-together">``output := graph.reachable(graph, initial)``</span> | ``output`` is the set of vertices [reachable](https://en.wikipedia.org/wiki/Reachability) from the ``initial`` vertices in the directed ``graph``. ``initial`` is a set or array of vertices, and ``graph`` is an object containing a set or array of neighboring vertices. | ✅ |
| <span class="opa-keep-it-together">``output := graph.reachable_paths(graph, initial)``</span> | ``output`` is the set of arrays of paths reachable from the ``initial`` vertices in the directed ``graph``. ``initial`` is a set or array of paths, and ``graph`` is an object containing a set or array of root vertices. | `SDK-dependent` |
{{< builtin-table cat=graph title=Graphs >}}
A common class of recursive rules can be reduced to a graph reachability
problem, so `graph.reachable` is useful for more than just graph analysis.
@@ -904,11 +706,7 @@ all_paths[entity_name]
```live:graph/reachable_paths/example:output
```
### HTTP
| Built-in | Description | Wasm Support |
| ------- |-------------|---------------|
| <span class="opa-keep-it-together">``response := http.send(request)``</span> | ``http.send`` executes an HTTP `request` and returns a `response`. | ``SDK-dependent`` |
{{< builtin-table cat=http title=HTTP >}}
{{< danger >}}
This built-in function **must not** be used for effecting changes in
@@ -1007,16 +805,7 @@ The table below shows examples of calling `http.send`:
| Environment variables containing TLS material | ``http.send({"method": "get", "url": "https://127.0.0.1:65360", "tls_ca_cert_env_variable": "CLIENT_CA_ENV", "tls_client_cert_env_variable": "CLIENT_CERT_ENV", "tls_client_key_env_variable": "CLIENT_KEY_ENV"})`` |
| Unix Socket URL Format| ``http.send({"method": "get", "url": "unix://localhost/?socket=%F2path%F2file.socket"})`` |
### Net
| Built-in | Description | Wasm Support |
| ------- |-------------|---------------|
| <span class="opa-keep-it-together">``net.lookup_ip_addr(name)``</span> | `output` is a set of IP addresses (both v4 and v6, strings) that the domain name resolves to using standard name resolution, [see the notes below](#notes-on-name-resolution-netlookup_ip_addr). | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``net.cidr_contains(cidr, cidr_or_ip)``</span> | `output` is `true` if `cidr_or_ip` (e.g. `127.0.0.64/26` or `127.0.0.1`) is contained within `cidr` (e.g. `127.0.0.1/24`) and false otherwise. Supports both IPv4 and IPv6 notations.| ✅ |
| <span class="opa-keep-it-together">``output := net.cidr_contains_matches(cidrs, cidrs_or_ips)``</span> | `output` is a `set` of tuples identifying matches where `cidrs_or_ips` are contained within `cidrs`. This function is similar to `net.cidr_contains` except it allows callers to pass collections of CIDRs or IPs as arguments and returns the matches (as opposed to a boolean result indicating a match between two CIDRs/IPs.) See below for examples. | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``net.cidr_intersects(cidr1, cidr2)``</span> | `output` is `true` if `cidr1` (e.g. `192.168.0.0/16`) overlaps with `cidr2` (e.g. `192.168.1.0/24`) and false otherwise. Supports both IPv4 and IPv6 notations.| ✅ |
| <span class="opa-keep-it-together">``net.cidr_expand(cidr)``</span> | `output` is the set of hosts in `cidr` (e.g., `net.cidr_expand("192.168.0.0/30")` generates 4 hosts: `{"192.168.0.0", "192.168.0.1", "192.168.0.2", "192.168.0.3"}` | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``net.cidr_merge(cidrs_or_ips)``</span> | `output` is the smallest possible set of CIDRs obtained after merging the provided list of IP addresses and subnets in `cidrs_or_ips` (e.g., `net.cidr_merge(["192.0.128.0/24", "192.0.129.0/24"])` generates `{"192.0.128.0/23"}`. This function merges adjacent subnets where possible, those contained within others and also removes any duplicates. Supports both IPv4 and IPv6 notations. IPv6 inputs need a prefix length (e.g. "/128"). | ``SDK-dependent`` |
{{< builtin-table net >}}
#### Notes on Name Resolution (`net.lookup_ip_addr`)
@@ -1079,26 +868,10 @@ net.cidr_contains_matches({["1.1.0.0/16", "foo"], "1.1.2.0/24"}, {"x": "1.1.1.12
```live:netcidrcontainsmatches/sets_and_objects:output:merge_down
```
### UUID
{{< builtin-table cat=uuid title=UUID >}}
{{< builtin-table cat=semver title="Semantic Versions" >}}
| Built-in | Description | Wasm Support |
| ------- |-------------|---------------|
| <span class="opa-keep-it-together">``output := uuid.rfc4122(str)``</span> | ``output`` is ``string`` representing a version 4 uuid. For any given str the output will be consistent throughout a query evaluation. | ``SDK-dependent`` |
### Semantic Versions
| Built-in | Description | Wasm Support |
| ------- |-------------|---------------|
| <span class="opa-keep-it-together">``output := semver.is_valid(str)``</span> | ``output`` is a ``boolean``. ``true`` means the input is a valid SemVer string (e.g. "1.0.0"). ``false`` is returned for invalid version strings and non-string input. | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := semver.compare(str, str)``</span> | ``output`` is a ``number``. ``-1`` means the version in the first operand is less than the second. ``1`` means the version in the first operand is greater than the second. ``0`` means the versions are equal. Only valid SemVer strings are accepted e.g. ``1.2.3`` or ``0.1.0`` | ``SDK-dependent`` |
### Rego
| Built-in | Description | Wasm Support |
| ------- |-------------|---------------|
| <span class="opa-keep-it-together">``output := rego.parse_module(filename, string)``</span> | ``rego.parse_module`` parses the input ``string`` as a Rego module and returns the AST as a JSON object ``output``. | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``output := rego.metadata.chain()``</span> | Each entry in the ``output`` array represents a node in the path ancestry (chain) of the active rule that also has declared [annotations](../annotations).``output`` is ordered starting at the active rule, going outward to the most distant node in its package ancestry. A chain entry is a JSON document with two members: ``path``, an array representing the path of the node; and ``annotations``, a JSON document containing the annotations declared for the node. The first entry in the chain always points to the active rule, even if it has no declared annotations (in which case the ``annotations`` member is not present). | ✅ |
| <span class="opa-keep-it-together">``output := rego.metadata.rule()``</span> | Returns a JSON object ``output`` containing the set of [annotations](../annotations) declared for the active rule and using the `rule` [scope](../annotations#scope). If no annotations are declared, an empty object is returned. | ✅ |
{{< builtin-table rego >}}
#### Example
@@ -1220,17 +993,18 @@ merge_annot(chain, name) = val {
} else = null
```
### OPA
{{< builtin-table cat=opa title=OPA >}}
| Built-in | Description | Wasm Support |
| ------- |-------------|---------------|
| <span class="opa-keep-it-together">``output := opa.runtime()``</span> | ``opa.runtime`` returns a JSON object ``output`` that describes the runtime environment where OPA is deployed. **Caution**: Policies that depend on the output of ``opa.runtime`` may return different answers depending on how OPA was started. If possible, prefer using an explicit `input` or `data` value instead of `opa.runtime`. The ``output`` of ``opa.runtime`` will include a ``"config"`` key if OPA was started with a configuration file. The ``output`` of ``opa.runtime`` will include a ``"env"`` key containing the environment variables that the OPA process was started with. The ``output`` of ``opa.runtime`` will include ``"version"`` and ``"commit"`` keys containing the semantic version and build commit of OPA. | ``SDK-dependent`` |
{{< danger >}}
Policies that depend on the output of `opa.runtime` may return different answers depending on how OPA was started.
If possible, prefer using an explicit `input` or `data` value instead of `opa.runtime`.
{{< /danger >}}
### Debugging
| Built-in | Description | Wasm Support |
| Built-in | Description | Details |
| ------- |-------------|---------------|
| <span class="opa-keep-it-together">``print(...)``</span> | ``print`` is used to output the values of variables for debugging purposes. ``print`` calls have no affect on the result of queries or rules. All variables passed to `print` must be assigned inside of the query or rule. If any of the `print` arguments are undefined, their values are represented as `<undefined>` in the output stream. Because policies can be invoked via different interfaces (e.g., CLI, HTTP API, etc.) the exact output format differs. See the table below for details. | ``SDK-dependent`` |
| <span class="opa-keep-it-together">``print(...)``</span> | ``print`` is used to output the values of variables for debugging purposes. ``print`` calls have no affect on the result of queries or rules. All variables passed to `print` must be assigned inside of the query or rule. If any of the `print` arguments are undefined, their values are represented as `<undefined>` in the output stream. Because policies can be invoked via different interfaces (e.g., CLI, HTTP API, etc.) the exact output format differs. See the table below for details. | {{< builtin-tags internal.print >}} |
API | Output | Memo
--- | --- | ---
@@ -1240,11 +1014,7 @@ API | Output | Memo
`opa run -s` (server) | `stderr` | Specify `--log-level=info` (default) or higher. Output is sent to the log stream. Use `--log-format=text` for pretty output.
Go (library) | `io.Writer` | [https://pkg.go.dev/github.com/open-policy-agent/opa/rego#example-Rego-Print_statements](https://pkg.go.dev/github.com/open-policy-agent/opa/rego#example-Rego-Print_statements)
### Tracing
| Built-in | Description | Wasm Support |
| ------- |-------------|---------------|
| <span class="opa-keep-it-together">``trace(string)``</span> | ``trace`` emits ``string`` as a ``Note`` event in the query explanation. Query explanations show the exact expressions evaluated by OPA during policy execution. For example, ``trace("Hello There!")`` includes ``Note "Hello There!"`` in the query explanation. To include variables in the message, use ``sprintf``. For example, ``person := "Bob"; trace(sprintf("Hello There! %v", [person]))`` will emit ``Note "Hello There! Bob"`` inside of the explanation. | ``SDK-dependent`` |
{{< builtin-table tracing >}}
By default, explanations are disabled. The following table summarizes how you can enable tracing:
+2 -1
View File
@@ -4,4 +4,5 @@ resources/
# Documentation versions
generated/*
data/releases.yaml
data/releases.yaml
data/builtin_metadata.json
+20
View File
@@ -65,3 +65,23 @@
+touch
width: 100%
div.bi-args
display: grid
grid-template-columns: min-content min-content auto
margin-bottom: 1rem
.name
grid-column-start: 1
.type
grid-column-start: 2
white-space: nowrap
.desc
grid-column-start: 3
margin-left: 1rem
table.bi-cat
code
white-space: nowrap
@@ -0,0 +1,100 @@
{{- $cat := .Get 0 }}
{{ if .IsNamedParams }}{{- $cat = .Get "cat" }}{{- end }}
{{- $id := $cat }}
{{- $title := $cat }}
{{- if .Get "id" }}{{ $id = .Get "id" }}{{ end }}
{{- if .Get "title" }}{{ $title = .Get "title" }}{{ end }}
{{- $version := index (split $.Page.File.Path "/") 1 -}}
{{- if (eq $version "latest") -}}
{{- $version = index site.Data.releases 1 -}}
{{- end -}}
<h3 id={{ $id }}>{{ $title | title }}</h3>
<table class="table bi-cat is-hoverable">
<tbody>
{{- range $name := index site.Data.builtin_metadata._categories $cat }}
{{- $anchor := anchorize (printf "builtin-%s-%s" $cat $name) }}
{{- $bi := index site.Data.builtin_metadata $name }}
{{- if in $bi.available $version }}
<tr id="{{ $anchor }}">
<th>
<a class="self-link" href="#{{ $anchor }}">
{{- if isset $bi "infix" }}
<code>
{{ index (index $bi.args 0) "name" }} {{ $bi.infix }} {{ index (index $bi.args 1) "name" }}
</code>
{{- else }}
<code>{{ $name }}</code>
{{- end }}
</a>
</th>
<td>
<p>
{{- if isset $bi "infix" }}
<code>
{{ $bi.result.name }} := {{ index (index $bi.args 0) "name" }} {{ $bi.infix }} {{ index (index $bi.args 1) "name" }}
</code>
{{- else if isset $bi "relation" }}
<code>
{{ $name }}(
{{- range $index, $element := $bi.args -}}
{{- if gt $index 0 -}}, {{ end -}}
{{- $element.name -}}
{{- end -}}
, {{ $bi.result.name -}}
)
</code>
{{- else }}
<code>
{{ $bi.result.name }} := {{ $name }}(
{{- range $index, $element := $bi.args -}}
{{- if gt $index 0 -}}, {{ end -}}
{{- $element.name -}}
{{- end -}}
)
</code>
{{- end }}
</p>
<p>{{ $bi.description | markdownify }}</p>
<div class="bi-args">
{{- range $element := $bi.args }}
<div class="name"><code>{{ $element.name }}</code></div>
<div class="type">({{ $element.type }})</div>
<div class="desc">{{ $element.description | markdownify }}</div>
{{- end }}
</div>
<emph>Returns:</emph>
<div class="bi-args">
<div class="name"><code>{{ $bi.result.name }}</code></div>
<div class="type">({{ $bi.result.type }})</div>
<div class="desc">{{ $bi.result.description | markdownify }}</div>
</dl>
</td>
<td>
<div class="tags">
{{- if eq $bi.introduced $version }}
<span class="tag is-primary">New</span>
{{- end }}
{{- if and (ne $bi.introduced "v0.17.0") (ne $bi.introduced "edge") }}
<a href="https://github.com/open-policy-agent/opa/releases/{{ $bi.introduced }}" target="_blank">
<span class="tag is-light">{{ $bi.introduced }}</span>
</a>
{{- else if eq $bi.introduced "edge" }}
<span class="tag is-danger">{{ $bi.introduced }}</span>
{{- end }}
{{- if index $bi "wasm" -}}
<span class="tag is-success">Wasm</span>
{{- else -}}
<span class="tag is-warning">SDK-dependent</span>
{{- end -}}
</div>
</td>
</tr>
{{- end }}
{{- end }}
</tbody>
</table>
@@ -0,0 +1,25 @@
{{- $name := .Get 0 -}}
{{- $metadata := index site.Data.builtin_metadata $name }}
{{- $version := index (split $.Page.File.Path "/") 1 -}}
{{- if (eq $version "latest") -}}
{{- $version = index site.Data.releases 1 -}}
{{- end -}}
<div class="tags">
{{- if eq $metadata.introduced $version }}
<span class="tag is-primary">New</span>
{{- end }}
{{- if and (ne $metadata.introduced "v0.17.0") (ne $metadata.introduced "edge") }}
<a href="https://github.com/open-policy-agent/opa/releases/{{ $metadata.introduced }}" target="_blank">
<span class="tag is-light">{{ $metadata.introduced }}</span>
</a>
{{- else if eq $metadata.introduced "edge" }}
<span class="tag is-danger">{{ $metadata.introduced }}</span>
{{- end }}
{{- if index $metadata "wasm" -}}
<span class="tag is-success">Wasm</span>
{{- else -}}
<span class="tag is-warning">SDK-dependent</span>
{{- end -}}
</div>
+158
View File
@@ -0,0 +1,158 @@
// Copyright 2022 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
package main
import (
"encoding/json"
"log"
"os"
"strings"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/internal/compiler/wasm"
"github.com/open-policy-agent/opa/types"
)
func main() {
f := ast.CapabilitiesForThisVersion()
sorted := append(sortedCaps(), versionedCaps{version: "edge", caps: f})
mdata := make(map[string]interface{})
categories := make(map[string][]string)
for _, bi := range f.Builtins {
latest := getLatest(bi.Name, sorted)
for _, cat := range builtinCategories(latest) {
categories[cat] = append(categories[cat], bi.Name)
}
argTypes := make([]map[string]interface{}, len(latest.Decl.FuncArgs().Args))
for i, typ := range latest.Decl.NamedFuncArgs().Args {
if n, ok := typ.(*types.NamedType); ok {
argTypes[i] = map[string]interface{}{
"name": n.Name,
"description": n.Descr,
"type": n.Type.String(),
}
} else {
argTypes[i] = map[string]interface{}{
"type": typ.String(),
}
}
}
res := map[string]interface{}{}
resType := latest.Decl.NamedResult()
if n, ok := resType.(*types.NamedType); ok {
res["name"] = n.Name
if n.Descr != "" {
res["description"] = n.Descr
}
res["type"] = n.Type.String()
} else if resType != nil {
res["type"] = resType.String()
}
versions := getVersions(bi.Name, sorted)
md := map[string]interface{}{
"introduced": versions[0],
"available": versions,
"wasm": getWasm(bi.Name),
"args": argTypes,
"result": res,
}
if latest.Relation {
md["relation"] = true
}
if latest.Infix != "" {
md["infix"] = latest.Infix
}
if latest.Description != "" {
md["description"] = latest.Description
}
mdata[bi.Name] = md
}
mdata["_categories"] = categories
md, err := os.Create(os.Args[1]) // metadata
if err != nil {
panic(err)
}
enc := json.NewEncoder(md)
enc.SetIndent("", " ")
if err := enc.Encode(mdata); err != nil {
panic(err)
}
if err := md.Close(); err != nil {
panic(err)
}
}
func getVersions(bi string, sorted []versionedCaps) []string {
vers := []string{}
for i := range sorted {
for j := range sorted[i].caps.Builtins {
if sorted[i].caps.Builtins[j].Name == bi {
vers = append(vers, sorted[i].version)
}
}
}
return vers
}
func getLatest(bi string, sorted []versionedCaps) *ast.Builtin {
for i := len(sorted) - 1; i >= 0; i++ {
for j := range sorted[i].caps.Builtins {
if sorted[i].caps.Builtins[j].Name == bi {
return sorted[i].caps.Builtins[j]
}
}
}
panic("unreachable")
}
func getWasm(bi string) bool {
return wasm.IsWasmEnabled(bi)
}
type versionedCaps struct {
version string
caps *ast.Capabilities
}
func sortedCaps() []versionedCaps {
vers, err := ast.LoadCapabilitiesVersions()
if err != nil {
panic(err)
}
sorted := make([]versionedCaps, len(vers))
for i, v := range vers {
caps, err := ast.LoadCapabilitiesVersion(v)
if err != nil {
panic(err)
}
sorted[i] = versionedCaps{
version: v,
caps: caps,
}
}
return sorted
}
func builtinCategories(b *ast.Builtin) []string {
if len(b.Categories) > 0 {
return b.Categories
}
if s := strings.Split(b.Name, "."); len(s) > 1 {
return []string{s[0]}
}
if !b.IsDeprecated() {
log.Printf("WARN: not categorized: %s", b.Name)
}
return nil
}
+13 -1
View File
@@ -9,10 +9,10 @@ import (
"os"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/types"
)
func main() {
f := ast.CapabilitiesForThisVersion()
fd, err := os.Create(os.Args[1])
@@ -23,6 +23,18 @@ func main() {
enc := json.NewEncoder(fd)
enc.SetIndent("", " ")
for i, bi := range f.Builtins {
// NOTE(sr): This ensures that there are no type names and descriptions in capabilities.json
fargs := bi.Decl.FuncArgs()
if fargs.Variadic != nil {
f.Builtins[i].Decl = types.NewVariadicFunction(fargs.Args, fargs.Variadic, bi.Decl.Result())
} else {
f.Builtins[i].Decl = types.NewFunction(fargs.Args, bi.Decl.Result())
}
f.Builtins[i].Categories = nil
f.Builtins[i].Description = ""
}
if err := enc.Encode(f); err != nil {
panic(err)
}
+5
View File
@@ -184,6 +184,11 @@ var builtinsUsingRE2 = [...]string{
builtinsFunctions[ast.GlobMatch.Name],
}
func IsWasmEnabled(bi string) bool {
_, ok := builtinsFunctions[bi]
return ok
}
type externalFunc struct {
ID int32
Decl *opatypes.Function
+5 -2
View File
@@ -14,13 +14,12 @@ import (
"strings"
"testing"
"github.com/open-policy-agent/opa/util"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/loader"
"github.com/open-policy-agent/opa/rego"
"github.com/open-policy-agent/opa/storage"
"github.com/open-policy-agent/opa/storage/inmem"
"github.com/open-policy-agent/opa/util"
"github.com/open-policy-agent/opa/util/test"
)
@@ -198,6 +197,8 @@ func TestOutputJSONErrorStructuredAstErr(t *testing.T) {
"want": {
"args": [
{
"description": "the set/array/object/string to be counted",
"name": "collection",
"of": [
{
"type": "string"
@@ -229,6 +230,8 @@ func TestOutputJSONErrorStructuredAstErr(t *testing.T) {
"type": "any"
},
{
"description": "the count of elements, key/val pairs, or characters, respectively.",
"name": "n",
"type": "number"
}
]
+2 -1
View File
@@ -18,8 +18,9 @@ func main() {
}
}
// Capabilities file generation:
// Capabilities + built-in metadata file generation:
//go:generate build/gen-run-go.sh internal/cmd/genopacapabilities/main.go capabilities.json
//go:generate build/gen-run-go.sh internal/cmd/genbuiltinmetadata/main.go builtin_metadata.json
// WASM base binary generation:
//go:generate build/gen-run-go.sh internal/cmd/genopawasm/main.go -o internal/compiler/wasm/opa/opa.go internal/compiler/wasm/opa/opa.wasm internal/compiler/wasm/opa/callgraph.csv
+98 -19
View File
@@ -48,6 +48,42 @@ func NewNull() Null {
return Null{}
}
type NamedType struct {
Name, Descr string
Type Type
}
func (n *NamedType) typeMarker() string { return n.Type.typeMarker() }
func (n *NamedType) String() string { return n.Name + ": " + n.Type.String() }
func (n *NamedType) MarshalJSON() ([]byte, error) {
var obj map[string]interface{}
switch x := n.Type.(type) {
case interface{ toMap() map[string]interface{} }:
obj = x.toMap()
default:
obj = map[string]interface{}{
"type": n.Type.typeMarker(),
}
}
obj["name"] = n.Name
if n.Descr != "" {
obj["description"] = n.Descr
}
return json.Marshal(obj)
}
func (n *NamedType) Description(d string) *NamedType {
n.Descr = d
return n
}
func Named(name string, t Type) *NamedType {
return &NamedType{
Type: t,
Name: name,
}
}
// MarshalJSON returns the JSON encoding of t.
func (t Null) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
@@ -55,6 +91,15 @@ func (t Null) MarshalJSON() ([]byte, error) {
})
}
func unwrap(t Type) Type {
switch t := t.(type) {
case *NamedType:
return t.Type
default:
return t
}
}
func (t Null) String() string {
return typeNull
}
@@ -100,7 +145,7 @@ func (t String) MarshalJSON() ([]byte, error) {
})
}
func (t String) String() string {
func (String) String() string {
return typeString
}
@@ -142,6 +187,10 @@ func NewArray(static []Type, dynamic Type) *Array {
// MarshalJSON returns the JSON encoding of t.
func (t *Array) MarshalJSON() ([]byte, error) {
return json.Marshal(t.toMap())
}
func (t *Array) toMap() map[string]interface{} {
repr := map[string]interface{}{
"type": t.typeMarker(),
}
@@ -151,7 +200,7 @@ func (t *Array) MarshalJSON() ([]byte, error) {
if t.dynamic != nil {
repr["dynamic"] = t.dynamic
}
return json.Marshal(repr)
return repr
}
func (t *Array) String() string {
@@ -207,13 +256,17 @@ func NewSet(of Type) *Set {
// MarshalJSON returns the JSON encoding of t.
func (t *Set) MarshalJSON() ([]byte, error) {
return json.Marshal(t.toMap())
}
func (t *Set) toMap() map[string]interface{} {
repr := map[string]interface{}{
"type": t.typeMarker(),
}
if t.of != nil {
repr["of"] = t.of
}
return json.Marshal(repr)
return repr
}
func (t *Set) String() string {
@@ -332,6 +385,10 @@ func (t *Object) Keys() []interface{} {
// MarshalJSON returns the JSON encoding of t.
func (t *Object) MarshalJSON() ([]byte, error) {
return json.Marshal(t.toMap())
}
func (t *Object) toMap() map[string]interface{} {
repr := map[string]interface{}{
"type": t.typeMarker(),
}
@@ -341,7 +398,7 @@ func (t *Object) MarshalJSON() ([]byte, error) {
if t.dynamic != nil {
repr["dynamic"] = t.dynamic
}
return json.Marshal(repr)
return repr
}
// Select returns the type of the named property.
@@ -395,13 +452,17 @@ func (t Any) Contains(other Type) bool {
// MarshalJSON returns the JSON encoding of t.
func (t Any) MarshalJSON() ([]byte, error) {
data := map[string]interface{}{
return json.Marshal(t.toMap())
}
func (t Any) toMap() map[string]interface{} {
repr := map[string]interface{}{
"type": t.typeMarker(),
}
if len(t) != 0 {
data["of"] = []Type(t)
repr["of"] = []Type(t)
}
return json.Marshal(data)
return repr
}
// Merge return a new Any type that is the superset of t and other.
@@ -487,8 +548,7 @@ func Arity(x Type) int {
return len(f.FuncArgs().Args)
}
// NewFunction returns a new Function object where xs[:len(xs)-1] are arguments
// and xs[len(xs)-1] is the result type.
// NewFunction returns a new Function object of the given argument and result types.
func NewFunction(args []Type, result Type) *Function {
return &Function{
args: args,
@@ -512,19 +572,34 @@ func NewVariadicFunction(args []Type, varargs Type, result Type) *Function {
// FuncArgs returns the function's arguments.
func (t *Function) FuncArgs() FuncArgs {
return FuncArgs{Args: t.Args(), Variadic: t.variadic}
return FuncArgs{Args: t.Args(), Variadic: unwrap(t.variadic)}
}
// NamedFuncArgs returns the function's arguments, with a name and
// description if available.
func (t *Function) NamedFuncArgs() FuncArgs {
args := make([]Type, len(t.args))
copy(args, t.args)
return FuncArgs{Args: args, Variadic: t.variadic}
}
// Args returns the function's arguments as a slice, ignoring variadic arguments.
// Deprecated: Use FuncArgs instead.
func (t *Function) Args() []Type {
cpy := make([]Type, len(t.args))
copy(cpy, t.args)
for i := range t.args {
cpy[i] = unwrap(t.args[i])
}
return cpy
}
// Result returns the function's result type.
func (t *Function) Result() Type {
return unwrap(t.result)
}
// Result returns the function's result type, without stripping name and description.
func (t *Function) NamedResult() Type {
return t.result
}
@@ -566,12 +641,13 @@ func (t *Function) UnmarshalJSON(bs []byte) error {
return nil
}
// Union returns a new function represnting the union of t and other. Functions
// Union returns a new function representing the union of t and other. Functions
// must have the same arity to be unioned.
func (t *Function) Union(other *Function) *Function {
if other == nil {
return t
} else if t == nil {
}
if t == nil {
return other
}
@@ -618,6 +694,7 @@ func (a FuncArgs) String() string {
return "(" + strings.Join(buf, ", ") + ")"
}
// Arg returns the nth argument's type.
func (a FuncArgs) Arg(x int) Type {
if x < len(a.Args) {
return a.Args[x]
@@ -627,6 +704,7 @@ func (a FuncArgs) Arg(x int) Type {
// Compare returns -1, 0, 1 based on comparison between a and b.
func Compare(a, b Type) int {
a, b = unwrap(a), unwrap(b)
x := typeOrder(a)
y := typeOrder(b)
if x > y {
@@ -731,7 +809,7 @@ func Compare(a, b Type) int {
// Contains returns true if a is a superset or equal to b.
func Contains(a, b Type) bool {
if any, ok := a.(Any); ok {
if any, ok := unwrap(a).(Any); ok {
return any.Contains(b)
}
return Compare(a, b) == 0
@@ -740,6 +818,7 @@ func Contains(a, b Type) bool {
// Or returns a type that represents the union of a and b. If one type is a
// superset of the other, the superset is returned unchanged.
func Or(a, b Type) Type {
a, b = unwrap(a), unwrap(b)
if a == nil {
return b
} else if b == nil {
@@ -768,7 +847,7 @@ func Or(a, b Type) Type {
// Select returns a property or item of a.
func Select(a Type, x interface{}) Type {
switch a := a.(type) {
switch a := unwrap(a).(type) {
case *Array:
n, ok := x.(json.Number)
if !ok {
@@ -811,7 +890,7 @@ func Select(a Type, x interface{}) Type {
// keys are always number types, for objects the keys are always string types,
// and for sets the keys are always the type of the set element.
func Keys(a Type) Type {
switch a := a.(type) {
switch a := unwrap(a).(type) {
case *Array:
return N
case *Object:
@@ -841,7 +920,7 @@ func Keys(a Type) Type {
// Values returns the type of values that can be enumerated for a.
func Values(a Type) Type {
switch a := a.(type) {
switch a := unwrap(a).(type) {
case *Array:
var tpe Type
for i := range a.static {
@@ -874,7 +953,7 @@ func Values(a Type) Type {
// Nil returns true if a's type is unknown.
func Nil(a Type) bool {
switch a := a.(type) {
switch a := unwrap(a).(type) {
case nil:
return true
case *Function:
@@ -969,7 +1048,7 @@ func typeSliceCompare(a, b []Type) int {
}
func typeOrder(x Type) int {
switch x.(type) {
switch unwrap(x).(type) {
case Null:
return 0
case Boolean: