B2: make fallible Node constructors throw instead of panicking

The 14 candle and multi-parameter indicator constructors passed raw
parameters through must() (an expect()), so invalid arguments such as
new MACD(0,0,0) or new BollingerBands(20,-1) aborted the whole process
over the napi boundary (the release profile sets panic=abort).

napi-rs 2.16 does accept a #[napi(constructor)] returning
napi::Result<Self> (the old must() comment was wrong), so each now
returns napi::Result<Self> and throws a clean JS error via map_err.
must()/clamp_period stay for the scalar macro, where period is clamped
and the Result is provably Ok. Verified: every invalid constructor
throws, valid ones still build.
This commit is contained in:
kingchenc
2026-05-22 03:44:56 +02:00
parent 4b6f25a32c
commit a0884fa010
+65 -73
View File
@@ -22,12 +22,13 @@ fn map_err(e: wc::Error) -> NapiError {
NapiError::new(Status::InvalidArg, e.to_string()) NapiError::new(Status::InvalidArg, e.to_string())
} }
/// Helper for constructors. `#[napi(constructor)]` in napi-rs 2.16 only accepts /// Helper for the scalar-indicator macro only. Scalar `new` functions can fail
/// an infallible `Self` return, so we clamp parameters to the smallest valid value /// solely on `period == 0`, which `clamp_period` already rules out, so the
/// (which only matters for the pathological `period = 0` case) and then `expect` /// `Result` is provably `Ok` here. Candle indicators and the multi-parameter
/// the rest, which can only fail for invariants that hold by construction. /// indicators have genuinely fallible parameters and instead use fallible
/// `#[napi(constructor)]`s that return `napi::Result<Self>` and throw a JS error.
fn must<T>(r: Result<T, wc::Error>) -> T { fn must<T>(r: Result<T, wc::Error>) -> T {
r.expect("wickra: invalid indicator parameters") r.expect("wickra: scalar indicator parameter clamped to a valid range")
} }
/// Clamp a period parameter so the underlying indicator never sees zero. JS /// Clamp a period parameter so the underlying indicator never sees zero. JS
@@ -120,14 +121,11 @@ pub struct MacdNode {
#[napi] #[napi]
impl MacdNode { impl MacdNode {
#[napi(constructor)] #[napi(constructor)]
pub fn new(fast: u32, slow: u32, signal: u32) -> Self { pub fn new(fast: u32, slow: u32, signal: u32) -> napi::Result<Self> {
Self { Ok(Self {
inner: must(wc::MacdIndicator::new( inner: wc::MacdIndicator::new(fast as usize, slow as usize, signal as usize)
fast as usize, .map_err(map_err)?,
slow as usize, })
signal as usize,
)),
}
} }
#[napi] #[napi]
pub fn update(&mut self, value: f64) -> Option<MacdValue> { pub fn update(&mut self, value: f64) -> Option<MacdValue> {
@@ -177,10 +175,10 @@ pub struct BollingerNode {
#[napi] #[napi]
impl BollingerNode { impl BollingerNode {
#[napi(constructor)] #[napi(constructor)]
pub fn new(period: u32, multiplier: f64) -> Self { pub fn new(period: u32, multiplier: f64) -> napi::Result<Self> {
Self { Ok(Self {
inner: must(wc::BollingerBands::new(period as usize, multiplier)), inner: wc::BollingerBands::new(period as usize, multiplier).map_err(map_err)?,
} })
} }
#[napi] #[napi]
pub fn update(&mut self, value: f64) -> Option<BollingerValue> { pub fn update(&mut self, value: f64) -> Option<BollingerValue> {
@@ -228,10 +226,10 @@ pub struct AtrNode {
#[napi] #[napi]
impl AtrNode { impl AtrNode {
#[napi(constructor)] #[napi(constructor)]
pub fn new(period: u32) -> Self { pub fn new(period: u32) -> napi::Result<Self> {
Self { Ok(Self {
inner: must(wc::Atr::new(period as usize)), inner: wc::Atr::new(period as usize).map_err(map_err)?,
} })
} }
#[napi] #[napi]
pub fn update(&mut self, high: f64, low: f64, close: f64) -> napi::Result<Option<f64>> { pub fn update(&mut self, high: f64, low: f64, close: f64) -> napi::Result<Option<f64>> {
@@ -283,10 +281,10 @@ pub struct StochNode {
#[napi] #[napi]
impl StochNode { impl StochNode {
#[napi(constructor)] #[napi(constructor)]
pub fn new(k_period: u32, d_period: u32) -> Self { pub fn new(k_period: u32, d_period: u32) -> napi::Result<Self> {
Self { Ok(Self {
inner: must(wc::Stochastic::new(k_period as usize, d_period as usize)), inner: wc::Stochastic::new(k_period as usize, d_period as usize).map_err(map_err)?,
} })
} }
#[napi] #[napi]
pub fn update( pub fn update(
@@ -386,10 +384,10 @@ pub struct AdxNode {
#[napi] #[napi]
impl AdxNode { impl AdxNode {
#[napi(constructor)] #[napi(constructor)]
pub fn new(period: u32) -> Self { pub fn new(period: u32) -> napi::Result<Self> {
Self { Ok(Self {
inner: must(wc::Adx::new(period as usize)), inner: wc::Adx::new(period as usize).map_err(map_err)?,
} })
} }
#[napi] #[napi]
pub fn update( pub fn update(
@@ -437,10 +435,10 @@ pub struct CciNode {
#[napi] #[napi]
impl CciNode { impl CciNode {
#[napi(constructor)] #[napi(constructor)]
pub fn new(period: u32) -> Self { pub fn new(period: u32) -> napi::Result<Self> {
Self { Ok(Self {
inner: must(wc::Cci::new(period as usize)), inner: wc::Cci::new(period as usize).map_err(map_err)?,
} })
} }
#[napi] #[napi]
pub fn update(&mut self, high: f64, low: f64, close: f64) -> napi::Result<Option<f64>> { pub fn update(&mut self, high: f64, low: f64, close: f64) -> napi::Result<Option<f64>> {
@@ -472,10 +470,10 @@ pub struct WilliamsRNode {
#[napi] #[napi]
impl WilliamsRNode { impl WilliamsRNode {
#[napi(constructor)] #[napi(constructor)]
pub fn new(period: u32) -> Self { pub fn new(period: u32) -> napi::Result<Self> {
Self { Ok(Self {
inner: must(wc::WilliamsR::new(period as usize)), inner: wc::WilliamsR::new(period as usize).map_err(map_err)?,
} })
} }
#[napi] #[napi]
pub fn update(&mut self, high: f64, low: f64, close: f64) -> napi::Result<Option<f64>> { pub fn update(&mut self, high: f64, low: f64, close: f64) -> napi::Result<Option<f64>> {
@@ -507,10 +505,10 @@ pub struct MfiNode {
#[napi] #[napi]
impl MfiNode { impl MfiNode {
#[napi(constructor)] #[napi(constructor)]
pub fn new(period: u32) -> Self { pub fn new(period: u32) -> napi::Result<Self> {
Self { Ok(Self {
inner: must(wc::Mfi::new(period as usize)), inner: wc::Mfi::new(period as usize).map_err(map_err)?,
} })
} }
#[napi] #[napi]
pub fn update( pub fn update(
@@ -549,10 +547,10 @@ pub struct PsarNode {
#[napi] #[napi]
impl PsarNode { impl PsarNode {
#[napi(constructor)] #[napi(constructor)]
pub fn new(af_start: f64, af_step: f64, af_max: f64) -> Self { pub fn new(af_start: f64, af_step: f64, af_max: f64) -> napi::Result<Self> {
Self { Ok(Self {
inner: must(wc::Psar::new(af_start, af_step, af_max)), inner: wc::Psar::new(af_start, af_step, af_max).map_err(map_err)?,
} })
} }
#[napi] #[napi]
pub fn update(&mut self, high: f64, low: f64, close: f64) -> napi::Result<Option<f64>> { pub fn update(&mut self, high: f64, low: f64, close: f64) -> napi::Result<Option<f64>> {
@@ -591,14 +589,11 @@ pub struct KeltnerNode {
#[napi] #[napi]
impl KeltnerNode { impl KeltnerNode {
#[napi(constructor)] #[napi(constructor)]
pub fn new(ema_period: u32, atr_period: u32, multiplier: f64) -> Self { pub fn new(ema_period: u32, atr_period: u32, multiplier: f64) -> napi::Result<Self> {
Self { Ok(Self {
inner: must(wc::Keltner::new( inner: wc::Keltner::new(ema_period as usize, atr_period as usize, multiplier)
ema_period as usize, .map_err(map_err)?,
atr_period as usize, })
multiplier,
)),
}
} }
#[napi] #[napi]
pub fn update( pub fn update(
@@ -649,10 +644,10 @@ pub struct DonchianNode {
#[napi] #[napi]
impl DonchianNode { impl DonchianNode {
#[napi(constructor)] #[napi(constructor)]
pub fn new(period: u32) -> Self { pub fn new(period: u32) -> napi::Result<Self> {
Self { Ok(Self {
inner: must(wc::Donchian::new(period as usize)), inner: wc::Donchian::new(period as usize).map_err(map_err)?,
} })
} }
#[napi] #[napi]
pub fn update(&mut self, high: f64, low: f64) -> napi::Result<Option<DonchianValue>> { pub fn update(&mut self, high: f64, low: f64) -> napi::Result<Option<DonchianValue>> {
@@ -733,10 +728,10 @@ pub struct AoNode {
#[napi] #[napi]
impl AoNode { impl AoNode {
#[napi(constructor)] #[napi(constructor)]
pub fn new(fast: u32, slow: u32) -> Self { pub fn new(fast: u32, slow: u32) -> napi::Result<Self> {
Self { Ok(Self {
inner: must(wc::AwesomeOscillator::new(fast as usize, slow as usize)), inner: wc::AwesomeOscillator::new(fast as usize, slow as usize).map_err(map_err)?,
} })
} }
#[napi] #[napi]
pub fn update(&mut self, high: f64, low: f64) -> napi::Result<Option<f64>> { pub fn update(&mut self, high: f64, low: f64) -> napi::Result<Option<f64>> {
@@ -769,10 +764,10 @@ pub struct AroonNode {
#[napi] #[napi]
impl AroonNode { impl AroonNode {
#[napi(constructor)] #[napi(constructor)]
pub fn new(period: u32) -> Self { pub fn new(period: u32) -> napi::Result<Self> {
Self { Ok(Self {
inner: must(wc::Aroon::new(period as usize)), inner: wc::Aroon::new(period as usize).map_err(map_err)?,
} })
} }
#[napi] #[napi]
pub fn update(&mut self, high: f64, low: f64) -> napi::Result<Option<AroonValue>> { pub fn update(&mut self, high: f64, low: f64) -> napi::Result<Option<AroonValue>> {
@@ -802,14 +797,11 @@ pub struct KamaNode {
#[napi] #[napi]
impl KamaNode { impl KamaNode {
#[napi(constructor)] #[napi(constructor)]
pub fn new(er_period: u32, fast: u32, slow: u32) -> Self { pub fn new(er_period: u32, fast: u32, slow: u32) -> napi::Result<Self> {
Self { Ok(Self {
inner: must(wc::Kama::new( inner: wc::Kama::new(er_period as usize, fast as usize, slow as usize)
er_period as usize, .map_err(map_err)?,
fast as usize, })
slow as usize,
)),
}
} }
#[napi] #[napi]
pub fn update(&mut self, value: f64) -> Option<f64> { pub fn update(&mut self, value: f64) -> Option<f64> {