UNPKG

@bufbuild/cel

Version:

A CEL evaluator for ECMAScript

156 lines (155 loc) 4.65 kB
// 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. import { celUint, isCelUint } from "./uint.js"; import { celMap, isCelMap } from "./map.js"; import { celList, isCelList } from "./list.js"; import { isReflectList, isReflectMap, isReflectMessage, reflect, } from "@bufbuild/protobuf/reflect"; import { isMessage } from "@bufbuild/protobuf"; import { getEvalContext, getMsgDesc } from "./eval.js"; import { AnySchema, anyUnpack, Int32ValueSchema, isWrapper, ListValueSchema, StructSchema, UInt32ValueSchema, UInt64ValueSchema, ValueSchema, } from "@bufbuild/protobuf/wkt"; import { isCelType } from "./type.js"; /** * Converts a CelInput to a CelValue. */ export function toCel(v) { switch (typeof v) { case "bigint": case "boolean": case "number": case "string": return v; case "object": break; default: throw new Error(`unsupported input ${typeof v}`); } switch (true) { case v === null: case v instanceof Uint8Array: case isCelList(v): case isCelMap(v): case isCelUint(v): case isCelType(v): return v; } if (isArray(v) || isReflectList(v)) { return celList(v); } if (isMap(v) || isReflectMap(v)) { return celMap(v); } if (isMessage(v)) { const value = wktToCel(v); if (value !== undefined) { return value; } return reflect(getMsgDesc(v.$typeName), v); } if (isReflectMessage(v)) { return reflectMsgToCel(v); } if (v.constructor.name === "Object") { return celMap(new Map(Object.entries(v))); } throw new Error(`Unsupported input ${v}`); } /** * Unwraps the given value if it is an Any. * * If the Any represents a Wrapper type or google.protobuf.Value/Struct/ListValue, it also converts them * to their corresponding CelValue. */ export function unwrapAny(v) { if (!isReflectAny(v)) { return v; } const unpacked = anyUnpack(v.message, getEvalContext().registry); if (unpacked === undefined) { throw new Error(`invalid Any or ${v.message.typeUrl} not found in registry`); } const value = wktToCel(unpacked); if (value !== undefined) { return value; } return reflect(getMsgDesc(unpacked.$typeName), unpacked); } export function reflectMsgToCel(v) { const value = wktToCel(v.message); if (value !== undefined) { return value; } return v; } function isReflectAny(v) { return isReflectMessage(v, AnySchema); } function isArray(v) { return Array.isArray(v); } function isMap(v) { return v instanceof Map; } function wktToCel(msg) { if (isWrapper(msg)) { return unwrapWrapper(msg); } return jsonWrapperToCel(msg); } function unwrapWrapper(v) { switch (v.$typeName) { case Int32ValueSchema.typeName: return BigInt(v.value); case UInt32ValueSchema.typeName: return celUint(BigInt(v.value)); case UInt64ValueSchema.typeName: return celUint(v.value); } return v.value; } function jsonWrapperToCel(msg) { switch (true) { case isMessage(msg, StructSchema): return structToCel(msg); case isMessage(msg, ValueSchema): return valueToCel(msg); case isMessage(msg, ListValueSchema): return listValueToCel(msg); } return undefined; } function structToCel(s) { const map = new Map(); for (const [k, v] of Object.entries(s.fields)) { map.set(k, valueToCel(v)); } return celMap(map); } function valueToCel(v) { switch (v.kind.case) { case "boolValue": case "numberValue": case "stringValue": return v.kind.value; case "nullValue": case undefined: return null; case "structValue": return structToCel(v.kind.value); case "listValue": return listValueToCel(v.kind.value); } } function listValueToCel(l) { return celList(l.values); }