//! Shared helpers for the conformance tests.
//!
//! The loader turns `serde_json::Value` into the crate's [`Value`]. Map and Set
//! values cannot be written as plain JSON, so the loader recognizes tagged
//! objects: `{"$$map": [[k, v], ...]}` becomes a `Map` and `{"$$set": [..]}`
//! becomes a `Set`. Everything else maps the obvious way.

// Each test binary that includes this module uses a different subset of it.
#![allow(dead_code)]

use immutability_helper::Value;
use serde_json::Value as Json;

/// Convert a JSON value into a crate [`Value`].
///
/// Numbers become `Number`, strings become `String`, arrays become `Array`, and
/// objects become `Object` unless tagged as a Map or Set.
pub fn to_value(json: &Json) -> Value {
    match json {
        Json::Null => Value::Null,
        Json::Bool(b) => Value::Bool(*b),
        Json::Number(n) => Value::Number(n.as_f64().expect("finite number")),
        Json::String(s) => Value::from(s.as_str()),
        Json::Array(items) => Value::array(items.iter().map(to_value).collect()),
        Json::Object(map) => {
            if let Some(Json::Array(pairs)) = map.get("$$map") {
                let entries = pairs.iter().map(|pair| {
                    let arr = pair.as_array().expect("map entry is a pair");
                    (to_value(&arr[0]), to_value(&arr[1]))
                });
                return Value::map(entries);
            }
            if let Some(Json::Array(items)) = map.get("$$set") {
                return Value::set(items.iter().map(to_value));
            }
            if let Some(Json::String(s)) = map.get("$$undefined") {
                if s == "undefined" {
                    return Value::Undefined;
                }
            }
            Value::object(map.iter().map(|(k, v)| (k.clone(), to_value(v))))
        }
    }
}

/// A single conformance row.
#[derive(serde::Deserialize)]
pub struct Case {
    /// The case name, for failure messages.
    pub name: String,
    /// The target value.
    pub input: Json,
    /// The raw spec value.
    pub spec: Json,
    /// The expected result, when the case should succeed.
    #[serde(default)]
    pub expect: Option<Json>,
    /// The expected error message, when the case should fail.
    #[serde(default)]
    pub error: Option<String>,
}

/// Parse a JSON array of cases.
pub fn load_cases(text: &str) -> Vec<Case> {
    serde_json::from_str(text).expect("fixtures parse as a case array")
}
