diff --git a/ast/builtins.go b/ast/builtins.go index f80610c260..b6743657f7 100644 --- a/ast/builtins.go +++ b/ast/builtins.go @@ -47,6 +47,9 @@ var DefaultBuiltins = [...]*Builtin{ // Strings Concat, FormatInt, IndexOf, Substring, Lower, Upper, Contains, StartsWith, EndsWith, + + // JSON + JSONMarshal, JSONUnmarshal, } // BuiltinMap provides a convenient mapping of built-in names to @@ -290,6 +293,24 @@ var Upper = &Builtin{ TargetPos: []int{1}, } +/** + * JSON + */ + +// JSONMarshal serializes the input term. +var JSONMarshal = &Builtin{ + Name: Var("json_marshal"), + NumArgs: 2, + TargetPos: []int{1}, +} + +// JSONUnmarshal deserializes the input string. +var JSONUnmarshal = &Builtin{ + Name: Var("json_unmarshal"), + NumArgs: 2, + TargetPos: []int{1}, +} + /** * Deprecated built-ins. */ diff --git a/topdown/json.go b/topdown/json.go new file mode 100644 index 0000000000..cbbab9d960 --- /dev/null +++ b/topdown/json.go @@ -0,0 +1,49 @@ +// Copyright 2017 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 topdown + +import ( + "encoding/json" + + "github.com/open-policy-agent/opa/ast" + "github.com/open-policy-agent/opa/topdown/builtins" + "github.com/open-policy-agent/opa/util" +) + +func builtinJSONMarshal(a ast.Value) (ast.Value, error) { + + asJSON, err := ast.JSON(a) + if err != nil { + return nil, err + } + + bs, err := json.Marshal(asJSON) + if err != nil { + return nil, err + } + + return ast.String(string(bs)), nil +} + +func builtinJSONUnmarshal(a ast.Value) (ast.Value, error) { + + str, err := builtins.StringOperand(a, 1) + if err != nil { + return nil, err + } + + var x interface{} + + if err := util.UnmarshalJSON([]byte(str), &x); err != nil { + return nil, err + } + + return ast.InterfaceToValue(x) +} + +func init() { + RegisterFunctionalBuiltin1(ast.JSONMarshal.Name, builtinJSONMarshal) + RegisterFunctionalBuiltin1(ast.JSONUnmarshal.Name, builtinJSONUnmarshal) +} diff --git a/topdown/topdown_test.go b/topdown/topdown_test.go index 7a0fa04e35..e2158478bd 100644 --- a/topdown/topdown_test.go +++ b/topdown/topdown_test.go @@ -1144,6 +1144,26 @@ func TestTopDownStrings(t *testing.T) { } } +func TestTopDownJSONBuiltins(t *testing.T) { + + tests := []struct { + note string + rules []string + expected interface{} + }{ + {"marshal", []string{`p = x { json_marshal([{"foo": {1,2,3}}], x) }`}, `"[{\"foo\":[1,2,3]}]"`}, + {"unmarshal", []string{`p = x { json_unmarshal("[{\"foo\":[1,2,3]}]", x) }`}, `[{"foo": [1,2,3]}]"`}, + {"unmarshal-non-string", []string{`p = x { json_unmarshal(data.a[0], x) }`}, fmt.Errorf("operand 1 must be string but got number")}, + } + + data := loadSmallTestData() + + for _, tc := range tests { + runTopDownTestCase(t, data, tc.note, tc.rules, tc.expected) + } + +} + func TestTopDownEmbeddedVirtualDoc(t *testing.T) { compiler := compileModules([]string{