rc-core 0.1.30

Core library for rustfs-cli S3 CLI client
Documentation
use rc_core::admin::{ClusterSnapshotDocument, DetailedHealthSnapshot, ExtensionsCatalog};

fn valid_health_snapshot() -> serde_json::Value {
    serde_json::json!({
        "version": "1.0.0-beta.10",
        "cpu": {
            "logical_cores": 8,
            "brand": "test-cpu",
            "frequency_mhz": 2400,
            "usage_percent": 12.5
        },
        "memory": {
            "total_bytes": 1024,
            "used_bytes": 512,
            "available_bytes": 512,
            "total_swap_bytes": 0,
            "used_swap_bytes": 0
        },
        "os": {
            "os": "linux",
            "kernel_version": "6.8",
            "os_version": "test",
            "hostname": "node-1",
            "arch": "x86_64",
            "uptime_secs": 60
        },
        "process": {
            "pid": 42,
            "cpu_usage_percent": 1.25,
            "memory_bytes": 128
        },
        "drives": [],
        "unsupported_probes": []
    })
}

#[test]
fn detailed_health_preserves_unsupported_probes_and_future_fields() {
    let value = serde_json::json!({
        "version": "1.0.0-beta.10",
        "deployment_id": "deployment-1",
        "region": "us-east-1",
        "timestamp": "2026-07-22T00:00:00Z",
        "cpu": {
            "logical_cores": 8,
            "brand": "test-cpu",
            "frequency_mhz": 2400,
            "usage_percent": 12.5,
            "future_cpu_field": true
        },
        "memory": {
            "total_bytes": 1024,
            "used_bytes": 512,
            "available_bytes": 512,
            "total_swap_bytes": 0,
            "used_swap_bytes": 0
        },
        "os": {
            "os": "linux",
            "kernel_version": "6.8",
            "os_version": "test",
            "hostname": "node-1",
            "arch": "x86_64",
            "uptime_secs": 60
        },
        "process": {
            "pid": 42,
            "cpu_usage_percent": 1.25,
            "memory_bytes": 128
        },
        "drives": [{
            "endpoint": "node-1",
            "drive_path": "/data1",
            "state": "ok",
            "total_space": 100,
            "used_space": 40,
            "available_space": 60,
            "read_throughput": 10.5,
            "write_throughput": 8.5,
            "read_latency": 0.1,
            "write_latency": 0.2,
            "future_drive_field": "kept"
        }],
        "unsupported_probes": ["perf-net", "config-obd"],
        "future_top_level": {"kept": true}
    });

    let snapshot: DetailedHealthSnapshot =
        serde_json::from_value(value).expect("health fixture should deserialize");

    assert_eq!(snapshot.unsupported_probes, ["perf-net", "config-obd"]);
    assert_eq!(snapshot.cpu.extra["future_cpu_field"], true);
    assert_eq!(snapshot.drives[0].extra["future_drive_field"], "kept");
    assert_eq!(snapshot.extra["future_top_level"]["kept"], true);
}

#[test]
fn detailed_health_rejects_missing_envelope_and_measurement_fields() {
    let mut fixtures = vec![
        ("empty envelope", serde_json::json!({})),
        ("empty cpu", valid_health_snapshot()),
        ("empty memory", valid_health_snapshot()),
        ("empty os", valid_health_snapshot()),
        ("empty process", valid_health_snapshot()),
        ("missing drives", valid_health_snapshot()),
        ("missing unsupported probes", valid_health_snapshot()),
    ];
    fixtures[1].1["cpu"] = serde_json::json!({});
    fixtures[2].1["memory"] = serde_json::json!({});
    fixtures[3].1["os"] = serde_json::json!({});
    fixtures[4].1["process"] = serde_json::json!({});
    fixtures[5]
        .1
        .as_object_mut()
        .expect("fixture must be an object")
        .remove("drives");
    fixtures[6]
        .1
        .as_object_mut()
        .expect("fixture must be an object")
        .remove("unsupported_probes");

    for (name, fixture) in fixtures {
        assert!(
            serde_json::from_value::<DetailedHealthSnapshot>(fixture).is_err(),
            "{name} must not deserialize as a valid health snapshot"
        );
    }
}

