use libassist_sys::ffi;
use crate::{Error, Result};
#[derive(Debug, Clone)]
pub enum Origin {
SolarSystemBarycenter,
Sun,
MercuryBarycenter,
VenusBarycenter,
Earth,
Moon,
MarsBarycenter,
JupiterBarycenter,
SaturnBarycenter,
UranusBarycenter,
NeptuneBarycenter,
PlutoBarycenter,
Observatory(String),
}
impl Origin {
pub fn parse(s: &str) -> Result<Self> {
if let Some(o) = parse_body_name(s) {
return Ok(o);
}
if is_mpc_code(s) {
return Ok(Origin::Observatory(s.to_string()));
}
Err(Error::InvalidBody(format!(
"{s:?} is neither a known body name nor a valid 3-character MPC code"
)))
}
pub(crate) fn body_id(&self) -> Option<i32> {
match self {
Origin::Sun => Some(ffi::ASSIST_BODY_SUN),
Origin::MercuryBarycenter => Some(ffi::ASSIST_BODY_MERCURY),
Origin::VenusBarycenter => Some(ffi::ASSIST_BODY_VENUS),
Origin::Earth => Some(ffi::ASSIST_BODY_EARTH),
Origin::Moon => Some(ffi::ASSIST_BODY_MOON),
Origin::MarsBarycenter => Some(ffi::ASSIST_BODY_MARS),
Origin::JupiterBarycenter => Some(ffi::ASSIST_BODY_JUPITER),
Origin::SaturnBarycenter => Some(ffi::ASSIST_BODY_SATURN),
Origin::UranusBarycenter => Some(ffi::ASSIST_BODY_URANUS),
Origin::NeptuneBarycenter => Some(ffi::ASSIST_BODY_NEPTUNE),
Origin::PlutoBarycenter => Some(ffi::ASSIST_BODY_PLUTO),
Origin::SolarSystemBarycenter | Origin::Observatory(_) => None,
}
}
pub fn observatory_code(&self) -> Option<&str> {
match self {
Origin::Observatory(code) => Some(code),
_ => None,
}
}
}
fn parse_body_name(s: &str) -> Option<Origin> {
Some(match s.to_lowercase().as_str() {
"ssb" | "solar_system_barycenter" => Origin::SolarSystemBarycenter,
"sun" => Origin::Sun,
"mercury" | "mercury_barycenter" => Origin::MercuryBarycenter,
"venus" | "venus_barycenter" => Origin::VenusBarycenter,
"earth" => Origin::Earth,
"moon" => Origin::Moon,
"mars" | "mars_barycenter" => Origin::MarsBarycenter,
"jupiter" | "jupiter_barycenter" => Origin::JupiterBarycenter,
"saturn" | "saturn_barycenter" => Origin::SaturnBarycenter,
"uranus" | "uranus_barycenter" => Origin::UranusBarycenter,
"neptune" | "neptune_barycenter" => Origin::NeptuneBarycenter,
"pluto" | "pluto_barycenter" => Origin::PlutoBarycenter,
_ => return None,
})
}
fn is_mpc_code(s: &str) -> bool {
s.len() == 3 && s.bytes().all(|b| b.is_ascii_alphanumeric())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_known_body_names() {
assert!(matches!(Origin::parse("earth").unwrap(), Origin::Earth));
assert!(matches!(
Origin::parse("JUPITER_BARYCENTER").unwrap(),
Origin::JupiterBarycenter
));
assert!(matches!(
Origin::parse("ssb").unwrap(),
Origin::SolarSystemBarycenter
));
}
#[test]
fn parses_three_char_mpc_codes() {
match Origin::parse("I11").unwrap() {
Origin::Observatory(c) => assert_eq!(c, "I11"),
other => panic!("expected observatory, got {other:?}"),
}
assert!(matches!(Origin::parse("500"), Ok(Origin::Observatory(_))));
assert!(matches!(Origin::parse("W84"), Ok(Origin::Observatory(_))));
}
#[test]
fn rejects_typoed_body_names() {
let err = Origin::parse("earty").unwrap_err();
assert!(err.to_string().contains("neither a known body name"));
}
#[test]
fn rejects_non_three_char_strings() {
assert!(Origin::parse("ABCD").is_err());
assert!(Origin::parse("AB").is_err());
assert!(Origin::parse("").is_err());
assert!(Origin::parse("I-1").is_err());
}
}