Skip to content

Commit 736aef0

Browse files
committed
Improve error handling of invalid $project specifications
1 parent 0e99c45 commit 736aef0

3 files changed

Lines changed: 27 additions & 22 deletions

File tree

core/src/main/java/de/bwaldvogel/mongo/backend/aggregation/Aggregation.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ public static Aggregation fromPipeline(List<Document> pipeline, DatabaseResolver
107107
aggregation.addStage(new SortStage(orderBy));
108108
break;
109109
case "$project":
110-
Document projection = (Document) stage.get(stageOperation);
110+
Object projection = stage.get(stageOperation);
111111
aggregation.addStage(new ProjectStage(projection));
112112
break;
113113
case "$count":

core/src/main/java/de/bwaldvogel/mongo/backend/aggregation/stage/ProjectStage.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,16 @@ public class ProjectStage implements AggregationStage {
2222
private final Document projection;
2323
private final boolean hasInclusions;
2424

25-
public ProjectStage(Document projection) {
26-
if (projection.isEmpty()) {
25+
public ProjectStage(Object projection) {
26+
if (!(projection instanceof Document)) {
27+
throw new MongoServerError(15969, "$project specification must be an object");
28+
}
29+
Document projectionSpecification = (Document) projection;
30+
if (projectionSpecification.isEmpty()) {
2731
throw new MongoServerError(40177, "specification must have at least one field");
2832
}
29-
this.projection = projection;
30-
this.hasInclusions = hasInclusions(projection);
33+
this.projection = projectionSpecification;
34+
this.hasInclusions = hasInclusions(projectionSpecification);
3135
validateProjection();
3236
}
3337

@@ -67,7 +71,7 @@ Document projectDocument(Document document) {
6771
Utils.removeSubdocumentValue(result, field);
6872
}
6973
} else if (projectionValue instanceof List) {
70-
List<Object> resolvedProjectionValues = ((List<Object>) projectionValue)
74+
List<Object> resolvedProjectionValues = ((List<?>) projectionValue)
7175
.stream()
7276
.map(value -> Expression.evaluateDocument(value, document))
7377
.collect(Collectors.toList());

test-common/src/main/java/de/bwaldvogel/mongo/backend/AbstractAggregationTest.java

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -630,29 +630,30 @@ void testAggregateWithNestedInclusiveProjection() throws Exception {
630630
);
631631
}
632632

633-
@Test
634-
void testAggregateWithIllegalProjection() throws Exception {
635-
List<Document> pipeline = jsonList("$project: {'x.b': 1, 'x.c': 1, 'x.d': 0, y: 0}");
633+
private static Stream<Arguments> aggregateWithProjectionArguments() {
634+
return Stream.of(
635+
Arguments.of("$project: {'x.b': 1, 'x.c': 1, 'x.d': 0, y: 0}",
636+
"Command failed with error 31254 (Location31254): 'Invalid $project :: caused by :: Cannot do exclusion on field x.d in inclusion projection'"),
636637

637-
collection.insertOne(json("_id: 1"));
638+
Arguments.of("$project: {_id: 0, v: '$x.1.'}",
639+
"Command failed with error 40353 (Location40353): 'Invalid $project :: caused by :: FieldPath must not end with a '.'.'"),
638640

639-
assertThatExceptionOfType(MongoCommandException.class)
640-
.isThrownBy(() -> collection.aggregate(pipeline).first())
641-
.withMessageContaining("Command failed with error 31254 (Location31254): " +
642-
"'Invalid $project :: caused by :: Cannot do exclusion on field x.d in inclusion projection'");
641+
Arguments.of("$project: {_id: 0, v: '$x..1'}",
642+
"Command failed with error 15998 (Location15998): 'Invalid $project :: caused by :: FieldPath field names may not be empty strings.'"),
643+
644+
Arguments.of("$project: 'abc'",
645+
"Command failed with error 15969 (Location15969): '$project specification must be an object'")
646+
);
643647
}
644648

645-
@Test
646-
void testAggregateWithProjection_IllegalFieldPath() throws Exception {
649+
@ParameterizedTest
650+
@MethodSource("aggregateWithProjectionArguments")
651+
void testAggregateWithProjection_IllegalFieldPath(String project, String expectedMessage) throws Exception {
647652
collection.insertOne(json("_id: 1, x: 10"));
648653

649654
assertThatExceptionOfType(MongoCommandException.class)
650-
.isThrownBy(() -> collection.aggregate(jsonList("$project: {_id: 0, v: '$x.1.'}")).first())
651-
.withMessageContaining("Command failed with error 40353 (Location40353): 'Invalid $project :: caused by :: FieldPath must not end with a '.'.'");
652-
653-
assertThatExceptionOfType(MongoCommandException.class)
654-
.isThrownBy(() -> collection.aggregate(jsonList("$project: {_id: 0, v: '$x..1'}")).first())
655-
.withMessageContaining("Command failed with error 15998 (Location15998): 'Invalid $project :: caused by :: FieldPath field names may not be empty strings.'");
655+
.isThrownBy(() -> collection.aggregate(jsonList(project)).first())
656+
.withMessageStartingWith(expectedMessage);
656657
}
657658

658659
// https://github.com/bwaldvogel/mongo-java-server/pull/189

0 commit comments

Comments
 (0)