Skip to main content

spice/core/
errors.rs

1/*!
2CSPICE error handling.
3
4## Description
5
6CSPICE keeps its own error state. By default, a routine that fails prints a long report to the
7screen and **terminates the process** — fine for a Fortran program, fatal for a library. Calling
8[`quiet`] once, at start-up, switches the toolkit to the `"RETURN"` action with no output device:
9failing routines then simply return, and [`check`] turns that state into a Rust [`Result`].
10
11```no_run
12# #[cfg(not(feature = "lock"))]
13# {
14spice::errors::quiet();
15
16spice::furnsh("does-not-exist.tm");
17if let Err(error) = spice::errors::check() {
18    eprintln!("{error}");
19}
20# }
21```
22
23[`check`] resets the error state, so the next call starts from a clean slate.
24
25See the [C documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/req/error.html).
26*/
27
28use crate::c::{SpiceChar, SpiceInt};
29use crate::core::ffi::{from_cbuf, to_cstring};
30use crate::MAX_LEN_OUT;
31use std::fmt;
32
33#[cfg(any(feature = "lock", doc))]
34use {crate::core::lock::SpiceLock, spice_derive::impl_for};
35
36/**
37An error reported by CSPICE.
38*/
39#[derive(Clone, Debug, Eq, PartialEq)]
40pub struct Error {
41    /// Short, machine readable name, such as `SPICE(NOSUCHFILE)`.
42    pub short: String,
43    /// Explanation of the short message.
44    pub explain: String,
45    /// Long message, describing what went wrong in this particular call.
46    pub long: String,
47    /// The chain of routines that were active when the error was signalled.
48    pub traceback: String,
49}
50
51impl fmt::Display for Error {
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        write!(f, "{}", self.short)?;
54        for detail in [&self.explain, &self.long] {
55            if !detail.is_empty() {
56                write!(f, ": {detail}")?;
57            }
58        }
59        if !self.traceback.is_empty() {
60            write!(f, " [{}]", self.traceback)?;
61        }
62        Ok(())
63    }
64}
65
66impl std::error::Error for Error {}
67
68/**
69Whether CSPICE is in an error state.
70*/
71#[cfg_attr(any(feature = "lock", doc), impl_for(SpiceLock))]
72pub fn failed() -> bool {
73    unsafe { crate::c::failed_c() != 0 }
74}
75
76/**
77Clear the CSPICE error state.
78*/
79#[cfg_attr(any(feature = "lock", doc), impl_for(SpiceLock))]
80pub fn reset() {
81    unsafe { crate::c::reset_c() }
82}
83
84/**
85Retrieve one of the error messages: `"SHORT"`, `"EXPLAIN"` or `"LONG"`.
86*/
87#[cfg_attr(any(feature = "lock", doc), impl_for(SpiceLock))]
88pub fn getmsg(option: &str) -> String {
89    let option = to_cstring(option);
90    let mut message = vec![0 as SpiceChar; MAX_LEN_OUT];
91    unsafe {
92        crate::c::getmsg_c(
93            option.as_ptr() as *mut SpiceChar,
94            message.len() as SpiceInt,
95            message.as_mut_ptr(),
96        )
97    };
98    from_cbuf(&message)
99}
100
101/**
102Return the traceback of the routines that were active when the error was signalled.
103*/
104#[cfg_attr(any(feature = "lock", doc), impl_for(SpiceLock))]
105pub fn qcktrc() -> String {
106    let mut trace = vec![0 as SpiceChar; MAX_LEN_OUT];
107    unsafe { crate::c::qcktrc_c(trace.len() as SpiceInt, trace.as_mut_ptr()) };
108    from_cbuf(&trace)
109}
110
111/**
112Turn the current CSPICE error state into a [`Result`], clearing it on the way.
113
114`Ok(())` when no routine has failed since the last reset.
115*/
116#[cfg_attr(any(feature = "lock", doc), impl_for(SpiceLock))]
117pub fn check() -> Result<(), Error> {
118    if !failed() {
119        return Ok(());
120    }
121
122    let error = Error {
123        short: getmsg("SHORT"),
124        explain: getmsg("EXPLAIN"),
125        long: getmsg("LONG"),
126        traceback: qcktrc(),
127    };
128    reset();
129    Err(error)
130}
131
132/// Build the in/out buffer the `err*_c` routines use for both `"SET"` and `"GET"`.
133fn inout(value: &str) -> Vec<SpiceChar> {
134    let mut buffer = vec![0 as SpiceChar; MAX_LEN_OUT.max(value.len() + 1)];
135    for (target, byte) in buffer.iter_mut().zip(value.as_bytes()) {
136        *target = *byte as SpiceChar;
137    }
138    buffer
139}
140
141/// Generate the wrappers of the three `operation`/`value` error routines.
142macro_rules! inout_routine {
143    ($($name:ident => $cname:ident, $doc:expr);* $(;)?) => {$(
144        #[doc = $doc]
145        ///
146        /// `op` is `"SET"` to install `value`, or `"GET"` to read the current one back; the current
147        /// value is returned either way.
148        #[cfg_attr(any(feature = "lock", doc), impl_for(SpiceLock))]
149        pub fn $name(op: &str, value: &str) -> String {
150            let op = to_cstring(op);
151            let mut buffer = inout(value);
152            unsafe {
153                crate::c::$cname(
154                    op.as_ptr() as *mut SpiceChar,
155                    buffer.len() as SpiceInt,
156                    buffer.as_mut_ptr(),
157                )
158            };
159            from_cbuf(&buffer)
160        }
161    )*};
162}
163
164inout_routine! {
165    erract => erract_c, "Retrieve or set the default error action.";
166    errdev => errdev_c, "Retrieve or set the name of the current output device for error messages.";
167    errprt => errprt_c, "Retrieve or set the list of error message items to be output.";
168}
169
170/**
171Stop CSPICE from writing to the screen and from terminating the process on error.
172
173Sets the error action to `"RETURN"` and the error device to `"NULL"`; pair it with [`check`].
174*/
175#[cfg_attr(any(feature = "lock", doc), impl_for(SpiceLock))]
176pub fn quiet() {
177    erract("SET", "RETURN");
178    errdev("SET", "NULL");
179}
180
181/**
182Restore the CSPICE defaults: report to the screen, then abort the process.
183*/
184#[cfg_attr(any(feature = "lock", doc), impl_for(SpiceLock))]
185pub fn loud() {
186    erract("SET", "ABORT");
187    errdev("SET", "SCREEN");
188}