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);
    }
}