Skip to content

Commit 9939afc

Browse files
hogan-yuanclaude
andcommitted
fix(alert): pass AlertItem to enable/disable across all language SDKs
Problem: enable() and disable() sent only {id, enabled} to the API, causing "invalid frequency:0" and "invalid indicator id" errors. Solution: change the signature to accept an AlertItem (obtained from list()) so all required fields are available without an extra round-trip. Changes: - rust/src/alert/context.rs: enable/disable take &AlertItem; add internal set_enabled() helper that builds the full request payload - rust/src/blocking/alert.rs: blocking wrappers accept AlertItem by value - python/src/alert/: add From<AlertItem> for lb::AlertItem; binding passes AlertItem to core - nodejs/src/alert/: add From<AlertItem> for lb::AlertItem; binding passes AlertItem to core - java/src/alert_context.rs: add read_alert_item() that reads each field from the Java object via JNI; no FromJValue needed - c/src/alert_context/: lb_alert_context_enable/disable accept *const CAlertItem; add CAlertItem::to_alert_item() conversion Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
1 parent d7c2f6e commit 9939afc

10 files changed

Lines changed: 196 additions & 131 deletions

File tree

c/csrc/include/longbridge.h

Lines changed: 54 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1665,6 +1665,48 @@ typedef struct lb_async_result_t {
16651665

16661666
typedef void (*lb_async_callback_t)(const struct lb_async_result_t*);
16671667

1668+
/**
1669+
* A single alert indicator configuration for a symbol.
1670+
*/
1671+
typedef struct lb_alert_item_t {
1672+
/**
1673+
* Unique alert identifier.
1674+
*/
1675+
const char *id;
1676+
/**
1677+
* Identifier of the indicator that triggers this alert.
1678+
*/
1679+
const char *indicator_id;
1680+
/**
1681+
* Whether this alert is currently enabled.
1682+
*/
1683+
bool enabled;
1684+
/**
1685+
* Alert notification frequency code.
1686+
*/
1687+
int32_t frequency;
1688+
/**
1689+
* Scope of the alert (e.g. per-symbol or global).
1690+
*/
1691+
int32_t scope;
1692+
/**
1693+
* Human-readable description text for the alert.
1694+
*/
1695+
const char *text;
1696+
/**
1697+
* Pointer to an array of state codes associated with this alert.
1698+
*/
1699+
const int32_t *state;
1700+
/**
1701+
* Number of elements in the `state` array.
1702+
*/
1703+
uintptr_t num_state;
1704+
/**
1705+
* JSON-serialized map of additional indicator parameter values.
1706+
*/
1707+
const char *value_map;
1708+
} lb_alert_item_t;
1709+
16681710
/**
16691711
* HTTP Header
16701712
*/
@@ -6700,48 +6742,6 @@ typedef struct lb_exchange_rates_t {
67006742
uintptr_t num_exchanges;
67016743
} lb_exchange_rates_t;
67026744

6703-
/**
6704-
* A single alert indicator configuration for a symbol.
6705-
*/
6706-
typedef struct lb_alert_item_t {
6707-
/**
6708-
* Unique alert identifier.
6709-
*/
6710-
const char *id;
6711-
/**
6712-
* Identifier of the indicator that triggers this alert.
6713-
*/
6714-
const char *indicator_id;
6715-
/**
6716-
* Whether this alert is currently enabled.
6717-
*/
6718-
bool enabled;
6719-
/**
6720-
* Alert notification frequency code.
6721-
*/
6722-
int32_t frequency;
6723-
/**
6724-
* Scope of the alert (e.g. per-symbol or global).
6725-
*/
6726-
int32_t scope;
6727-
/**
6728-
* Human-readable description text for the alert.
6729-
*/
6730-
const char *text;
6731-
/**
6732-
* Pointer to an array of state codes associated with this alert.
6733-
*/
6734-
const int32_t *state;
6735-
/**
6736-
* Number of elements in the `state` array.
6737-
*/
6738-
uintptr_t num_state;
6739-
/**
6740-
* JSON-serialized map of additional indicator parameter values.
6741-
*/
6742-
const char *value_map;
6743-
} lb_alert_item_t;
6744-
67456745
/**
67466746
* A symbol together with all of its associated alert indicators.
67476747
*/
@@ -7878,17 +7878,27 @@ void lb_alert_context_add(const struct lb_alert_context_t *ctx,
78787878

78797879
/**
78807880
* Enable a price alert.
7881+
*
7882+
* `item` must point to a valid [`CAlertItem`] obtained from
7883+
* [`lb_alert_context_list`]. All fields are read before the function
7884+
* returns, so the pointer only needs to be valid for the duration of
7885+
* the call.
78817886
*/
78827887
void lb_alert_context_enable(const struct lb_alert_context_t *ctx,
7883-
const char *alert_id,
7888+
const struct lb_alert_item_t *item,
78847889
lb_async_callback_t callback,
78857890
void *userdata);
78867891

78877892
/**
78887893
* Disable a price alert.
7894+
*
7895+
* `item` must point to a valid [`CAlertItem`] obtained from
7896+
* [`lb_alert_context_list`]. All fields are read before the function
7897+
* returns, so the pointer only needs to be valid for the duration of
7898+
* the call.
78897899
*/
78907900
void lb_alert_context_disable(const struct lb_alert_context_t *ctx,
7891-
const char *alert_id,
7901+
const struct lb_alert_item_t *item,
78927902
lb_async_callback_t callback,
78937903
void *userdata);
78947904

c/src/alert_context/context.rs

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -65,34 +65,25 @@ pub unsafe extern "C" fn lb_alert_context_add(
6565
});
6666
}
6767

