greentic-interfaces 0.4.113

Greentic ABI: WIT contracts, generated bindings, thin mappers
Documentation
use std::collections::BTreeSet;
use std::path::Path;

use wit_parser::Resolve;

fn collect_package_files(root: &Path, out: &mut Vec<std::path::PathBuf>) {
    let entries = std::fs::read_dir(root)
        .unwrap_or_else(|_| panic!("missing WIT root at {}", root.display()));
    for entry in entries {
        let entry = entry.expect("read wit entry");
        let path = entry.path();
        if path.is_dir() {
            collect_package_files(&path, out);
            continue;
        }
        if path.file_name().and_then(|n| n.to_str()) == Some("package.wit") {
            out.push(path);
        }
    }
}

#[test]
fn staged_wit_packages_are_valid() {
    let staged_root = Path::new(env!("WIT_STAGING_DIR"));
    let entries = std::fs::read_dir(staged_root)
        .unwrap_or_else(|_| panic!("missing staged WIT packages in {}", staged_root.display()));

    for entry in entries {
        let entry = entry.expect("read staged entry");
        if !entry.path().is_dir() {
            continue;
        }
        let mut resolve = Resolve::new();
        resolve
            .push_dir(entry.path())
            .unwrap_or_else(|err| panic!("failed to parse {}: {err}", entry.path().display()));
    }
}

#[test]
fn crate_local_wit_interfaces_do_not_redefine_imported_types() {
    let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
    let wit_root = manifest_dir.join("wit");
    let mut package_files = Vec::new();
    collect_package_files(&wit_root, &mut package_files);
    package_files.sort();
    assert!(
        !package_files.is_empty(),
        "no package.wit files discovered under {}",
        wit_root.display()
    );

    for package_file in package_files {
        let contents = std::fs::read_to_string(&package_file)
            .unwrap_or_else(|_| panic!("failed to read {}", package_file.display()));
        let mut in_interface = false;
        let mut depth: i32 = 0;
        let mut imported = BTreeSet::new();
        let mut local_defs = BTreeSet::new();
        let mut interface_name = String::new();

        for raw_line in contents.lines() {
            let line = raw_line.trim();
            if !in_interface {
                if let Some(rest) = line.strip_prefix("interface ") {
                    interface_name = rest
                        .split_whitespace()
                        .next()
                        .unwrap_or("<unknown>")
                        .trim_end_matches('{')
                        .to_string();
                    in_interface = true;
                    depth += line.matches('{').count() as i32;
                    depth -= line.matches('}').count() as i32;
                    imported.clear();
                    local_defs.clear();
                }
                continue;
            }

            if let Some(rest) = line.strip_prefix("use ")
                && let Some(start) = rest.find('{')
                && let Some(end) = rest[start + 1..].find('}')
            {
                for name in rest[start + 1..start + 1 + end].split(',') {
                    let ty = name.trim();
                    if !ty.is_empty() {
                        imported.insert(ty.to_string());
                    }
                }
            }

            for keyword in ["record", "enum", "variant", "flags", "resource", "type"] {
                if let Some(rest) = line.strip_prefix(&format!("{keyword} ")) {
                    let name = rest
                        .split(|c: char| c.is_whitespace() || c == '{' || c == '=')
                        .next()
                        .unwrap_or("")
                        .trim();
                    if !name.is_empty() {
                        local_defs.insert(name.to_string());
                    }
                }
            }

            depth += line.matches('{').count() as i32;
            depth -= line.matches('}').count() as i32;
            if depth <= 0 {
                let overlap: Vec<String> = imported.intersection(&local_defs).cloned().collect();
                assert!(
                    overlap.is_empty(),
                    "{} interface `{}` redefines imported types: {}",
                    package_file.display(),
                    interface_name,
                    overlap.join(", ")
                );
                in_interface = false;
                depth = 0;
            }
        }
    }
}

