GET /csv
/csvRFC 4180 compliant CSV control. Header + 3 rows, all 3 columns, no BOM, fields containing commas are quoted, CRLF line endings throughout. Build a client against this baseline; flip hostname to chaos.catastrophic.io to exercise the four failure modes.
expect: 200 OK, Content-Type: text/csv; charset=utf-8. CSV parses cleanly under any RFC 4180 parser. X-Chaos-Origin: control. X-Chaos-Counterpart points at chaos.catastrophic.io/csv.
curl -i 'https://not.catastrophic.io/csv'
# stdlib csv.reader is forgiving — it accepts unquoted commas (just
# gets the column count wrong) and embedded newlines (silently splits
# the row). pandas raises on ragged columns by default.
import urllib.request, io, csv
raw = urllib.request.urlopen("https://not.catastrophic.io/csv").read().decode("utf-8")
reader = csv.reader(io.StringIO(raw))
for i, row in enumerate(reader):
print(f"row {i}: {len(row)} cols -> {row}")
// Papa Parse is the de-facto browser CSV library; it surfaces
// delimiter / column-count errors via the `errors` array.
// Without Papa, parsing CSV correctly in vanilla JS is a footgun
// because of the embedded-newlines mode below.
const res = await fetch("https://not.catastrophic.io/csv");
const raw = await res.text();
console.log("Mode:", res.headers.get("X-Chaos-Csv-Mode"));
const result = Papa.parse(raw, { header: true });
console.log("Errors:", result.errors);
console.log("Rows:", result.data);
package main
import (
"encoding/csv"
"fmt"
"net/http"
"strings"
"io"
)
func main() {
resp, _ := http.Get("https://not.catastrophic.io/csv")
defer resp.Body.Close()
raw, _ := io.ReadAll(resp.Body)
fmt.Println("Mode:", resp.Header.Get("X-Chaos-Csv-Mode"))
// encoding/csv: FieldsPerRecord=0 enforces the header's count;
// FieldsPerRecord=-1 disables the check entirely.
r := csv.NewReader(strings.NewReader(string(raw)))
r.FieldsPerRecord = 0
for {
rec, err := r.Read()
if err == io.EOF { break }
if err != nil { fmt.Println("Parse error:", err); break }
fmt.Println(rec)
}
}
// Cargo.toml: reqwest = { version = "0.12", features = ["blocking"] }
// csv = "1"
fn main() -> Result<(), Box> {
let raw = reqwest::blocking::get("https://not.catastrophic.io/csv")?.text()?;
let mut rdr = csv::ReaderBuilder::new()
.has_headers(true)
.flexible(false)
.from_reader(raw.as_bytes());
for result in rdr.records() {
match result {
Ok(rec) => println!("{:?}", rec),
Err(e) => { eprintln!("Parse error: {e}"); break; }
}
}
Ok(())
}
// Requires Apache Commons CSV: org.apache.commons:commons-csv
import org.apache.commons.csv.*;
import java.net.URI;
import java.net.http.*;
import java.io.StringReader;
public class CsvChaos {
public static void main(String[] args) throws Exception {
var client = HttpClient.newHttpClient();
var req = HttpRequest.newBuilder(URI.create("https://not.catastrophic.io/csv")).build();
var resp = client.send(req, HttpResponse.BodyHandlers.ofString());
var fmt = CSVFormat.RFC4180.builder().setHeader().build();
try (var p = CSVParser.parse(new StringReader(resp.body()), fmt)) {
for (var record : p) System.out.println(record.toMap());
} catch (Exception e) {
System.err.println("Parse error: " + e.getMessage());
}
}
}
// Requires CsvHelper.
using CsvHelper;
using CsvHelper.Configuration;
using System.Globalization;
using var client = new HttpClient();
var raw = await client.GetStringAsync("https://not.catastrophic.io/csv");
var config = new CsvConfiguration(CultureInfo.InvariantCulture) {
HasHeaderRecord = true,
MissingFieldFound = null,
BadDataFound = ctx => Console.Error.WriteLine($"Bad data at row {ctx.RawRecord}"),
};
using var reader = new StringReader(raw);
using var csv = new CsvReader(reader, config);
var rows = csv.GetRecords().ToList();
Console.WriteLine($"Rows: {rows.Count}");
# stdlib CSV is RFC-mostly. liberal_parsing: true accepts the unquoted
# comma mode without raising; default settings raise on ragged columns.
require "net/http"
require "csv"
resp = Net::HTTP.get_response(URI("https://not.catastrophic.io/csv"))
raw = resp.body
puts "Mode: #{resp['X-Chaos-Csv-Mode']}"
begin
CSV.parse(raw, headers: true).each_with_index do |row, i|
puts "row #{i}: #{row.length} cols -> #{row.to_h}"
end
rescue CSV::MalformedCSVError => e
puts "Refused: #{e.message}"
end
# Import-Csv is strict on column counts by default. Headers come from
# row 1; ragged rows produce $null for missing columns.
$resp = Invoke-WebRequest -Uri 'https://not.catastrophic.io/csv' -SkipHttpErrorCheck
$resp.Headers['X-Chaos-Csv-Mode']
$resp.Content | ConvertFrom-Csv | Format-Table
headers
body