UNPKG

@bufbuild/protovalidate

Version:

Protocol Buffer Validation for ECMAScript

238 lines (237 loc) 11.8 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.Planner = void 0; const protobuf_1 = require("@bufbuild/protobuf"); const wkt_1 = require("@bufbuild/protobuf/wkt"); const validate_pb_js_1 = require("./gen/buf/validate/validate_pb.js"); const reflect_1 = require("@bufbuild/protobuf/reflect"); const eval_js_1 = require("./eval.js"); const rules_js_1 = require("./rules.js"); const condition_js_1 = require("./condition.js"); const cel_js_1 = require("./cel.js"); const error_js_1 = require("./error.js"); class Planner { constructor(celMan, legacyRequired) { this.celMan = celMan; this.legacyRequired = legacyRequired; this.messageCache = new Map(); } plan(message) { const existing = this.messageCache.get(message); if (existing) { return existing; } const messageRules = (0, protobuf_1.getOption)(message, validate_pb_js_1.message); const e = new eval_js_1.EvalMany(); this.messageCache.set(message, e); e.add(this.fields(messageRules.oneof, message.fields)); e.add(this.messageCel(messageRules)); e.add(this.messageOneofs(message, messageRules.oneof)); e.add(this.oneofs(message.oneofs)); e.prune(); return e; } oneofs(oneofs) { return new eval_js_1.EvalMany(...oneofs .filter((o) => (0, protobuf_1.getOption)(o, validate_pb_js_1.oneof).required) .map((o) => new eval_js_1.EvalOneofRequired(o))); } messageOneofs(message, oneofRules) { return new eval_js_1.EvalMany(...oneofRules.map((rule) => { if (rule.fields.length == 0) { throw new error_js_1.CompilationError(`at least one field must be specified in oneof rule for the ${message}`); } const seen = new Set(); return new eval_js_1.EvalMessageOneofRule(rule.fields.map((fieldName) => { if (seen.has(fieldName)) { throw new error_js_1.CompilationError(`duplicate ${fieldName} in oneof rule for the ${message}`); } seen.add(fieldName); const found = message.fields.find((descField) => descField.name === fieldName); if (!found) { throw new error_js_1.CompilationError(`field "${fieldName}" not found in ${message}`); } return found; }), rule.required); })); } fields(oneofs, fields) { const evals = new eval_js_1.EvalMany(); for (const field of fields) { const fieldRules = (0, protobuf_1.getOption)(field, validate_pb_js_1.field); let ignore = fieldRules.ignore; if (!(0, protobuf_1.isFieldSet)(fieldRules, validate_pb_js_1.FieldRulesSchema.field.ignore) && oneofs.some((oneof) => oneof.fields.includes(field.name))) { ignore = validate_pb_js_1.Ignore.IF_ZERO_VALUE; } if (fieldRules.required && ignore !== validate_pb_js_1.Ignore.ALWAYS) { evals.add(new eval_js_1.EvalFieldRequired(field)); } if (this.legacyRequired && field.presence == wkt_1.FeatureSet_FieldPresence.LEGACY_REQUIRED) { evals.add(new eval_js_1.EvalFieldLegacyRequired(field)); } const baseRulePath = (0, reflect_1.buildPath)(validate_pb_js_1.FieldRulesSchema); switch (field.fieldKind) { case "message": { evals.add(new eval_js_1.EvalField(field, (0, condition_js_1.ignoreMessageField)(field, ignore), this.message(field.message, fieldRules, baseRulePath, field))); break; } case "list": { evals.add(new eval_js_1.EvalField(field, (0, condition_js_1.ignoreListOrMapField)(field, ignore), this.planList(field, fieldRules, baseRulePath))); break; } case "map": { evals.add(new eval_js_1.EvalField(field, (0, condition_js_1.ignoreListOrMapField)(field, ignore), this.map(field, fieldRules, baseRulePath))); break; } case "enum": { evals.add(new eval_js_1.EvalField(field, (0, condition_js_1.ignoreScalarOrEnumField)(field, ignore), this.enumeration(field.enum, fieldRules, baseRulePath, field))); break; } case "scalar": { evals.add(new eval_js_1.EvalField(field, (0, condition_js_1.ignoreScalarOrEnumField)(field, ignore), this.scalar(field.scalar, fieldRules, baseRulePath, false, field))); break; } } } return evals; } planList(field, fieldRules, baseRulePath) { const evals = new eval_js_1.EvalMany(this.fieldCel(fieldRules, baseRulePath, false, undefined)); const [rules, rulePath, rulePathItems] = (0, rules_js_1.getListRules)(baseRulePath, fieldRules, field); if (rules) { evals.add(this.rules(rules, rulePath, false)); } const itemsRules = rules?.items; switch (field.listKind) { case "enum": { evals.add(new eval_js_1.EvalListItems((0, condition_js_1.ignoreEnumValue)(field.enum, itemsRules?.ignore), this.enumeration(field.enum, itemsRules, rulePathItems, field))); break; } case "scalar": { evals.add(new eval_js_1.EvalListItems((0, condition_js_1.ignoreScalarValue)(field.scalar, itemsRules?.ignore), this.scalar(field.scalar, itemsRules, rulePathItems, false, field))); break; } case "message": { evals.add(new eval_js_1.EvalListItems((0, condition_js_1.ignoreMessageValue)(itemsRules?.ignore), this.message(field.message, itemsRules, rulePathItems, field))); break; } } return evals; } map(field, fieldRules, baseRulePath) { const evals = new eval_js_1.EvalMany(this.fieldCel(fieldRules, baseRulePath, false, undefined)); const [rules, rulePath, rulePathKeys, rulePathValues] = (0, rules_js_1.getMapRules)(baseRulePath, fieldRules, field); if (rules) { evals.add(this.rules(rules, rulePath, false)); } const ignoreKey = (0, condition_js_1.ignoreScalarValue)(field.mapKey, rules?.keys?.ignore); const evalKey = this.scalar(field.mapKey, rules?.keys, rulePathKeys, true, field); const valuesRules = rules?.values; switch (field.mapKind) { case "message": { evals.add(new eval_js_1.EvalMapEntries(ignoreKey, evalKey, (0, condition_js_1.ignoreMessageValue)(valuesRules?.ignore), this.message(field.message, valuesRules, rulePathValues, field))); break; } case "enum": { evals.add(new eval_js_1.EvalMapEntries(ignoreKey, evalKey, (0, condition_js_1.ignoreEnumValue)(field.enum, valuesRules?.ignore), this.enumeration(field.enum, valuesRules, rulePathValues, field))); break; } case "scalar": { evals.add(new eval_js_1.EvalMapEntries(ignoreKey, evalKey, (0, condition_js_1.ignoreScalarValue)(field.scalar, valuesRules?.ignore), this.scalar(field.scalar, valuesRules, rulePathValues, false, field))); break; } } return evals; } enumeration(descEnum, fieldRules, baseRulePath, fieldContext) { const evals = new eval_js_1.EvalMany(this.fieldCel(fieldRules, baseRulePath, false, protobuf_1.ScalarType.INT32)); const [rules, rulePath] = (0, rules_js_1.getEnumRules)(baseRulePath, fieldRules, fieldContext); if (rules) { evals.add(new eval_js_1.EvalEnumDefinedOnly(descEnum, rulePath, rules)); evals.add(this.rules(rules, rulePath, false)); } return evals; } scalar(scalar, fieldRules, baseRulePath, forMapKey, fieldContext) { const evals = new eval_js_1.EvalMany(this.fieldCel(fieldRules, baseRulePath, forMapKey, scalar)); const [rules, rulePath] = (0, rules_js_1.getScalarRules)(scalar, baseRulePath, fieldRules, fieldContext); if (rules) { evals.add(this.rules(rules, rulePath, forMapKey)); } return evals; } message(descMessage, fieldRules, baseRulePath, fieldContext) { const evals = new eval_js_1.EvalMany(this.fieldCel(fieldRules, baseRulePath, false, undefined)); evals.add(this.plan(descMessage)); const [rules, rulePath] = (0, rules_js_1.getMessageRules)(descMessage, baseRulePath, fieldRules, fieldContext); if (rules) { if ((0, protobuf_1.isMessage)(rules, validate_pb_js_1.AnyRulesSchema)) { evals.add(new eval_js_1.EvalAnyRules(rulePath, rules)); } evals.add(this.rules(rules, rulePath, false)); } return evals; } rules(rules, rulePath, forMapKey) { const ruleDesc = (0, rules_js_1.getRuleDescriptor)(rules.$typeName); const prepared = this.celMan.compileRules(ruleDesc); const evalStandard = new cel_js_1.EvalStandardRulesCel(this.celMan, rules, forMapKey); for (const plan of prepared.standard) { if (!(0, protobuf_1.isFieldSet)(rules, plan.field)) { continue; } evalStandard.add(plan.compiled, rulePath.clone().field(plan.field).toPath()); } const evalExtended = new cel_js_1.EvalExtendedRulesCel(this.celMan, rules, forMapKey); if (rules.$unknown) { for (const uf of rules.$unknown) { const plans = prepared.extensions.get(uf.no); if (!plans) { continue; } for (const plan of plans) { evalExtended.add(plan.compiled, rulePath.clone().extension(plan.ext).toPath(), (0, protobuf_1.getExtension)(rules, plan.ext), plan.ext.fieldKind == "scalar" ? plan.ext.scalar : undefined); } } } return new eval_js_1.EvalMany(evalStandard, evalExtended); } messageCel(messageRules) { const e = new cel_js_1.EvalCustomCel(this.celMan, false, undefined); for (const rule of messageRules.cel) { e.add(this.celMan.compileRule(rule), []); } return e; } fieldCel(fieldRules, baseRulePath, forMapKey, scalarType) { if (!fieldRules) { return eval_js_1.EvalNoop.get(); } const e = new cel_js_1.EvalCustomCel(this.celMan, forMapKey, scalarType); for (const [index, rule] of fieldRules.cel.entries()) { const rulePath = baseRulePath .clone() .field(validate_pb_js_1.FieldRulesSchema.field.cel) .list(index) .toPath(); e.add(this.celMan.compileRule(rule), rulePath); } return e; } } exports.Planner = Planner;