Protocol Buffers - Google's data interchange format (grpc依赖)
https://developers.google.com/protocol-buffers/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
708 lines
18 KiB
708 lines
18 KiB
/** |
|
* @fileoverview Proto internal runtime checks. |
|
* |
|
* Checks are grouped into different severity, see: |
|
* http://g3doc/third_party/protobuf/javascript/README.md#configurable-check-support-in-protocol-buffers |
|
* |
|
* Checks are also grouped into different sections: |
|
* - CHECK_BOUNDS: |
|
* Checks that ensure that indexed access is within bounds |
|
* (e.g. an array being accessed past its size). |
|
* - CHECK_STATE |
|
* Checks related to the state of an object |
|
* (e.g. a parser hitting an invalid case). |
|
* - CHECK_TYPE: |
|
* Checks that relate to type errors (e.g. code receives a number instead |
|
* of a string). |
|
*/ |
|
goog.module('protobuf.internal.checks'); |
|
|
|
const ByteString = goog.require('protobuf.ByteString'); |
|
const Int64 = goog.require('protobuf.Int64'); |
|
const WireType = goog.require('protobuf.binary.WireType'); |
|
|
|
// |
|
// See |
|
// http://g3doc/third_party/protobuf/javascript/README.md#configurable-check-support-in-protocol-buffers |
|
// |
|
/** @define{string} */ |
|
const CHECK_LEVEL_DEFINE = goog.define('protobuf.defines.CHECK_LEVEL', ''); |
|
|
|
/** @define{boolean} */ |
|
const POLYFILL_TEXT_ENCODING = |
|
goog.define('protobuf.defines.POLYFILL_TEXT_ENCODING', true); |
|
|
|
/** |
|
* @const {number} |
|
*/ |
|
const MAX_FIELD_NUMBER = Math.pow(2, 29) - 1; |
|
|
|
/** |
|
* The largest finite float32 value. |
|
* @const {number} |
|
*/ |
|
const FLOAT32_MAX = 3.4028234663852886e+38; |
|
|
|
/** @enum {number} */ |
|
const CheckLevel = { |
|
DEBUG: 0, |
|
CRITICAL: 1, |
|
OFF: 2 |
|
}; |
|
|
|
|
|
/** @return {!CheckLevel} */ |
|
function calculateCheckLevel() { |
|
const definedLevel = CHECK_LEVEL_DEFINE.toUpperCase(); |
|
if (definedLevel === '') { |
|
// user did not set a value, value now just depends on goog.DEBUG |
|
return goog.DEBUG ? CheckLevel.DEBUG : CheckLevel.CRITICAL; |
|
} |
|
|
|
if (definedLevel === 'CRITICAL') { |
|
return CheckLevel.CRITICAL; |
|
} |
|
|
|
if (definedLevel === 'OFF') { |
|
return CheckLevel.OFF; |
|
} |
|
|
|
if (definedLevel === 'DEBUG') { |
|
return CheckLevel.DEBUG; |
|
} |
|
|
|
throw new Error(`Unknown value for CHECK_LEVEL: ${CHECK_LEVEL_DEFINE}`); |
|
} |
|
|
|
const /** !CheckLevel */ CHECK_LEVEL = calculateCheckLevel(); |
|
|
|
const /** boolean */ CHECK_STATE = CHECK_LEVEL === CheckLevel.DEBUG; |
|
|
|
const /** boolean */ CHECK_CRITICAL_STATE = |
|
CHECK_LEVEL === CheckLevel.CRITICAL || CHECK_LEVEL === CheckLevel.DEBUG; |
|
|
|
const /** boolean */ CHECK_BOUNDS = CHECK_LEVEL === CheckLevel.DEBUG; |
|
|
|
const /** boolean */ CHECK_CRITICAL_BOUNDS = |
|
CHECK_LEVEL === CheckLevel.CRITICAL || CHECK_LEVEL === CheckLevel.DEBUG; |
|
|
|
const /** boolean */ CHECK_TYPE = CHECK_LEVEL === CheckLevel.DEBUG; |
|
|
|
const /** boolean */ CHECK_CRITICAL_TYPE = |
|
CHECK_LEVEL === CheckLevel.CRITICAL || CHECK_LEVEL === CheckLevel.DEBUG; |
|
|
|
/** |
|
* Ensures the truth of an expression involving the state of the calling |
|
* instance, but not involving any parameters to the calling method. |
|
* |
|
* For cases where failing fast is pretty important and not failing early could |
|
* cause bugs that are much harder to debug. |
|
* @param {boolean} state |
|
* @param {string=} message |
|
* @throws {!Error} If the state is false and the check state is critical. |
|
*/ |
|
function checkCriticalState(state, message = '') { |
|
if (!CHECK_CRITICAL_STATE) { |
|
return; |
|
} |
|
if (!state) { |
|
throw new Error(message); |
|
} |
|
} |
|
|
|
/** |
|
* Ensures the truth of an expression involving the state of the calling |
|
* instance, but not involving any parameters to the calling method. |
|
* |
|
* @param {boolean} state |
|
* @param {string=} message |
|
* @throws {!Error} If the state is false and the check state is debug. |
|
*/ |
|
function checkState(state, message = '') { |
|
if (!CHECK_STATE) { |
|
return; |
|
} |
|
checkCriticalState(state, message); |
|
} |
|
|
|
/** |
|
* Ensures that `index` specifies a valid position in an indexable object of |
|
* size `size`. A position index may range from zero to size, inclusive. |
|
* @param {number} index |
|
* @param {number} size |
|
* @throws {!Error} If the index is out of range and the check state is debug. |
|
*/ |
|
function checkPositionIndex(index, size) { |
|
if (!CHECK_BOUNDS) { |
|
return; |
|
} |
|
checkCriticalPositionIndex(index, size); |
|
} |
|
|
|
/** |
|
* Ensures that `index` specifies a valid position in an indexable object of |
|
* size `size`. A position index may range from zero to size, inclusive. |
|
* @param {number} index |
|
* @param {number} size |
|
* @throws {!Error} If the index is out of range and the check state is |
|
* critical. |
|
*/ |
|
function checkCriticalPositionIndex(index, size) { |
|
if (!CHECK_CRITICAL_BOUNDS) { |
|
return; |
|
} |
|
if (index < 0 || index > size) { |
|
throw new Error(`Index out of bounds: index: ${index} size: ${size}`); |
|
} |
|
} |
|
|
|
/** |
|
* Ensures that `index` specifies a valid element in an indexable object of |
|
* size `size`. A element index may range from zero to size, exclusive. |
|
* @param {number} index |
|
* @param {number} size |
|
* @throws {!Error} If the index is out of range and the check state is |
|
* debug. |
|
*/ |
|
function checkElementIndex(index, size) { |
|
if (!CHECK_BOUNDS) { |
|
return; |
|
} |
|
checkCriticalElementIndex(index, size); |
|
} |
|
|
|
/** |
|
* Ensures that `index` specifies a valid element in an indexable object of |
|
* size `size`. A element index may range from zero to size, exclusive. |
|
* @param {number} index |
|
* @param {number} size |
|
* @throws {!Error} If the index is out of range and the check state is |
|
* critical. |
|
*/ |
|
function checkCriticalElementIndex(index, size) { |
|
if (!CHECK_CRITICAL_BOUNDS) { |
|
return; |
|
} |
|
if (index < 0 || index >= size) { |
|
throw new Error(`Index out of bounds: index: ${index} size: ${size}`); |
|
} |
|
} |
|
|
|
/** |
|
* Ensures the range of [start, end) is with the range of [0, size). |
|
* @param {number} start |
|
* @param {number} end |
|
* @param {number} size |
|
* @throws {!Error} If start and end are out of range and the check state is |
|
* debug. |
|
*/ |
|
function checkRange(start, end, size) { |
|
if (!CHECK_BOUNDS) { |
|
return; |
|
} |
|
checkCriticalRange(start, end, size); |
|
} |
|
|
|
/** |
|
* Ensures the range of [start, end) is with the range of [0, size). |
|
* @param {number} start |
|
* @param {number} end |
|
* @param {number} size |
|
* @throws {!Error} If start and end are out of range and the check state is |
|
* critical. |
|
*/ |
|
function checkCriticalRange(start, end, size) { |
|
if (!CHECK_CRITICAL_BOUNDS) { |
|
return; |
|
} |
|
if (start < 0 || end < 0 || start > size || end > size) { |
|
throw new Error(`Range error: start: ${start} end: ${end} size: ${size}`); |
|
} |
|
if (start > end) { |
|
throw new Error(`Start > end: ${start} > ${end}`); |
|
} |
|
} |
|
|
|
/** |
|
* Ensures that field number is an integer and within the range of |
|
* [1, MAX_FIELD_NUMBER]. |
|
* @param {number} fieldNumber |
|
* @throws {!Error} If the field number is out of range and the check state is |
|
* debug. |
|
*/ |
|
function checkFieldNumber(fieldNumber) { |
|
if (!CHECK_TYPE) { |
|
return; |
|
} |
|
checkCriticalFieldNumber(fieldNumber); |
|
} |
|
|
|
/** |
|
* Ensures that the value is neither null nor undefined. |
|
* |
|
* @param {T} value |
|
* @return {R} |
|
* |
|
* @template T |
|
* @template R := |
|
* mapunion(T, (V) => |
|
* cond(eq(V, 'null'), |
|
* none(), |
|
* cond(eq(V, 'undefined'), |
|
* none(), |
|
* V))) |
|
* =: |
|
*/ |
|
function checkDefAndNotNull(value) { |
|
if (CHECK_TYPE) { |
|
// Note that undefined == null. |
|
if (value == null) { |
|
throw new Error(`Value can't be null`); |
|
} |
|
} |
|
return value; |
|
} |
|
|
|
/** |
|
* Ensures that the value exists and is a function. |
|
* |
|
* @param {function(?): ?} func |
|
*/ |
|
function checkFunctionExists(func) { |
|
if (CHECK_TYPE) { |
|
if (typeof func !== 'function') { |
|
throw new Error(`${func} is not a function`); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* Ensures that field number is an integer and within the range of |
|
* [1, MAX_FIELD_NUMBER]. |
|
* @param {number} fieldNumber |
|
* @throws {!Error} If the field number is out of range and the check state is |
|
* critical. |
|
*/ |
|
function checkCriticalFieldNumber(fieldNumber) { |
|
if (!CHECK_CRITICAL_TYPE) { |
|
return; |
|
} |
|
if (fieldNumber <= 0 || fieldNumber > MAX_FIELD_NUMBER) { |
|
throw new Error(`Field number is out of range: ${fieldNumber}`); |
|
} |
|
} |
|
|
|
/** |
|
* Ensures that wire type is valid. |
|
* @param {!WireType} wireType |
|
* @throws {!Error} If the wire type is invalid and the check state is debug. |
|
*/ |
|
function checkWireType(wireType) { |
|
if (!CHECK_TYPE) { |
|
return; |
|
} |
|
checkCriticalWireType(wireType); |
|
} |
|
|
|
/** |
|
* Ensures that wire type is valid. |
|
* @param {!WireType} wireType |
|
* @throws {!Error} If the wire type is invalid and the check state is critical. |
|
*/ |
|
function checkCriticalWireType(wireType) { |
|
if (!CHECK_CRITICAL_TYPE) { |
|
return; |
|
} |
|
if (wireType < WireType.VARINT || wireType > WireType.FIXED32) { |
|
throw new Error(`Invalid wire type: ${wireType}`); |
|
} |
|
} |
|
|
|
/** |
|
* Ensures the given value has the correct type. |
|
* @param {boolean} expression |
|
* @param {string} errorMsg |
|
* @throws {!Error} If the value has the wrong type and the check state is |
|
* critical. |
|
*/ |
|
function checkCriticalType(expression, errorMsg) { |
|
if (!CHECK_CRITICAL_TYPE) { |
|
return; |
|
} |
|
if (!expression) { |
|
throw new Error(errorMsg); |
|
} |
|
} |
|
|
|
/** |
|
* Checks whether a given object is an array. |
|
* @param {*} value |
|
* @return {!Array<*>} |
|
*/ |
|
function checkCriticalTypeArray(value) { |
|
checkCriticalType( |
|
Array.isArray(value), `Must be an array, but got: ${value}`); |
|
return /** @type {!Array<*>} */ (value); |
|
} |
|
|
|
/** |
|
* Checks whether a given object is an iterable. |
|
* @param {*} value |
|
* @return {!Iterable<*>} |
|
*/ |
|
function checkCriticalTypeIterable(value) { |
|
checkCriticalType( |
|
!!value[Symbol.iterator], `Must be an iterable, but got: ${value}`); |
|
return /** @type {!Iterable<*>} */ (value); |
|
} |
|
|
|
/** |
|
* Checks whether a given object is a boolean. |
|
* @param {*} value |
|
*/ |
|
function checkCriticalTypeBool(value) { |
|
checkCriticalType( |
|
typeof value === 'boolean', `Must be a boolean, but got: ${value}`); |
|
} |
|
|
|
/** |
|
* Checks whether a given object is an array of boolean. |
|
* @param {*} values |
|
*/ |
|
function checkCriticalTypeBoolArray(values) { |
|
// TODO(b/134765672) |
|
if (!CHECK_CRITICAL_TYPE) { |
|
return; |
|
} |
|
const array = checkCriticalTypeArray(values); |
|
for (const value of array) { |
|
checkCriticalTypeBool(value); |
|
} |
|
} |
|
|
|
/** |
|
* Checks whether a given object is a ByteString. |
|
* @param {*} value |
|
*/ |
|
function checkCriticalTypeByteString(value) { |
|
checkCriticalType( |
|
value instanceof ByteString, `Must be a ByteString, but got: ${value}`); |
|
} |
|
|
|
/** |
|
* Checks whether a given object is an array of ByteString. |
|
* @param {*} values |
|
*/ |
|
function checkCriticalTypeByteStringArray(values) { |
|
// TODO(b/134765672) |
|
if (!CHECK_CRITICAL_TYPE) { |
|
return; |
|
} |
|
const array = checkCriticalTypeArray(values); |
|
for (const value of array) { |
|
checkCriticalTypeByteString(value); |
|
} |
|
} |
|
|
|
/** |
|
* Checks whether a given object is a number. |
|
* @param {*} value |
|
* @throws {!Error} If the value is not float and the check state is debug. |
|
*/ |
|
function checkTypeDouble(value) { |
|
if (!CHECK_TYPE) { |
|
return; |
|
} |
|
checkCriticalTypeDouble(value); |
|
} |
|
|
|
/** |
|
* Checks whether a given object is a number. |
|
* @param {*} value |
|
* @throws {!Error} If the value is not float and the check state is critical. |
|
*/ |
|
function checkCriticalTypeDouble(value) { |
|
checkCriticalType( |
|
typeof value === 'number', `Must be a number, but got: ${value}`); |
|
} |
|
|
|
/** |
|
* Checks whether a given object is an array of double. |
|
* @param {*} values |
|
*/ |
|
function checkCriticalTypeDoubleArray(values) { |
|
// TODO(b/134765672) |
|
if (!CHECK_CRITICAL_TYPE) { |
|
return; |
|
} |
|
const array = checkCriticalTypeArray(values); |
|
for (const value of array) { |
|
checkCriticalTypeDouble(value); |
|
} |
|
} |
|
|
|
/** |
|
* Checks whether a given object is a number. |
|
* @param {*} value |
|
* @throws {!Error} If the value is not signed int32 and the check state is |
|
* debug. |
|
*/ |
|
function checkTypeSignedInt32(value) { |
|
if (!CHECK_TYPE) { |
|
return; |
|
} |
|
checkCriticalTypeSignedInt32(value); |
|
} |
|
|
|
/** |
|
* Checks whether a given object is a number. |
|
* @param {*} value |
|
* @throws {!Error} If the value is not signed int32 and the check state is |
|
* critical. |
|
*/ |
|
function checkCriticalTypeSignedInt32(value) { |
|
checkCriticalTypeDouble(value); |
|
const valueAsNumber = /** @type {number} */ (value); |
|
if (CHECK_CRITICAL_TYPE) { |
|
if (valueAsNumber < -Math.pow(2, 31) || valueAsNumber > Math.pow(2, 31) || |
|
!Number.isInteger(valueAsNumber)) { |
|
throw new Error(`Must be int32, but got: ${valueAsNumber}`); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* Checks whether a given object is an array of numbers. |
|
* @param {*} values |
|
*/ |
|
function checkCriticalTypeSignedInt32Array(values) { |
|
// TODO(b/134765672) |
|
if (!CHECK_CRITICAL_TYPE) { |
|
return; |
|
} |
|
const array = checkCriticalTypeArray(values); |
|
for (const value of array) { |
|
checkCriticalTypeSignedInt32(value); |
|
} |
|
} |
|
|
|
/** |
|
* Ensures that value is a long instance. |
|
* @param {*} value |
|
* @throws {!Error} If the value is not a long instance and check state is |
|
* debug. |
|
*/ |
|
function checkTypeSignedInt64(value) { |
|
if (!CHECK_TYPE) { |
|
return; |
|
} |
|
checkCriticalTypeSignedInt64(value); |
|
} |
|
|
|
/** |
|
* Ensures that value is a long instance. |
|
* @param {*} value |
|
* @throws {!Error} If the value is not a long instance and check state is |
|
* critical. |
|
*/ |
|
function checkCriticalTypeSignedInt64(value) { |
|
if (!CHECK_CRITICAL_TYPE) { |
|
return; |
|
} |
|
if (!(value instanceof Int64)) { |
|
throw new Error(`Must be Int64 instance, but got: ${value}`); |
|
} |
|
} |
|
|
|
/** |
|
* Checks whether a given object is an array of long instances. |
|
* @param {*} values |
|
* @throws {!Error} If values is not an array of long instances. |
|
*/ |
|
function checkCriticalTypeSignedInt64Array(values) { |
|
// TODO(b/134765672) |
|
if (!CHECK_CRITICAL_TYPE) { |
|
return; |
|
} |
|
const array = checkCriticalTypeArray(values); |
|
for (const value of array) { |
|
checkCriticalTypeSignedInt64(value); |
|
} |
|
} |
|
|
|
/** |
|
* Checks whether a given object is a number and within float32 precision. |
|
* @param {*} value |
|
* @throws {!Error} If the value is not float and the check state is debug. |
|
*/ |
|
function checkTypeFloat(value) { |
|
if (!CHECK_TYPE) { |
|
return; |
|
} |
|
checkCriticalTypeFloat(value); |
|
} |
|
|
|
/** |
|
* Checks whether a given object is a number and within float32 precision. |
|
* @param {*} value |
|
* @throws {!Error} If the value is not float and the check state is critical. |
|
*/ |
|
function checkCriticalTypeFloat(value) { |
|
checkCriticalTypeDouble(value); |
|
if (CHECK_CRITICAL_TYPE) { |
|
const valueAsNumber = /** @type {number} */ (value); |
|
if (Number.isFinite(valueAsNumber) && |
|
(valueAsNumber > FLOAT32_MAX || valueAsNumber < -FLOAT32_MAX)) { |
|
throw new Error( |
|
`Given number does not fit into float precision: ${value}`); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* Checks whether a given object is an iterable of floats. |
|
* @param {*} values |
|
*/ |
|
function checkCriticalTypeFloatIterable(values) { |
|
// TODO(b/134765672) |
|
if (!CHECK_CRITICAL_TYPE) { |
|
return; |
|
} |
|
const iterable = checkCriticalTypeIterable(values); |
|
for (const value of iterable) { |
|
checkCriticalTypeFloat(value); |
|
} |
|
} |
|
|
|
/** |
|
* Checks whether a given object is a string. |
|
* @param {*} value |
|
*/ |
|
function checkCriticalTypeString(value) { |
|
checkCriticalType( |
|
typeof value === 'string', `Must be string, but got: ${value}`); |
|
} |
|
|
|
/** |
|
* Checks whether a given object is an array of string. |
|
* @param {*} values |
|
*/ |
|
function checkCriticalTypeStringArray(values) { |
|
// TODO(b/134765672) |
|
if (!CHECK_CRITICAL_TYPE) { |
|
return; |
|
} |
|
const array = checkCriticalTypeArray(values); |
|
for (const value of array) { |
|
checkCriticalTypeString(value); |
|
} |
|
} |
|
|
|
/** |
|
* Ensures that value is a valid unsigned int32. |
|
* @param {*} value |
|
* @throws {!Error} If the value is out of range and the check state is debug. |
|
*/ |
|
function checkTypeUnsignedInt32(value) { |
|
if (!CHECK_TYPE) { |
|
return; |
|
} |
|
checkCriticalTypeUnsignedInt32(value); |
|
} |
|
|
|
/** |
|
* Ensures that value is a valid unsigned int32. |
|
* @param {*} value |
|
* @throws {!Error} If the value is out of range and the check state |
|
* is critical. |
|
*/ |
|
function checkCriticalTypeUnsignedInt32(value) { |
|
if (!CHECK_CRITICAL_TYPE) { |
|
return; |
|
} |
|
checkCriticalTypeDouble(value); |
|
const valueAsNumber = /** @type {number} */ (value); |
|
if (valueAsNumber < 0 || valueAsNumber > Math.pow(2, 32) - 1 || |
|
!Number.isInteger(valueAsNumber)) { |
|
throw new Error(`Must be uint32, but got: ${value}`); |
|
} |
|
} |
|
|
|
/** |
|
* Checks whether a given object is an array of unsigned int32. |
|
* @param {*} values |
|
*/ |
|
function checkCriticalTypeUnsignedInt32Array(values) { |
|
// TODO(b/134765672) |
|
if (!CHECK_CRITICAL_TYPE) { |
|
return; |
|
} |
|
const array = checkCriticalTypeArray(values); |
|
for (const value of array) { |
|
checkCriticalTypeUnsignedInt32(value); |
|
} |
|
} |
|
|
|
/** |
|
* Checks whether a given object is an array of message. |
|
* @param {*} values |
|
*/ |
|
function checkCriticalTypeMessageArray(values) { |
|
// TODO(b/134765672) |
|
if (!CHECK_CRITICAL_TYPE) { |
|
return; |
|
} |
|
const array = checkCriticalTypeArray(values); |
|
for (const value of array) { |
|
checkCriticalType( |
|
value !== null, 'Given value is not a message instance: null'); |
|
} |
|
} |
|
|
|
exports = { |
|
checkDefAndNotNull, |
|
checkCriticalElementIndex, |
|
checkCriticalFieldNumber, |
|
checkCriticalPositionIndex, |
|
checkCriticalRange, |
|
checkCriticalState, |
|
checkCriticalTypeBool, |
|
checkCriticalTypeBoolArray, |
|
checkCriticalTypeByteString, |
|
checkCriticalTypeByteStringArray, |
|
checkCriticalTypeDouble, |
|
checkTypeDouble, |
|
checkCriticalTypeDoubleArray, |
|
checkTypeFloat, |
|
checkCriticalTypeFloat, |
|
checkCriticalTypeFloatIterable, |
|
checkCriticalTypeMessageArray, |
|
checkCriticalTypeSignedInt32, |
|
checkCriticalTypeSignedInt32Array, |
|
checkCriticalTypeSignedInt64, |
|
checkTypeSignedInt64, |
|
checkCriticalTypeSignedInt64Array, |
|
checkCriticalTypeString, |
|
checkCriticalTypeStringArray, |
|
checkCriticalTypeUnsignedInt32, |
|
checkCriticalTypeUnsignedInt32Array, |
|
checkCriticalType, |
|
checkCriticalWireType, |
|
checkElementIndex, |
|
checkFieldNumber, |
|
checkFunctionExists, |
|
checkPositionIndex, |
|
checkRange, |
|
checkState, |
|
checkTypeUnsignedInt32, |
|
checkTypeSignedInt32, |
|
checkWireType, |
|
CHECK_BOUNDS, |
|
CHECK_CRITICAL_BOUNDS, |
|
CHECK_STATE, |
|
CHECK_CRITICAL_STATE, |
|
CHECK_TYPE, |
|
CHECK_CRITICAL_TYPE, |
|
MAX_FIELD_NUMBER, |
|
POLYFILL_TEXT_ENCODING, |
|
};
|
|
|