euroscope 0.0.1

Safe, idiomatic Rust interface for writing EuroScope plugins
//! Small shared helpers for converting between C strings and Rust strings at
//! the FFI boundary.

// `unreachable_pub` (stable, enabled) wants `pub(crate)` on these free functions
// in this private module, but `redundant_pub_crate` (nursery) then flags that as
// redundant. The two lints are in direct conflict here; defer to the stable one.
#![allow(clippy::redundant_pub_crate)]

use std::ffi::{CStr, CString, c_char};

/// Borrow a C string as `&str`. ATC data is ASCII, so we fall back to `""` on
/// the rare non-UTF-8 byte or null. The result borrows `'a`.
///
/// # Safety
/// `ptr` must be null or a valid NUL-terminated string that outlives `'a`.
pub(crate) unsafe fn cstr<'a>(ptr: *const c_char) -> &'a str {
    if ptr.is_null() {
        return "";
    }
    unsafe { CStr::from_ptr(ptr) }.to_str().unwrap_or("")
}

/// Build a `CString` from `&str`, dropping any interior NUL bytes rather than
/// failing.
///
/// `CString::new` errors on an interior NUL (it would truncate the C string).
/// EuroScope's data is NUL-free in practice, so instead of surfacing a `Result`
/// through every setter we strip the offending bytes and retry — the second
/// construction cannot fail because the input is now guaranteed NUL-free.
pub(crate) fn cstr_lossy(s: &str) -> CString {
    CString::new(s)
        .unwrap_or_else(|_| CString::new(s.replace('\0', "")).expect("no interior NUL after strip"))
}