CsvToSleepData.java
package com.seebie.server.mapper.dtotoentity;
import com.seebie.server.dto.SleepData;
import com.seebie.server.mapper.entitytodto.LocalDateTimeConverter;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVRecord;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.io.StringReader;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import static com.seebie.server.mapper.entitytodto.SleepDetailsToCsv.HEADER;
@Component
public class CsvToSleepData implements Function<String, List<SleepData>> {
    // allowMissingColumnNames is only used when parsing
    public static final CSVFormat CSV_INPUT = CSVFormat.RFC4180.builder()
                                                                .setAllowMissingColumnNames(false)
                                                                .setSkipHeaderRecord(true)
                                                                .setHeader(HEADER.class)
                                                                .build();
    public static IllegalArgumentException missingHeader() {
        return new IllegalArgumentException("CSV input does not contain all required columns");
    }
    @Override
    public List<SleepData> apply(String rawCsv) {
        if(HEADER.values().length != Arrays.stream(HEADER.values()).map(Enum::name).filter(rawCsv::contains).count()) {
            throw missingHeader();
        }
        try {
            var list = CSV_INPUT.parse(new StringReader(rawCsv))
                    .stream()
                    .map(this::fromCsvRow)
                    .toList();
            return list;
        }
        catch (IOException | IllegalStateException e) {
            throw new IllegalArgumentException("Could not parse CSV input", e);
        }
    }
    private SleepData fromCsvRow(CSVRecord record) {
        var start = LocalDateTimeConverter.parse(record.get(HEADER.TIME_ASLEEP));
        var end = LocalDateTimeConverter.parse(record.get(HEADER.TIME_AWAKE));
        var zoneId = record.get(HEADER.TIMEZONE);
        var minutesAwake = Integer.parseInt(record.get(HEADER.MINUTES_AWAKE));
        var notes = record.get(HEADER.NOTES);
        return new SleepData(notes, minutesAwake, start, end, zoneId);
    }
}