shakrs-json-parser 0.1.0

Parser, validator, scaffolder, and canonical formatter for the shakrs.json workspace config. Zero I/O; no policy-registry knowledge.
Documentation
//! Behavioral tests for the `shakrs.json` parser, canonicalizer, and scaffold.

#![expect(
    clippy::unwrap_used,
    reason = "Tests unwrap Results to assert success and shape; a failed unwrap is the test failing."
)]

// The lib's transitive deps are visible to this integration-test crate but
// unused here; silence `unused_crate_dependencies` per the compiler's hint.
use garde as _;
use serde as _;
use serde_json as _;

use shakrs_json_parser::{ConfigParseError, PolicyOverride, canonicalize, parse, scaffold_default};

#[test]
fn parse_valid() {
    let json = br#"{
        "version": 1,
        "policies": {
            "defaultEnabled": false,
            "overrides": {
                "baseline": true,
                "astro": { "enabled": true, "params": { "strict": true } }
            }
        },
        "waivers": [
            { "key": "[workspace.lints.clippy].unwrap_used", "subject": "Cargo.toml", "selector": null, "reason": "legacy" }
        ]
    }"#;
    let config = parse(json).unwrap();
    assert_eq!(config.version, 1, "version parses");
    assert!(
        !config.policies.default_enabled,
        "defaultEnabled camelCase maps onto default_enabled"
    );
    let baseline = config.policies.overrides.get("baseline").unwrap();
    assert!(
        matches!(baseline, &PolicyOverride::Enabled(true)),
        "a bare boolean override deserializes to Enabled"
    );
    let astro = config.policies.overrides.get("astro").unwrap();
    assert!(
        matches!(astro, &PolicyOverride::Detailed { enabled: true, .. }),
        "an object override deserializes to Detailed"
    );
    assert_eq!(config.waivers.len(), 1, "one waiver parses");
    let waiver = config.waivers.first().unwrap();
    assert_eq!(
        waiver.key, "[workspace.lints.clippy].unwrap_used",
        "waiver key parses"
    );
}

/// Parse `json`, expect failure, and assert the error matches `is_kind`.
fn assert_rejected(json: &[u8], is_kind: impl Fn(&ConfigParseError) -> bool, why: &str) {
    let err = parse(json).unwrap_err();
    assert!(is_kind(&err), "{why}: got {err}");
}

#[test]
fn rejects_unknown_field() {
    assert_rejected(
        br#"{ "version": 1, "bogus": true }"#,
        |err| matches!(err, ConfigParseError::Schema(_)),
        "an unknown field is a schema error",
    );
}

#[test]
fn rejects_empty_reason() {
    assert_rejected(
        br#"{ "version": 1, "waivers": [ { "key": "[workspace.lints.clippy].unwrap_used", "subject": "Cargo.toml", "selector": null, "reason": "" } ] }"#,
        |err| matches!(err, ConfigParseError::Semantic(_)),
        "an empty waiver reason fails the semantic pass",
    );
}

#[test]
fn rejects_bad_version() {
    assert_rejected(
        br#"{ "version": 2 }"#,
        |err| matches!(err, ConfigParseError::Semantic(_)),
        "an unsupported version fails the garde range rule",
    );
}

#[test]
fn canonicalize_sorted_and_idempotent() {
    let json = br#"{ "version": 1, "policies": { "defaultEnabled": true, "overrides": {} }, "waivers": [] }"#;
    let config = parse(json).unwrap();
    let bytes = canonicalize(&config).unwrap();
    let text = std::str::from_utf8(&bytes).unwrap();

    let policies_at = text.find("policies").unwrap();
    let version_at = text.find("version").unwrap();
    let waivers_at = text.find("waivers").unwrap();
    assert!(
        policies_at < version_at && version_at < waivers_at,
        "top-level keys are sorted in canonical output:\n{text}"
    );

    let reparsed = parse(&bytes).unwrap();
    let again = canonicalize(&reparsed).unwrap();
    assert_eq!(bytes, again, "canonicalize is idempotent");
}

#[test]
fn scaffold_round_trips() {
    let bytes = scaffold_default().unwrap();
    let config = parse(&bytes).unwrap();
    assert_eq!(config.version, 1, "scaffold is version 1");
    assert!(
        config.policies.default_enabled,
        "scaffold defaults to the opt-out model"
    );
    assert!(config.waivers.is_empty(), "scaffold has no waivers");
}