Skip to content

Commit e18620e

Browse files
committed
Implement _validateUserData for search stage
1 parent 343c8ec commit e18620e

5 files changed

Lines changed: 79 additions & 18 deletions

File tree

handwritten/firestore/dev/src/pipelines/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,5 +161,8 @@ export {
161161
switchOn,
162162
ifNull,
163163
coalesce,
164+
documentMatches,
165+
score,
166+
geoDistance,
164167
// TODO(new-expression): Add new expression exports above this line
165168
} from './expression';

handwritten/firestore/dev/src/pipelines/pipelines.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1428,17 +1428,16 @@ export class Pipeline implements firestore.Pipelines.Pipeline {
14281428
*/
14291429
search(options: firestore.Pipelines.SearchStageOptions): Pipeline {
14301430
// Convert user land convenience types to internal types
1431-
const normalizedQuery: firestore.Pipelines.BooleanExpression = isExpr(
1432-
options.query,
1433-
)
1434-
? options.query
1431+
const normalizedQuery: BooleanExpression = isExpr(options.query)
1432+
? (options.query as BooleanExpression)
14351433
: documentMatches(options.query);
14361434
// const normalizedSelect: Record<string, Expression> | undefined =
14371435
// options.select ? selectablesToObject(options.select) : undefined;
14381436
const normalizedAddFields: Record<string, Expression> | undefined =
14391437
options.addFields ? selectablesToObject(options.addFields) : undefined;
1440-
const normalizedSort: firestore.Pipelines.Ordering[] | undefined =
1441-
isOrdering(options.sort) ? [options.sort] : options.sort;
1438+
const normalizedSort: Ordering[] | undefined = isOrdering(options.sort)
1439+
? [options.sort as Ordering]
1440+
: (options.sort as Ordering[]);
14421441

14431442
const internalOptions: InternalSearchStageOptions = {
14441443
...options,

handwritten/firestore/dev/src/pipelines/stage.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -728,10 +728,10 @@ export type InternalSearchStageOptions = Omit<
728728
firestore.Pipelines.SearchStageOptions,
729729
'query' | 'sort' | 'select' | 'addFields'
730730
> & {
731-
query: firestore.Pipelines.BooleanExpression;
732-
sort?: Array<firestore.Pipelines.Ordering>;
733-
select?: Record<string, firestore.Pipelines.Expression>;
734-
addFields?: Record<string, firestore.Pipelines.Expression>;
731+
query: BooleanExpression;
732+
sort?: Array<Ordering>;
733+
select?: Record<string, Expression>;
734+
addFields?: Record<string, Expression>;
735735
};
736736

737737
/**
@@ -742,6 +742,25 @@ export class Search implements Stage {
742742

743743
constructor(private options: InternalSearchStageOptions) {}
744744

745+
_validateUserData(ignoreUndefinedProperties: boolean): void {
746+
validateUserDataHelper(this.options.query, ignoreUndefinedProperties);
747+
if (this.options.sort) {
748+
validateUserDataHelper(this.options.sort, ignoreUndefinedProperties);
749+
}
750+
if (this.options.select) {
751+
validateUserDataHelper(
752+
Object.values(this.options.select) as Expression[],
753+
ignoreUndefinedProperties,
754+
);
755+
}
756+
if (this.options.addFields) {
757+
validateUserDataHelper(
758+
Object.values(this.options.addFields) as Expression[],
759+
ignoreUndefinedProperties,
760+
);
761+
}
762+
}
763+
745764
readonly optionsUtil = new OptionsUtil({
746765
query: {
747766
serverName: 'query',
@@ -841,7 +860,7 @@ export class DeleteStage implements Stage {
841860
};
842861
}
843862

844-
_validateUserData(ignoreUndefinedProperties: boolean): void {}
863+
_validateUserData(_: boolean): void {}
845864
}
846865

847866
/**

handwritten/firestore/dev/system-test/pipeline.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -186,19 +186,19 @@ const timestampDeltaMS = 3000;
186186
let beginDocCreation = 0;
187187
let endDocCreation = 0;
188188

189-
async function testCollectionWithDocs(
190-
targetCol: CollectionReference,
191-
docs: {
192-
[id: string]: DocumentData;
189+
async function testCollectionWithDocs(
190+
targetCol: CollectionReference,
191+
docs: {
192+
[id: string]: DocumentData;
193193
},
194194
): Promise<CollectionReference<DocumentData>> {
195195
beginDocCreation = new Date().valueOf();
196196
for (const id in docs) {
197-
const ref = targetCol.doc(id);
197+
const ref = targetCol.doc(id);
198198
await ref.set(docs[id]);
199199
}
200200
endDocCreation = new Date().valueOf();
201-
return targetCol;
201+
return targetCol;
202202
}
203203

204204
function expectResults(result: PipelineSnapshot, ...docs: string[]): void;
@@ -226,7 +226,7 @@ describe.skipClassic('Pipeline class', () => {
226226
let randomCol: CollectionReference;
227227

228228
async function setupBookDocs(
229-
targetCol: CollectionReference,
229+
targetCol: CollectionReference,
230230
): Promise<CollectionReference<DocumentData>> {
231231
const bookDocs: {[id: string]: DocumentData} = {
232232
book1: {

handwritten/firestore/dev/test/pipelines/stage.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import field = Pipelines.field;
2525
import sum = Pipelines.sum;
2626
import descending = Pipelines.descending;
2727
import constant = Pipelines.constant;
28+
import documentMatches = Pipelines.documentMatches;
2829
import IValue = google.firestore.v1.IValue;
2930

3031
const FIRST_CALL = 0;
@@ -357,3 +358,42 @@ describe('stage option serialization', () => {
357358
});
358359
});
359360
});
361+
362+
describe('stage _validateUserData', () => {
363+
it('search stage validation', async () => {
364+
const firestore = await createInstance();
365+
366+
// Should throw when ignoreUndefinedProperties is false (default)
367+
expect(() => {
368+
void firestore
369+
.pipeline()
370+
.collection('foo')
371+
.search({
372+
query: documentMatches(undefined as unknown as string),
373+
})
374+
.execute();
375+
}).to.throw(
376+
'Value for argument "value" is not a valid constant value. Cannot use "undefined" as a Firestore value',
377+
);
378+
379+
// Should not throw when ignoreUndefinedProperties is true
380+
const spy = sinon.fake.returns(stream());
381+
const firestoreWithIgnore = await createInstance(
382+
{
383+
executePipeline: spy,
384+
},
385+
{
386+
ignoreUndefinedProperties: true,
387+
},
388+
);
389+
390+
await firestoreWithIgnore
391+
.pipeline()
392+
.collection('foo')
393+
.search({
394+
query: 'foo',
395+
addFields: [constant({foo: undefined}).as('bar')],
396+
})
397+
.execute();
398+
});
399+
});

0 commit comments

Comments
 (0)