Skip to content

Commit c150173

Browse files
authored
Merge pull request #35 from joaquinbejar/M1/issue-15-cancel-on-disconnect
feat: implement cancel-on-disconnect methods
2 parents b066de0 + 77ef3ba commit c150173

File tree

4 files changed

+252
-0
lines changed

4 files changed

+252
-0
lines changed

src/client.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,113 @@ impl DeribitWebSocketClient {
409409
self.send_request(request).await
410410
}
411411

412+
/// Enable automatic order cancellation on disconnect
413+
///
414+
/// When enabled, all open orders will be automatically cancelled if the WebSocket
415+
/// connection is lost. This is a safety feature to prevent unintended order
416+
/// execution when the client loses connectivity.
417+
///
418+
/// # Returns
419+
///
420+
/// Returns `"ok"` on success
421+
///
422+
/// # Errors
423+
///
424+
/// Returns an error if the request fails or requires authentication
425+
pub async fn enable_cancel_on_disconnect(&self) -> Result<String, WebSocketError> {
426+
let request = {
427+
let mut builder = self.request_builder.lock().await;
428+
builder.build_enable_cancel_on_disconnect_request()
429+
};
430+
431+
let response = self.send_request(request).await?;
432+
433+
match response.result {
434+
JsonRpcResult::Success { result } => {
435+
result.as_str().map(String::from).ok_or_else(|| {
436+
WebSocketError::InvalidMessage(
437+
"Expected string result from enable_cancel_on_disconnect".to_string(),
438+
)
439+
})
440+
}
441+
JsonRpcResult::Error { error } => {
442+
Err(WebSocketError::ApiError(error.code, error.message))
443+
}
444+
}
445+
}
446+
447+
/// Disable automatic order cancellation on disconnect
448+
///
449+
/// When disabled, orders will remain active even if the WebSocket connection
450+
/// is lost.
451+
///
452+
/// # Returns
453+
///
454+
/// Returns `"ok"` on success
455+
///
456+
/// # Errors
457+
///
458+
/// Returns an error if the request fails or requires authentication
459+
pub async fn disable_cancel_on_disconnect(&self) -> Result<String, WebSocketError> {
460+
let request = {
461+
let mut builder = self.request_builder.lock().await;
462+
builder.build_disable_cancel_on_disconnect_request()
463+
};
464+
465+
let response = self.send_request(request).await?;
466+
467+
match response.result {
468+
JsonRpcResult::Success { result } => {
469+
result.as_str().map(String::from).ok_or_else(|| {
470+
WebSocketError::InvalidMessage(
471+
"Expected string result from disable_cancel_on_disconnect".to_string(),
472+
)
473+
})
474+
}
475+
JsonRpcResult::Error { error } => {
476+
Err(WebSocketError::ApiError(error.code, error.message))
477+
}
478+
}
479+
}
480+
481+
/// Get current cancel-on-disconnect status
482+
///
483+
/// Returns whether automatic order cancellation on disconnect is currently enabled.
484+
///
485+
/// # Returns
486+
///
487+
/// Returns `true` if cancel-on-disconnect is enabled, `false` otherwise
488+
///
489+
/// # Errors
490+
///
491+
/// Returns an error if the request fails or requires authentication
492+
pub async fn get_cancel_on_disconnect(&self) -> Result<bool, WebSocketError> {
493+
let request = {
494+
let mut builder = self.request_builder.lock().await;
495+
builder.build_get_cancel_on_disconnect_request()
496+
};
497+
498+
let response = self.send_request(request).await?;
499+
500+
match response.result {
501+
JsonRpcResult::Success { result } => {
502+
// The result contains "enabled" field
503+
result
504+
.get("enabled")
505+
.and_then(|v| v.as_bool())
506+
.ok_or_else(|| {
507+
WebSocketError::InvalidMessage(
508+
"Expected 'enabled' boolean in get_cancel_on_disconnect response"
509+
.to_string(),
510+
)
511+
})
512+
}
513+
JsonRpcResult::Error { error } => {
514+
Err(WebSocketError::ApiError(error.code, error.message))
515+
}
516+
}
517+
}
518+
412519
/// Place mass quotes
413520
pub async fn mass_quote(
414521
&self,

src/constants.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,14 @@ pub mod methods {
8585
/// Move positions between subaccounts
8686
pub const PRIVATE_MOVE_POSITIONS: &str = "private/move_positions";
8787

88+
// Cancel-on-disconnect
89+
/// Enable cancel-on-disconnect
90+
pub const PRIVATE_ENABLE_CANCEL_ON_DISCONNECT: &str = "private/enable_cancel_on_disconnect";
91+
/// Disable cancel-on-disconnect
92+
pub const PRIVATE_DISABLE_CANCEL_ON_DISCONNECT: &str = "private/disable_cancel_on_disconnect";
93+
/// Get cancel-on-disconnect status
94+
pub const PRIVATE_GET_CANCEL_ON_DISCONNECT: &str = "private/get_cancel_on_disconnect";
95+
8896
// Test
8997
/// Test connection
9098
pub const PUBLIC_TEST: &str = "public/test";

src/message/request.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,51 @@ impl RequestBuilder {
167167
self.build_request("public/get_time", None)
168168
}
169169

170+
/// Build enable_cancel_on_disconnect request
171+
///
172+
/// Enables automatic cancellation of all open orders when the WebSocket connection
173+
/// is lost. This is a safety feature to prevent unintended order execution when
174+
/// the client loses connectivity.
175+
///
176+
/// # Returns
177+
///
178+
/// A JSON-RPC request for enabling cancel-on-disconnect
179+
pub fn build_enable_cancel_on_disconnect_request(&mut self) -> JsonRpcRequest {
180+
self.build_request(
181+
crate::constants::methods::PRIVATE_ENABLE_CANCEL_ON_DISCONNECT,
182+
Some(serde_json::json!({})),
183+
)
184+
}
185+
186+
/// Build disable_cancel_on_disconnect request
187+
///
188+
/// Disables automatic cancellation of orders on disconnect. Orders will remain
189+
/// active even if the WebSocket connection is lost.
190+
///
191+
/// # Returns
192+
///
193+
/// A JSON-RPC request for disabling cancel-on-disconnect
194+
pub fn build_disable_cancel_on_disconnect_request(&mut self) -> JsonRpcRequest {
195+
self.build_request(
196+
crate::constants::methods::PRIVATE_DISABLE_CANCEL_ON_DISCONNECT,
197+
Some(serde_json::json!({})),
198+
)
199+
}
200+
201+
/// Build get_cancel_on_disconnect request
202+
///
203+
/// Retrieves the current cancel-on-disconnect status for the session.
204+
///
205+
/// # Returns
206+
///
207+
/// A JSON-RPC request for getting the cancel-on-disconnect status
208+
pub fn build_get_cancel_on_disconnect_request(&mut self) -> JsonRpcRequest {
209+
self.build_request(
210+
crate::constants::methods::PRIVATE_GET_CANCEL_ON_DISCONNECT,
211+
Some(serde_json::json!({})),
212+
)
213+
}
214+
170215
/// Build mass quote request
171216
pub fn build_mass_quote_request(&mut self, request: MassQuoteRequest) -> JsonRpcRequest {
172217
let params = serde_json::to_value(request).expect("Failed to serialize mass quote request");

tests/unit/message.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,3 +269,95 @@ fn test_request_builder_incremental_ids_session_methods() {
269269
assert_eq!(id2, id1 + 1);
270270
assert_eq!(id3, id2 + 1);
271271
}
272+
273+
// =============================================================================
274+
// Cancel-on-disconnect request tests (Issue #15)
275+
// =============================================================================
276+
277+
#[test]
278+
fn test_request_builder_enable_cancel_on_disconnect() {
279+
let mut builder = RequestBuilder::new();
280+
let request = builder.build_enable_cancel_on_disconnect_request();
281+
282+
assert_eq!(request.method, "private/enable_cancel_on_disconnect");
283+
assert_eq!(request.jsonrpc, "2.0");
284+
assert!(request.id.is_number());
285+
assert!(request.params.is_some());
286+
}
287+
288+
#[test]
289+
fn test_request_builder_disable_cancel_on_disconnect() {
290+
let mut builder = RequestBuilder::new();
291+
let request = builder.build_disable_cancel_on_disconnect_request();
292+
293+
assert_eq!(request.method, "private/disable_cancel_on_disconnect");
294+
assert_eq!(request.jsonrpc, "2.0");
295+
assert!(request.id.is_number());
296+
assert!(request.params.is_some());
297+
}
298+
299+
#[test]
300+
fn test_request_builder_get_cancel_on_disconnect() {
301+
let mut builder = RequestBuilder::new();
302+
let request = builder.build_get_cancel_on_disconnect_request();
303+
304+
assert_eq!(request.method, "private/get_cancel_on_disconnect");
305+
assert_eq!(request.jsonrpc, "2.0");
306+
assert!(request.id.is_number());
307+
assert!(request.params.is_some());
308+
}
309+
310+
#[test]
311+
fn test_request_builder_enable_cancel_on_disconnect_serialization() {
312+
let mut builder = RequestBuilder::new();
313+
let request = builder.build_enable_cancel_on_disconnect_request();
314+
315+
let serialized = serde_json::to_string(&request).unwrap();
316+
let parsed: serde_json::Value = serde_json::from_str(&serialized).unwrap();
317+
318+
assert_eq!(parsed["jsonrpc"], "2.0");
319+
assert_eq!(parsed["method"], "private/enable_cancel_on_disconnect");
320+
assert!(parsed["id"].is_number());
321+
}
322+
323+
#[test]
324+
fn test_request_builder_disable_cancel_on_disconnect_serialization() {
325+
let mut builder = RequestBuilder::new();
326+
let request = builder.build_disable_cancel_on_disconnect_request();
327+
328+
let serialized = serde_json::to_string(&request).unwrap();
329+
let parsed: serde_json::Value = serde_json::from_str(&serialized).unwrap();
330+
331+
assert_eq!(parsed["jsonrpc"], "2.0");
332+
assert_eq!(parsed["method"], "private/disable_cancel_on_disconnect");
333+
assert!(parsed["id"].is_number());
334+
}
335+
336+
#[test]
337+
fn test_request_builder_get_cancel_on_disconnect_serialization() {
338+
let mut builder = RequestBuilder::new();
339+
let request = builder.build_get_cancel_on_disconnect_request();
340+
341+
let serialized = serde_json::to_string(&request).unwrap();
342+
let parsed: serde_json::Value = serde_json::from_str(&serialized).unwrap();
343+
344+
assert_eq!(parsed["jsonrpc"], "2.0");
345+
assert_eq!(parsed["method"], "private/get_cancel_on_disconnect");
346+
assert!(parsed["id"].is_number());
347+
}
348+
349+
#[test]
350+
fn test_request_builder_incremental_ids_cancel_on_disconnect() {
351+
let mut builder = RequestBuilder::new();
352+
353+
let req1 = builder.build_enable_cancel_on_disconnect_request();
354+
let req2 = builder.build_disable_cancel_on_disconnect_request();
355+
let req3 = builder.build_get_cancel_on_disconnect_request();
356+
357+
let id1 = req1.id.as_u64().unwrap();
358+
let id2 = req2.id.as_u64().unwrap();
359+
let id3 = req3.id.as_u64().unwrap();
360+
361+
assert_eq!(id2, id1 + 1);
362+
assert_eq!(id3, id2 + 1);
363+
}

0 commit comments

Comments
 (0)