use chrono::{DateTime, Utc};
use derive_builder::Builder;
use serde::{self, Deserialize, Serialize, Serializer};
use serde_json::Value;
fn serialize_json_as_string<S>(value: &Option<Value>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match value {
Some(v) => serializer.serialize_str(&v.to_string()),
None => serializer.serialize_none(),
}
}
use crate::common::enums::{
BitmexContingencyType, BitmexExecInstruction, BitmexOrderType, BitmexPegPriceType, BitmexSide,
BitmexTimeInForce,
};
fn serialize_string_vec_as_json<S>(
values: &Option<Vec<String>>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match values {
Some(vec) => {
let json_array = serde_json::to_string(vec).map_err(serde::ser::Error::custom)?;
serializer.serialize_str(&json_array)
}
None => serializer.serialize_none(),
}
}
#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
#[builder(default)]
#[builder(setter(into, strip_option))]
#[serde(rename_all = "camelCase")]
pub struct GetTradeParams {
pub symbol: Option<String>,
#[serde(
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_json_as_string"
)]
pub filter: Option<Value>,
#[serde(
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_json_as_string"
)]
pub columns: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub count: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub start: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reverse: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub start_time: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub end_time: Option<DateTime<Utc>>,
}
#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
#[builder(default)]
#[builder(setter(into, strip_option))]
#[serde(rename_all = "camelCase")]
pub struct GetTradeBucketedParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub symbol: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bin_size: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub partial: Option<bool>,
#[serde(
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_json_as_string"
)]
pub filter: Option<Value>,
#[serde(
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_json_as_string"
)]
pub columns: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub count: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub start: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reverse: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub start_time: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub end_time: Option<DateTime<Utc>>,
}
#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
#[builder(default)]
#[builder(setter(into, strip_option))]
#[serde(rename_all = "camelCase")]
pub struct GetOrderParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub symbol: Option<String>,
#[serde(
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_json_as_string"
)]
pub filter: Option<Value>,
#[serde(
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_json_as_string"
)]
pub columns: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub count: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub start: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reverse: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub start_time: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub end_time: Option<DateTime<Utc>>,
}
#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
#[builder(default)]
#[builder(setter(into, strip_option))]
#[serde(rename_all = "camelCase")]
pub struct PostOrderParams {
pub symbol: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub side: Option<BitmexSide>,
#[serde(skip_serializing_if = "Option::is_none")]
pub order_qty: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub price: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub display_qty: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop_px: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "clOrdID")]
pub cl_ord_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "clOrdLinkID")]
pub cl_ord_link_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub peg_offset_value: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub peg_price_type: Option<BitmexPegPriceType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ord_type: Option<BitmexOrderType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub time_in_force: Option<BitmexTimeInForce>,
#[serde(
serialize_with = "serialize_exec_instructions_optional",
skip_serializing_if = "is_exec_inst_empty"
)]
pub exec_inst: Option<Vec<BitmexExecInstruction>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub contingency_type: Option<BitmexContingencyType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
}
fn is_exec_inst_empty(exec_inst: &Option<Vec<BitmexExecInstruction>>) -> bool {
exec_inst.as_ref().is_none_or(Vec::is_empty)
}
fn serialize_exec_instructions_optional<S>(
instructions: &Option<Vec<BitmexExecInstruction>>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match instructions {
Some(inst) if !inst.is_empty() => {
let joined = inst
.iter()
.map(std::string::ToString::to_string)
.collect::<Vec<_>>()
.join(",");
serializer.serialize_some(&joined)
}
_ => serializer.serialize_none(),
}
}
#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
#[builder(default)]
#[builder(setter(into, strip_option))]
#[serde(rename_all = "camelCase")]
pub struct DeleteOrderParams {
#[serde(
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_string_vec_as_json",
rename = "orderID"
)]
pub order_id: Option<Vec<String>>,
#[serde(
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_string_vec_as_json",
rename = "clOrdID"
)]
pub cl_ord_id: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
}
impl DeleteOrderParamsBuilder {
pub fn build_validated(self) -> Result<DeleteOrderParams, String> {
let params = self.build().map_err(|e| format!("Failed to build: {e}"))?;
if params.order_id.is_some() && params.cl_ord_id.is_some() {
return Err("Cannot provide both order_id and cl_ord_id - use only one".to_string());
}
if params.order_id.is_none() && params.cl_ord_id.is_none() {
return Err("Must provide either order_id or cl_ord_id".to_string());
}
Ok(params)
}
}
#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
#[builder(default)]
#[builder(setter(into, strip_option))]
#[serde(rename_all = "camelCase")]
pub struct DeleteAllOrdersParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub symbol: Option<String>,
#[serde(
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_json_as_string"
)]
pub filter: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
#[builder(default)]
#[builder(setter(into, strip_option))]
#[serde(rename_all = "camelCase")]
pub struct PutOrderParams {
#[serde(rename = "orderID")]
pub order_id: Option<String>,
#[serde(rename = "origClOrdID")]
pub orig_cl_ord_id: Option<String>,
#[serde(rename = "clOrdID")]
pub cl_ord_id: Option<String>,
pub order_qty: Option<u32>,
pub leaves_qty: Option<u32>,
pub price: Option<f64>,
pub stop_px: Option<f64>,
pub peg_offset_value: Option<f64>,
pub text: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
#[builder(default)]
#[builder(setter(into, strip_option))]
#[serde(rename_all = "camelCase")]
pub struct GetExecutionParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub symbol: Option<String>,
#[serde(
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_json_as_string"
)]
pub filter: Option<Value>,
#[serde(
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_json_as_string"
)]
pub columns: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub count: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub start: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reverse: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub start_time: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub end_time: Option<DateTime<Utc>>,
}
#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
#[builder(default)]
#[builder(setter(into, strip_option))]
#[serde(rename_all = "camelCase")]
pub struct PostPositionLeverageParams {
pub symbol: String,
pub leverage: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub target_account_id: Option<i64>,
}
#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
#[builder(default)]
#[builder(setter(into, strip_option))]
pub struct PostCancelAllAfterParams {
pub timeout: u64,
}
#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
#[builder(default)]
#[builder(setter(into, strip_option))]
#[serde(rename_all = "camelCase")]
pub struct GetPositionParams {
#[serde(
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_json_as_string"
)]
pub filter: Option<Value>,
#[serde(
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_json_as_string"
)]
pub columns: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub count: Option<i32>,
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[rstest]
fn test_cancel_all_after_params_serializes() {
let params = PostCancelAllAfterParams { timeout: 60_000 };
let encoded = serde_urlencoded::to_string(¶ms).unwrap();
assert_eq!(encoded, "timeout=60000");
}
#[rstest]
fn test_cancel_all_after_params_disarm_serializes() {
let params = PostCancelAllAfterParams { timeout: 0 };
let encoded = serde_urlencoded::to_string(¶ms).unwrap();
assert_eq!(encoded, "timeout=0");
}
}