@bufbuild/cel
Version:
A CEL evaluator for ECMAScript
156 lines (155 loc) • 4.65 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.
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);
}