#[cfg(test)]
use std::ffi::CStr;
use std::ffi::{c_char, c_double, c_int};
use std::sync::OnceLock;
use xalen_ayanamsa::Ayanamsa;
use xalen_coords::obliquity::mean_obliquity;
use xalen_ephem::{Almanac, Body};
use xalen_houses::{GeoLocation, HouseSystem, compute_houses};
use xalen_time::{JdUT1, JulianDay};
static ALMANAC: OnceLock<Almanac> = OnceLock::new();
fn get_almanac() -> &'static Almanac {
ALMANAC.get_or_init(Almanac::default_vedic)
}
#[repr(C)]
pub struct XalenPosition {
pub longitude_deg: c_double,
pub latitude_deg: c_double,
pub distance_au: c_double,
pub status: c_int,
}
#[repr(C)]
pub struct XalenPositionFull {
pub longitude_deg: c_double,
pub latitude_deg: c_double,
pub distance_au: c_double,
pub lon_speed_deg: c_double,
pub lat_speed_deg: c_double,
pub dist_speed_au: c_double,
pub is_retrograde: c_int,
pub status: c_int,
}
#[repr(C)]
pub struct XalenHouses {
pub cusps: [c_double; 12],
pub ascendant_deg: c_double,
pub mc_deg: c_double,
pub status: c_int,
}
const XALEN_FFI_PANIC: c_int = -99;
const XALEN_FFI_PANIC_F64: c_double = -99.0;
fn guard_int(f: impl FnOnce() -> c_int + std::panic::UnwindSafe, sentinel: c_int) -> c_int {
std::panic::catch_unwind(f).unwrap_or(sentinel)
}
fn guard_f64(
f: impl FnOnce() -> c_double + std::panic::UnwindSafe,
sentinel: c_double,
) -> c_double {
std::panic::catch_unwind(f).unwrap_or(sentinel)
}
#[unsafe(no_mangle)]
pub extern "C" fn xalen_init() -> c_int {
guard_int(
|| {
let _ = get_almanac();
0
},
XALEN_FFI_PANIC,
)
}
fn body_from_id(body_id: c_int) -> Option<Body> {
match body_id {
0 => Some(Body::Sun),
1 => Some(Body::Moon),
2 => Some(Body::Mercury),
3 => Some(Body::Venus),
4 => Some(Body::Mars),
5 => Some(Body::Jupiter),
6 => Some(Body::Saturn),
7 => Some(Body::Uranus),
8 => Some(Body::Neptune),
9 => Some(Body::MeanNode),
10 => Some(Body::TrueNode),
11 => Some(Body::Pluto),
12 => Some(Body::Chiron),
_ => None,
}
}
fn ayanamsa_from_id(id: c_int) -> Option<Ayanamsa> {
match id {
0 => Some(Ayanamsa::Lahiri),
1 => Some(Ayanamsa::KPKrishnamurti),
2 => Some(Ayanamsa::Raman),
3 => Some(Ayanamsa::FaganBradley),
4 => Some(Ayanamsa::TrueChitra),
5 => Some(Ayanamsa::TrueRevati),
6 => Some(Ayanamsa::SuryaSiddhanta),
7 => Some(Ayanamsa::YukteswarSriSS),
8 => Some(Ayanamsa::JNBhasin),
9 => Some(Ayanamsa::DeLuce),
10 => Some(Ayanamsa::Ushashashi),
11 => Some(Ayanamsa::PushyaPaksha),
12 => Some(Ayanamsa::GalacticCenter0Sag),
13 => Some(Ayanamsa::LahiriICRC),
14 => Some(Ayanamsa::KPStraightLine),
15 => Some(Ayanamsa::Hipparchos),
16 => Some(Ayanamsa::LahiriVP285),
_ => None,
}
}
fn house_system_from_id(id: c_int) -> Option<HouseSystem> {
match id {
0 => Some(HouseSystem::WholeSign),
1 => Some(HouseSystem::Equal),
2 => Some(HouseSystem::Placidus),
3 => Some(HouseSystem::Koch),
4 => Some(HouseSystem::Porphyry),
5 => Some(HouseSystem::Regiomontanus),
6 => Some(HouseSystem::Campanus),
7 => Some(HouseSystem::Morinus),
8 => Some(HouseSystem::Alcabitius),
9 => Some(HouseSystem::Topocentric),
10 => Some(HouseSystem::Sripati),
11 => Some(HouseSystem::Vehlow),
12 => Some(HouseSystem::Meridian),
13 => Some(HouseSystem::KrusinskiPisa),
_ => None,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn xalen_planet_position(
jd_ut1: c_double,
body_id: c_int,
out: *mut XalenPosition,
) -> c_int {
if out.is_null() {
return -1;
}
unsafe {
std::ptr::write_bytes(out, 0, 1);
}
let out_guard = std::panic::AssertUnwindSafe(out);
let result = std::panic::catch_unwind(move || {
let out = out_guard.0;
if !jd_ut1.is_finite() {
unsafe {
(*out).status = -2;
}
return -2;
}
if body_id == 13 {
let almanac = get_almanac();
return match almanac.geocentric_ecliptic(Body::MeanNode, JdUT1(jd_ut1)) {
Ok(pos) => {
unsafe {
(*out).longitude_deg =
(pos.longitude.to_degrees() + 180.0).rem_euclid(360.0);
(*out).latitude_deg = -pos.latitude.to_degrees();
(*out).distance_au = pos.distance;
(*out).status = 0;
}
0
}
Err(_) => {
unsafe {
(*out).status = -3;
}
-3
}
};
}
let body = match body_from_id(body_id) {
Some(b) => b,
None => {
unsafe {
(*out).status = -2;
}
return -2;
}
};
let almanac = get_almanac();
match almanac.geocentric_ecliptic(body, JdUT1(jd_ut1)) {
Ok(pos) => {
unsafe {
(*out).longitude_deg = pos.longitude.to_degrees().rem_euclid(360.0);
(*out).latitude_deg = pos.latitude.to_degrees();
(*out).distance_au = pos.distance;
(*out).status = 0;
}
0
}
Err(_) => {
unsafe {
(*out).status = -3;
}
-3
}
}
});
match result {
Ok(code) => code,
Err(_) => {
unsafe {
(*out).status = XALEN_FFI_PANIC;
}
XALEN_FFI_PANIC
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn xalen_planet_position_full(
jd_ut1: c_double,
body_id: c_int,
out: *mut XalenPositionFull,
) -> c_int {
if out.is_null() {
return -1;
}
unsafe {
std::ptr::write_bytes(out, 0, 1);
}
let out_guard = std::panic::AssertUnwindSafe(out);
let result = std::panic::catch_unwind(move || {
let out = out_guard.0;
if !jd_ut1.is_finite() {
unsafe {
(*out).status = -2;
}
return -2;
}
let (body, ketu_shift) = if body_id == 13 {
(Body::MeanNode, true)
} else {
match body_from_id(body_id) {
Some(b) => (b, false),
None => {
unsafe {
(*out).status = -2;
}
return -2;
}
}
};
let almanac = get_almanac();
let jd = JdUT1(jd_ut1);
let pos = match almanac.geocentric_ecliptic(body, jd) {
Ok(p) => p,
Err(_) => {
unsafe {
(*out).status = -3;
}
return -3;
}
};
let speed = match almanac.geocentric_speed(body, jd) {
Ok(s) => s,
Err(_) => {
unsafe {
(*out).status = -3;
}
return -3;
}
};
let mut longitude = pos.longitude.to_degrees().rem_euclid(360.0);
let mut latitude = pos.latitude.to_degrees();
if ketu_shift {
longitude = (longitude + 180.0).rem_euclid(360.0);
latitude = -latitude; }
unsafe {
(*out).longitude_deg = longitude;
(*out).latitude_deg = latitude;
(*out).distance_au = pos.distance;
(*out).lon_speed_deg = speed.longitude_deg_per_day();
(*out).lat_speed_deg = speed.latitude_deg_per_day();
(*out).dist_speed_au = speed.distance;
(*out).is_retrograde = c_int::from(speed.longitude < 0.0);
(*out).status = 0;
}
0
});
match result {
Ok(code) => code,
Err(_) => {
unsafe {
(*out).status = XALEN_FFI_PANIC;
}
XALEN_FFI_PANIC
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn xalen_sidereal_longitude(
jd_ut1: c_double,
body_id: c_int,
ayanamsa_id: c_int,
) -> c_double {
guard_f64(
move || {
if !jd_ut1.is_finite() {
return -2.0;
}
let ayanamsa = match ayanamsa_from_id(ayanamsa_id) {
Some(a) => a,
None => return -2.0,
};
let almanac = get_almanac();
let jd = JdUT1(jd_ut1);
let tt = jd.to_tt(&xalen_time::DeltaTModel::StephensonMorrisonHohenkerk2016);
let aya_deg = ayanamsa.compute_deg(tt.as_f64());
if body_id == 13 {
return match almanac.sidereal_longitude_deg(Body::MeanNode, jd, aya_deg) {
Ok(lon) => (lon + 180.0).rem_euclid(360.0),
Err(_) => -1.0,
};
}
let body = match body_from_id(body_id) {
Some(b) => b,
None => return -1.0,
};
almanac
.sidereal_longitude_deg(body, jd, aya_deg)
.unwrap_or(-1.0)
},
XALEN_FFI_PANIC_F64,
)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn xalen_houses(
jd_ut1: c_double,
latitude_deg: c_double,
longitude_deg: c_double,
system_id: c_int,
out: *mut XalenHouses,
) -> c_int {
if out.is_null() {
return -1;
}
unsafe {
std::ptr::write_bytes(out, 0, 1);
}
let out_guard = std::panic::AssertUnwindSafe(out);
let result = std::panic::catch_unwind(move || {
let out = out_guard.0;
if !jd_ut1.is_finite()
|| !latitude_deg.is_finite()
|| !longitude_deg.is_finite()
|| !(-90.0..=90.0).contains(&latitude_deg)
{
unsafe {
(*out).status = -2;
}
return -2;
}
let system = match house_system_from_id(system_id) {
Some(s) => s,
None => {
unsafe {
(*out).status = -2;
}
return -2;
}
};
let loc = GeoLocation::new(latitude_deg, longitude_deg);
let t = (jd_ut1 - 2_451_545.0) / 36525.0;
let epsilon = mean_obliquity(t);
let houses = compute_houses(jd_ut1, &loc, epsilon, system);
unsafe {
for i in 0..12 {
(*out).cusps[i] = houses.cusp_deg(i);
}
(*out).ascendant_deg = houses.ascendant.to_degrees().rem_euclid(360.0);
(*out).mc_deg = houses.mc.to_degrees().rem_euclid(360.0);
(*out).status = 0;
}
0
});
match result {
Ok(code) => code,
Err(_) => {
unsafe {
(*out).status = XALEN_FFI_PANIC;
}
XALEN_FFI_PANIC
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn xalen_version() -> *const c_char {
let ptr = std::panic::catch_unwind(|| {
concat!("XALEN Ephemeris ", env!("CARGO_PKG_VERSION"), "\0").as_ptr() as usize
})
.unwrap_or(0);
ptr as *const c_char
}
#[unsafe(no_mangle)]
pub extern "C" fn xalen_ayanamsa(jd_ut1: c_double, ayanamsa_id: c_int) -> c_double {
guard_f64(
move || {
let ayanamsa = match ayanamsa_from_id(ayanamsa_id) {
Some(a) => a,
None => return -1.0,
};
if !jd_ut1.is_finite() {
return -1.0;
}
let jd = JdUT1(jd_ut1);
let tt = jd.to_tt(&xalen_time::DeltaTModel::StephensonMorrisonHohenkerk2016);
ayanamsa.compute_deg(tt.as_f64())
},
XALEN_FFI_PANIC_F64,
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn init_succeeds() {
assert_eq!(xalen_init(), 0);
}
#[test]
fn planet_position_sun() {
xalen_init();
let mut pos = XalenPosition {
longitude_deg: 0.0,
latitude_deg: 0.0,
distance_au: 0.0,
status: -1,
};
let ret = unsafe { xalen_planet_position(2451545.0, 0, &mut pos) };
assert_eq!(ret, 0);
assert_eq!(pos.status, 0);
assert!(pos.longitude_deg >= 0.0 && pos.longitude_deg < 360.0);
}
#[test]
fn all_body_ids_valid() {
xalen_init();
for id in 0..=12 {
let mut pos = XalenPosition {
longitude_deg: 0.0,
latitude_deg: 0.0,
distance_au: 0.0,
status: -1,
};
let ret = unsafe { xalen_planet_position(2451545.0, id, &mut pos) };
assert_eq!(ret, 0, "Body ID {id} should succeed");
assert!(
pos.longitude_deg >= 0.0 && pos.longitude_deg < 360.0,
"Body {id} longitude should be [0,360), got {}",
pos.longitude_deg
);
}
}
#[test]
fn ketu_body_id_13() {
xalen_init();
let mut rahu_pos = XalenPosition {
longitude_deg: 0.0,
latitude_deg: 0.0,
distance_au: 0.0,
status: -1,
};
unsafe { xalen_planet_position(2451545.0, 9, &mut rahu_pos) }; let mut ketu_pos = XalenPosition {
longitude_deg: 0.0,
latitude_deg: 0.0,
distance_au: 0.0,
status: -1,
};
let ret = unsafe { xalen_planet_position(2451545.0, 13, &mut ketu_pos) }; assert_eq!(ret, 0, "Ketu (body 13) should succeed");
let expected = (rahu_pos.longitude_deg + 180.0).rem_euclid(360.0);
assert!(
(ketu_pos.longitude_deg - expected).abs() < 1e-10,
"Ketu should be Rahu+180: expected {expected}, got {}",
ketu_pos.longitude_deg
);
assert!(
(ketu_pos.latitude_deg + rahu_pos.latitude_deg).abs() < 1e-10,
"Ketu latitude {} should be −Rahu latitude {}",
ketu_pos.latitude_deg,
rahu_pos.latitude_deg
);
}
#[test]
fn planet_position_full_six_tuple_and_speed() {
xalen_init();
let mut p = XalenPositionFull {
longitude_deg: 0.0,
latitude_deg: 0.0,
distance_au: 0.0,
lon_speed_deg: 0.0,
lat_speed_deg: 0.0,
dist_speed_au: 0.0,
is_retrograde: -1,
status: -1,
};
let ret = unsafe { xalen_planet_position_full(2451545.0, 0, &mut p) }; assert_eq!(ret, 0);
assert_eq!(p.status, 0);
assert!(p.longitude_deg >= 0.0 && p.longitude_deg < 360.0);
assert!(
(p.lon_speed_deg - 1.019432).abs() < 0.01,
"Sun lon_speed {} vs pyswisseph 1.019432",
p.lon_speed_deg
);
assert!(
(p.distance_au - 0.98332764).abs() < 1e-3,
"Sun distance {} vs pyswisseph 0.98332764",
p.distance_au
);
assert_eq!(p.is_retrograde, 0, "Sun is never retrograde");
}
#[test]
fn planet_position_full_node_retrograde_and_ketu() {
xalen_init();
let mut rahu = XalenPositionFull {
longitude_deg: 0.0,
latitude_deg: 0.0,
distance_au: 0.0,
lon_speed_deg: 0.0,
lat_speed_deg: 0.0,
dist_speed_au: 0.0,
is_retrograde: -1,
status: -1,
};
let ret = unsafe { xalen_planet_position_full(2451545.0, 9, &mut rahu) };
assert_eq!(ret, 0);
assert_eq!(rahu.is_retrograde, 1, "mean node is always retrograde");
assert!(
rahu.lon_speed_deg < 0.0,
"node lon_speed {} should be < 0",
rahu.lon_speed_deg
);
let mut ketu = XalenPositionFull {
longitude_deg: 0.0,
latitude_deg: 0.0,
distance_au: 0.0,
lon_speed_deg: 0.0,
lat_speed_deg: 0.0,
dist_speed_au: 0.0,
is_retrograde: -1,
status: -1,
};
let ret = unsafe { xalen_planet_position_full(2451545.0, 13, &mut ketu) };
assert_eq!(ret, 0);
let expected = (rahu.longitude_deg + 180.0).rem_euclid(360.0);
assert!(
(ketu.longitude_deg - expected).abs() < 1e-9,
"Ketu lon {} != Rahu+180 {}",
ketu.longitude_deg,
expected
);
assert!(
(ketu.latitude_deg + rahu.latitude_deg).abs() < 1e-9,
"Ketu latitude {} should be −Rahu latitude {}",
ketu.latitude_deg,
rahu.latitude_deg
);
assert_eq!(
ketu.is_retrograde, rahu.is_retrograde,
"Ketu shares Rahu retrograde"
);
}
#[test]
fn planet_position_full_rejects_bad_input() {
xalen_init();
let ret = unsafe { xalen_planet_position_full(2451545.0, 0, std::ptr::null_mut()) };
assert_eq!(ret, -1);
let mut p = XalenPositionFull {
longitude_deg: 0.0,
latitude_deg: 0.0,
distance_au: 0.0,
lon_speed_deg: 0.0,
lat_speed_deg: 0.0,
dist_speed_au: 0.0,
is_retrograde: -1,
status: 0,
};
assert_eq!(
unsafe { xalen_planet_position_full(f64::NAN, 0, &mut p) },
-2
);
assert_eq!(p.status, -2);
assert_eq!(
unsafe { xalen_planet_position_full(2451545.0, 99, &mut p) },
-2
);
assert_eq!(p.status, -2);
}
#[test]
fn sidereal_longitude_reasonable() {
xalen_init();
let lon = xalen_sidereal_longitude(2451545.0, 0, 0); assert!(
lon >= 0.0 && lon < 360.0,
"Sidereal Sun should be 0-360°, got {lon}°"
);
}
#[test]
fn sidereal_all_ayanamsa_ids_valid() {
xalen_init();
for id in 0..=16 {
let lon = xalen_sidereal_longitude(2451545.0, 0, id); assert!(
lon >= 0.0 && lon < 360.0,
"Ayanamsa ID {id} should produce valid sidereal lon, got {lon}"
);
}
}
#[test]
fn sidereal_invalid_ayanamsa_returns_error() {
let lon = xalen_sidereal_longitude(2451545.0, 0, 99);
assert!(
lon < 0.0,
"Invalid ayanamsa should return negative, got {lon}"
);
}
#[test]
fn sidereal_ketu_body_13() {
xalen_init();
let rahu_lon = xalen_sidereal_longitude(2451545.0, 9, 0); let ketu_lon = xalen_sidereal_longitude(2451545.0, 13, 0); assert!(
ketu_lon >= 0.0 && ketu_lon < 360.0,
"Ketu sidereal should be [0,360), got {ketu_lon}"
);
let expected = (rahu_lon + 180.0).rem_euclid(360.0);
assert!(
(ketu_lon - expected).abs() < 1e-10,
"Ketu sidereal should be Rahu+180: expected {expected}, got {ketu_lon}"
);
}
#[test]
fn houses_compute() {
xalen_init();
let mut h = XalenHouses {
cusps: [0.0; 12],
ascendant_deg: 0.0,
mc_deg: 0.0,
status: -1,
};
let ret = unsafe { xalen_houses(2451545.0, 18.52, 73.85, 0, &mut h) };
assert_eq!(ret, 0);
assert_eq!(h.status, 0);
assert!(h.ascendant_deg >= 0.0 && h.ascendant_deg < 360.0);
}
#[test]
fn houses_all_system_ids_valid() {
xalen_init();
for id in 0..=13 {
let mut h = XalenHouses {
cusps: [0.0; 12],
ascendant_deg: 0.0,
mc_deg: 0.0,
status: -1,
};
let ret = unsafe { xalen_houses(2451545.0, 18.52, 73.85, id, &mut h) };
assert_eq!(ret, 0, "House system ID {id} should succeed");
assert!(
h.ascendant_deg >= 0.0 && h.ascendant_deg < 360.0,
"House system {id} should produce valid ascendant"
);
}
}
#[test]
fn houses_invalid_system_returns_error() {
let mut h = XalenHouses {
cusps: [0.0; 12],
ascendant_deg: 0.0,
mc_deg: 0.0,
status: 0,
};
let ret = unsafe { xalen_houses(2451545.0, 18.52, 73.85, 99, &mut h) };
assert_eq!(ret, -2, "Invalid house system should return -2");
}
#[test]
fn invalid_body_returns_error() {
let mut pos = XalenPosition {
longitude_deg: 0.0,
latitude_deg: 0.0,
distance_au: 0.0,
status: 0,
};
let ret = unsafe { xalen_planet_position(2451545.0, 99, &mut pos) };
assert_eq!(ret, -2);
}
#[test]
fn null_pointer_returns_error() {
let ret = unsafe { xalen_planet_position(2451545.0, 0, std::ptr::null_mut()) };
assert_eq!(ret, -1);
}
#[test]
fn ayanamsa_at_j2000() {
let aya = xalen_ayanamsa(2451545.0, 0);
assert!(
(aya - 23.85).abs() < 0.1,
"Lahiri at J2000 should be ~23.85°, got {aya}°"
);
}
#[test]
fn ayanamsa_all_ids_valid() {
for id in 0..=16 {
let aya = xalen_ayanamsa(2451545.0, id);
assert!(
aya.is_finite() && aya > 0.0,
"Ayanamsa ID {id} should produce finite positive value, got {aya}"
);
}
}
#[test]
fn ayanamsa_invalid_id_returns_error() {
let aya = xalen_ayanamsa(2451545.0, 99);
assert!(
aya < 0.0,
"Invalid ayanamsa ID should return negative, got {aya}"
);
}
#[test]
fn ayanamsa_non_finite_jd_returns_error() {
assert_eq!(xalen_ayanamsa(f64::NAN, 0), -1.0);
assert_eq!(xalen_ayanamsa(f64::INFINITY, 0), -1.0);
}
#[test]
fn houses_reject_non_finite_and_out_of_range_lat() {
xalen_init();
let bad_inputs: [(c_double, c_double, c_double); 9] = [
(f64::NAN, 18.52, 73.85), (f64::INFINITY, 18.52, 73.85), (2451545.0, f64::NAN, 73.85), (2451545.0, f64::INFINITY, 73.85), (2451545.0, 18.52, f64::NAN), (2451545.0, 18.52, f64::INFINITY), (2451545.0, 18.52, f64::NEG_INFINITY), (2451545.0, 91.0, 73.85), (2451545.0, -90.5, 73.85), ];
for (jd, lat, lon) in bad_inputs {
let mut h = XalenHouses {
cusps: [0.0; 12],
ascendant_deg: 0.0,
mc_deg: 0.0,
status: 0,
};
let ret = unsafe { xalen_houses(jd, lat, lon, 0, &mut h) };
assert_eq!(
ret, -2,
"jd={jd} lat={lat} lon={lon} should be rejected with -2"
);
assert_eq!(
h.status, -2,
"status should be -2 for jd={jd} lat={lat} lon={lon}"
);
}
let mut h = XalenHouses {
cusps: [0.0; 12],
ascendant_deg: 0.0,
mc_deg: 0.0,
status: -1,
};
let ret = unsafe { xalen_houses(2451545.0, 18.52, 540.0, 0, &mut h) };
assert_eq!(ret, 0, "Periodic longitude 540.0 should be accepted");
assert_eq!(h.status, 0);
}
#[test]
fn planet_position_rejects_non_finite_jd() {
xalen_init();
for jd in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
let mut pos = XalenPosition {
longitude_deg: 0.0,
latitude_deg: 0.0,
distance_au: 0.0,
status: 0,
};
let ret = unsafe { xalen_planet_position(jd, 0, &mut pos) };
assert_eq!(ret, -2, "Non-finite jd={jd} should return -2");
assert_eq!(pos.status, -2, "status should be -2 for jd={jd}");
}
let mut pos = XalenPosition {
longitude_deg: 0.0,
latitude_deg: 0.0,
distance_au: 0.0,
status: 0,
};
let ret = unsafe { xalen_planet_position(f64::NAN, 13, &mut pos) };
assert_eq!(ret, -2, "Non-finite jd on Ketu path should return -2");
assert_eq!(pos.status, -2);
}
#[test]
fn sidereal_longitude_rejects_non_finite_jd() {
xalen_init();
for jd in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
let lon = xalen_sidereal_longitude(jd, 0, 0);
assert_eq!(lon, -2.0, "Non-finite jd={jd} should return -2.0");
}
}
#[test]
fn panic_sentinels_are_distinct_from_normal_errors() {
assert_ne!(XALEN_FFI_PANIC, -1);
assert_ne!(XALEN_FFI_PANIC, -2);
assert_ne!(XALEN_FFI_PANIC, -3);
assert!((XALEN_FFI_PANIC_F64 - (-1.0)).abs() > 0.5);
assert!((XALEN_FFI_PANIC_F64 - (-2.0)).abs() > 0.5);
}
#[test]
fn catch_unwind_wrappers_preserve_normal_returns() {
assert_eq!(xalen_init(), 0);
let mut pos = XalenPosition {
longitude_deg: 0.0,
latitude_deg: 0.0,
distance_au: 0.0,
status: -1,
};
assert_eq!(unsafe { xalen_planet_position(2451545.0, 0, &mut pos) }, 0);
assert_eq!(pos.status, 0);
let aya = xalen_ayanamsa(2451545.0, 0);
assert!(aya.is_finite() && aya > 0.0);
let lon = xalen_sidereal_longitude(2451545.0, 0, 0);
assert!(lon >= 0.0 && lon < 360.0);
}
#[test]
fn version_string() {
let v = xalen_version();
assert!(!v.is_null());
let s = unsafe { CStr::from_ptr(v) }.to_str().unwrap();
assert!(
s.contains("XALEN"),
"Version should contain XALEN, got '{s}'"
);
assert!(
s.contains(env!("CARGO_PKG_VERSION")),
"Version should contain the crate version {}, got '{s}'",
env!("CARGO_PKG_VERSION")
);
}
}