Skip to content

Commit 44aeeb4

Browse files
authored
feat(templates): add TemplateValidationContext to TemplatePayload.validate() (#856)
- Introduce **TemplateValidationContext** class with `transactional` field (default `false`) and a backward-compatibility contract: new fields are added via getter/setter with sensible defaults, so older payloads work unchanged - Change `TemplatePayload.validate()` signature to `validate(TemplateValidationContext)`, enabling context-aware payload validation - Add `buildValidationContext()` helper in **AbstractTemplateLoadedChange** to construct the context from change metadata, used by both **SimpleTemplateLoadedChange** and **MultiStepTemplateLoadedChange** - Update all implementations: **TemplateVoid**, **TemplateString**, and test payloads
1 parent 064e3fa commit 44aeeb4

8 files changed

Lines changed: 91 additions & 13 deletions

File tree

core/flamingock-core-api/src/main/java/io/flamingock/api/template/TemplatePayload.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@
2626
public interface TemplatePayload {
2727

2828
/**
29-
* Validates this payload and returns any errors found.
29+
* Validates this payload using the supplied change-level context
30+
* and returns any errors found.
3031
*
32+
* @param context change-level metadata available during validation
3133
* @return list of validation errors, empty if payload is valid
3234
*/
33-
List<TemplatePayloadValidationError> validate();
35+
List<TemplatePayloadValidationError> validate(TemplateValidationContext context);
3436
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright 2026 Flamingock (https://www.flamingock.io)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.flamingock.api.template;
17+
18+
/**
19+
* Context provided to {@link TemplatePayload#validate(TemplateValidationContext)}
20+
* so payload validation can access change-level metadata.
21+
*
22+
* <p><b>Binary-compatibility contract:</b> new fields are added as
23+
* getter/setter pairs with sensible defaults. Older template payloads
24+
* that ignore the new fields continue to work unchanged.
25+
*
26+
* <p>Current fields:
27+
* <ul>
28+
* <li>{@code transactional} (default {@code false}) — whether the
29+
* enclosing change is declared transactional.</li>
30+
* </ul>
31+
*/
32+
public class TemplateValidationContext {
33+
34+
private boolean transactional;
35+
36+
/**
37+
* Creates a context with all fields set to their defaults.
38+
*/
39+
public TemplateValidationContext() {
40+
}
41+
42+
/**
43+
* Returns whether the enclosing change is declared transactional.
44+
*
45+
* @return {@code true} if the change is transactional, {@code false} otherwise
46+
*/
47+
public boolean isTransactional() {
48+
return transactional;
49+
}
50+
51+
/**
52+
* Sets whether the enclosing change is declared transactional.
53+
*
54+
* @param transactional {@code true} if the change is transactional
55+
*/
56+
public void setTransactional(boolean transactional) {
57+
this.transactional = transactional;
58+
}
59+
}

core/flamingock-core-api/src/main/java/io/flamingock/api/template/wrappers/TemplateString.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import io.flamingock.api.template.TemplatePayload;
1919
import io.flamingock.api.template.TemplatePayloadValidationError;
20+
import io.flamingock.api.template.TemplateValidationContext;
2021

2122
import java.util.Collections;
2223
import java.util.List;
@@ -58,7 +59,7 @@ public void setValue(String value) {
5859
}
5960

6061
@Override
61-
public List<TemplatePayloadValidationError> validate() {
62+
public List<TemplatePayloadValidationError> validate(TemplateValidationContext context) {
6263
if (value == null || value.trim().isEmpty()) {
6364
return Collections.singletonList(
6465
new TemplatePayloadValidationError("value", "must not be null or blank"));

core/flamingock-core-api/src/main/java/io/flamingock/api/template/wrappers/TemplateVoid.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import io.flamingock.api.template.TemplatePayload;
1919
import io.flamingock.api.template.TemplatePayloadValidationError;
20+
import io.flamingock.api.template.TemplateValidationContext;
2021

2122
import java.util.Collections;
2223
import java.util.List;
@@ -32,7 +33,7 @@
3233
public class TemplateVoid implements TemplatePayload {
3334

3435
@Override
35-
public List<TemplatePayloadValidationError> validate() {
36+
public List<TemplatePayloadValidationError> validate(TemplateValidationContext context) {
3637
return Collections.emptyList();
3738
}
3839
}

core/flamingock-core-api/src/test/java/io/flamingock/api/template/AbstractChangeTemplateReflectiveClassesTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public static class TestConfig implements TemplatePayload {
3636
public String configValue;
3737

3838
@Override
39-
public List<TemplatePayloadValidationError> validate() {
39+
public List<TemplatePayloadValidationError> validate(TemplateValidationContext context) {
4040
return Collections.emptyList();
4141
}
4242
}
@@ -46,7 +46,7 @@ public static class TestApplyPayload implements TemplatePayload {
4646
public String applyData;
4747

4848
@Override
49-
public List<TemplatePayloadValidationError> validate() {
49+
public List<TemplatePayloadValidationError> validate(TemplateValidationContext context) {
5050
return Collections.emptyList();
5151
}
5252
}
@@ -56,7 +56,7 @@ public static class TestRollbackPayload implements TemplatePayload {
5656
public String rollbackData;
5757

5858
@Override
59-
public List<TemplatePayloadValidationError> validate() {
59+
public List<TemplatePayloadValidationError> validate(TemplateValidationContext context) {
6060
return Collections.emptyList();
6161
}
6262
}

core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/AbstractTemplateLoadedChange.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import io.flamingock.api.annotations.Rollback;
2020
import io.flamingock.api.template.ChangeTemplate;
2121
import io.flamingock.api.template.TemplatePayload;
22+
import io.flamingock.api.template.TemplateValidationContext;
2223
import io.flamingock.internal.common.core.error.validation.ValidationError;
2324
import io.flamingock.internal.common.core.task.RecoveryDescriptor;
2425
import io.flamingock.internal.common.core.task.TargetSystemDescriptor;
@@ -103,6 +104,12 @@ public List<ValidationError> getValidationErrors(StageValidationContext context)
103104
return errors;
104105
}
105106

107+
protected TemplateValidationContext buildValidationContext() {
108+
TemplateValidationContext ctx = new TemplateValidationContext();
109+
ctx.setTransactional(isTransactional());
110+
return ctx;
111+
}
112+
106113
abstract protected List<ValidationError> validateConfigurationPayload();
107114

108115
abstract protected List<ValidationError> validateApplyPayload();

core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/MultiStepTemplateLoadedChange.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import io.flamingock.api.template.TemplatePayload;
2020
import io.flamingock.api.template.TemplatePayloadValidationError;
2121
import io.flamingock.api.template.TemplateStep;
22+
import io.flamingock.api.template.TemplateValidationContext;
2223
import io.flamingock.internal.common.core.error.validation.ValidationError;
2324
import io.flamingock.internal.common.core.task.RecoveryDescriptor;
2425
import io.flamingock.internal.common.core.task.TargetSystemDescriptor;
@@ -71,7 +72,8 @@ public List<TemplateStep<APPLY, ROLLBACK>> getSteps() {
7172
protected List<ValidationError> validateConfigurationPayload() {
7273
CONFIG config = getConfigurationPayload();
7374
if (config != null) {
74-
List<TemplatePayloadValidationError> payloadErrors = config.validate();
75+
TemplateValidationContext context = buildValidationContext();
76+
List<TemplatePayloadValidationError> payloadErrors = config.validate(context);
7577
if (!payloadErrors.isEmpty()) {
7678
List<ValidationError> errors = new ArrayList<>();
7779
for (TemplatePayloadValidationError e : payloadErrors) {
@@ -89,14 +91,15 @@ protected List<ValidationError> validateConfigurationPayload() {
8991
protected List<ValidationError> validateApplyPayload() {
9092
List<ValidationError> errors = new ArrayList<>();
9193
if (steps != null) {
94+
TemplateValidationContext context = buildValidationContext();
9295
for (int i = 0; i < steps.size(); i++) {
9396
APPLY applyPayload = steps.get(i).getApplyPayload();
9497
if (applyPayload == null) {
9598
errors.add(new ValidationError(
9699
String.format("Template '%s', step %d: missing required 'apply' payload", getSource(), i + 1),
97100
getId(), "change"));
98101
} else {
99-
List<TemplatePayloadValidationError> payloadErrors = applyPayload.validate();
102+
List<TemplatePayloadValidationError> payloadErrors = applyPayload.validate(context);
100103
for (TemplatePayloadValidationError e : payloadErrors) {
101104
errors.add(new ValidationError(
102105
String.format("Template '%s', step %d apply payload: %s", getSource(), i + 1, e.getFormattedMessage()),
@@ -112,14 +115,15 @@ protected List<ValidationError> validateApplyPayload() {
112115
protected List<ValidationError> validateRollbackPayload() {
113116
List<ValidationError> errors = new ArrayList<>();
114117
if (steps != null) {
118+
TemplateValidationContext context = buildValidationContext();
115119
for (int i = 0; i < steps.size(); i++) {
116120
ROLLBACK rollbackPayload = steps.get(i).getRollbackPayload();
117121
if (rollbackPayloadRequired && !steps.get(i).hasRollbackPayload()) {
118122
errors.add(new ValidationError(
119123
String.format("Template '%s', step %d: missing required 'rollback' payload (rollbackPayloadRequired=true)", getSource(), i + 1),
120124
getId(), "change"));
121125
} else if (rollbackPayload != null) {
122-
List<TemplatePayloadValidationError> payloadErrors = rollbackPayload.validate();
126+
List<TemplatePayloadValidationError> payloadErrors = rollbackPayload.validate(context);
123127
for (TemplatePayloadValidationError e : payloadErrors) {
124128
errors.add(new ValidationError(
125129
String.format("Template '%s', step %d rollback payload: %s", getSource(), i + 1, e.getFormattedMessage()),

core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/SimpleTemplateLoadedChange.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import io.flamingock.api.template.AbstractChangeTemplate;
1919
import io.flamingock.api.template.TemplatePayload;
2020
import io.flamingock.api.template.TemplatePayloadValidationError;
21+
import io.flamingock.api.template.TemplateValidationContext;
2122
import io.flamingock.internal.common.core.error.validation.ValidationError;
2223
import io.flamingock.internal.common.core.task.RecoveryDescriptor;
2324
import io.flamingock.internal.common.core.task.TargetSystemDescriptor;
@@ -82,7 +83,8 @@ public boolean hasRollbackPayload() {
8283
protected List<ValidationError> validateConfigurationPayload() {
8384
CONFIG config = getConfigurationPayload();
8485
if (config != null) {
85-
List<TemplatePayloadValidationError> payloadErrors = config.validate();
86+
TemplateValidationContext context = buildValidationContext();
87+
List<TemplatePayloadValidationError> payloadErrors = config.validate(context);
8688
if (!payloadErrors.isEmpty()) {
8789
List<ValidationError> errors = new ArrayList<>();
8890
for (TemplatePayloadValidationError e : payloadErrors) {
@@ -103,7 +105,8 @@ protected List<ValidationError> validateApplyPayload() {
103105
String.format("Template '%s' requires 'apply' payload", getSource()),
104106
getId(), "change"));
105107
}
106-
List<TemplatePayloadValidationError> payloadErrors = applyPayload.validate();
108+
TemplateValidationContext context = buildValidationContext();
109+
List<TemplatePayloadValidationError> payloadErrors = applyPayload.validate(context);
107110
if (!payloadErrors.isEmpty()) {
108111
List<ValidationError> errors = new ArrayList<>();
109112
for (TemplatePayloadValidationError e : payloadErrors) {
@@ -124,7 +127,8 @@ protected List<ValidationError> validateRollbackPayload() {
124127
getId(), "change"));
125128
}
126129
if (rollbackPayload != null) {
127-
List<TemplatePayloadValidationError> payloadErrors = rollbackPayload.validate();
130+
TemplateValidationContext context = buildValidationContext();
131+
List<TemplatePayloadValidationError> payloadErrors = rollbackPayload.validate(context);
128132
if (!payloadErrors.isEmpty()) {
129133
List<ValidationError> errors = new ArrayList<>();
130134
for (TemplatePayloadValidationError e : payloadErrors) {

0 commit comments

Comments
 (0)