sheetport-spec 0.3.1

SheetPort spec: schema, validation, and fio-lint CLI for portable spreadsheet IO
Documentation
use serde_json::json;
use sheetport_spec::{Manifest, Schema, load_manifest_from_str, manifest_to_yaml};

#[test]
fn load_helpers_match_methods() {
    let yaml = r#"spec: fio
spec_version: "0.3.0"
manifest: { id: util-test, name: Util Test }
ports: []
"#;
    let manifest_method = Manifest::from_yaml_str(yaml).unwrap();
    let manifest_fn = load_manifest_from_str(yaml).unwrap();
    assert_eq!(
        manifest_method.to_yaml().unwrap(),
        manifest_fn.to_yaml().unwrap()
    );
    assert_eq!(
        manifest_to_yaml(&manifest_method).unwrap(),
        manifest_method.to_yaml().unwrap()
    );
}

#[test]
fn normalize_orders_ports_and_tags() {
    let yaml = r#"spec: fio
spec_version: "0.3.0"
manifest:
  id: normalize-test
  name: Normalize Test
  tags: ["supply", "finance", "supply"]
ports:
  - id: beta_port
    dir: in
    shape: table
    location:
      layout: { sheet: Inv, header_row: 1, anchor_col: A, terminate: first_blank_row }
    schema:
      kind: table
      columns:
        - { name: sku, type: string }
        - { name: warehouse, type: string }
      keys: ["warehouse", "sku", "sku"]
  - id: alpha_port
    dir: in
    shape: record
    location:
      a1: Inputs!A1:B1
    constraints:
      enum: ["Z", "A", "A"]
    schema:
      kind: record
      fields:
        month:
          type: integer
          location: { a1: Inputs!A1 }
          constraints:
            enum: [3, 1, 1, 2]
        region:
          type: string
          location: { a1: Inputs!B1 }
"#;

    let mut manifest = Manifest::from_yaml_str(yaml).unwrap();
    manifest.normalize();

    let tags = manifest.manifest.tags.unwrap();
    assert_eq!(tags, vec!["finance", "supply"]);

    let ids: Vec<_> = manifest.ports.iter().map(|p| p.id.as_str()).collect();
    assert_eq!(ids, vec!["alpha_port", "beta_port"]);

    if let Some(constraints) = &manifest.ports[0].constraints {
        assert_eq!(
            constraints.r#enum.as_ref().unwrap(),
            &vec![json!("A"), json!("Z")]
        );
    } else {
        panic!("expected constraints on alpha_port");
    }

    if let Schema::Record(record) = &manifest.ports[0].schema {
        let month = record.fields.get("month").unwrap();
        assert_eq!(
            month.constraints.as_ref().unwrap().r#enum.as_ref().unwrap(),
            &vec![json!(1), json!(2), json!(3)]
        );
    } else {
        panic!("alpha_port should be record");
    }

    if let Schema::Table(table) = &manifest.ports[1].schema {
        assert_eq!(
            table.keys.as_ref().unwrap(),
            &vec!["sku".to_string(), "warehouse".to_string()]
        );
    } else {
        panic!("beta_port should be table");
    }
}

#[test]
fn normalized_produces_sorted_copy() {
    let yaml = r#"spec: fio
spec_version: "0.3.0"
manifest: { id: norm-copy, name: Norm Copy }
ports:
  - id: b
    dir: out
    shape: scalar
    location: { a1: Outputs!B2 }
    schema: { type: string }
  - id: a
    dir: in
    shape: scalar
    location: { a1: Inputs!B2 }
    schema: { type: string }
"#;
    let manifest = Manifest::from_yaml_str(yaml).unwrap();
    let mut clone = manifest.clone();
    clone.normalize();
    let normalized = manifest.normalized();
    assert_eq!(clone.to_yaml().unwrap(), normalized.to_yaml().unwrap());
}

#[test]
fn crate_version_tracks_protocol_line_without_lagging_it() {
    let protocol = semver::Version::parse(sheetport_spec::CURRENT_SPEC_VERSION).unwrap();
    let package = semver::Version::parse(sheetport_spec::CRATE_VERSION).unwrap();

    assert_eq!(
        (package.major, package.minor),
        (protocol.major, protocol.minor)
    );
    assert!(
        package >= protocol,
        "crate patch releases may advance validator behavior but must not lag the protocol"
    );
}