//! CSV parser conformance test corpus.
//!
//! This crate ships a fixed set of CSV files that exercise the tricky parts of
//! CSV parsing, each paired with the JSON its parse result should equal. Feed
//! every [`Sample::csv`] to your parser and assert the output matches
//! `Sample::json`, except for `location_coordinates`, whose masked phone field
//! and lone-object JSON cannot equal a correct parse. The crate parses nothing
//! itself. It hands you the raw bytes.
//!
//! The corpus covers quoting, commas inside quoted fields, escaped (doubled)
//! quotes, embedded LF and CRLF inside fields, empty quoted fields, multibyte
//! UTF-8, UTF-8 replacement bytes, and a JSON document stored in one cell.
//!
//! # Bytes, not strings
//!
//! [`Sample::csv`] and [`Sample::json`] are `&[u8]`. The bytes are returned
//! exactly as stored. CRLF stays CRLF. The `0xEF 0xBF 0xBD` replacement
//! sequences in `location_coordinates` stay put. Trailing newlines are kept or
//! omitted per file. No decoding, no normalization, no trimming.
//!
//! # Example
//!
//! ```
//! let samples = csv_spectrum::spectrum();
//! assert_eq!(samples.len(), 12);
//! assert_eq!(samples[0].name, "comma_in_quotes");
//!
//! // The first sample's quoted field holds a comma that must not split.
//! let csv = std::str::from_utf8(samples[0].csv).unwrap();
//! assert!(csv.contains("\"Anytown, WW\""));
//! ```

#![forbid(unsafe_code)]
#![warn(missing_docs)]

/// One corpus entry: a CSV input and the JSON its parse result should equal.
///
/// `csv` and `json` are raw bytes, stored verbatim. `name` is the shared base
/// name of the pair with no extension, for example `"comma_in_quotes"`.
///
/// [`SAMPLES`] is sorted by unique `name` values. For that table, the derived
/// `Ord` implementation gives the same order because `name` decides every
/// comparison.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Sample {
    /// Base name shared by the CSV and JSON files, extension removed.
    pub name: &'static str,
    /// Raw bytes of the CSV input, unparsed.
    pub csv: &'static [u8],
    /// Raw bytes of the expected JSON parse result, unparsed.
    pub json: &'static [u8],
}

/// Loads a CSV and its matching JSON from `fixtures/` by base name.
macro_rules! sample {
    ($name:literal) => {
        Sample {
            name: $name,
            csv: include_bytes!(concat!("../fixtures/csvs/", $name, ".csv")),
            json: include_bytes!(concat!("../fixtures/json/", $name, ".json")),
        }
    };
}

/// Every sample, in ascending order by name.
///
/// The order is fixed and observable. Downstream code may index by position.
pub const SAMPLES: [Sample; 12] = [
    sample!("comma_in_quotes"),
    sample!("empty"),
    sample!("empty_crlf"),
    sample!("escaped_quotes"),
    sample!("json"),
    sample!("location_coordinates"),
    sample!("newlines"),
    sample!("newlines_crlf"),
    sample!("quotes_and_newlines"),
    sample!("simple"),
    sample!("simple_crlf"),
    sample!("utf8"),
];

/// Returns all 12 samples, in ascending order by name.
///
/// This borrows the static [`SAMPLES`] table. There is no allocation. The
/// slice supports `.len()`, indexing, `.iter()`, and `for s in spectrum()`.
///
/// # Example
///
/// ```
/// let samples = csv_spectrum::spectrum();
/// let names: Vec<_> = samples.iter().map(|s| s.name).collect();
/// assert_eq!(names[1], "empty");
/// ```
pub fn spectrum() -> &'static [Sample] {
    &SAMPLES
}
