@bufbuild/cel
Version:
A CEL evaluator for ECMAScript
532 lines (531 loc) • 16.7 kB
JavaScript
"use strict";
// 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.
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConcreteAttributeFactory = exports.ErrorAttr = void 0;
const planner_js_1 = require("./planner.js");
const error_js_1 = require("./error.js");
const field_js_1 = require("./field.js");
const uint_js_1 = require("./uint.js");
const type_js_1 = require("./type.js");
function attrAccess(factory, vars, obj, accAttr) {
const val = accAttr.resolve(vars);
if (val === undefined) {
return undefined;
}
if ((0, error_js_1.isCelError)(val)) {
return val;
}
const access = factory.newAccess(accAttr.id, val, accAttr.isOptional());
return access.access(vars, obj);
}
function attrIsPresent(factory, vars, obj, accAttr) {
const val = accAttr.resolve(vars);
if (val === undefined) {
return false;
}
if ((0, error_js_1.isCelError)(val)) {
return val;
}
const access = factory.newAccess(accAttr.id, val, accAttr.isOptional());
return access.isPresent(vars, obj);
}
function attrAccessIfPresent(factory, vars, obj, accAttr, presenceOnly) {
const val = accAttr.resolve(vars);
if (val === undefined) {
return undefined;
}
if ((0, error_js_1.isCelError)(val)) {
return val;
}
const access = factory.newAccess(accAttr.id, val, accAttr.isOptional());
return access.accessIfPresent(vars, obj, presenceOnly);
}
function applyAccesses(vars, obj, accesses) {
if (accesses.length === 0) {
return obj;
}
let cur = obj;
for (const access of accesses) {
const result = access.access(vars, cur);
if (result === undefined) {
return undefined;
}
if ((0, error_js_1.isCelError)(result)) {
return result;
}
cur = result;
}
return cur;
}
class AbsoluteAttr {
constructor(id, nsNames, accesses_, registry, factory) {
this.id = id;
this.nsNames = nsNames;
this.accesses_ = accesses_;
this.registry = registry;
this.factory = factory;
if (nsNames.length === 0) {
throw new Error("No names provided");
}
}
isOptional() {
return false;
}
addAccess(access) {
this.accesses_.push(access);
}
candidateNames() {
return this.nsNames;
}
accesses() {
return this.accesses_;
}
access(vars, obj) {
return attrAccess(this.factory, vars, obj, this);
}
isPresent(vars, obj) {
return attrIsPresent(this.factory, vars, obj, this);
}
accessIfPresent(vars, obj, presenceOnly) {
return attrAccessIfPresent(this.factory, vars, obj, this, presenceOnly);
}
resolve(vars) {
for (const name of this.nsNames) {
const raw = vars.resolve(name);
if (raw !== undefined) {
if ((0, error_js_1.isCelError)(raw)) {
return raw;
}
return applyAccesses(vars, raw, this.accesses_);
}
const typ = this.findIdent(name);
if (typ !== undefined && this.accesses_.length === 0) {
return typ;
}
}
return undefined;
}
findIdent(ident) {
const desc = this.registry.getMessage(ident);
if (desc) {
return (0, type_js_1.objectType)(desc);
}
// Must be an enum
if (ident.indexOf(".") > 1) {
const lastDot = ident.lastIndexOf(".");
const enumName = ident.substring(0, lastDot);
const valueName = ident.substring(lastDot + 1);
const descEnum = this.registry.getEnum(enumName);
if (descEnum) {
const enumValue = descEnum.values.find((v) => v.name === valueName);
if (enumValue) {
return BigInt(enumValue.number);
}
}
}
switch (ident) {
case "int":
return type_js_1.CelScalar.INT;
case "uint":
return type_js_1.CelScalar.UINT;
case "double":
return type_js_1.CelScalar.DOUBLE;
case "bool":
return type_js_1.CelScalar.BOOL;
case "string":
return type_js_1.CelScalar.STRING;
case "bytes":
return type_js_1.CelScalar.BYTES;
case "list":
return (0, type_js_1.listType)(type_js_1.CelScalar.DYN);
case "map":
return (0, type_js_1.mapType)(type_js_1.CelScalar.DYN, type_js_1.CelScalar.DYN);
case "null_type":
return type_js_1.CelScalar.NULL;
case "type":
return type_js_1.CelScalar.TYPE;
default:
return undefined;
}
}
}
class ConditionalAttr {
constructor(id, cond, t, f, factory) {
this.id = id;
this.cond = cond;
this.t = t;
this.f = f;
this.factory = factory;
}
isOptional() {
return false;
}
addAccess(access) {
this.t.addAccess(access);
this.f.addAccess(access);
}
access(vars, obj) {
return attrAccess(this.factory, vars, obj, this);
}
isPresent(vars, obj) {
return attrIsPresent(this.factory, vars, obj, this);
}
accessIfPresent(vars, obj, presenceOnly) {
return attrAccessIfPresent(this.factory, vars, obj, this, presenceOnly);
}
resolve(vars) {
const cond = this.cond.eval(vars);
switch (true) {
case cond === true:
return this.t.resolve(vars);
case cond === false:
return this.f.resolve(vars);
case (0, error_js_1.isCelError)(cond):
return cond;
}
return (0, error_js_1.celError)(`found no matching overload for _?_:_ applied to '(${(0, type_js_1.celType)(cond).name})'`, this.id);
}
}
class MaybeAttr {
constructor(id, attrs, factory) {
this.id = id;
this.attrs = attrs;
this.factory = factory;
}
isOptional() {
return false;
}
addAccess(access) {
const augmentedNames = [];
let str;
if (access instanceof StringAccess) {
str = access.name;
}
// Add the access to all existing attributes.
for (const attr of this.attrs) {
if (str !== undefined && attr.accesses().length === 0) {
for (const name of attr.candidateNames()) {
augmentedNames.push(name + "." + str);
}
}
attr.addAccess(access);
}
if (augmentedNames.length > 0) {
// Next, ensure the most specific variable / type reference is searched first.
const newAttr = this.factory.createAbsolute(this.id, augmentedNames);
// Insert it as the first attribute to search.
this.attrs.unshift(newAttr);
}
}
access(vars, obj) {
return attrAccess(this.factory, vars, obj, this);
}
isPresent(vars, obj) {
return attrIsPresent(this.factory, vars, obj, this);
}
accessIfPresent(vars, obj, presenceOnly) {
return attrAccessIfPresent(this.factory, vars, obj, this, presenceOnly);
}
resolve(vars) {
for (const attr of this.attrs) {
const val = attr.resolve(vars);
if (val !== undefined) {
return val;
}
}
return undefined;
}
}
class RelativeAttr {
constructor(id, operand, accesses_, factory) {
this.id = id;
this.operand = operand;
this.accesses_ = accesses_;
this.factory = factory;
}
isOptional() {
return false;
}
addAccess(access) {
this.accesses_.push(access);
}
access(vars, obj) {
return attrAccess(this.factory, vars, obj, this);
}
isPresent(vars, obj) {
return attrIsPresent(this.factory, vars, obj, this);
}
accessIfPresent(vars, obj, presenceOnly) {
return attrAccessIfPresent(this.factory, vars, obj, this, presenceOnly);
}
resolve(vars) {
const v = this.operand.eval(vars);
if ((0, error_js_1.isCelError)(v)) {
return v;
}
return applyAccesses(vars, v, this.accesses_);
}
}
class StringAccess {
constructor(id, name, celVal, optional) {
this.id = id;
this.name = name;
this.celVal = celVal;
this.optional = optional;
}
isOptional() {
return this.optional;
}
access(_vars, obj) {
const val = (0, field_js_1.accessByName)(obj, this.name);
if (val === undefined && !this.optional) {
return fieldNotFound(this.id, this.name);
}
return val;
}
isPresent(_vars, obj) {
const set = (0, field_js_1.isSet)(obj, this.name);
if (set === undefined) {
return fieldNotFound(this.id, this.name);
}
return set;
}
accessIfPresent(_vars, obj, presenceOnly) {
const val = (0, field_js_1.accessByName)(obj, this.name);
if (val === undefined && !this.optional && !presenceOnly) {
return fieldNotFound(this.id, this.name);
}
return val;
}
}
class BoolAccess {
constructor(id, index, optional) {
this.id = id;
this.index = index;
this.optional = optional;
}
isOptional() {
return this.optional;
}
access(_vars, obj) {
if (obj === undefined) {
return obj;
}
const raw = (0, field_js_1.accessByIndex)(obj, this.index);
if (raw === undefined && !this.optional) {
return fieldNotFound(this.id, this.index);
}
return raw;
}
isPresent(_vars, obj) {
const raw = (0, field_js_1.accessByIndex)(obj, this.index);
if (raw === undefined && !this.optional) {
return fieldNotFound(this.id, this.index);
}
return true;
}
accessIfPresent(_vars, obj, _presenceOnly) {
const raw = (0, field_js_1.accessByIndex)(obj, this.index);
if (raw === undefined && !this.optional) {
return fieldNotFound(this.id, this.index);
}
return raw;
}
}
class NumAccess {
constructor(id, index, celVal, optional) {
this.id = id;
this.index = index;
this.celVal = celVal;
this.optional = optional;
}
isOptional() {
return this.optional;
}
access(_vars, obj) {
if (obj === undefined) {
return obj;
}
const raw = (0, field_js_1.accessByIndex)(obj, this.index);
if (raw === undefined && !this.optional) {
return indexOutOfBounds(this.id, this.index, -1);
}
return raw;
}
isPresent(_vars, obj) {
const raw = (0, field_js_1.accessByIndex)(obj, this.index);
if (raw === undefined && !this.optional) {
return indexOutOfBounds(this.id, this.index, -1);
}
return true;
}
accessIfPresent(_vars, obj, _presenceOnly) {
const raw = (0, field_js_1.accessByIndex)(obj, this.index);
if (raw === undefined && !this.optional) {
return indexOutOfBounds(this.id, this.index, -1);
}
return raw;
}
}
class IntAccess {
constructor(id, index, celVal, optional) {
this.id = id;
this.index = index;
this.celVal = celVal;
this.optional = optional;
}
access(_vars, obj) {
if (obj === undefined) {
return obj;
}
const raw = (0, field_js_1.accessByIndex)(obj, this.index);
if (raw === undefined && !this.optional) {
return indexOutOfBounds(this.id, Number(this.index), -1);
}
return raw;
}
isPresent(_vars, obj) {
const raw = (0, field_js_1.accessByIndex)(obj, this.index);
if (raw === undefined && !this.optional) {
return indexOutOfBounds(this.id, Number(this.index), -1);
}
return true;
}
accessIfPresent(_vars, obj, _presenceOnly) {
const raw = (0, field_js_1.accessByIndex)(obj, this.index);
if (raw === undefined && !this.optional) {
return indexOutOfBounds(this.id, Number(this.index), -1);
}
return raw;
}
isOptional() {
return this.optional;
}
}
class ErrorAttr {
constructor(id, error, opt) {
this.id = id;
this.error = error;
this.opt = opt;
}
addAccess(_access) {
// Do nothing
}
resolve(_vars) {
return this.error;
}
isOptional() {
return this.opt;
}
access(_vars, _obj) {
return this.error;
}
isPresent(_vars, _obj) {
return this.error;
}
accessIfPresent(_vars, _obj, _presenceOnly) {
return this.error;
}
}
exports.ErrorAttr = ErrorAttr;
class EvalAccess {
constructor(id, key, factory, optional) {
this.id = id;
this.key = key;
this.factory = factory;
this.optional = optional;
}
access(vars, obj) {
if (obj === undefined) {
return obj;
}
const key = this.key.eval(vars);
if ((0, error_js_1.isCelError)(key)) {
return key;
}
const access = this.factory.newAccess(this.id, key, this.optional);
return access.access(vars, obj);
}
isPresent(vars, obj) {
if (obj === undefined) {
return false;
}
const key = this.key.eval(vars);
if ((0, error_js_1.isCelError)(key)) {
return key;
}
const access = this.factory.newAccess(this.id, key, this.optional);
return access.isPresent(vars, obj);
}
accessIfPresent(vars, obj, presenceOnly) {
const key = this.key.eval(vars);
if ((0, error_js_1.isCelError)(key)) {
return key;
}
const access = this.factory.newAccess(this.id, key, this.optional);
return access.accessIfPresent(vars, obj, presenceOnly);
}
isOptional() {
return this.optional;
}
}
class ConcreteAttributeFactory {
constructor(registry, container) {
this.registry = registry;
this.container = container;
}
createAbsolute(id, names) {
return new AbsoluteAttr(id, names, [], this.registry, this);
}
createConditional(id, cond, t, f) {
return new ConditionalAttr(id, cond, t, f, this);
}
createMaybe(id, name) {
return new MaybeAttr(id, [this.createAbsolute(id, this.container.resolveCandidateNames(name))], this);
}
createRelative(id, operand) {
return new RelativeAttr(id, operand, [], this);
}
newAccess(id, val, opt) {
switch (typeof val) {
case "boolean":
return new BoolAccess(id, val, opt);
case "number":
return new NumAccess(id, val, val, opt);
case "bigint":
return new IntAccess(id, val, val, opt);
case "string":
return new StringAccess(id, val, val, opt);
case "object":
if (val instanceof planner_js_1.EvalAttr) {
return new EvalAccess(id, val, this, opt);
}
if ((0, uint_js_1.isCelUint)(val)) {
return new IntAccess(id, val.value, val, opt);
}
break;
default:
break;
}
return new ErrorAttr(id, (0, error_js_1.celError)("unsupported key type", id), opt);
}
}
exports.ConcreteAttributeFactory = ConcreteAttributeFactory;
function fieldNotFound(id, name) {
return (0, error_js_1.celError)(`field not found: ${String(name)}`, id);
}
function indexOutOfBounds(id, index, length) {
return (0, error_js_1.celError)(`index ${index} out of bounds [0, ${length})`, id);
}