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
23 changes: 22 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -510,4 +510,25 @@ See `docs/TEMPLATES_EVOLUTION_PROPOSALS.md` for comprehensive proposals includin
- JSON Schema validation
- Explicit dependencies (`depends_on`)
- Policy as Code
- And more...
- And more...

## Commit Message Convention

All commits must follow [Conventional Commits](https://www.conventionalcommits.org/) with a well-structured body suitable for changelog extraction from `git log`.

### Format

```
<type>(<scope>): <concise imperative summary>

<body: precise explanation of what changed and why, using markdown formatting>
```

### Rules

- **Title**: imperative mood, lowercase, no period, under 72 characters
- **Type**: `feat`, `fix`, `refactor`, `chore`, `ci`, `docs`, `test`
- **Scope**: optional but encouraged (e.g., `templates`, `core`, `cli`)
- **Body**: required for `feat` and `fix`. Explains *what* changed and *why* at a level suitable for a professional changelog. Use bullet points, bold for class/concept names, and keep it concise — no filler, no redundancy
- **No PR number** in the title when committing locally (GitHub adds it on merge)
- The body is the source of truth for the changelog — write it as if a technical user will read it to understand the release
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@
*
* <p>Use {@code @ChangeTemplate(steppable = true)} for steppable templates with multiple steps.
*
* @param <SHARED_CONFIGURATION_FIELD> shared configuration type (use {@code Void} if none)
* @param <SHARED_CONFIGURATION_FIELD> shared configuration type (use {@code TemplateVoid} if none)
* @param <APPLY_FIELD> apply payload type
* @param <ROLLBACK_FIELD> rollback payload type
* @see io.flamingock.api.annotations.ChangeTemplate
*/
public abstract class AbstractChangeTemplate<SHARED_CONFIGURATION_FIELD, APPLY_FIELD extends TemplatePayload, ROLLBACK_FIELD extends TemplatePayload> implements ChangeTemplate<SHARED_CONFIGURATION_FIELD, APPLY_FIELD, ROLLBACK_FIELD> {
public abstract class AbstractChangeTemplate<SHARED_CONFIGURATION_FIELD extends TemplatePayload, APPLY_FIELD extends TemplatePayload, ROLLBACK_FIELD extends TemplatePayload> implements ChangeTemplate<SHARED_CONFIGURATION_FIELD, APPLY_FIELD, ROLLBACK_FIELD> {

private final Class<SHARED_CONFIGURATION_FIELD> configurationClass;
private final Class<APPLY_FIELD> applyPayloadClass;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@
* by extending {@link AbstractChangeTemplate} and annotating with
* {@link io.flamingock.api.annotations.ChangeTemplate}.
*
* @param <SHARED_CONFIG_FIELD> shared configuration type (use {@code Void} if none)
* @param <SHARED_CONFIG_FIELD> shared configuration type (use {@code TemplateVoid} if none)
* @param <APPLY_FIELD> apply payload type parsed from YAML
* @param <ROLLBACK_FIELD> rollback payload type parsed from YAML
* @see AbstractChangeTemplate
* @see io.flamingock.api.annotations.ChangeTemplate
*/
public interface ChangeTemplate<SHARED_CONFIG_FIELD, APPLY_FIELD extends TemplatePayload, ROLLBACK_FIELD extends TemplatePayload> extends ReflectionMetadataProvider {
public interface ChangeTemplate<SHARED_CONFIG_FIELD extends TemplatePayload, APPLY_FIELD extends TemplatePayload, ROLLBACK_FIELD extends TemplatePayload> extends ReflectionMetadataProvider {

void setChangeId(String changeId);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2026 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.api.template.wrappers;

import io.flamingock.api.template.TemplatePayload;
import io.flamingock.api.template.TemplatePayloadValidationError;

import java.util.Collections;
import java.util.List;

/**
* A {@link TemplatePayload} sentinel representing "no configuration needed".
*
* <p>Replaces {@code Void} as the CONFIG type parameter in templates that
* have no shared configuration. Unlike {@code Void}, this class implements
* {@code TemplatePayload}, satisfying the {@code CONFIG extends TemplatePayload}
* bound on the template system.
*/
public class TemplateVoid implements TemplatePayload {

@Override
public List<TemplatePayloadValidationError> validate() {
return Collections.emptyList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.flamingock.api.annotations.ChangeTemplate;
import io.flamingock.api.annotations.Rollback;
import io.flamingock.api.template.wrappers.TemplateString;
import io.flamingock.api.template.wrappers.TemplateVoid;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

Expand All @@ -31,8 +32,13 @@
class AbstractChangeTemplateReflectiveClassesTest {

// Simple test configuration class
public static class TestConfig {
public static class TestConfig implements TemplatePayload {
public String configValue;

@Override
public List<TemplatePayloadValidationError> validate() {
return Collections.emptyList();
}
}

// Simple test apply payload class
Expand Down Expand Up @@ -103,10 +109,10 @@ public void rollback() {
}
}

// Test template with Void configuration
// Test template with TemplateVoid configuration
@ChangeTemplate(name = "test-template-with-void-config")
public static class TestTemplateWithVoidConfig
extends AbstractChangeTemplate<Void, TemplateString, TemplateString> {
extends AbstractChangeTemplate<TemplateVoid, TemplateString, TemplateString> {

public TestTemplateWithVoidConfig() {
super();
Expand Down Expand Up @@ -196,14 +202,14 @@ void multipleCallsShouldReturnEquivalentSets() {
}

@Test
@DisplayName("getReflectiveClasses with Void configuration should include Void class")
void getReflectiveClassesWithVoidConfigShouldIncludeVoidClass() {
@DisplayName("getReflectiveClasses with TemplateVoid configuration should include TemplateVoid class")
void getReflectiveClassesWithVoidConfigShouldIncludeTemplateVoidClass() {
TestTemplateWithVoidConfig template = new TestTemplateWithVoidConfig();

Collection<Class<?>> reflectiveClasses = template.getReflectiveClasses();

assertTrue(reflectiveClasses.contains(Void.class),
"Should contain Void class for configuration");
assertTrue(reflectiveClasses.contains(TemplateVoid.class),
"Should contain TemplateVoid class for configuration");
assertTrue(reflectiveClasses.contains(TemplateString.class),
"Should contain TemplateString class for apply/rollback payloads");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import io.flamingock.api.annotations.Rollback;
import io.flamingock.api.template.AbstractChangeTemplate;
import io.flamingock.api.template.wrappers.TemplateString;
import io.flamingock.api.template.wrappers.TemplateVoid;
import io.flamingock.internal.common.core.error.FlamingockException;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
Expand All @@ -31,7 +32,7 @@
class ChangeTemplateManagerTest {

@ChangeTemplate(name = "annotated-simple-template")
public static class AnnotatedSimpleTemplate extends AbstractChangeTemplate<Void, TemplateString, TemplateString> {
public static class AnnotatedSimpleTemplate extends AbstractChangeTemplate<TemplateVoid, TemplateString, TemplateString> {
public AnnotatedSimpleTemplate() {
super();
}
Expand All @@ -46,7 +47,7 @@ public void rollback() {
}

@ChangeTemplate(name = "annotated-steppable-template", multiStep = true)
public static class AnnotatedSteppableTemplate extends AbstractChangeTemplate<Void, TemplateString, TemplateString> {
public static class AnnotatedSteppableTemplate extends AbstractChangeTemplate<TemplateVoid, TemplateString, TemplateString> {
public AnnotatedSteppableTemplate() {
super();
}
Expand All @@ -60,7 +61,7 @@ public void rollback() {
}
}

public static class UnannotatedTemplate extends AbstractChangeTemplate<Void, TemplateString, TemplateString> {
public static class UnannotatedTemplate extends AbstractChangeTemplate<TemplateVoid, TemplateString, TemplateString> {
public UnannotatedTemplate() {
super();
}
Expand Down Expand Up @@ -114,7 +115,7 @@ void getTemplateForUnregisteredNameShouldReturnEmpty() {
}

@ChangeTemplate(name = "template-rollback-not-required", rollbackPayloadRequired = false)
public static class TemplateWithRollbackNotRequired extends AbstractChangeTemplate<Void, TemplateString, TemplateString> {
public static class TemplateWithRollbackNotRequired extends AbstractChangeTemplate<TemplateVoid, TemplateString, TemplateString> {
public TemplateWithRollbackNotRequired() {
super();
}
Expand All @@ -141,7 +142,7 @@ void addTemplateWithRollbackPayloadRequiredFalseShouldPropagate() {
}

@ChangeTemplate(name = "template-without-rollback")
public static class TemplateWithoutRollback extends AbstractChangeTemplate<Void, TemplateString, TemplateString> {
public static class TemplateWithoutRollback extends AbstractChangeTemplate<TemplateVoid, TemplateString, TemplateString> {
public TemplateWithoutRollback() {
super();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import io.flamingock.api.annotations.Rollback;
import io.flamingock.api.template.AbstractChangeTemplate;
import io.flamingock.api.template.wrappers.TemplateString;
import io.flamingock.api.template.wrappers.TemplateVoid;
import io.flamingock.internal.common.core.error.validation.ValidationResult;
import io.flamingock.internal.common.core.preview.TemplatePreviewChange;
import org.junit.jupiter.api.BeforeEach;
Expand All @@ -41,7 +42,7 @@ class TemplateValidatorTest {

// Test template with @ChangeTemplate (simple template)
@ChangeTemplate(name = "test-simple-template")
public static class TestSimpleTemplate extends AbstractChangeTemplate<Void, TemplateString, TemplateString> {
public static class TestSimpleTemplate extends AbstractChangeTemplate<TemplateVoid, TemplateString, TemplateString> {
public TestSimpleTemplate() {
super();
}
Expand All @@ -58,7 +59,7 @@ public void rollback() {

// Test template with @ChangeTemplate(multiStep = true)
@ChangeTemplate(name = "test-steppable-template", multiStep = true)
public static class TestSteppableTemplate extends AbstractChangeTemplate<Void, TemplateString, TemplateString> {
public static class TestSteppableTemplate extends AbstractChangeTemplate<TemplateVoid, TemplateString, TemplateString> {
public TestSteppableTemplate() {
super();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@

import io.flamingock.api.template.ChangeTemplate;
import io.flamingock.api.template.TemplatePayload;
import io.flamingock.api.template.wrappers.TemplateVoid;
import io.flamingock.internal.common.core.recovery.action.ChangeAction;
import io.flamingock.internal.core.task.loaded.AbstractTemplateLoadedChange;
import io.flamingock.internal.util.FileUtil;
import io.flamingock.internal.util.log.FlamingockLoggerFactory;
import org.slf4j.Logger;

Expand All @@ -36,7 +36,7 @@
* @param <ROLLBACK> the rollback payload type
* @param <T> the type of template loaded change
*/
public abstract class AbstractTemplateExecutableTask<CONFIG, APPLY extends TemplatePayload, ROLLBACK extends TemplatePayload,
public abstract class AbstractTemplateExecutableTask<CONFIG extends TemplatePayload, APPLY extends TemplatePayload, ROLLBACK extends TemplatePayload,
T extends AbstractTemplateLoadedChange<CONFIG, APPLY, ROLLBACK>> extends ReflectionExecutableTask<T> {
protected final Logger logger = FlamingockLoggerFactory.getLogger("TemplateTask");

Expand All @@ -52,9 +52,9 @@ protected void setConfigurationData(ChangeTemplate<CONFIG, ?, ?> instance) {
Class<CONFIG> parameterClass = instance.getConfigurationClass();
CONFIG data = descriptor.getConfigurationPayload();

if (data != null && Void.class != parameterClass) {
instance.setConfiguration(FileUtil.convertToType(parameterClass, data));
} else if (Void.class != parameterClass) {
if (data != null && TemplateVoid.class != parameterClass) {
instance.setConfiguration(data);
} else if (TemplateVoid.class != parameterClass) {
logger.warn("No 'Configuration' section provided for template-based change[{}] of type[{}]",
descriptor.getId(), descriptor.getTemplateClass().getName());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
* @param <ROLLBACK> the rollback payload type
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public class SimpleTemplateExecutableTask<CONFIG, APPLY extends TemplatePayload, ROLLBACK extends TemplatePayload>
public class SimpleTemplateExecutableTask<CONFIG extends TemplatePayload, APPLY extends TemplatePayload, ROLLBACK extends TemplatePayload>
extends AbstractTemplateExecutableTask<CONFIG, APPLY, ROLLBACK,
SimpleTemplateLoadedChange<CONFIG, APPLY, ROLLBACK>> {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
* @param <ROLLBACK> the rollback payload type
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public class SteppableTemplateExecutableTask<CONFIG, APPLY extends TemplatePayload, ROLLBACK extends TemplatePayload>
public class SteppableTemplateExecutableTask<CONFIG extends TemplatePayload, APPLY extends TemplatePayload, ROLLBACK extends TemplatePayload>
extends AbstractTemplateExecutableTask<CONFIG, APPLY, ROLLBACK,
MultiStepTemplateLoadedChange<CONFIG, APPLY, ROLLBACK>> {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
* @param <APPLY> the apply payload type
* @param <ROLLBACK> the rollback payload type
*/
public abstract class AbstractTemplateLoadedChange<CONFIG, APPLY extends TemplatePayload, ROLLBACK extends TemplatePayload> extends AbstractLoadedChange {
public abstract class AbstractTemplateLoadedChange<CONFIG extends TemplatePayload, APPLY extends TemplatePayload, ROLLBACK extends TemplatePayload> extends AbstractLoadedChange {

private final List<String> profiles;
private final CONFIG configurationPayload;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
* @param <APPLY> the apply payload type
* @param <ROLLBACK> the rollback payload type
*/
public class MultiStepTemplateLoadedChange<CONFIG, APPLY extends TemplatePayload, ROLLBACK extends TemplatePayload>
public class MultiStepTemplateLoadedChange<CONFIG extends TemplatePayload, APPLY extends TemplatePayload, ROLLBACK extends TemplatePayload>
extends AbstractTemplateLoadedChange<CONFIG, APPLY, ROLLBACK> {

private final List<TemplateStep<APPLY, ROLLBACK>> steps;
Expand Down Expand Up @@ -69,6 +69,19 @@ public List<TemplateStep<APPLY, ROLLBACK>> getSteps() {

@Override
protected List<ValidationError> validateConfigurationPayload() {
CONFIG config = getConfigurationPayload();
if (config != null) {
List<TemplatePayloadValidationError> payloadErrors = config.validate();
if (!payloadErrors.isEmpty()) {
List<ValidationError> errors = new ArrayList<>();
for (TemplatePayloadValidationError e : payloadErrors) {
errors.add(new ValidationError(
String.format("Template '%s' configuration payload: %s", getSource(), e.getFormattedMessage()),
getId(), "change"));
}
return errors;
}
}
return Collections.emptyList();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
* @param <APPLY> the apply payload type
* @param <ROLLBACK> the rollback payload type
*/
public class SimpleTemplateLoadedChange<CONFIG, APPLY extends TemplatePayload, ROLLBACK extends TemplatePayload>
public class SimpleTemplateLoadedChange<CONFIG extends TemplatePayload, APPLY extends TemplatePayload, ROLLBACK extends TemplatePayload>
extends AbstractTemplateLoadedChange<CONFIG, APPLY, ROLLBACK> {

// Already converted to typed payload (no longer raw Object from YAML)
Expand Down Expand Up @@ -80,6 +80,19 @@ public boolean hasRollbackPayload() {

@Override
protected List<ValidationError> validateConfigurationPayload() {
CONFIG config = getConfigurationPayload();
if (config != null) {
List<TemplatePayloadValidationError> payloadErrors = config.validate();
if (!payloadErrors.isEmpty()) {
List<ValidationError> errors = new ArrayList<>();
for (TemplatePayloadValidationError e : payloadErrors) {
errors.add(new ValidationError(
String.format("Template '%s' configuration payload: %s", getSource(), e.getFormattedMessage()),
getId(), "change"));
}
return errors;
}
}
return Collections.emptyList();
}

Expand Down
Loading
Loading