vesper-player-cli 0.5.4

The vesper command-line tool for authoring, validating, and installing plugins.
Documentation
#![cfg(vesper_source_checkout)]

use std::collections::BTreeMap;
use std::fs;
use std::path::Path;

use serde::Deserialize;
use syn::{Expr, ExprLit, Item, Lit};

#[derive(Debug, Deserialize)]
struct BridgeManifest {
    private_declarations: Vec<BridgeDeclaration>,
}

#[derive(Debug, Deserialize)]
#[serde(tag = "kind")]
enum BridgeDeclaration {
    #[serde(rename = "enum")]
    Enum {
        name: String,
        variants: Vec<BridgeVariant>,
    },
    #[serde(other)]
    Other,
}

#[derive(Debug, Deserialize)]
struct BridgeVariant {
    name: String,
    value: i64,
}

#[test]
fn ios_bridge_manifest_enums_match_rust_ffi_values() {
    let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../..");
    let manifest_path = root.join("scripts/ios/bridge-shim/manifest.json");
    let rust_types_path = root.join("crates/ffi/player-ffi-ios/src/types.rs");

    let manifest: BridgeManifest = serde_json::from_str(
        &fs::read_to_string(&manifest_path).expect("read iOS bridge manifest"),
    )
    .expect("parse iOS bridge manifest");
    let syntax =
        syn::parse_file(&fs::read_to_string(&rust_types_path).expect("read Rust FFI types"))
            .expect("parse Rust iOS FFI types");

    let rust_enums = syntax
        .items
        .iter()
        .filter_map(|item| match item {
            Item::Enum(item) if item.ident.to_string().starts_with("PlayerFfi") => Some((
                item.ident.to_string(),
                enum_variants(&item.ident.to_string(), item),
            )),
            _ => None,
        })
        .collect::<BTreeMap<_, _>>();

    for declaration in manifest.private_declarations {
        let BridgeDeclaration::Enum { name, variants } = declaration else {
            continue;
        };
        let rust_variants = rust_enums
            .get(&name)
            .unwrap_or_else(|| panic!("bridge enum {name} is missing from Rust FFI types"));
        let manifest_variants = variants
            .into_iter()
            .map(|variant| (variant.name, variant.value))
            .collect::<BTreeMap<_, _>>();
        assert_eq!(
            &manifest_variants, rust_variants,
            "bridge enum {name} differs from Rust FFI enum"
        );
    }
}

fn enum_variants(enum_name: &str, item: &syn::ItemEnum) -> BTreeMap<String, i64> {
    item.variants
        .iter()
        .map(|variant| {
            let value = variant
                .discriminant
                .as_ref()
                .and_then(|(_, expression)| match expression {
                    Expr::Lit(ExprLit {
                        lit: Lit::Int(value),
                        ..
                    }) => value.base10_parse().ok(),
                    _ => None,
                })
                .unwrap_or_else(|| {
                    panic!(
                        "Rust FFI enum variant {} lacks an integer value",
                        variant.ident
                    )
                });
            (format!("{enum_name}{}", variant.ident), value)
        })
        .collect()
}