UNPKG

@bufbuild/cel

Version:

A CEL evaluator for ECMAScript

198 lines (197 loc) 8.03 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. 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`); }