UNPKG

@bufbuild/cel

Version:

A CEL evaluator for ECMAScript

346 lines (345 loc) 13.7 kB
"use strict"; // Copyright 2024-2025 Buf Technologies, Inc. // // Licensed 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. Object.defineProperty(exports, "__esModule", { value: true }); exports.celObject = celObject; const protobuf_1 = require("@bufbuild/protobuf"); const uint_js_1 = require("./uint.js"); const value_js_1 = require("./value.js"); const eval_js_1 = require("./eval.js"); const reflect_1 = require("@bufbuild/protobuf/reflect"); const list_js_1 = require("./list.js"); const map_js_1 = require("./map.js"); const wkt_1 = require("@bufbuild/protobuf/wkt"); const wire_1 = require("@bufbuild/protobuf/wire"); const type_js_1 = require("./type.js"); const wire_2 = require("@bufbuild/protobuf/wire"); /** * Creates a new CelValue of the given type and with the given fields. * * Wrappers are converted to their corresponding scalars. */ function celObject(typeName, fields) { const desc = (0, eval_js_1.getMsgDesc)(typeName); const msg = (0, reflect_1.reflect)(desc); for (const [k, v] of fields.entries()) { const field = desc.fields.find((f) => f.name === k); if (field === undefined) { throw new Error(`Unknown field ${k} for ${typeName}`); } setMsgField(msg, field, v); } return (0, value_js_1.reflectMsgToCel)(msg); } function setMsgField(msg, field, v) { switch (field.fieldKind) { case "list": if (!(0, list_js_1.isCelList)(v)) { throw unexpectedTypeError(field, "list", v); } const list = (0, reflect_1.reflectList)(field, undefined, false); copyList(list, v); msg.set(field, list); return; case "map": if (!(0, map_js_1.isCelMap)(v)) { throw unexpectedTypeError(field, "map", v); } const map = (0, reflect_1.reflectMap)(field, undefined, false); copyMap(map, v); msg.set(field, map); return; case "enum": msg.set(field, enumFromCel(field, v)); return; case "message": const msgVal = msgFromCel(field, v); if (msgVal !== null) { msg.set(field, msgVal); } return; } msg.set(field, scalarFromCel(field, field.scalar, v)); } function copyList(list, cList) { const field = list.field(); for (const v of cList) { switch (field.listKind) { case "enum": list.add(enumFromCel(field, v)); break; case "message": list.add(msgFromCel(field, v)); break; case "scalar": list.add(scalarFromCel(field, field.scalar, v)); break; } } } function copyMap(map, cMap) { const field = map.field(); for (const [k, v] of cMap.entries()) { const key = scalarFromCel(field, field.mapKey, k); switch (field.mapKind) { case "enum": map.set(key, enumFromCel(field, v)); break; case "scalar": map.set(key, scalarFromCel(field, field.scalar, v)); break; case "message": map.set(key, msgFromCel(field, v)); break; } } } function enumFromCel(field, v) { if (typeof v !== "bigint") { throw unexpectedTypeError(field, "int", v); } return intToInt32(v); } function msgFromCel(field, v) { switch (field.message.typeName) { case wkt_1.AnySchema.typeName: if ((0, reflect_1.isReflectMessage)(v, wkt_1.AnySchema)) { return v; } return (0, reflect_1.reflect)(wkt_1.AnySchema, anyFromCel(v)); case wkt_1.ValueSchema.typeName: return (0, reflect_1.reflect)(wkt_1.ValueSchema, valueFromCel(v)); case wkt_1.StructSchema.typeName: if (!(0, map_js_1.isCelMap)(v)) { throw unexpectedTypeError(field, "map", v); } return (0, reflect_1.reflect)(wkt_1.StructSchema, structFromCel(v)); case wkt_1.ListValueSchema.typeName: if (!(0, list_js_1.isCelList)(v)) { throw unexpectedTypeError(field, "list", v); } return (0, reflect_1.reflect)(wkt_1.ListValueSchema, listValueFromCel(v)); } if (v === null) { return null; } if ((0, wkt_1.isWrapperDesc)(field.message)) { const msg = (0, reflect_1.reflect)(field.message, undefined, false); msg.set(field.message.fields[0], scalarFromCel(field.message.fields[0], field.message.fields[0].scalar, v)); return msg; } if (!(0, reflect_1.isReflectMessage)(v, field.message)) { throw unexpectedTypeError(field, field.message.typeName, v); } return v; } /** * Converts a CelValue to google.protobuf.Any. * * While the CEL spec doesn't say anything about converting * CEL values to types, there a couple conformance tests around * WKTs that expect this behavior. The go implementation also handles * converting all CEL types except for type. */ function anyFromCel(v) { switch (typeof v) { case "string": return (0, wkt_1.anyPack)(wkt_1.StringValueSchema, (0, protobuf_1.create)(wkt_1.StringValueSchema, { value: v })); case "boolean": return (0, wkt_1.anyPack)(wkt_1.BoolValueSchema, (0, protobuf_1.create)(wkt_1.BoolValueSchema, { value: v })); case "bigint": return (0, wkt_1.anyPack)(wkt_1.Int64ValueSchema, (0, protobuf_1.create)(wkt_1.Int64ValueSchema, { value: v })); case "number": return (0, wkt_1.anyPack)(wkt_1.DoubleValueSchema, (0, protobuf_1.create)(wkt_1.DoubleValueSchema, { value: v })); default: switch (true) { case v instanceof Uint8Array: return (0, wkt_1.anyPack)(wkt_1.BytesValueSchema, (0, protobuf_1.create)(wkt_1.BytesValueSchema, { value: v })); case v == null: return (0, wkt_1.anyPack)(wkt_1.ValueSchema, (0, protobuf_1.create)(wkt_1.ValueSchema, { kind: { case: "nullValue", value: wkt_1.NullValue.NULL_VALUE }, })); case (0, list_js_1.isCelList)(v): return (0, wkt_1.anyPack)(wkt_1.ListValueSchema, listValueFromCel(v)); case (0, map_js_1.isCelMap)(v): return (0, wkt_1.anyPack)(wkt_1.StructSchema, structFromCel(v)); case (0, uint_js_1.isCelUint)(v): return (0, wkt_1.anyPack)(wkt_1.UInt64ValueSchema, (0, protobuf_1.create)(wkt_1.UInt64ValueSchema, { value: v.value })); case (0, reflect_1.isReflectMessage)(v): return (0, wkt_1.anyPack)(v.desc, v.message); default: // Only CelType is left. throw new Error(`type cannot be converted ${wkt_1.AnySchema}`); } } } /** * Converts a CelValue to google.protobuf.Value. * * CEL defines conversion to/from JSON. Since Value represents the JSON type * in CEL and protobuf, we can use the same logic to convert them. * * Ref: https://github.com/google/cel-spec/blob/master/doc/langdef.md#json-data-conversion */ function valueFromCel(v) { const value = (0, protobuf_1.create)(wkt_1.ValueSchema); switch (typeof v) { case "string": value.kind = { case: "stringValue", value: v }; break; case "boolean": value.kind = { case: "boolValue", value: v }; break; case "bigint": if (v > BigInt(Number.MAX_SAFE_INTEGER) || v < BigInt(Number.MIN_SAFE_INTEGER)) { value.kind = { case: "stringValue", value: v.toString() }; } else { value.kind = { case: "numberValue", value: Number(v) }; } break; case "number": if (Number.isNaN(v)) { value.kind = { case: "stringValue", value: "NaN" }; } else if (v === Number.POSITIVE_INFINITY) { value.kind = { case: "stringValue", value: "Infinity" }; } else if (v === Number.NEGATIVE_INFINITY) { value.kind = { case: "stringValue", value: "-Infinity" }; } else { value.kind = { case: "numberValue", value: Number(v) }; } break; default: switch (true) { case v instanceof Uint8Array: value.kind = { case: "stringValue", value: (0, wire_1.base64Encode)(v) }; break; case v == null: value.kind = { case: "nullValue", value: wkt_1.NullValue.NULL_VALUE }; break; case (0, list_js_1.isCelList)(v): value.kind = { case: "listValue", value: listValueFromCel(v) }; break; case (0, map_js_1.isCelMap)(v): value.kind = { case: "structValue", value: structFromCel(v) }; break; case (0, uint_js_1.isCelUint)(v): if (v.value > BigInt(Number.MAX_SAFE_INTEGER)) { value.kind = { case: "stringValue", value: v.value.toString() }; } else { value.kind = { case: "numberValue", value: Number(v.value) }; } break; case (0, reflect_1.isReflectMessage)(v): // We can skip the intermediary step, but that will require // us to reimplement all of toJson just with a different result type. return (0, protobuf_1.fromJson)(wkt_1.ValueSchema, (0, protobuf_1.toJson)(v.desc, v.message)); default: // Only CelType is left which is not supported. throw new Error(`type cannot be converted ${wkt_1.ValueSchema}`); } } return value; } function listValueFromCel(list) { const listValue = (0, protobuf_1.create)(wkt_1.ListValueSchema); for (const v of list) { listValue.values.push(valueFromCel(v)); } return listValue; } function structFromCel(map) { const struct = (0, protobuf_1.create)(wkt_1.StructSchema); for (const [k, v] of map.entries()) { if (typeof k !== "string") { throw new Error(`Invalid key type: ${typeof k} for google.protobuf.Struct, expected string`); } struct.fields[k] = valueFromCel(v); } return struct; } function scalarFromCel(field, type, v) { if ((0, uint_js_1.isCelUint)(v)) { v = v.value; } switch (type) { case protobuf_1.ScalarType.UINT32: case protobuf_1.ScalarType.FIXED32: if (typeof v !== "bigint") { throw unexpectedTypeError(field, "int", v); } return intToUint32(v); case protobuf_1.ScalarType.INT32: case protobuf_1.ScalarType.SINT32: case protobuf_1.ScalarType.SFIXED32: if (typeof v !== "bigint") { throw unexpectedTypeError(field, "int", v); } return intToInt32(v); case protobuf_1.ScalarType.UINT64: case protobuf_1.ScalarType.FIXED64: case protobuf_1.ScalarType.INT64: case protobuf_1.ScalarType.SINT64: case protobuf_1.ScalarType.SFIXED64: if (typeof v !== "bigint") { throw unexpectedTypeError(field, "int", v); } return v; case protobuf_1.ScalarType.FLOAT: if (typeof v !== "number") { throw unexpectedTypeError(field, "double", v); } return Math.fround(v); case protobuf_1.ScalarType.DOUBLE: if (typeof v !== "number") { throw unexpectedTypeError(field, "double", v); } return v; case protobuf_1.ScalarType.STRING: if (typeof v !== "string") { throw unexpectedTypeError(field, "string", v); } return v; case protobuf_1.ScalarType.BOOL: if (typeof v !== "boolean") { throw unexpectedTypeError(field, "bool", v); } return v; case protobuf_1.ScalarType.BYTES: if (!(v instanceof Uint8Array)) { throw unexpectedTypeError(field, "bytes", v); } return v; } } function unexpectedTypeError(field, expected, actValue) { return new Error(`Expected ${expected} but got ${(0, type_js_1.celType)(actValue)} for ${field.parent}.${field.name}`); } function intToInt32(v) { if (v < wire_2.INT32_MIN || v > wire_2.INT32_MAX) { throw new Error("int32 out of range"); } return Number(v); } function intToUint32(v) { if (v < 0 || v > wire_2.UINT32_MAX) { throw new Error("uint32 out of range"); } return Number(v); }