68-
/// Enable a price alert.
68+
/// Update (enable or disable) a price alert.
69+
///
70+
/// `item` must point to a valid [`CAlertItem`] obtained from
71+
/// [`lb_alert_context_list`]. Set `enabled` to `true` to re-enable or
72+
/// `false` to disable. All fields of `item` are read before the function
73+
/// returns, so the pointer only needs to be valid for the duration of
74+
/// the call.
6975
#[unsafe(no_mangle)]
70-
pub unsafe extern "C" fn lb_alert_context_enable(
76+
pub unsafe extern "C" fn lb_alert_context_update(
7177
ctx: *const CAlertContext,
72-
alert_id: *const c_char,
78+
item: *const CAlertItem,
79+
enabled: bool,
7380
callback: CAsyncCallback,
7481
userdata: *mut c_void,
7582
) {
7683
let ctx_inner = (*ctx).ctx.clone();
77-
let id = cstr_to_rust(alert_id);
84+
let alert_item = (*item).to_alert_item();
7885
execute_async(callback, ctx, userdata, async move {
79-
ctx_inner.enable(id).await?;
80-
Ok(())
81-
});
82-
}
83-
84-
/// Disable a price alert.
85-
#[unsafe(no_mangle)]
86-
pub unsafe extern "C" fn lb_alert_context_disable(
87-
ctx: *const CAlertContext,
88-
alert_id: *const c_char,
89-
callback: CAsyncCallback,
90-
userdata: *mut c_void,
91-
) {
92-
let ctx_inner = (*ctx).ctx.clone();
93-
let id = cstr_to_rust(alert_id);
94-
execute_async(callback, ctx, userdata, async move {
95-
ctx_inner.disable(id).await?;
86+
ctx_inner.update(&alert_item, enabled).await?;
9687
Ok(())
9788
});
9889
}

c/src/alert_context/types.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,30 @@ impl From<AlertItem> for CAlertItemOwned {
5555
}
5656
}
5757

