HeaderValidator.java

package de.siegmar.fastcsv.reader;

import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;

/// Implementations of this class are used within [NamedCsvRecordHandler] to validate the header
/// of a CSV file once it is determined.
///
/// Example:
/// ```
/// NamedCsvRecordHandler handler = NamedCsvRecordHandler.builder()
///     .headerValidator(HeaderValidator.containsExactly("col1", "col2"))
///     .build();
/// ```
@FunctionalInterface
public interface HeaderValidator {

    /// Builds a validator that throws a [CsvParseException] if the header does not consist of
    /// exactly the given fields, in the given order.
    ///
    /// @param expectedHeader the expected header fields, must not be `null`
    /// @return the validator
    /// @throws NullPointerException if `null` is passed or the fields contain `null` elements
    /// @see #containsExactly(List)
    static HeaderValidator containsExactly(final String... expectedHeader) {
        Objects.requireNonNull(expectedHeader, "expectedHeader must not be null");
        return containsExactly(List.of(expectedHeader));
    }

    /// Builds a validator that throws a [CsvParseException] if the header does not consist of
    /// exactly the given fields, in the given order.
    ///
    /// @param expectedHeader the expected header fields, must not be `null`
    /// @return the validator
    /// @throws NullPointerException if `null` is passed or the fields contain `null` elements
    /// @see #containsExactly(String...)
    static HeaderValidator containsExactly(final List<String> expectedHeader) {
        Objects.requireNonNull(expectedHeader, "expectedHeader must not be null");
        final List<String> expected = List.copyOf(expectedHeader);
        return header -> {
            if (!expected.equals(header)) {
                throw new CsvParseException("Header mismatch: expected %s but found %s"
                    .formatted(expected, header));
            }
        };
    }

    /// Builds a validator that throws a [CsvParseException] if the header does not contain all
    /// of the given fields, in any order. Additional fields are allowed.
    ///
    /// @param requiredFields the required header fields, must not be `null`
    /// @return the validator
    /// @throws NullPointerException if `null` is passed or the fields contain `null` elements
    /// @see #containsAtLeast(List)
    static HeaderValidator containsAtLeast(final String... requiredFields) {
        Objects.requireNonNull(requiredFields, "requiredFields must not be null");
        return containsAtLeast(List.of(requiredFields));
    }

    /// Builds a validator that throws a [CsvParseException] if the header does not contain all
    /// of the given fields, in any order. Additional fields are allowed.
    ///
    /// @param requiredFields the required header fields, must not be `null`
    /// @return the validator
    /// @throws NullPointerException if `null` is passed or the fields contain `null` elements
    /// @see #containsAtLeast(String...)
    static HeaderValidator containsAtLeast(final List<String> requiredFields) {
        Objects.requireNonNull(requiredFields, "requiredFields must not be null");
        final List<String> required = List.copyOf(requiredFields);
        return header -> {
            final var missingFields = new LinkedHashSet<>(required);
            header.forEach(missingFields::remove);
            if (!missingFields.isEmpty()) {
                throw new CsvParseException("Header is missing fields %s: %s"
                    .formatted(missingFields, header));
            }
        };
    }

    /// Gets called once the header is determined ��� either from the first record (that is not a
    /// comment or an empty line) or from a predefined header.
    ///
    /// @param header the header fields, never `null`
    /// @throws RuntimeException if the header is not acceptable
    void validate(List<String> header);

}