UNPKG

@bufbuild/cel

Version:

A CEL evaluator for ECMAScript

200 lines (199 loc) 5.82 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. var _a, _b; import { isCelList } from "./list.js"; import { CelScalar, isCelType, } from "./type.js"; import { isCelError, celErrorMerge, celError, } from "./error.js"; import { isCelMap } from "./map.js"; import { isCelUint } from "./uint.js"; import { isReflectMessage } from "@bufbuild/protobuf/reflect"; import { unwrapAny, toCel } from "./value.js"; const privateFuncSymbol = Symbol.for("@bufbuild/cel/func"); const privateOverloadSymbol = Symbol.for("@bufbuild/cel/overload"); /** * Creates a new CelFunc. */ export function celFunc(name, overloads) { return new Func(name, overloads); } /** * Creates a new CelOverload. */ export function celOverload(parameters, result, impl) { return new FuncOverload(parameters, result, impl); } class Func { constructor(_name, _overloads) { this._name = _name; this._overloads = _overloads; this[_a] = {}; } get name() { return this._name; } get overloads() { return this._overloads; } dispatch(id, args) { const vals = unwrapResults(args); if (isCelError(vals)) { return vals; } for (const overload of this._overloads) { if (overload.parameters.length !== vals.length) { continue; } const checkedVals = []; for (let i = 0; i < vals.length; i++) { const celValue = unwrapAny(vals[i]); if (!isOfType(celValue, overload.parameters[i])) { break; } checkedVals.push(celValue); } if (checkedVals.length !== vals.length) { continue; } try { return toCel(overload.impl(...checkedVals)); } catch (ex) { return celError(ex, id); } } return undefined; } } _a = privateFuncSymbol; class FuncOverload { constructor(_parameters, _result, _impl) { this._parameters = _parameters; this._result = _result; this._impl = _impl; this[_b] = {}; } get parameters() { return this._parameters; } get result() { return this._result; } get impl() { return this._impl; } } _b = privateOverloadSymbol; /** * Set of functions uniquely identified by their name. */ export class FuncRegistry { constructor(funcs) { this.functions = new Map(); funcs && this.add(funcs); } add(nameOrFunc, call) { if (typeof nameOrFunc !== "string") { if (Array.isArray(nameOrFunc)) { for (const func of nameOrFunc) { this.add(func); } return; } call = nameOrFunc; nameOrFunc = nameOrFunc.name; } if (call === undefined) { throw new Error("dispatch is required with name"); } this.addCall(nameOrFunc, call); } /** * Find a function by name. */ find(name) { return this.functions.get(name); } addCall(name, call) { if (this.functions.has(name)) { throw new Error(`Function ${name} already registered`); } this.functions.set(name, call); } } export class OrderedDispatcher { constructor(dispatchers) { this.dispatchers = dispatchers; } add(dispatcher) { this.dispatchers.unshift(dispatcher); } find(name) { for (const dispatcher of this.dispatchers) { const result = dispatcher.find(name); if (result !== undefined) { return result; } } return undefined; } } function isOfType(val, type) { switch (type.kind) { case "list": return isCelList(val); case "map": return isCelMap(val); case "object": return isReflectMessage(val, type.desc); case "type": return isCelType(val); case "scalar": switch (type) { case CelScalar.DYN: return true; case CelScalar.INT: return typeof val === "bigint"; case CelScalar.UINT: return isCelUint(val); case CelScalar.BOOL: return typeof val === "boolean"; case CelScalar.DOUBLE: return typeof val === "number"; case CelScalar.NULL: return val === null; case CelScalar.STRING: return typeof val === "string"; case CelScalar.BYTES: return val instanceof Uint8Array; } } return false; } function unwrapResults(args) { const errors = []; const vals = []; for (let i = 0; i < args.length; i++) { const arg = args[i]; if (isCelError(arg)) { errors.push(arg); } else { vals.push(arg); } } if (errors.length > 0) { return celErrorMerge(errors[0], ...errors.slice(1)); } return vals; }