UNPKG

@bufbuild/cel

Version:

A CEL evaluator for ECMAScript

532 lines (531 loc) 16.7 kB
"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); }