package internal import ( "encoding/csv" "errors" "fmt" "io" "os" "strconv" ) type Headings []string func (h Headings) IndexOf(heading string) int { for i, value := range h { if value == heading { return i } } return -1 } func ReadCsvRedirects(file string) (Redirects, error) { reader, err := os.Open(file) if err != nil { return nil, err } defer reader.Close() csvReader := csv.NewReader(reader) // Process headings textHeadings, err := csvReader.Read() if err != nil { return nil, err } headings := Headings(textHeadings) fromCol := headings.IndexOf("From") toCol := headings.IndexOf("To") statusCol := headings.IndexOf("Status Code") if fromCol == -1 || toCol == -1 || statusCol == -1 { return nil, errors.New("CSV must contain From, To, and Status Code headings") } // Read data redirects := Redirects{} for line := 2; ; line++ { row, err := csvReader.Read() if err != nil { if errors.Is(err, io.EOF) { break } return nil, err } statusCode, err := strconv.Atoi(row[statusCol]) if err != nil { return nil, fmt.Errorf("line %d: status code %q is not a number", line, row[statusCol]) } if statusCode < 300 || statusCode > 399 { return nil, fmt.Errorf("line %d: status code %d is not a redirect (must be 3xx)", line, statusCode) } redirects = append(redirects, &Redirect{ From: row[fromCol], To: row[toCol], StatusCode: statusCode, }) } return redirects, nil }