Skip to content
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
53 changes: 29 additions & 24 deletions ANALYSIS.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright 2025 Flamingock (https://www.flamingock.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.flamingock.template.mongodb;

/**
* Wraps MongoDB driver exceptions with template-level context (operation type and collection name),
* making failures in multi-step changes easier to debug. The original exception is preserved as the cause.
*/
public class MongoTemplateExecutionException extends RuntimeException {

public MongoTemplateExecutionException(String type, String collection, Throwable cause) {
super("Failed to execute '" + type + "' on collection '" + collection + "': " + cause.getMessage(), cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,16 @@

import com.mongodb.client.model.CreateViewOptions;

import java.util.Collections;
import java.util.Map;
import java.util.Set;

import static io.flamingock.template.mongodb.mapper.MapperUtil.getCollation;

public final class CreateViewOptionsMapper {

public static final Set<String> RECOGNIZED_KEYS = Collections.singleton("collation");

private CreateViewOptionsMapper() {}

public static CreateViewOptions map(Map<String, Object> options) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@
import com.mongodb.client.model.IndexOptions;


import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import static io.flamingock.template.mongodb.mapper.MapperUtil.getBoolean;
import static io.flamingock.template.mongodb.mapper.MapperUtil.getBson;
Expand All @@ -32,6 +36,14 @@

public final class IndexOptionsMapper {

public static final Set<String> RECOGNIZED_KEYS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
"background", "unique", "name", "sparse", "expireAfterSeconds",
"version", "weights", "defaultLanguage", "languageOverride",
"textVersion", "sphereVersion", "bits", "min", "max",
"bucketSize", "storageEngine", "partialFilterExpression",
"collation", "wildcardProjection", "hidden"
)));

private IndexOptionsMapper() {}

public static IndexOptions mapToIndexOptions(Map<String, Object> options) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,20 @@
import com.mongodb.client.model.InsertManyOptions;
import com.mongodb.client.model.InsertOneOptions;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import static io.flamingock.template.mongodb.mapper.MapperUtil.getBoolean;

public final class InsertOptionsMapper {

public static final Set<String> RECOGNIZED_KEYS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
"bypassDocumentValidation", "ordered"
)));

private InsertOptionsMapper() {}

public static InsertOneOptions mapToInsertOneOptions(Map<String, Object> options) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,16 @@

import com.mongodb.client.model.RenameCollectionOptions;

import java.util.Collections;
import java.util.Map;
import java.util.Set;

import static io.flamingock.template.mongodb.mapper.MapperUtil.getBoolean;

public final class RenameCollectionOptionsMapper {

public static final Set<String> RECOGNIZED_KEYS = Collections.singleton("dropTarget");

private RenameCollectionOptionsMapper() {}

public static RenameCollectionOptions map(Map<String, Object> options) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,21 @@

import com.mongodb.client.model.UpdateOptions;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import static io.flamingock.template.mongodb.mapper.MapperUtil.getBoolean;
import static io.flamingock.template.mongodb.mapper.MapperUtil.getCollation;

public final class UpdateOptionsMapper {

public static final Set<String> RECOGNIZED_KEYS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
"upsert", "bypassDocumentValidation", "collation", "arrayFilters"
)));

private UpdateOptionsMapper() {}

public static UpdateOptions mapToUpdateOptions(Map<String, Object> options) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import io.flamingock.template.mongodb.validation.DropIndexParametersValidator;
import io.flamingock.template.mongodb.validation.InsertParametersValidator;
import io.flamingock.template.mongodb.validation.ModifyCollectionParametersValidator;
import io.flamingock.template.mongodb.validation.NoParametersValidator;
import io.flamingock.template.mongodb.validation.OperationValidator;
import io.flamingock.template.mongodb.validation.RenameCollectionParametersValidator;
import io.flamingock.template.mongodb.validation.UpdateParametersValidator;
Expand All @@ -44,17 +45,17 @@

