Skip to content

Commit

Permalink
Tree-shake ValidationPath (#4594)
Browse files Browse the repository at this point in the history
  • Loading branch information
schmidt-sebastian authored Mar 8, 2021
1 parent 82ea467 commit dccd23d
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 66 deletions.
114 changes: 57 additions & 57 deletions packages/database/src/core/util/Path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
import { nameCompare } from './util';
import { stringLength } from '@firebase/util';

/** Maximum key depth. */
const MAX_PATH_DEPTH = 32;

/** Maximum number of (UTF8) bytes in a Firebase path. */
const MAX_PATH_LENGTH_BYTES = 768;

/**
* An immutable object representing a parsed path. It's immutable so that you
* can pass them around to other functions without worrying about them changing
Expand Down Expand Up @@ -252,84 +258,78 @@ export function pathContains(path: Path, other: Path): boolean {
* The definition of a path always begins with '/'.
*/
export class ValidationPath {
private parts_: string[];
parts_: string[];
/** Initialize to number of '/' chars needed in path. */
private byteLength_: number;
byteLength_: number;

/**
* @param path Initial Path.
* @param errorPrefix_ Prefix for any error messages.
*/
constructor(path: Path, private errorPrefix_: string) {
constructor(path: Path, public errorPrefix_: string) {
this.parts_ = pathSlice(path, 0);
/** Initialize to number of '/' chars needed in path. */
this.byteLength_ = Math.max(1, this.parts_.length);

for (let i = 0; i < this.parts_.length; i++) {
this.byteLength_ += stringLength(this.parts_[i]);
}
this.checkValid_();
validationPathCheckValid(this);
}
}

/** @const {number} Maximum key depth. */
static get MAX_PATH_DEPTH() {
return 32;
export function validationPathPush(
validationPath: ValidationPath,
child: string
): void {
// Count the needed '/'
if (validationPath.parts_.length > 0) {
validationPath.byteLength_ += 1;
}
validationPath.parts_.push(child);
validationPath.byteLength_ += stringLength(child);
validationPathCheckValid(validationPath);
}

/** @const {number} Maximum number of (UTF8) bytes in a Firebase path. */
static get MAX_PATH_LENGTH_BYTES() {
return 768;
}

/** @param child */
push(child: string) {
// Count the needed '/'
if (this.parts_.length > 0) {
this.byteLength_ += 1;
}
this.parts_.push(child);
this.byteLength_ += stringLength(child);
this.checkValid_();
export function validationPathPop(validationPath: ValidationPath): void {
const last = validationPath.parts_.pop();
validationPath.byteLength_ -= stringLength(last);
// Un-count the previous '/'
if (validationPath.parts_.length > 0) {
validationPath.byteLength_ -= 1;
}
}

pop() {
const last = this.parts_.pop();
this.byteLength_ -= stringLength(last);
// Un-count the previous '/'
if (this.parts_.length > 0) {
this.byteLength_ -= 1;
}
function validationPathCheckValid(validationPath: ValidationPath): void {
if (validationPath.byteLength_ > MAX_PATH_LENGTH_BYTES) {
throw new Error(
validationPath.errorPrefix_ +
'has a key path longer than ' +
MAX_PATH_LENGTH_BYTES +
' bytes (' +
validationPath.byteLength_ +
').'
);
}

private checkValid_() {
if (this.byteLength_ > ValidationPath.MAX_PATH_LENGTH_BYTES) {
throw new Error(
this.errorPrefix_ +
'has a key path longer than ' +
ValidationPath.MAX_PATH_LENGTH_BYTES +
' bytes (' +
this.byteLength_ +
').'
);
}
if (this.parts_.length > ValidationPath.MAX_PATH_DEPTH) {
throw new Error(
this.errorPrefix_ +
'path specified exceeds the maximum depth that can be written (' +
ValidationPath.MAX_PATH_DEPTH +
') or object contains a cycle ' +
this.toErrorString()
);
}
if (validationPath.parts_.length > MAX_PATH_DEPTH) {
throw new Error(
validationPath.errorPrefix_ +
'path specified exceeds the maximum depth that can be written (' +
MAX_PATH_DEPTH +
') or object contains a cycle ' +
validationPathToErrorString(validationPath)
);
}
}

/**
* String for use in error messages - uses '.' notation for path.
*/
toErrorString(): string {
if (this.parts_.length === 0) {
return '';
}
return "in property '" + this.parts_.join('.') + "'";
/**
* String for use in error messages - uses '.' notation for path.
*/
export function validationPathToErrorString(
validationPath: ValidationPath
): string {
if (validationPath.parts_.length === 0) {
return '';
}
return "in property '" + validationPath.parts_.join('.') + "'";
}
27 changes: 18 additions & 9 deletions packages/database/src/core/util/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ import {
pathGetBack,
pathGetFront,
pathSlice,
ValidationPath
ValidationPath,
validationPathPop,
validationPathPush,
validationPathToErrorString
} from './Path';
import {
contains,
Expand Down Expand Up @@ -119,20 +122,26 @@ export const validateFirebaseData = function (
path_ instanceof Path ? new ValidationPath(path_, errorPrefix) : path_;

if (data === undefined) {
throw new Error(errorPrefix + 'contains undefined ' + path.toErrorString());
throw new Error(
errorPrefix + 'contains undefined ' + validationPathToErrorString(path)
);
}
if (typeof data === 'function') {
throw new Error(
errorPrefix +
'contains a function ' +
path.toErrorString() +
validationPathToErrorString(path) +
' with contents = ' +
data.toString()
);
}
if (isInvalidJSONNumber(data)) {
throw new Error(
errorPrefix + 'contains ' + data.toString() + ' ' + path.toErrorString()
errorPrefix +
'contains ' +
data.toString() +
' ' +
validationPathToErrorString(path)
);
}

Expand All @@ -147,7 +156,7 @@ export const validateFirebaseData = function (
'contains a string greater than ' +
MAX_LEAF_SIZE_ +
' utf8 bytes ' +
path.toErrorString() +
validationPathToErrorString(path) +
" ('" +
data.substring(0, 50) +
"...')"
Expand All @@ -170,23 +179,23 @@ export const validateFirebaseData = function (
' contains an invalid key (' +
key +
') ' +
path.toErrorString() +
validationPathToErrorString(path) +
'. Keys must be non-empty strings ' +
'and can\'t contain ".", "#", "$", "/", "[", or "]"'
);
}
}

path.push(key);
validationPathPush(path, key);
validateFirebaseData(errorPrefix, value, path);
path.pop();
validationPathPop(path);
});

if (hasDotValue && hasActualChild) {
throw new Error(
errorPrefix +
' contains ".value" child ' +
path.toErrorString() +
validationPathToErrorString(path) +
' in addition to actual children.'
);
}
Expand Down

0 comments on commit dccd23d

Please sign in to comment.