@bufbuild/cel
Version:
A CEL evaluator for ECMAScript
188 lines (187 loc) • 5.8 kB
JavaScript
// 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());