@bufbuild/cel
Version:
A CEL evaluator for ECMAScript
198 lines (197 loc) • 8.03 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 { create } from "@bufbuild/protobuf";
import { DurationSchema, TimestampSchema } from "@bufbuild/protobuf/wkt";
import { celOverload, celFunc } from "../func.js";
import * as opc from "../gen/dev/cel/expr/operator_const.js";
import { CelScalar, DURATION, listType, TIMESTAMP, } from "../type.js";
import { celListConcat } from "../list.js";
import { celUint } from "../uint.js";
import { createDuration } from "../duration.js";
import { createTimestamp } from "../timestamp.js";
const MAX_INT = 9223372036854775807n;
// biome-ignore lint/correctness/noPrecisionLoss: No symbol exists in the std.
const MAX_INT_NUM = 9223372036854775807.0;
const MIN_INT = -9223372036854775808n;
// biome-ignore lint/correctness/noPrecisionLoss: No symbol exists in the std.
const MIN_INT_NUM = -9223372036854775808.0;
const MAX_UINT = 18446744073709551615n;
// biome-ignore lint/correctness/noPrecisionLoss: No symbol exists in the std.
const MAX_UINT_NUM = 18446744073709551616.0;
const MIN_UINT = 0n;
const MIN_UINT_NUM = 0.0;
export function isOverflowInt(val) {
return val < MIN_INT || val > MAX_INT;
}
export function isOverflowIntNum(val) {
return Number.isNaN(val) || val <= MIN_INT_NUM || val >= MAX_INT_NUM;
}
export function isOverflowUint(val) {
return val < MIN_UINT || val > MAX_UINT;
}
export function isOverflowUintNum(val) {
return Number.isNaN(val) || val < MIN_UINT_NUM || val > MAX_UINT_NUM;
}
export function addMath(funcs) {
funcs.add(add);
funcs.add(subtract);
funcs.add(multiply);
funcs.add(divide);
funcs.add(modulo);
funcs.add(negate);
}
function addTimestamp(lhs, rhs) {
let seconds = lhs.message.seconds + rhs.message.seconds;
let nanos = lhs.message.nanos + rhs.message.nanos;
if (nanos > 999999999) {
seconds += BigInt(Math.floor(nanos / 1000000000));
nanos = nanos % 1000000000;
}
if (seconds > 253402300799 || seconds < -62135596800) {
throw overflow(opc.ADD, TIMESTAMP);
}
return create(TimestampSchema, { seconds: seconds, nanos: nanos });
}
function addDuration(lhs, rhs) {
let seconds = lhs.message.seconds + rhs.message.seconds;
let nanos = lhs.message.nanos + rhs.message.nanos;
if (nanos > 999999999) {
seconds += BigInt(Math.floor(nanos / 1000000000));
nanos = nanos % 1000000000;
}
if (seconds > 315576000000 || seconds < -315576000000) {
throw overflow(opc.ADD, DURATION);
}
return create(DurationSchema, { seconds: seconds, nanos: nanos });
}
function subtractDurationOrTimestamp(lhs, rhs) {
return createDuration(lhs.message.seconds - rhs.message.seconds, lhs.message.nanos - rhs.message.nanos);
}
const add = celFunc(opc.ADD, [
celOverload([CelScalar.INT, CelScalar.INT], CelScalar.INT, (lhs, rhs) => {
const val = lhs + rhs;
if (isOverflowInt(val)) {
throw overflow(opc.SUBTRACT, CelScalar.INT);
}
return val;
}),
celOverload([CelScalar.UINT, CelScalar.UINT], CelScalar.UINT, (lhs, rhs) => {
const val = lhs.value + rhs.value;
if (isOverflowUint(val)) {
throw overflow(opc.SUBTRACT, CelScalar.UINT);
}
return celUint(val);
}),
celOverload([CelScalar.DOUBLE, CelScalar.DOUBLE], CelScalar.DOUBLE, (lhs, rhs) => lhs + rhs),
celOverload([CelScalar.STRING, CelScalar.STRING], CelScalar.STRING, (lhs, rhs) => lhs + rhs),
celOverload([CelScalar.BYTES, CelScalar.BYTES], CelScalar.BYTES, (lhs, rhs) => {
const val = new Uint8Array(lhs.length + rhs.length);
val.set(lhs);
val.set(rhs, lhs.length);
return val;
}),
celOverload([TIMESTAMP, TIMESTAMP], TIMESTAMP, addTimestamp),
celOverload([TIMESTAMP, DURATION], TIMESTAMP, addTimestamp),
celOverload([DURATION, TIMESTAMP], TIMESTAMP, (lhs, rhs) => addTimestamp(rhs, lhs)),
celOverload([DURATION, DURATION], DURATION, addDuration),
celOverload([listType(CelScalar.DYN), listType(CelScalar.DYN)], listType(CelScalar.DYN), celListConcat),
]);
const subtract = celFunc(opc.SUBTRACT, [
celOverload([CelScalar.INT, CelScalar.INT], CelScalar.INT, (lhs, rhs) => {
const val = lhs - rhs;
if (isOverflowInt(val)) {
throw overflow(opc.SUBTRACT, CelScalar.INT);
}
return val;
}),
celOverload([CelScalar.UINT, CelScalar.UINT], CelScalar.UINT, (lhs, rhs) => {
const val = lhs.value - rhs.value;
if (isOverflowUint(val)) {
throw overflow(opc.SUBTRACT, CelScalar.UINT);
}
return celUint(val);
}),
celOverload([CelScalar.DOUBLE, CelScalar.DOUBLE], CelScalar.DOUBLE, (lhs, rhs) => lhs - rhs),
celOverload([TIMESTAMP, TIMESTAMP], DURATION, subtractDurationOrTimestamp),
celOverload([DURATION, DURATION], DURATION, subtractDurationOrTimestamp),
celOverload([TIMESTAMP, DURATION], TIMESTAMP, (lhs, rhs) => createTimestamp(lhs.message.seconds - rhs.message.seconds, lhs.message.nanos - rhs.message.nanos)),
]);
const multiply = celFunc(opc.MULTIPLY, [
celOverload([CelScalar.INT, CelScalar.INT], CelScalar.INT, (lhs, rhs) => {
const product = lhs * rhs;
if (isOverflowInt(product)) {
throw overflow(opc.MULTIPLY, CelScalar.INT);
}
return product;
}),
celOverload([CelScalar.UINT, CelScalar.UINT], CelScalar.UINT, (lhs, rhs) => {
const product = lhs.value * rhs.value;
if (isOverflowUint(product)) {
throw overflow(opc.MULTIPLY, CelScalar.UINT);
}
return celUint(product);
}),
celOverload([CelScalar.DOUBLE, CelScalar.DOUBLE], CelScalar.DOUBLE, (lhs, rhs) => lhs * rhs),
]);
const divide = celFunc(opc.DIVIDE, [
celOverload([CelScalar.INT, CelScalar.INT], CelScalar.INT, (lhs, rhs) => {
if (rhs === 0n) {
throw divisionByZero(CelScalar.INT);
}
if (lhs === MIN_INT && rhs === -1n) {
throw overflow(opc.DIVIDE, CelScalar.INT);
}
return lhs / rhs;
}),
celOverload([CelScalar.DOUBLE, CelScalar.DOUBLE], CelScalar.DOUBLE, (lhs, rhs) => lhs / rhs),
celOverload([CelScalar.UINT, CelScalar.UINT], CelScalar.UINT, (lhs, rhs) => {
if (rhs.value === 0n) {
throw divisionByZero(CelScalar.UINT);
}
return celUint(lhs.value / rhs.value);
}),
]);
const modulo = celFunc(opc.MODULO, [
celOverload([CelScalar.INT, CelScalar.INT], CelScalar.INT, (lhs, rhs) => {
if (rhs === 0n) {
throw moduloByZero(CelScalar.INT);
}
return lhs % rhs;
}),
celOverload([CelScalar.UINT, CelScalar.UINT], CelScalar.UINT, (lhs, rhs) => {
if (rhs.value === 0n) {
throw moduloByZero(CelScalar.UINT);
}
return celUint(lhs.value % rhs.value);
}),
]);
const negate = celFunc(opc.NEGATE, [
celOverload([CelScalar.INT], CelScalar.INT, (arg) => {
const val = -arg;
if (isOverflowInt(val)) {
throw overflow(opc.NEGATE, CelScalar.INT);
}
return val;
}),
celOverload([CelScalar.DOUBLE], CelScalar.DOUBLE, (arg) => -arg),
]);
function overflow(op, type) {
return new Error(`${type.name} return error for overflow during ${op}`);
}
function divisionByZero(type) {
return new Error(`${type.name} divide by zero`);
}
function moduloByZero(type) {
return new Error(`${type.name} modulus by zero`);
}