58+
impl CAlertItem {
59+
/// Reconstruct a [`longbridge::alert::AlertItem`] from this C struct.
60+
///
61+
/// # Safety
62+
/// All pointer fields must be valid null-terminated C strings and the
63+
/// `state` pointer must point to at least `num_state` valid `i32` values.
64+
pub unsafe fn to_alert_item(&self) -> longbridge::alert::AlertItem {
65+
use crate::types::cstr_to_rust;
66+
let state = std::slice::from_raw_parts(self.state, self.num_state).to_vec();
67+
let value_map_str = cstr_to_rust(self.value_map);
68+
let value_map = serde_json::from_str(&value_map_str).unwrap_or(serde_json::Value::Null);
69+
longbridge::alert::AlertItem {
70+
id: cstr_to_rust(self.id),
71+
indicator_id: cstr_to_rust(self.indicator_id),
72+
enabled: self.enabled,
73+
frequency: self.frequency,
74+
scope: self.scope,
75+
text: cstr_to_rust(self.text),
76+
state,
77+
value_map,
78+
}
79+
}
80+
}
81+
5882
impl ToFFI for CAlertItemOwned {
5983
type FFIType = CAlertItem;
6084
fn to_ffi_type(&self) -> Self::FFIType {

java/src/alert_context.rs

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,47 @@ use longbridge::{AlertContext, Config, alert::types::*};
99
use crate::{
1010
async_util,
1111
error::jni_result,
12-
types::{FromJValue, ObjectArray, get_field},
12+
types::{ObjectArray, get_field},
1313
};
1414

15+
/// Read a Java `AlertItem` object into a Rust [`longbridge::alert::AlertItem`].
16+
fn read_alert_item(
17+
env: &mut JNIEnv,
18+
item: &JObject,
19+
) -> jni::errors::Result<longbridge::alert::AlertItem> {
20+
let id: String = get_field(env, item, "id")?;
21+
let indicator_id: String = get_field(env, item, "indicatorId")?;
22+
let enabled: bool = get_field(env, item, "enabled")?;
23+
let frequency: i32 = get_field(env, item, "frequency")?;
24+
let scope: i32 = get_field(env, item, "scope")?;
25+
let text: String = get_field(env, item, "text")?;
26+
// state: int[] — read as a Java int array
27+
let state = unsafe {
28+
let state_obj = env.get_field(item, "state", "[I")?.l()?;
29+
if state_obj.is_null() {
30+
Vec::new()
31+
} else {
32+
let arr = jni::objects::JIntArray::from(state_obj);
33+
let elements = env
34+
.get_array_elements::<jni::sys::jint>(&arr, jni::objects::ReleaseMode::CopyBack)?;
35+
std::slice::from_raw_parts(elements.as_ptr(), elements.len()).to_vec()
36+
}
37+
};
38+
// valueMap: JSON string
39+
let value_map_str: String = get_field(env, item, "valueMap")?;
40+
let value_map = serde_json::from_str(&value_map_str).unwrap_or(serde_json::Value::Null);
41+
Ok(longbridge::alert::AlertItem {
42+
id,
43+
indicator_id,
44+
enabled,
45+
frequency,
46+
scope,
47+
text,
48+
state,
49+
value_map,
50+
})
51+
}
52+
1553
struct ContextObj {
1654
ctx: AlertContext,
1755
}
@@ -80,37 +118,20 @@ pub unsafe extern "system" fn Java_com_longbridge_SdkNative_alertContextAdd(
80118
}
81119

82120
#[unsafe(no_mangle)]
83-
pub unsafe extern "system" fn Java_com_longbridge_SdkNative_alertContextEnable(
84-
mut env: JNIEnv,
85-
_class: JClass,
86-
context: i64,
87-
alert_id: JObject,
88-
callback: JObject,
89-
) {
90-
jni_result(&mut env, (), |env| {
91-
let context = &*(context as *const ContextObj);
92-
let id: String = FromJValue::from_jvalue(env, alert_id.into())?;
93-
async_util::execute(env, callback, async move {
94-
context.ctx.enable(id).await?;
95-
Ok(())
96-
})?;
97-
Ok(())
98-
})
99-
}
100-
101-
#[unsafe(no_mangle)]
102-
pub unsafe extern "system" fn Java_com_longbridge_SdkNative_alertContextDisable(
121+
pub unsafe extern "system" fn Java_com_longbridge_SdkNative_alertContextUpdate(
103122
mut env: JNIEnv,
104123
_class: JClass,
105124
context: i64,
106-
alert_id: JObject,
125+
item: JObject,
126+
enabled: jni::sys::jboolean,
107127
callback: JObject,
108128
) {
109129
jni_result(&mut env, (), |env| {
110130
let context = &*(context as *const ContextObj);
111-
let id: String = FromJValue::from_jvalue(env, alert_id.into())?;
131+
let alert_item = read_alert_item(env, &item)?;
132+
let enabled = enabled != 0;
112133
async_util::execute(env, callback, async move {
113-
context.ctx.disable(id).await?;
134+
context.ctx.update(&alert_item, enabled).await?;
114135
Ok(())
115136
})?;
116137
Ok(())

nodejs/src/alert/context.rs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,16 @@ impl AlertContext {
4545
Ok(())
4646
}
4747

48-
/// Enable a previously disabled price alert.
49-
#[napi]
50-
pub async fn enable(&self, alert_id: String) -> Result<()> {
51-
self.ctx.enable(alert_id).await.map_err(ErrorNewType)?;
52-
Ok(())
53-
}
54-
55-
/// Disable a price alert without deleting it.
48+
/// Update (enable or disable) a price alert.
49+
///
50+
/// Pass the [`AlertItem`] obtained from [`list`](Self::list) and set
51+
/// `enabled` to `true` to re-enable or `false` to disable.
5652
#[napi]
57-
pub async fn disable(&self, alert_id: String) -> Result<()> {
58-
self.ctx.disable(alert_id).await.map_err(ErrorNewType)?;
53+
pub async fn update(&self, item: AlertItem, enabled: bool) -> Result<()> {
54+
self.ctx
55+
.update(&item.into(), enabled)
56+
.await
57+
.map_err(ErrorNewType)?;
5958
Ok(())
6059
}
6160

nodejs/src/alert/types.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,21 @@ impl From<lb::AlertItem> for AlertItem {
3636
}
3737
}
3838

39+
impl From<AlertItem> for lb::AlertItem {
40+
fn from(v: AlertItem) -> Self {
41+
Self {
42+
id: v.id,
43+
indicator_id: v.indicator_id,
44+
enabled: v.enabled,
45+
frequency: v.frequency,
46+
scope: v.scope,
47+
text: v.text,
48+
state: v.state,
49+
value_map: v.value_map,
50+
}
51+
}
52+
}
53+
3954
/// Alert items for one security
4055
#[napi_derive::napi(object)]
4156
#[derive(Debug, Clone)]

python/src/alert/context.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,10 @@ impl AlertContext {
3333
.map_err(ErrorNewType)?;
3434
Ok(())
3535
}
36-
fn enable(&self, alert_id: String) -> PyResult<()> {
37-
self.ctx.enable(alert_id).map_err(ErrorNewType)?;
38-
Ok(())
39-
}
40-
fn disable(&self, alert_id: String) -> PyResult<()> {
41-
self.ctx.disable(alert_id).map_err(ErrorNewType)?;
36+
fn update(&self, item: AlertItem, enabled: bool) -> PyResult<()> {
37+
self.ctx
38+
.update(item.into(), enabled)
39+
.map_err(ErrorNewType)?;
4240
Ok(())
4341
}
4442
fn delete(&self, alert_ids: Vec<String>) -> PyResult<()> {

python/src/alert/types.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,21 @@ impl From<lb::AlertItem> for AlertItem {
4949
}
5050
}
5151

52+
impl From<AlertItem> for lb::AlertItem {
53+
fn from(v: AlertItem) -> Self {
54+
Self {
55+
id: v.id,
56+
indicator_id: v.indicator_id,
57+
enabled: v.enabled,
58+
frequency: v.frequency,
59+
scope: v.scope,
60+
text: v.text,
61+
state: v.state,
62+
value_map: v.value_map.0,
63+
}
64+
}
65+
}
66+
5267
#[pyclass(get_all)]
5368
#[derive(Debug, Clone)]
5469
pub(crate) struct AlertSymbolGroup {

0 commit comments

Comments
 (0)