public enum MongoOperationType {

CREATE_COLLECTION("createCollection", CreateCollectionOperator::new, OperationValidator.NO_OP),
CREATE_COLLECTION("createCollection", CreateCollectionOperator::new, new NoParametersValidator("CreateCollection")),
CREATE_INDEX("createIndex", CreateIndexOperator::new, new CreateIndexParametersValidator()),
INSERT("insert", InsertOperator::new, new InsertParametersValidator()),
UPDATE("update", UpdateOperator::new, new UpdateParametersValidator()),
DELETE("delete", DeleteOperator::new, new DeleteParametersValidator()),
DROP_COLLECTION("dropCollection", DropCollectionOperator::new, OperationValidator.NO_OP),
DROP_COLLECTION("dropCollection", DropCollectionOperator::new, new NoParametersValidator("DropCollection")),
DROP_INDEX("dropIndex", DropIndexOperator::new, new DropIndexParametersValidator()),
RENAME_COLLECTION("renameCollection", RenameCollectionOperator::new, new RenameCollectionParametersValidator()),
MODIFY_COLLECTION("modifyCollection", ModifyCollectionOperator::new, new ModifyCollectionParametersValidator()),
CREATE_VIEW("createView", CreateViewOperator::new, new CreateViewParametersValidator()),
DROP_VIEW("dropView", DropViewOperator::new, OperationValidator.NO_OP);
DROP_VIEW("dropView", DropViewOperator::new, new NoParametersValidator("DropView"));

private final String value;
private final BiFunction<MongoDatabase, MongoOperation, MongoOperator> createOperatorFunction;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import com.mongodb.client.ClientSession;
import com.mongodb.client.MongoDatabase;
import io.flamingock.template.mongodb.MongoTemplateExecutionException;
import io.flamingock.template.mongodb.model.MongoOperation;
import io.flamingock.internal.util.log.FlamingockLoggerFactory;
import org.slf4j.Logger;
Expand All @@ -36,7 +37,13 @@ protected MongoOperator(MongoDatabase mongoDatabase, MongoOperation op, boolean

public final void apply(ClientSession clientSession) {
logOperation(clientSession != null);
applyInternal(clientSession);
try {
applyInternal(clientSession);
} catch (MongoTemplateExecutionException e) {
throw e;
} catch (Exception e) {
throw new MongoTemplateExecutionException(op.getType(), op.getCollection(), e);
}
}

private void logOperation(boolean withClientSession) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package io.flamingock.template.mongodb.validation;

import io.flamingock.api.template.TemplatePayloadValidationError;
import io.flamingock.template.mongodb.mapper.IndexOptionsMapper;
import io.flamingock.template.mongodb.model.MongoOperation;

import java.util.ArrayList;
Expand Down Expand Up @@ -62,6 +63,11 @@ public List<TemplatePayloadValidationError> validate(MongoOperation operation) {
if (options != null && !(options instanceof Map)) {
errors.add(new TemplatePayloadValidationError("parameters.options",
"'options' must be a document"));
} else if (options instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> optionsMap = (Map<String, Object>) options;
errors.addAll(OperationValidator.checkUnrecognizedOptionKeys(
optionsMap, IndexOptionsMapper.RECOGNIZED_KEYS, "CreateIndex"));
}

errors.addAll(OperationValidator.checkUnrecognizedKeys(params, RECOGNIZED_KEYS, "CreateIndex"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package io.flamingock.template.mongodb.validation;

import io.flamingock.api.template.TemplatePayloadValidationError;
import io.flamingock.template.mongodb.mapper.CreateViewOptionsMapper;
import io.flamingock.template.mongodb.model.MongoOperation;

import java.util.ArrayList;
Expand Down Expand Up @@ -79,6 +80,11 @@ public List<TemplatePayloadValidationError> validate(MongoOperation operation) {
if (options != null && !(options instanceof Map)) {
errors.add(new TemplatePayloadValidationError("parameters.options",
"'options' must be a document"));
} else if (options instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> optionsMap = (Map<String, Object>) options;
errors.addAll(OperationValidator.checkUnrecognizedOptionKeys(
optionsMap, CreateViewOptionsMapper.RECOGNIZED_KEYS, "CreateView"));
}

errors.addAll(OperationValidator.checkUnrecognizedKeys(params, RECOGNIZED_KEYS, "CreateView"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package io.flamingock.template.mongodb.validation;

import io.flamingock.api.template.TemplatePayloadValidationError;
import io.flamingock.template.mongodb.mapper.InsertOptionsMapper;
import io.flamingock.template.mongodb.model.MongoOperation;

import java.util.ArrayList;
Expand Down Expand Up @@ -65,6 +66,11 @@ public List<TemplatePayloadValidationError> validate(MongoOperation operation) {
if (options != null && !(options instanceof Map)) {
errors.add(new TemplatePayloadValidationError("parameters.options",
"'options' must be a document"));
} else if (options instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> optionsMap = (Map<String, Object>) options;
errors.addAll(OperationValidator.checkUnrecognizedOptionKeys(
optionsMap, InsertOptionsMapper.RECOGNIZED_KEYS, "Insert"));
}

errors.addAll(OperationValidator.checkUnrecognizedKeys(params, RECOGNIZED_KEYS, "Insert"));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2025 Flamingock (https://www.flamingock.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.flamingock.template.mongodb.validation;

import io.flamingock.api.template.TemplatePayloadValidationError;
import io.flamingock.template.mongodb.model.MongoOperation;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
* Validator for operations that do not accept any parameters (e.g., createCollection, dropCollection, dropView).
* Rejects non-null, non-empty parameters to prevent silent misconfiguration.
*/
public class NoParametersValidator implements OperationValidator {

private final String operationName;

public NoParametersValidator(String operationName) {
this.operationName = operationName;
}

@Override
public List<TemplatePayloadValidationError> validate(MongoOperation operation) {
Map<String, Object> params = operation.getParameters();
if (params == null || params.isEmpty()) {
return Collections.emptyList();
}
List<TemplatePayloadValidationError> errors = new ArrayList<>();
errors.add(new TemplatePayloadValidationError("parameters",
operationName + " operation does not accept parameters"));
return errors;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,13 @@
import io.flamingock.template.mongodb.model.MongoOperation;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

@FunctionalInterface
public interface OperationValidator {

OperationValidator NO_OP = operation -> Collections.emptyList();

List<TemplatePayloadValidationError> validate(MongoOperation operation);

static List<TemplatePayloadValidationError> checkUnrecognizedKeys(
Expand All @@ -46,6 +43,21 @@ static List<TemplatePayloadValidationError> checkUnrecognizedKeys(
return errors;
}

static List<TemplatePayloadValidationError> checkUnrecognizedOptionKeys(
Map<String, Object> options, Set<String> recognizedKeys, String operationName) {
List<TemplatePayloadValidationError> errors = new ArrayList<>();
if (options == null) {
return errors;
}
for (String key : options.keySet()) {
if (!recognizedKeys.contains(key)) {
errors.add(new TemplatePayloadValidationError("parameters.options." + key,
operationName + " operation does not recognize option '" + key + "'"));
}
}
return errors;
}

static List<TemplatePayloadValidationError> checkListElementTypes(
List<?> list, String fieldPath, String elementName) {
List<TemplatePayloadValidationError> errors = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package io.flamingock.template.mongodb.validation;

import io.flamingock.api.template.TemplatePayloadValidationError;
import io.flamingock.template.mongodb.mapper.RenameCollectionOptionsMapper;
import io.flamingock.template.mongodb.model.MongoOperation;

import java.util.ArrayList;
Expand Down Expand Up @@ -60,6 +61,11 @@ public List<TemplatePayloadValidationError> validate(MongoOperation operation) {
if (options != null && !(options instanceof Map)) {
errors.add(new TemplatePayloadValidationError("parameters.options",
"'options' must be a document"));
} else if (options instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> optionsMap = (Map<String, Object>) options;
errors.addAll(OperationValidator.checkUnrecognizedOptionKeys(
optionsMap, RenameCollectionOptionsMapper.RECOGNIZED_KEYS, "RenameCollection"));
}

errors.addAll(OperationValidator.checkUnrecognizedKeys(params, RECOGNIZED_KEYS, "RenameCollection"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package io.flamingock.template.mongodb.validation;

import io.flamingock.api.template.TemplatePayloadValidationError;
import io.flamingock.template.mongodb.mapper.UpdateOptionsMapper;
import io.flamingock.template.mongodb.model.MongoOperation;

import java.util.ArrayList;
Expand Down Expand Up @@ -68,6 +69,11 @@ public List<TemplatePayloadValidationError> validate(MongoOperation operation) {
if (options != null && !(options instanceof Map)) {
errors.add(new TemplatePayloadValidationError("parameters.options",
"'options' must be a document"));
} else if (options instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> optionsMap = (Map<String, Object>) options;
errors.addAll(OperationValidator.checkUnrecognizedOptionKeys(
optionsMap, UpdateOptionsMapper.RECOGNIZED_KEYS, "Update"));
}

errors.addAll(OperationValidator.checkUnrecognizedKeys(params, RECOGNIZED_KEYS, "Update"));
Expand Down
Loading
Loading