UNPKG

@bufbuild/protovalidate

Version:

Protocol Buffer Validation for ECMAScript

341 lines (340 loc) 12.4 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.Violation = exports.ValidationError = exports.RuntimeError = exports.CompilationError = void 0; exports.violationsToProto = violationsToProto; exports.violationToProto = violationToProto; exports.pathFromViolationProto = pathFromViolationProto; const reflect_1 = require("@bufbuild/protobuf/reflect"); const validate_pb_js_1 = require("./gen/buf/validate/validate_pb.js"); const protobuf_1 = require("@bufbuild/protobuf"); const wkt_1 = require("@bufbuild/protobuf/wkt"); /** * A CompilationError is raised if a CEL expression cannot be compiled, or if * invalid standard rules are applied. */ class CompilationError extends Error { constructor(message, options) { super(message, options); this.name = "CompilationError"; // see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#example Object.setPrototypeOf(this, new.target.prototype); } } exports.CompilationError = CompilationError; /** * A RuntimeError is raised if a CEL expression errors or returns an * unexpected value, or if the schema and message provided to the * validator mismatch. */ class RuntimeError extends Error { constructor(message, options) { super(message, options); this.name = "RuntimeError"; // see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#example Object.setPrototypeOf(this, new.target.prototype); } } exports.RuntimeError = RuntimeError; /** * A ValidationError is raised if one or more rule violations were * detected. */ class ValidationError extends Error { constructor(violations) { super(validationErrorMessage(violations)); this.name = "ValidationError"; // see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#example Object.setPrototypeOf(this, new.target.prototype); this.violations = violations; } } exports.ValidationError = ValidationError; function validationErrorMessage(violations) { if (violations.length == 0) { return "validation failed"; } if (violations.length == 1) { return violations[0].toString(); } return (violations[0].toString() + `, and ${violations.length - 1} more violation${violations.length > 2 ? "s" : ""}`); } /** * Violation represents a single instance where a validation rule was not met. * It provides information about the field that caused the violation, the * specific unfulfilled rule, and a human-readable error message. */ class Violation { constructor(message, ruleId, field, rule, forKey) { this.message = message; this.ruleId = ruleId; this.field = field; this.rule = rule; this.forKey = forKey; } toString() { let path = (0, reflect_1.pathToString)(this.field); if (path.length > 0) { path += ": "; } return path + `${this.message} [${this.ruleId}]`; } } exports.Violation = Violation; /** * Convert an array of Violation[] to the Protobuf message buf.validate.Violations. */ function violationsToProto(violation) { return (0, protobuf_1.create)(validate_pb_js_1.ViolationsSchema, { violations: violation.map(violationToProto), }); } /** * Convert a Violation to the Protobuf message buf.validate.Violation. */ function violationToProto(violation) { return (0, protobuf_1.create)(validate_pb_js_1.ViolationSchema, { field: violation.field.length > 0 ? pathToProto(violation.field) : undefined, rule: violation.rule.length > 0 ? pathToProto(violation.rule) : undefined, ruleId: violation.ruleId, message: violation.message.length > 0 ? violation.message : undefined, forKey: violation.forKey, }); } /** * Convert a Protobuf message buf.validate.FieldPath to a Path. * * Raises an error if the Protobuf message cannot be converted because of a schema * mismatch, or because it is invalid. */ function pathFromViolationProto(schema, proto, registry) { const path = []; let parent = schema; for (const [i, e] of proto.elements.entries()) { if (!parent) { throw errInvPathProto(i); } const field = parent.fields.find((f) => f.number === e.fieldNumber || f.name === e.fieldName); if (!field) { const oneof = parent.oneofs.find((o) => o.name === e.fieldName); if (oneof) { path.push(oneof); parent = undefined; continue; } if (registry) { const ext = registry.getExtensionFor(parent, e.fieldNumber); if (ext) { path.push(ext); parent = ext.message; continue; } } throw errInvPathProto(i); } path.push(field); parent = field.message; if (e.subscript.case == "index") { if (field.fieldKind != "list") { throw errInvPathProto(i); } path.push({ kind: "list_sub", index: Number(e.subscript.value), }); } else if (e.subscript.case != undefined) { if (field.fieldKind != "map") { throw errInvPathProto(i); } switch (e.subscript.case) { case "boolKey": if (field.mapKey != protobuf_1.ScalarType.BOOL) { throw errInvPathProto(i); } break; case "stringKey": if (field.mapKey != protobuf_1.ScalarType.STRING) { throw errInvPathProto(i); } break; case "uintKey": switch (field.mapKey) { case protobuf_1.ScalarType.UINT32: case protobuf_1.ScalarType.FIXED32: case protobuf_1.ScalarType.UINT64: case protobuf_1.ScalarType.FIXED64: // ok break; default: throw errInvPathProto(i); } break; case "intKey": switch (field.mapKey) { case protobuf_1.ScalarType.INT32: case protobuf_1.ScalarType.SINT32: case protobuf_1.ScalarType.SFIXED32: case protobuf_1.ScalarType.UINT32: case protobuf_1.ScalarType.FIXED32: // ok break; default: throw errInvPathProto(i); } break; } path.push({ kind: "map_sub", key: e.subscript.value, }); } } return path; } function errInvPathProto(index) { return new Error(`invalid field path element ${index + 1}`); } /** * Convert a Path to the Protobuf message buf.validate.FieldPath. * * For an invalid or unsupported Path (buf.validate.FieldPath currently does not * support extensions, but Path does), this function will drop data instead of * throwing an error. */ function pathToProto(path) { const elements = []; for (const [i, e] of path.entries()) { switch (e.kind) { case "field": elements.push((0, protobuf_1.create)(validate_pb_js_1.FieldPathElementSchema, { fieldName: e.name, fieldNumber: e.number, fieldType: fieldType(e), })); break; case "extension": elements.push((0, protobuf_1.create)(validate_pb_js_1.FieldPathElementSchema, { fieldName: "[" + e.typeName + "]", fieldNumber: e.number, fieldType: e.proto.type, })); break; case "oneof": elements.push((0, protobuf_1.create)(validate_pb_js_1.FieldPathElementSchema, { fieldName: e.name, })); break; case "list_sub": { const prevProto = elements[elements.length - 1]; if (prevProto) { const prevPath = path[i - 1]; prevProto.subscript = getListSub(e.index, prevPath); } break; } case "map_sub": { const prevProto = elements[elements.length - 1]; if (prevProto) { const prevPath = path[i - 1]; setMapSub(prevProto, e.key, prevPath); } break; } } } return (0, protobuf_1.create)(validate_pb_js_1.FieldPathSchema, { elements }); } function fieldType(field) { if (field.fieldKind == "message" && field.delimitedEncoding) { return wkt_1.FieldDescriptorProto_Type.GROUP; } return field.proto.type; } function getListSub(index, prevPath) { if (prevPath?.kind == "field" && prevPath.fieldKind == "list") { return { case: "index", value: BigInt(index), }; } return { case: undefined }; } function setMapSub(proto, key, prevPath) { if (prevPath?.kind != "field" || prevPath.fieldKind != "map") { return; } proto.keyType = prevPath.mapKey; switch (prevPath.mapKind) { case "scalar": proto.valueType = prevPath.scalar; break; case "enum": proto.valueType = wkt_1.FieldDescriptorProto_Type.ENUM; break; case "message": // map fields are always LENGTH_PREFIXED proto.valueType = wkt_1.FieldDescriptorProto_Type.MESSAGE; break; } switch (typeof key) { case "boolean": switch (prevPath.mapKey) { case protobuf_1.ScalarType.BOOL: proto.subscript = { case: "boolKey", value: key, }; break; } break; case "string": switch (prevPath.mapKey) { case protobuf_1.ScalarType.STRING: proto.subscript = { case: "stringKey", value: key, }; break; } break; case "number": case "bigint": switch (prevPath.mapKey) { case protobuf_1.ScalarType.INT32: case protobuf_1.ScalarType.SINT32: case protobuf_1.ScalarType.SFIXED32: case protobuf_1.ScalarType.INT64: case protobuf_1.ScalarType.SINT64: case protobuf_1.ScalarType.SFIXED64: proto.subscript = { case: "intKey", value: BigInt(key), }; break; case protobuf_1.ScalarType.UINT32: case protobuf_1.ScalarType.FIXED32: case protobuf_1.ScalarType.UINT64: case protobuf_1.ScalarType.FIXED64: proto.subscript = { case: "uintKey", value: BigInt(key), }; break; } break; } }