Skip to content

Commit

Permalink
topdown: add yaml.is_valid and json.is_valid
Browse files Browse the repository at this point in the history
Signed-off-by: Jasper Van der Jeugt <jasper@fugue.co>
  • Loading branch information
jaspervdj-luminal authored and tsandall committed Oct 27, 2020
1 parent 99a8143 commit d8947db
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 0 deletions.
20 changes: 20 additions & 0 deletions ast/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ var DefaultBuiltins = [...]*Builtin{
// Encoding
JSONMarshal,
JSONUnmarshal,
JSONIsValid,
Base64Encode,
Base64Decode,
Base64IsValid,
Expand All @@ -139,6 +140,7 @@ var DefaultBuiltins = [...]*Builtin{
URLQueryDecodeObject,
YAMLMarshal,
YAMLUnmarshal,
YAMLIsValid,

// Object Manipulation
ObjectUnion,
Expand Down Expand Up @@ -1075,6 +1077,15 @@ var JSONUnmarshal = &Builtin{
),
}

// JSONIsValid verifies the input string is a valid JSON document.
var JSONIsValid = &Builtin{
Name: "json.is_valid",
Decl: types.NewFunction(
types.Args(types.S),
types.B,
),
}

// JSONFilter filters the JSON object
var JSONFilter = &Builtin{
Name: "json.filter",
Expand Down Expand Up @@ -1324,6 +1335,15 @@ var YAMLUnmarshal = &Builtin{
),
}

// YAMLIsValid verifies the input string is a valid YAML document.
var YAMLIsValid = &Builtin{
Name: "yaml.is_valid",
Decl: types.NewFunction(
types.Args(types.S),
types.B,
),
}

/**
* Tokens
*/
Expand Down
28 changes: 28 additions & 0 deletions capabilities.json
Original file line number Diff line number Diff line change
Expand Up @@ -1420,6 +1420,20 @@
"type": "function"
}
},
{
"name": "json.is_valid",
"decl": {
"args": [
{
"type": "string"
}
],
"result": {
"type": "boolean"
},
"type": "function"
}
},
{
"name": "json.marshal",
"decl": {
Expand Down Expand Up @@ -3211,6 +3225,20 @@
},
"relation": true
},
{
"name": "yaml.is_valid",
"decl": {
"args": [
{
"type": "string"
}
],
"result": {
"type": "boolean"
},
"type": "function"
}
},
{
"name": "yaml.marshal",
"decl": {
Expand Down
2 changes: 2 additions & 0 deletions docs/content/policy-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -450,8 +450,10 @@ The following table shows examples of how ``glob.match`` works:
| <span class="opa-keep-it-together">``output := urlquery.decode_object(string)``</span> | ``output`` is ``object`` deserialized from a URL query parameter string |
| <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 |
| <span class="opa-keep-it-together">``output := yaml.unmarshal(string)``</span> | ``output`` is ``string`` deserialized to a term from YAML encoded string |
| <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` |

### Token Signing

Expand Down
68 changes: 68 additions & 0 deletions test/cases/testdata/jsonbuiltins/test-is-valid.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
cases:
- note: jsonbuiltins/json is_valid
query: data.generated.p = x
modules:
- |
package generated
documents = [
`plainstring`,
`{`,
`{"json": "ok"}`,
]
p = [x | doc = documents[_]; json.is_valid(doc, x)]
want_result:
- x:
- false
- false
- true

- note: jsonbuiltins/json is_valid not string
modules:
- |
package generated
p = x {
json.is_valid(input.foo, x)
}
query: data.generated.p = x
input: {"foo": 1}
want_error: operand 1 must be string but got number
want_error_code: eval_type_error

- note: jsonbuiltins/yaml is_valid
query: data.generated.p = x
modules:
- |
package generated
documents = [
`foo:
- qux: bar
- baz: 2`,
`foo:
- qux: bar
- baz: {`,
`{"json": "ok"}`,
]
p = [x | doc = documents[_]; yaml.is_valid(doc, x)]
want_result:
- x:
- true
- false
- true

- note: jsonbuiltins/yaml is_valid not string
modules:
- |
package generated
p = x {
yaml.is_valid(input.foo, x)
}
query: data.generated.p = x
input: {"foo": 1}
want_error: operand 1 must be string but got number
want_error_code: eval_type_error
25 changes: 25 additions & 0 deletions topdown/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,18 @@ func builtinJSONUnmarshal(a ast.Value) (ast.Value, error) {
return ast.InterfaceToValue(x)
}

func builtinJSONIsValid(a ast.Value) (ast.Value, error) {

str, err := builtins.StringOperand(a, 1)
if err != nil {
return nil, err
}

var x interface{}
err = util.UnmarshalJSON([]byte(str), &x)
return ast.Boolean(err == nil), nil
}

func builtinBase64Encode(a ast.Value) (ast.Value, error) {
str, err := builtins.StringOperand(a, 1)
if err != nil {
Expand Down Expand Up @@ -235,9 +247,21 @@ func builtinYAMLUnmarshal(a ast.Value) (ast.Value, error) {
return ast.InterfaceToValue(val)
}

func builtinYAMLIsValid(a ast.Value) (ast.Value, error) {
str, err := builtins.StringOperand(a, 1)
if err != nil {
return nil, err
}

var x interface{}
err = ghodss.Unmarshal([]byte(str), &x)
return ast.Boolean(err == nil), nil
}

func init() {
RegisterFunctionalBuiltin1(ast.JSONMarshal.Name, builtinJSONMarshal)
RegisterFunctionalBuiltin1(ast.JSONUnmarshal.Name, builtinJSONUnmarshal)
RegisterFunctionalBuiltin1(ast.JSONIsValid.Name, builtinJSONIsValid)
RegisterFunctionalBuiltin1(ast.Base64Encode.Name, builtinBase64Encode)
RegisterFunctionalBuiltin1(ast.Base64Decode.Name, builtinBase64Decode)
RegisterFunctionalBuiltin1(ast.Base64IsValid.Name, builtinBase64IsValid)
Expand All @@ -249,4 +273,5 @@ func init() {
RegisterBuiltinFunc(ast.URLQueryDecodeObject.Name, builtinURLQueryDecodeObject)
RegisterFunctionalBuiltin1(ast.YAMLMarshal.Name, builtinYAMLMarshal)
RegisterFunctionalBuiltin1(ast.YAMLUnmarshal.Name, builtinYAMLUnmarshal)
RegisterFunctionalBuiltin1(ast.YAMLIsValid.Name, builtinYAMLIsValid)
}

0 comments on commit d8947db

Please sign in to comment.