UNPKG

@bufbuild/cel

Version:

A CEL evaluator for ECMAScript

188 lines (187 loc) 5.8 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 { isReflectMap, } from "@bufbuild/protobuf/reflect"; import { isCelUint } from "./uint.js"; import { ScalarType } from "@bufbuild/protobuf"; import { celFromScalar } from "./proto.js"; import { reflectMsgToCel, toCel } from "./value.js"; const privateSymbol = Symbol.for("@bufbuild/cel/map"); /** * Create a new map from a native map or a ReflectMap. */ export function celMap(mapOrReflectMap) { if (isReflectMap(mapOrReflectMap)) { return new ProtoMap(mapOrReflectMap); } return new NativeMap(mapOrReflectMap); } /** * Returns true if the given value is a CelMap. */ export function isCelMap(v) { return typeof v === "object" && v !== null && privateSymbol in v; } class NativeMap { constructor(_map) { this._map = _map; this[_a] = {}; } get size() { return this._map.size; } get(key) { if (isCelUint(key)) { key = key.value; } // According to CEL equality all numerical types are // equal if they have the same value. if (typeof key === "number") { if (!Number.isInteger(key)) { return undefined; } key = BigInt(key); } // Direct check for maps with string, boolean, and bigint keys. const value = this._map.get(key); if (value !== undefined) { return toCel(value); } // For maps with CelUint keys we have to loop through all keys to check because // JS maps use SameValueZero algorithm which is the same as '===' for objects. // // Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map#key_equality if (typeof key === "bigint") { for (const mapKey of this._map.keys()) { if (!isCelUint(mapKey)) { continue; } if (mapKey.value === key) { return toCel(this._map.get(mapKey)); } } } return undefined; } has(key) { return this.get(key) != undefined; } forEach(callback, // biome-ignore lint/suspicious/noExplicitAny: Part of the Map interface. thisArg) { this._map.forEach((value, key, _) => callback.call(thisArg, toCel(value), key, this)); } *entries() { for (const [key, value] of this._map.entries()) { yield [key, toCel(value)]; } } keys() { return this._map.keys(); } *values() { for (const value of this._map.values()) { yield toCel(value); } } [(_a = privateSymbol, Symbol.iterator)]() { return this.entries(); } } class ProtoMap { constructor(_map) { this._map = _map; this[_b] = {}; } get size() { return this._map.size; } get(key) { const value = this._map.get(mapKeyFromCel(this._map.field(), key)); if (value === undefined) { return undefined; } return celFromMapValue(this._map.field(), value); } has(key) { return this._map.has(mapKeyFromCel(this._map.field(), key)); } forEach(callback, // biome-ignore lint/suspicious/noExplicitAny: Part of the Map interface. thisArg) { this._map.forEach((value, key, _) => callback.call(thisArg, celFromMapValue(this._map.field(), value), celFromMapKey(this._map.field(), key), this)); } *entries() { for (const [key, value] of this._map.entries()) { yield [ celFromMapKey(this._map.field(), key), celFromMapValue(this._map.field(), value), ]; } } *keys() { for (const key of this._map.keys()) { yield celFromMapKey(this._map.field(), key); } } *values() { for (const value of this._map.keys()) { yield celFromMapValue(this._map.field(), value); } } [(_b = privateSymbol, Symbol.iterator)]() { return this.entries(); } } function mapKeyFromCel(desc, v) { if (isCelUint(v)) { v = v.value; } switch (desc.mapKey) { case ScalarType.SINT32: case ScalarType.INT32: case ScalarType.FIXED32: case ScalarType.UINT32: case ScalarType.SFIXED32: if (typeof v === "bigint") { return Number(v); } return v; case ScalarType.SINT64: case ScalarType.INT64: case ScalarType.FIXED64: case ScalarType.UINT64: case ScalarType.SFIXED64: if (v === "number" && Number.isInteger(v)) { return BigInt(v); } return v; default: return v; } } function celFromMapKey(desc, v) { return celFromScalar(desc.mapKey, v); } function celFromMapValue(desc, v) { switch (desc.mapKind) { case "enum": return BigInt(v); case "message": return reflectMsgToCel(v); case "scalar": return celFromScalar(desc.scalar, v); } } export const EMPTY_MAP = celMap(new Map());