Skip to content

Compat class for FieldPath #4038

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 19 additions & 9 deletions packages/firestore/lite/src/api/field_path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@
* limitations under the License.
*/

import { _BaseFieldPath } from '../../../src/api/field_path';
import { DOCUMENT_KEY_NAME } from '../../../src/model/path';
import {
DOCUMENT_KEY_NAME,
FieldPath as InternalFieldPath
} from '../../../src/model/path';
import { Code, FirestoreError } from '../../../src/util/error';

/**
* A `FieldPath` refers to a field in a document. The path may consist of a
Expand All @@ -26,12 +29,9 @@ import { DOCUMENT_KEY_NAME } from '../../../src/model/path';
* Create a `FieldPath` by providing field names. If more than one field
* name is provided, the path will point to a nested field in a document.
*/
export class FieldPath extends _BaseFieldPath {
// Note: This class is stripped down a copy of the FieldPath class in the
// legacy SDK. The changes are:
// - The `documentId()` static method has been removed
// - Input validation is limited to errors that cannot be caught by the
// TypeScript transpiler.
export class FieldPath {
/** Internal representation of a Firestore field path. */
readonly _internalPath: InternalFieldPath;

/**
* Creates a FieldPath from the provided field names. If more than one field
Expand All @@ -40,7 +40,17 @@ export class FieldPath extends _BaseFieldPath {
* @param fieldNames A list of field names.
*/
constructor(...fieldNames: string[]) {
super(fieldNames);
for (let i = 0; i < fieldNames.length; ++i) {
if (fieldNames[i].length === 0) {
throw new FirestoreError(
Code.INVALID_ARGUMENT,
`Invalid field name at argument $(i + 1). ` +
'Field names must not be empty.'
);
}
}

this._internalPath = new InternalFieldPath(fieldNames);
}

/**
Expand Down
77 changes: 38 additions & 39 deletions packages/firestore/src/api/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ import {
} from '../util/input_validation';
import { logWarn, setLogLevel as setClientLogLevel } from '../util/log';
import { AutoId } from '../util/misc';
import { _BaseFieldPath, FieldPath as ExternalFieldPath } from './field_path';
import { FieldPath as ExpFieldPath } from '../../lite/src/api/field_path';
import {
CompleteFn,
ErrorFn,
Expand Down Expand Up @@ -119,6 +119,7 @@ import {
DocumentData as PublicDocumentData,
DocumentReference as PublicDocumentReference,
DocumentSnapshot as PublicDocumentSnapshot,
FieldPath as PublicFieldPath,
FirebaseFirestore as PublicFirestore,
FirestoreDataConverter as PublicFirestoreDataConverter,
GetOptions as PublicGetOptions,
Expand Down Expand Up @@ -518,28 +519,33 @@ export class Transaction implements PublicTransaction {
): Transaction;
update(
documentRef: PublicDocumentReference<unknown>,
field: string | ExternalFieldPath,
field: string | PublicFieldPath,
value: unknown,
...moreFieldsAndValues: unknown[]
): Transaction;
update(
documentRef: PublicDocumentReference<unknown>,
fieldOrUpdateData: string | ExternalFieldPath | PublicUpdateData,
fieldOrUpdateData: string | PublicFieldPath | PublicUpdateData,
value?: unknown,
...moreFieldsAndValues: unknown[]
): Transaction {
let ref;
let parsed;
const ref = validateReference(
'Transaction.update',
documentRef,
this._firestore
);

// For Compat types, we have to "extract" the underlying types before
// performing validation.
if (fieldOrUpdateData instanceof Compat) {
fieldOrUpdateData = (fieldOrUpdateData as Compat<ExpFieldPath>)._delegate;
}

let parsed;
if (
typeof fieldOrUpdateData === 'string' ||
fieldOrUpdateData instanceof ExternalFieldPath
fieldOrUpdateData instanceof ExpFieldPath
) {
ref = validateReference(
'Transaction.update',
documentRef,
this._firestore
);
parsed = parseUpdateVarargs(
this._dataReader,
'Transaction.update',
Expand All @@ -549,11 +555,6 @@ export class Transaction implements PublicTransaction {
moreFieldsAndValues
);
} else {
ref = validateReference(
'Transaction.update',
documentRef,
this._firestore
);
parsed = parseUpdateData(
this._dataReader,
'Transaction.update',
Expand Down Expand Up @@ -629,30 +630,34 @@ export class WriteBatch implements PublicWriteBatch {
): WriteBatch;
update(
documentRef: PublicDocumentReference<unknown>,
field: string | ExternalFieldPath,
field: string | PublicFieldPath,
value: unknown,
...moreFieldsAndValues: unknown[]
): WriteBatch;
update(
documentRef: PublicDocumentReference<unknown>,
fieldOrUpdateData: string | ExternalFieldPath | PublicUpdateData,
fieldOrUpdateData: string | PublicFieldPath | PublicUpdateData,
value?: unknown,
...moreFieldsAndValues: unknown[]
): WriteBatch {
this.verifyNotCommitted();
const ref = validateReference(
'WriteBatch.update',
documentRef,
this._firestore
);

let ref;
let parsed;
// For Compat types, we have to "extract" the underlying types before
// performing validation.
if (fieldOrUpdateData instanceof Compat) {
fieldOrUpdateData = (fieldOrUpdateData as Compat<ExpFieldPath>)._delegate;
}

let parsed;
if (
typeof fieldOrUpdateData === 'string' ||
fieldOrUpdateData instanceof ExternalFieldPath
fieldOrUpdateData instanceof ExpFieldPath
) {
ref = validateReference(
'WriteBatch.update',
documentRef,
this._firestore
);
parsed = parseUpdateVarargs(
this._dataReader,
'WriteBatch.update',
Expand All @@ -662,11 +667,6 @@ export class WriteBatch implements PublicWriteBatch {
moreFieldsAndValues
);
} else {
ref = validateReference(
'WriteBatch.update',
documentRef,
this._firestore
);
parsed = parseUpdateData(
this._dataReader,
'WriteBatch.update',
Expand Down Expand Up @@ -825,26 +825,25 @@ export class DocumentReference<T = PublicDocumentData>

update(value: PublicUpdateData): Promise<void>;
update(
field: string | ExternalFieldPath,
field: string | PublicFieldPath,
value: unknown,
...moreFieldsAndValues: unknown[]
): Promise<void>;
update(
fieldOrUpdateData: string | ExternalFieldPath | PublicUpdateData,
fieldOrUpdateData: string | PublicFieldPath | PublicUpdateData,
value?: unknown,
...moreFieldsAndValues: unknown[]
): Promise<void> {
// For Compat types, we have to "extract" the underlying types before
// performing validation.
if (fieldOrUpdateData instanceof Compat) {
fieldOrUpdateData = (fieldOrUpdateData as Compat<_BaseFieldPath>)
._delegate;
fieldOrUpdateData = (fieldOrUpdateData as Compat<ExpFieldPath>)._delegate;
}

let parsed;
if (
typeof fieldOrUpdateData === 'string' ||
fieldOrUpdateData instanceof _BaseFieldPath
fieldOrUpdateData instanceof ExpFieldPath
) {
parsed = parseUpdateVarargs(
this._dataReader,
Expand Down Expand Up @@ -1080,7 +1079,7 @@ export class DocumentSnapshot<T = PublicDocumentData>
}

get(
fieldPath: string | ExternalFieldPath,
fieldPath: string | PublicFieldPath,
options: PublicSnapshotOptions = {}
): unknown {
if (this._document) {
Expand Down Expand Up @@ -1556,7 +1555,7 @@ export class Query<T = PublicDocumentData> implements PublicQuery<T> {
}

where(
field: string | ExternalFieldPath,
field: string | PublicFieldPath,
opStr: PublicWhereFilterOp,
value: unknown
): PublicQuery<T> {
Expand All @@ -1578,7 +1577,7 @@ export class Query<T = PublicDocumentData> implements PublicQuery<T> {
}

orderBy(
field: string | ExternalFieldPath,
field: string | PublicFieldPath,
directionStr?: PublicOrderByDirection
): PublicQuery<T> {
let direction: Direction;
Expand Down
67 changes: 9 additions & 58 deletions packages/firestore/src/api/field_path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,52 +17,28 @@

import { FieldPath as PublicFieldPath } from '@firebase/firestore-types';

import { FieldPath as ExpFieldPath } from '../../lite/src/api/field_path';
import { FieldPath as InternalFieldPath } from '../model/path';
import { Code, FirestoreError } from '../util/error';
import { Compat } from '../compat/compat';

// The objects that are a part of this API are exposed to third-parties as
// compiled javascript so we want to flag our private members with a leading
// underscore to discourage their use.

/**
* A field class base class that is shared by the lite, full and legacy SDK,
* which supports shared code that deals with FieldPaths.
*/
// Use underscore prefix to hide this class from our Public API.
// eslint-disable-next-line @typescript-eslint/naming-convention
export abstract class _BaseFieldPath {
/** Internal representation of a Firestore field path. */
readonly _internalPath: InternalFieldPath;

constructor(fieldNames: string[]) {
for (let i = 0; i < fieldNames.length; ++i) {
if (fieldNames[i].length === 0) {
throw new FirestoreError(
Code.INVALID_ARGUMENT,
`Invalid field name at argument $(i + 1). ` +
'Field names must not be empty.'
);
}
}

this._internalPath = new InternalFieldPath(fieldNames);
}
}

/**
* A `FieldPath` refers to a field in a document. The path may consist of a
* single field name (referring to a top-level field in the document), or a list
* of field names (referring to a nested field in the document).
*/
export class FieldPath extends _BaseFieldPath implements PublicFieldPath {
export class FieldPath extends Compat<ExpFieldPath> implements PublicFieldPath {
/**
* Creates a FieldPath from the provided field names. If more than one field
* name is provided, the path will point to a nested field in a document.
*
* @param fieldNames A list of field names.
*/
constructor(...fieldNames: string[]) {
super(fieldNames);
super(new ExpFieldPath(...fieldNames));
}

static documentId(): FieldPath {
Expand All @@ -76,37 +52,12 @@ export class FieldPath extends _BaseFieldPath implements PublicFieldPath {
}

isEqual(other: PublicFieldPath): boolean {
if (!(other instanceof FieldPath)) {
if (other instanceof Compat) {
other = other._delegate;
}
if (!(other instanceof ExpFieldPath)) {
return false;
}
return this._internalPath.isEqual(other._internalPath);
}
}

/**
* Matches any characters in a field path string that are reserved.
*/
const RESERVED = new RegExp('[~\\*/\\[\\]]');

/**
* Parses a field path string into a FieldPath, treating dots as separators.
*/
export function fromDotSeparatedString(path: string): FieldPath {
const found = path.search(RESERVED);
if (found >= 0) {
throw new FirestoreError(
Code.INVALID_ARGUMENT,
`Invalid field path (${path}). Paths must not contain ` +
`'~', '*', '/', '[', or ']'`
);
}
try {
return new FieldPath(...path.split('.'));
} catch (e) {
throw new FirestoreError(
Code.INVALID_ARGUMENT,
`Invalid field path (${path}). Paths must not be empty, ` +
`begin with '.', end with '.', or contain '..'`
);
return this._delegate._internalPath.isEqual(other._internalPath);
}
}
Loading