#[test]
fn cluster_snapshot_accepts_beta10_current_main_and_null_shapes() {
    let beta10 = serde_json::json!({
        "snapshot": {
            "summary": {
                "runtime": {"state": "supported", "future_status_field": 9},
                "topology": {"state": "supported"},
                "membership": {"state": "supported"},
                "peer_health": {"state": "supported"},
                "rpc_boundary": {"state": "supported"},
                "observability": {"state": "supported"},
                "workload_admission": {"state": "supported"},
                "actionable_pressure": {"state": "disabled"}
            },
            "runtime_capabilities_path": "/rustfs/admin/v4/runtime/capabilities",
            "extensions_catalog_path": "/rustfs/admin/v4/extensions/catalog",
            "topology": {"mode": "distributed"},
            "membership": {"nodes": [], "drives": []},
            "pool_state": {"pools": []},
            "local_storage": {"nodes": []},
            "peer_health": {"peers": []},
            "rpc_boundary": {"control_channels": [], "data_channels": []},
            "observability": {},
            "workload_admission": [],
            "runtime_status": {"state": "ready"},
            "actionable_pressure": false,
            "future_snapshot_field": 7
        },
        "future_envelope_field": true
    });
    let current = serde_json::json!({
        "snapshot": {
            "summary": {
                "runtime": {"state": "supported"},
                "topology": {"state": "supported"},
                "membership": {"state": "supported"},
                "storage": {"state": "supported"},
                "peer_health": {"state": "supported"},
                "listing": {"state": "supported"},
                "usage": {"state": "unknown", "reason": "scanner data is stale"},
                "rpc_boundary": {"state": "supported"},
                "observability": {"state": "supported"},
                "workload_admission": {"state": "supported"},
                "actionable_pressure": {"state": "disabled"}
            },
            "runtime_capabilities_path": "/rustfs/admin/v4/runtime/capabilities",
            "extensions_catalog_path": "/rustfs/admin/v4/extensions/catalog",
            "components": {
                "storage": {"source": "runtime_readiness", "condition": "healthy", "status": {"state": "supported"}},
                "peer_health": {"source": "cluster_peer_health", "condition": "healthy", "status": {"state": "supported"}},
                "listing": {
                    "source": "workload_admission+internode_metrics",
                    "condition": "healthy",
                    "status": {"state": "supported"},
                    "internode_stall_timeouts_total": 2,
                    "hint": "inspect metrics"
                },
                "usage": {
                    "source": "scanner_metrics",
                    "condition": "stale",
                    "status": {"state": "unknown"},
                    "dirty_pending_buckets": 3,
                    "last_usage_save_unix_secs": 123,
                    "last_usage_save_result": "skipped_stale"
                },
                "workload_admission": {"source": "workload_admission", "condition": "healthy", "status": {"state": "supported"}}
            },
            "topology": {}, "membership": {}, "pool_state": {}, "local_storage": {},
            "peer_health": {}, "rpc_boundary": {}, "observability": {},
            "workload_admission": [], "runtime_status": {}, "actionable_pressure": false
        }
    });

    let beta10: ClusterSnapshotDocument =
        serde_json::from_value(beta10).expect("beta.10 snapshot should deserialize");
    let current: ClusterSnapshotDocument =
        serde_json::from_value(current).expect("current snapshot should deserialize");
    let unavailable: ClusterSnapshotDocument =
        serde_json::from_value(serde_json::json!({"snapshot": null}))
            .expect("null snapshot should deserialize");

    let beta10 = beta10.snapshot.expect("beta.10 snapshot should exist");
    assert!(beta10.components.is_none());
    assert_eq!(beta10.summary.runtime.extra["future_status_field"], 9);
    assert_eq!(beta10.extra["future_snapshot_field"], 7);
    let current = current.snapshot.expect("current snapshot should exist");
    assert_eq!(
        current
            .components
            .expect("current components should exist")
            .usage
            .expect("usage component should exist")
            .condition,
        "stale"
    );
    assert!(unavailable.snapshot.is_none());
}

#[test]
fn cluster_snapshot_requires_the_snapshot_envelope_field() {
    assert!(serde_json::from_value::<ClusterSnapshotDocument>(serde_json::json!({})).is_err());
    let unavailable = serde_json::from_value::<ClusterSnapshotDocument>(serde_json::json!({
        "snapshot": null
    }))
    .expect("an explicit null snapshot must remain valid");
    assert!(unavailable.snapshot.is_none());
}

#[test]
fn extension_catalog_preserves_schema_and_runtime_future_fields() {
    let value = serde_json::json!({
        "extensions": [{
            "schema_version": "rustfs.extension-schema.v1",
            "extension_id": "builtin:ops-diagnostics",
            "display_name": "Operations Diagnostics",
            "provider": "rustfs",
            "version": "1",
            "kind": "ops_diagnostics",
            "runtime": {"api_version": "v1", "boundary": "builtin", "future_runtime": true},
            "capabilities": ["ops.diagnostics.v1"],
            "disabled_by_default": false,
            "future_schema": "kept"
        }],
        "runtime_capabilities": {"ops_diagnostics": {"runtime_capability_summary": {"state": "supported"}}},
        "cluster_snapshot": {"path": "/rustfs/admin/v4/cluster/snapshot"},
        "external_plugin_flow": {"enabled": false},
        "future_catalog": 11
    });

    let catalog: ExtensionsCatalog =
        serde_json::from_value(value).expect("catalog should deserialize");

    assert_eq!(catalog.extensions[0].capabilities, ["ops.diagnostics.v1"]);
    assert_eq!(catalog.extensions[0].extra["future_schema"], "kept");
    assert_eq!(catalog.extra["future_catalog"], 11);
}

#[test]
fn extension_catalog_requires_object_valued_contexts() {
    assert!(
        serde_json::from_value::<ExtensionsCatalog>(serde_json::json!({"extensions": []})).is_err()
    );

    for field in [
        "runtime_capabilities",
        "cluster_snapshot",
        "external_plugin_flow",
    ] {
        for invalid in [serde_json::Value::Null, serde_json::json!([])] {
            let mut fixture = serde_json::json!({
                "extensions": [],
                "runtime_capabilities": {},
                "cluster_snapshot": {},
                "external_plugin_flow": {}
            });
            fixture[field] = invalid;
            assert!(
                serde_json::from_value::<ExtensionsCatalog>(fixture).is_err(),
                "{field} must be a JSON object"
            );
        }
    }
}