interoptopus 0.16.0-alpha.11

The polyglot bindings generator for your library (C#, C, Python, ...). 🐙
Documentation
//! Ensures bindings match the used DLL, used via [`api_guard!`](crate::api_guard).
//!
//! Using an API guard is as simple as defining and exporting a function `my_api_guard` returning an
//! [`ApiVersion`] as in the example below. Backends supporting API guards will automatically
//! generate guard code executed when the library is loaded.
//!
//! When developing we **highly recommend** adding API guards, as mismatching bindings are the #1
//! cause of "inexplicable" errors (undefined behavior) that often take hours to hunt down.
//!
//! # Example
//!
//! This will create an API guard function, and backends might automatically
//! create guards calling this function when loading the DLL.
//!
//! ```rust
//! use interoptopus::inventory::RustInventory;
//! use interoptopus::{api_guard, function};
//!
//! fn ffi_inventory() -> RustInventory {
//!     RustInventory::new()
//!         .register(api_guard!(ffi_inventory)) // <- You must name the current function.
//!         .validate()                          //    since it will be called at runtime
//!                                              //    but cannot be inferred.
//! }
//! ```
//! In backends that support API guards an error message like this might be emitted if you try load
//! a library with mismatching bindings:
//!
//! ```csharp
//! Exception: API reports hash X which differs from hash in bindings (Y). You probably forgot to update / copy either the bindings or the library.
//! ```
//!
//!
//! # Hash Value
//!
//! The hash value
//!
//! - is based on the signatures of the involved functions, types and constants,
//! - is expected to change when the API changes, e.g., functions, types, fields, ... are added
//!   changed or removed,
//! - will even react to benign API changes (e.g., just adding functions),
//! - might even react to documentation changes (subject to change; feedback welcome).
//!
use crate::inventory::RustInventory;
use crate::inventory::TypeId;
use crate::lang::meta::{Docs, Emission, FileEmission, Visibility};
use crate::lang::types::{TypeInfo, TypeKind, TypePattern, WireIO};
use crate::wire::SerializationError;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::io::{Read, Write};

/// Holds the API version hash of the given library.
#[repr(transparent)]
#[allow(dead_code)]
#[derive(Debug, Default, PartialOrd, PartialEq, Eq, Copy, Clone)]
pub struct ApiVersion {
    version: u64,
}

impl ApiVersion {
    /// Create a new API version from the given hash.
    #[must_use]
    pub const fn new(version: u64) -> Self {
        Self { version }
    }

    /// Create a new API version from the given library.
    #[must_use]
    pub fn from_inventory(inventory: &RustInventory) -> Self {
        let version = ApiHash::from(inventory).hash;
        Self { version }
    }
}

unsafe impl TypeInfo for ApiVersion {
    const WIRE_SAFE: bool = true;
    const RAW_SAFE: bool = true;
    const ASYNC_SAFE: bool = true;
    const SERVICE_SAFE: bool = false;
    const SERVICE_CTOR_SAFE: bool = false;

    fn id() -> TypeId {
        TypeId::new(0xA6B162106C410FCAD91327A85E3FE14E)
    }

    fn kind() -> TypeKind {
        TypeKind::TypePattern(TypePattern::APIVersion)
    }

    fn ty() -> crate::lang::types::Type {
        crate::lang::types::Type {
            emission: Emission::FileEmission(FileEmission::Common),
            docs: Docs::empty(),
            visibility: Visibility::Public,
            name: "ApiVersion".to_string(),
            kind: Self::kind(),
        }
    }

    fn register(inventory: &mut impl crate::inventory::Inventory) {
        inventory.register_type(Self::id(), Self::ty());
    }
}

unsafe impl WireIO for ApiVersion {
    fn write(&self, w: &mut impl Write) -> Result<(), SerializationError> {
        <u64 as WireIO>::write(&self.version, w)
    }

    fn read(r: &mut impl Read) -> Result<Self, SerializationError> {
        Ok(Self { version: u64::read(r)? })
    }

    fn live_size(&self) -> usize {
        self.version.live_size()
    }
}

impl From<RustInventory> for ApiVersion {
    fn from(i: RustInventory) -> Self {
        Self::from_inventory(&i)
    }
}

/// Represents the API hash.
pub struct ApiHash {
    pub hash: u64,
    pub hash_hex: String,
}

impl ApiHash {
    /// Returns a unique hash for an inventory; used by backends.
    #[must_use]
    pub fn from(inventory: &RustInventory) -> Self {
        let mut hasher = DefaultHasher::new();

        let mut types: Vec<_> = inventory.types.iter().collect();
        let mut functions: Vec<_> = inventory.functions.iter().collect();
        let mut constants: Vec<_> = inventory.constants.iter().collect();

        types.sort_by_key(|(id, _)| *id);
        functions.sort_by_key(|(id, _)| *id);
        constants.sort_by_key(|(id, _)| *id);

        for t in types {
            t.hash(&mut hasher);
        }

        for f in functions {
            f.hash(&mut hasher);
        }

        for c in constants {
            c.1.name.hash(&mut hasher);
            c.1.value.hash(&mut hasher);
        }

        Self::new(hasher.finish())
    }

    /// Creates a new hash from the given raw hash value.
    #[must_use]
    pub fn new(hash: u64) -> Self {
        let hash_hex = format!("{hash:x}");
        Self { hash, hash_hex }
    }

    #[must_use]
    pub const fn hash(&self) -> u64 {
        self.hash
    }

    #[must_use]
    pub fn hash_hex(&self) -> &str {
        self.hash_hex.as_str()
    }
}

/// Creates and registers an [API guard](crate::pattern::api_guard) for the current library.
///
/// # Example
/// ```rust
/// # use interoptopus::inventory::RustInventory;
/// # use interoptopus::{api_guard, function};
///
/// fn ffi_inventory() -> RustInventory {
///     RustInventory::new()
///         .register(api_guard!(ffi_inventory)) // <- You must name the current function.
///         .validate()                          
/// }
/// ```
#[macro_export]
macro_rules! api_guard {
    ($f:tt) => {{
        #[$crate::ffi]
        pub fn __api_guard() -> $crate::pattern::api_guard::ApiVersion {
            $f().into()
        }

        |x: &mut $crate::inventory::RustInventory| {
            <__api_guard as $crate::lang::function::FunctionInfo>::register(x);
        }
    }};
}