ComplianceRule.java

package com.cloudforgeci.api.core.rules;

import java.util.Optional;

/**
 * Represents a compliance rule with its validation status and AWS Config rule mapping.
 *
 * <p>This structured approach links compliance validation to actual AWS Config rules,
 * providing traceability between framework requirements and infrastructure monitoring.</p>
 *
 * @param ruleId Unique identifier for this compliance rule (e.g., "SOC2-CC6.1", "PCI-DSS-Req3.4")
 * @param description Human-readable description of the requirement
 * @param configRuleId Optional AWS Config rule ID that monitors this requirement
 * @param passed Whether the validation check passed -- true for both a clean pass and an advisory
 * @param errorMessage Optional error message if validation failed
 * @param recommendation Optional recommendation for an ADVISORY-tier control that's off; present only
 *        on a passed=true result, distinguishing "clean pass" from "passed, but here's a recommendation"
 *        without a separate boolean. See {@link #advisory}.
 */
public record ComplianceRule(
    String ruleId,
    String description,
    Optional<String> configRuleId,
    boolean passed,
    Optional<String> errorMessage,
    Optional<String> recommendation
) {
    /**
     * Create a passing compliance rule.
     */
    public static ComplianceRule pass(String ruleId, String description, String configRuleId) {
        return new ComplianceRule(
            ruleId,
            description,
            Optional.ofNullable(configRuleId),
            true,
            Optional.empty(),
            Optional.empty()
        );
    }

    /**
     * Create a passing compliance rule without Config rule mapping.
     */
    public static ComplianceRule pass(String ruleId, String description) {
        return new ComplianceRule(
            ruleId,
            description,
            Optional.empty(),
            true,
            Optional.empty(),
            Optional.empty()
        );
    }

    /**
     * Create a failing compliance rule with error message.
     */
    public static ComplianceRule fail(String ruleId, String description, String errorMessage) {
        return new ComplianceRule(
            ruleId,
            description,
            Optional.empty(),
            false,
            Optional.of(errorMessage),
            Optional.empty()
        );
    }

    /**
     * Create a failing compliance rule with Config rule mapping.
     */
    public static ComplianceRule fail(String ruleId, String description, String configRuleId, String errorMessage) {
        return new ComplianceRule(
            ruleId,
            description,
            Optional.ofNullable(configRuleId),
            false,
            Optional.of(errorMessage),
            Optional.empty()
        );
    }

    /**
     * Create an ADVISORY-tier finding: the control is off, but the framework doesn't require it, so
     * this must never block synthesis. {@code passed} stays true; {@code recommendation} carries the
     * specific, actionable text for the compliance report.
     */
    public static ComplianceRule advisory(String ruleId, String description, String recommendation) {
        return new ComplianceRule(
            ruleId,
            description,
            Optional.empty(),
            true,
            Optional.empty(),
            Optional.of(recommendation)
        );
    }

    /**
     * Create an ADVISORY-tier finding with Config rule mapping.
     */
    public static ComplianceRule advisory(String ruleId, String description, String configRuleId,
                                          String recommendation) {
        return new ComplianceRule(
            ruleId,
            description,
            Optional.ofNullable(configRuleId),
            true,
            Optional.empty(),
            Optional.of(recommendation)
        );
    }

    /**
     * True for a passed result that carries a recommendation -- distinguishes an advisory finding
     * from a clean pass without a separate constructor flag.
     */
    public boolean isAdvisory() {
        return passed && recommendation.isPresent();
    }

    /**
     * Convert to legacy string error format for CDK validation.
     */
    public Optional<String> toErrorString() {
        if (passed) {
            return Optional.empty();
        }

        StringBuilder error = new StringBuilder();
        error.append(ruleId).append(": ").append(description);

        errorMessage.ifPresent(msg -> error.append(" - ").append(msg));

        configRuleId.ifPresent(id -> error.append(" (Config Rule: ").append(id).append(")"));

        return Optional.of(error.toString());
    }
}