statum-macros 0.6.8

Proc macros for representing legal workflow and protocol states explicitly in Rust
Documentation
use std::sync::{OnceLock, RwLock};

use super::{MachineInfo, MachinePath};
use crate::{current_crate_root, source_file_fingerprint};

static LOADED_MACHINES: OnceLock<RwLock<Vec<MachineInfo>>> = OnceLock::new();

#[derive(Clone)]
pub enum LoadedMachineLookupFailure {
    NotFound,
    Ambiguous(Vec<MachineInfo>),
}

fn loaded_machines() -> &'static RwLock<Vec<MachineInfo>> {
    LOADED_MACHINES.get_or_init(|| RwLock::new(Vec::new()))
}

fn same_loaded_machine(left: &MachineInfo, right: &MachineInfo) -> bool {
    left.name == right.name
        && left.module_path.as_ref() == right.module_path.as_ref()
        && left.file_path == right.file_path
        && left.line_number == right.line_number
}

fn upsert_loaded_machine(machine_info: &MachineInfo) {
    let Ok(mut machines) = loaded_machines().write() else {
        return;
    };

    if let Some(existing) = machines
        .iter_mut()
        .find(|existing| same_loaded_machine(existing, machine_info))
    {
        *existing = machine_info.clone();
    } else {
        machines.push(machine_info.clone());
    }
}

fn loaded_machine_candidates_matching<F>(matches: F) -> Vec<MachineInfo>
where
    F: Fn(&MachineInfo) -> bool,
{
    let current_crate_root = current_crate_root();
    let Ok(machines) = loaded_machines().read() else {
        return Vec::new();
    };

    machines
        .iter()
        .filter(|machine| loaded_machine_is_current(machine, current_crate_root.as_deref()))
        .filter(|machine| matches(machine))
        .cloned()
        .collect()
}

fn loaded_machine_is_current(machine: &MachineInfo, current_crate_root: Option<&str>) -> bool {
    if current_crate_root.is_some() && machine.crate_root.as_deref() != current_crate_root {
        return false;
    }

    match (machine.file_path.as_deref(), machine.file_fingerprint.as_ref()) {
        (Some(file_path), Some(fingerprint)) => {
            source_file_fingerprint(file_path).as_ref() == Some(fingerprint)
        }
        _ => true,
    }
}

fn lookup_loaded_machine_candidates(
    candidates: Vec<MachineInfo>,
) -> Result<MachineInfo, LoadedMachineLookupFailure> {
    match candidates.len() {
        0 => Err(LoadedMachineLookupFailure::NotFound),
        1 => Ok(candidates.into_iter().next().expect("single candidate")),
        _ => Err(LoadedMachineLookupFailure::Ambiguous(candidates)),
    }
}

pub fn lookup_loaded_machine_in_module(
    machine_path: &MachinePath,
    machine_name: &str,
) -> Result<MachineInfo, LoadedMachineLookupFailure> {
    lookup_loaded_machine_candidates(loaded_machine_candidates_matching(|machine| {
        machine.module_path.as_ref() == machine_path.as_ref() && machine.name == machine_name
    }))
}

pub fn lookup_unique_loaded_machine_by_name(
    machine_name: &str,
) -> Result<MachineInfo, LoadedMachineLookupFailure> {
    lookup_loaded_machine_candidates(loaded_machine_candidates_matching(|machine| {
        machine.name == machine_name
    }))
}

pub fn format_loaded_machine_candidates(candidates: &[MachineInfo]) -> String {
    candidates
        .iter()
        .map(|candidate| {
            let file_path = candidate.file_path.as_deref().unwrap_or("<unknown file>");
            format!(
                "`{}` in `{}` ({file_path}:{})",
                candidate.name, candidate.module_path, candidate.line_number
            )
        })
        .collect::<Vec<_>>()
        .join(", ")
}

pub fn store_machine_struct(machine_info: &MachineInfo) {
    upsert_loaded_machine(machine_info);
}