FargateCpuMemoryValidator.java

package com.cloudforge.core.config;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Validates AWS Fargate CPU/memory combinations.
 *
 * <p>AWS Fargate requires specific CPU/memory combinations. This validator ensures
 * that the selected CPU and memory values form a valid combination according to
 * AWS Fargate task definition requirements.</p>
 *
 * <h2>Valid Combinations:</h2>
 * <table border="1">
 *   <caption>AWS Fargate CPU and Memory Combinations</caption>
 *   <tr><th>CPU (vCPU)</th><th>Memory (MB)</th></tr>
 *   <tr><td>256 (0.25 vCPU)</td><td>512, 1024, 2048</td></tr>
 *   <tr><td>512 (0.5 vCPU)</td><td>1024, 2048, 3072, 4096</td></tr>
 *   <tr><td>1024 (1 vCPU)</td><td>2048, 3072, 4096, 5120, 6144, 7168, 8192</td></tr>
 *   <tr><td>2048 (2 vCPU)</td><td>4096, 5120, 6144, 7168, 8192, 9216, 10240, 11264, 12288, 13312, 14336, 15360, 16384</td></tr>
 *   <tr><td>4096 (4 vCPU)</td><td>8192 to 30720 (in 1024 MB increments)</td></tr>
 *   <tr><td>8192 (8 vCPU)</td><td>16384 to 61440 (in 4096 MB increments)</td></tr>
 *   <tr><td>16384 (16 vCPU)</td><td>32768 to 122880 (in 8192 MB increments)</td></tr>
 * </table>
 *
 * <h2>Usage:</h2>
 * <pre>{@code
 * @ConfigField(
 *     displayName = "Fargate Memory (MB)",
 *     validators = {"FargateCpuMemoryValidator"}
 * )
 * public int fargateMemory = 2048;
 * }</pre>
 *
 * @since 3.0.0
 */
public class FargateCpuMemoryValidator implements FieldValidator {

    // Valid Fargate CPU/memory combinations
    private static final Map<Integer, List<Integer>> VALID_COMBINATIONS = new HashMap<>();

    static {
        // 0.25 vCPU (256)
        VALID_COMBINATIONS.put(256, List.of(512, 1024, 2048));

        // 0.5 vCPU (512)
        VALID_COMBINATIONS.put(512, List.of(1024, 2048, 3072, 4096));

        // 1 vCPU (1024)
        VALID_COMBINATIONS.put(1024, List.of(2048, 3072, 4096, 5120, 6144, 7168, 8192));

        // 2 vCPU (2048)
        VALID_COMBINATIONS.put(2048, List.of(
            4096, 5120, 6144, 7168, 8192, 9216, 10240, 11264, 12288, 13312, 14336, 15360, 16384
        ));

        // 4 vCPU (4096) - 8192 to 30720 in 1024 increments
        VALID_COMBINATIONS.put(4096, generateRange(8192, 30720, 1024));

        // 8 vCPU (8192) - 16384 to 61440 in 4096 increments
        VALID_COMBINATIONS.put(8192, generateRange(16384, 61440, 4096));

        // 16 vCPU (16384) - 32768 to 122880 in 8192 increments
        VALID_COMBINATIONS.put(16384, generateRange(32768, 122880, 8192));
    }

    /**
     * Generates a range of values with a given increment.
     */
    private static List<Integer> generateRange(int start, int end, int increment) {
        java.util.List<Integer> range = new java.util.ArrayList<>();
        for (int value = start; value <= end; value += increment) {
            range.add(value);
        }
        return range;
    }

    @Override
    public ValidationResult validate(ConfigFieldInfo field, Object value, Object config) {
        if (config == null || value == null) {
            return ValidationResult.ok();
        }

        // Only validate if this is fargateCpu or fargateMemory field
        String fieldName = field.fieldName();
        if (!fieldName.equals("fargateCpu") && !fieldName.equals("fargateMemory")) {
            return ValidationResult.ok();
        }

        try {
            // Get both CPU and memory values
            Integer cpu = getFieldValue(config, "fargateCpu", Integer.class);
            Integer memory = getFieldValue(config, "fargateMemory", Integer.class);

            if (cpu == null || memory == null) {
                return ValidationResult.ok();
            }

            // Check if combination is valid
            List<Integer> validMemoryValues = VALID_COMBINATIONS.get(cpu);
            if (validMemoryValues == null) {
                return ValidationResult.error(
                    "Invalid Fargate CPU value: " + cpu + ". Valid values: 256, 512, 1024, 2048, 4096, 8192, 16384"
                );
            }

            if (!validMemoryValues.contains(memory)) {
                return ValidationResult.error(
                    "Invalid Fargate CPU/Memory combination. CPU " + cpu + " supports memory values: " +
                    formatMemoryValues(validMemoryValues)
                );
            }

            return ValidationResult.ok();

        } catch (Exception e) {
            // Reflection errors - log but don't fail
            System.err.println("Warning: FargateCpuMemoryValidator failed: " + e.getMessage());
            return ValidationResult.ok();
        }
    }

    /**
     * Gets a field value with type casting.
     */
    private <T> T getFieldValue(Object config, String fieldName, Class<T> type) {
        try {
            java.lang.reflect.Field field = config.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            Object value = field.get(config);
            if (value != null && type.isInstance(value)) {
                return type.cast(value);
            }
            if (value instanceof Number) {
                return type.cast(((Number) value).intValue());
            }
            return null;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Formats memory values for error messages.
     */
    private String formatMemoryValues(List<Integer> values) {
        if (values.size() <= 5) {
            return values.stream()
                .map(Object::toString)
                .reduce((a, b) -> a + ", " + b)
                .orElse("");
        } else {
            // Show first 3, last 2
            return values.get(0) + ", " + values.get(1) + ", " + values.get(2) +
                " ... " + values.get(values.size() - 2) + ", " + values.get(values.size() - 1);
        }
    }
}