lib/json.go (170 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package lib import ( "bytes" "encoding/json" "fmt" "io" structpb "github.com/golang/protobuf/ptypes/struct" "github.com/google/cel-go/cel" "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" ) // JSON returns a cel.EnvOption to configure extended functions for JSON // coding and decoding. The parameter specifies the CEL type adapter to use. // A nil adapter is valid an will give an option using the default type // adapter, types.DefaultTypeAdapter. // // # Encode JSON // // encode_json returns a string of the JSON encoding of the receiver or // parameter: // // encode_json(<dyn>) -> <string> // <dyn>.encode_json() -> <string> // // Examples: // // {"a":1, "b":[1, 2, 3]}.encode_json() // return "{\"a\":1,\"b\":[1,2,3]}" // encode_json({"a":1, "b":[1, 2, 3]}) // return "{\"a\":1,\"b\":[1,2,3]}" // // # Decode JSON // // decode_json returns the object described by the JSON encoding of the receiver // or parameter: // // <bytes>.decode_json() -> <dyn> // <string>.decode_json() -> <dyn> // decode_json(<bytes>) -> <dyn> // decode_json(<string>) -> <dyn> // // Examples: // // "{\"a\":1,\"b\":[1,2,3]}".decode_json() // return {"a":1, "b":[1, 2, 3]} // b"{\"a\":1,\"b\":[1,2,3]}".decode_json() // return {"a":1, "b":[1, 2, 3]} // // # Decode JSON Stream // // decode_json_stream returns a list of objects described by the JSON stream // of the receiver or parameter: // // <bytes>.decode_json_stream() -> <list<dyn>> // <string>.decode_json_stream() -> <list<dyn>> // decode_json_stream(<bytes>) -> <list<dyn>> // decode_json_stream(<string>) -> <list<dyn>> // // Examples: // // '{"a":1}{"b":2}'.decode_json_stream() // return [{"a":1}, {"b":2}] // b'{"a":1}{"b":2}'.decode_json_stream() // return [{"a":1}, {"b":2}] func JSON(adapter types.Adapter) cel.EnvOption { if adapter == nil { adapter = types.DefaultTypeAdapter } return cel.Lib(jsonLib{adapter}) } type jsonLib struct { adapter types.Adapter } func (l jsonLib) CompileOptions() []cel.EnvOption { return []cel.EnvOption{ cel.Function("encode_json", cel.MemberOverload( "dyn_encode_json", []*cel.Type{cel.DynType}, cel.StringType, cel.UnaryBinding(catch(encodeJSON)), ), cel.Overload( "encode_json_dyn", []*cel.Type{cel.DynType}, cel.StringType, cel.UnaryBinding(catch(encodeJSON)), ), ), cel.Function("decode_json", cel.MemberOverload( "string_decode_json", []*cel.Type{cel.StringType}, cel.DynType, cel.UnaryBinding(catch(l.decodeJSON)), ), cel.Overload( "decode_json_string", []*cel.Type{cel.StringType}, cel.DynType, cel.UnaryBinding(catch(l.decodeJSON)), ), cel.MemberOverload( "bytes_decode_json", []*cel.Type{cel.BytesType}, cel.DynType, cel.UnaryBinding(catch(l.decodeJSON)), ), cel.Overload( "decode_json_bytes", []*cel.Type{cel.BytesType}, cel.DynType, cel.UnaryBinding(catch(l.decodeJSON)), ), ), cel.Function("decode_json_stream", cel.MemberOverload( "string_decode_json_stream", []*cel.Type{cel.StringType}, cel.DynType, cel.UnaryBinding(catch(l.decodeJSONStream)), ), cel.Overload( "decode_json_stream_string", []*cel.Type{cel.StringType}, cel.DynType, cel.UnaryBinding(catch(l.decodeJSONStream)), ), cel.MemberOverload( "bytes_decode_json_stream", []*cel.Type{cel.BytesType}, cel.DynType, cel.UnaryBinding(catch(l.decodeJSONStream)), ), cel.Overload( "decode_json_stream_bytes", []*cel.Type{cel.BytesType}, cel.DynType, cel.UnaryBinding(catch(l.decodeJSONStream)), ), ), } } func (jsonLib) ProgramOptions() []cel.ProgramOption { return nil } func encodeJSON(val ref.Val) ref.Val { var v interface{} // Avoid type conversions if possible. switch under := val.Value().(type) { case map[string]any: v = under case map[ref.Val]ref.Val: pb, err := val.ConvertToNative(structpbValueType) if err != nil { return types.NewErr("failed proto conversion: %v", err) } v = pb.(*structpb.Value).AsInterface() default: var err error typ, ok := encodableTypes[val.Type()] if ok { v, err = val.ConvertToNative(typ) if err != nil { // This should never happen. panic(fmt.Sprintf("json encode mapping out of sync: %v", err)) } } else { for _, typ := range protobufTypes { v, err = val.ConvertToNative(typ) if err != nil { v = nil } else { break } } } if v == nil { return types.NewErr("failed to get native value for JSON") } } b, err := json.Marshal(v) if err != nil { return types.NewErr("failed to marshal value to JSON: %v", err) } return types.String(b) } func (l jsonLib) decodeJSON(val ref.Val) ref.Val { var ( v interface{} err error ) switch msg := val.(type) { case types.Bytes: err = json.Unmarshal([]byte(msg), &v) case types.String: err = json.Unmarshal([]byte(msg), &v) default: return types.NoSuchOverloadErr() } if err != nil { return types.NewErr("failed to unmarshal JSON message: %v", err) } return l.adapter.NativeToValue(v) } func (l jsonLib) decodeJSONStream(val ref.Val) ref.Val { var r io.Reader switch msg := val.(type) { case types.Bytes: r = bytes.NewReader(msg) case types.String: r = bytes.NewReader([]byte(msg)) default: return types.NoSuchOverloadErr() } var s []interface{} dec := json.NewDecoder(r) for dec.More() { var v interface{} err := dec.Decode(&v) if err != nil { return types.NewErr("failed to unmarshal JSON stream: %v", err) } s = append(s, v) } return l.adapter.NativeToValue(s) }