#[test]
fn oauth_broker_worlds_include_client() {
    use std::collections::BTreeSet;

    let staged_root = Path::new(env!("WIT_STAGING_DIR"));
    let package_dir = staged_root.join("greentic-oauth-broker-1.0.0");

    assert!(
        package_dir.exists(),
        "staged oauth-broker package missing at {}",
        package_dir.display()
    );

    let mut resolve = Resolve::new();
    let (pkg, _) = resolve
        .push_dir(&package_dir)
        .unwrap_or_else(|err| panic!("failed to parse {}: {err}", package_dir.display()));

    let worlds: BTreeSet<String> = resolve.packages[pkg]
        .worlds
        .keys()
        .map(|name| name.to_string())
        .collect();

    assert!(
        worlds.contains("broker"),
        "expected existing broker world to remain"
    );
    assert!(
        worlds.contains("broker-client"),
        "expected additive broker-client world to be staged"
    );
}

#[test]
fn component_v0_v6_exports_node_interface() {
    use std::collections::BTreeSet;
    use wit_parser::WorldKey;

    let staged_root = Path::new(env!("WIT_STAGING_DIR"));
    let package_dir = staged_root.join("greentic-component-0.6.0");

    assert!(
        package_dir.exists(),
        "staged component package missing at {}",
        package_dir.display()
    );

    let mut resolve = Resolve::new();
    let (pkg, _) = resolve
        .push_dir(&package_dir)
        .unwrap_or_else(|err| panic!("failed to parse {}: {err}", package_dir.display()));

    let world_id = resolve.packages[pkg]
        .worlds
        .get("component")
        .copied()
        .expect("missing component world");

    let world = &resolve.worlds[world_id];
    let export_names: BTreeSet<String> = world
        .exports
        .keys()
        .filter_map(|key| match key {
            WorldKey::Name(name) => Some(name.clone()),
            WorldKey::Interface(id) => resolve.interfaces[*id].name.clone(),
        })
        .collect();
    let import_names: BTreeSet<String> = world
        .imports
        .keys()
        .filter_map(|key| match key {
            WorldKey::Name(name) => Some(name.clone()),
            WorldKey::Interface(id) => resolve.interfaces[*id].name.clone(),
        })
        .collect();

    assert!(
        export_names.contains("node"),
        "expected component world to export node"
    );
    assert!(
        import_names.contains("control"),
        "expected component world to import control"
    );
}

#[test]
fn no_legacy_component_v0_v6_wit_mirrors_exist() {
    let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
    let canonical = manifest_dir.join("wit/greentic/component@0.6.0/package.wit");
    let guest =
        manifest_dir.join("../greentic-interfaces-guest/wit/greentic/component@0.6.0/package.wit");
    let wasmtime = manifest_dir
        .join("../greentic-interfaces-wasmtime/wit/greentic/component@0.6.0/package.wit");

    assert!(
        canonical.exists(),
        "canonical component@0.6.0 package missing at {}",
        canonical.display()
    );

    let canonical_contents = std::fs::read_to_string(&canonical)
        .unwrap_or_else(|_| panic!("failed to read {}", canonical.display()));

    if guest.exists() {
        let guest_contents = std::fs::read_to_string(&guest)
            .unwrap_or_else(|_| panic!("failed to read {}", guest.display()));
        assert_eq!(
            guest_contents,
            canonical_contents,
            "guest-local component@0.6.0 package must match canonical WIT at {}",
            guest.display()
        );
        assert!(
            !guest_contents.contains("component-v0-v6-v0"),
            "guest-local component@0.6.0 package must not contain legacy alias markers at {}",
            guest.display()
        );
    }

    if wasmtime.exists() {
        let wasmtime_contents = std::fs::read_to_string(&wasmtime)
            .unwrap_or_else(|_| panic!("failed to read {}", wasmtime.display()));
        assert_eq!(
            wasmtime_contents,
            canonical_contents,
            "wasmtime-local component@0.6.0 package must match canonical WIT at {}",
            wasmtime.display()
        );
        assert!(
            !wasmtime_contents.contains("component-v0-v6-v0"),
            "wasmtime-local component@0.6.0 package must not contain legacy alias markers at {}",
            wasmtime.display()
        );
    }

    assert!(
        !wasmtime.exists(),
        "legacy wasmtime mirror should not exist at {}",
        wasmtime.display()
    );
}

#[test]
fn wit_all_uses_packaged_wit_paths() {
    let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
    let source = manifest_dir.join("src/wit_all.rs");
    let contents = std::fs::read_to_string(&source)
        .unwrap_or_else(|_| panic!("failed to read {}", source.display()));

    assert!(
        !contents.contains("target/wit-staging"),
        "src/wit_all.rs must not reference build-local staged WIT paths"
    );
}