1use brokapi_core::{Charset, CidCaps, CidCodec, CidError, ClientOrderId, NativeCid};
5use uuid::Uuid;
6
7#[derive(Debug, Clone, Copy, Default)]
12pub struct BybitCidCodec;
13
14impl CidCodec for BybitCidCodec {
15 fn caps(&self) -> CidCaps {
16 CidCaps {
17 max_len: 36,
18 charset: Charset::Ascii,
19 native_bits: 128,
20 lossless: true,
21 }
22 }
23
24 fn encode(&self, cid: ClientOrderId) -> Result<NativeCid, CidError> {
25 let s = cid.hyphenated().to_string();
26 if s.len() > self.caps().max_len {
27 return Err(CidError::TooLong {
28 len: s.len(),
29 max: self.caps().max_len,
30 });
31 }
32 Ok(NativeCid::new(s))
33 }
34
35 fn decode(&self, native: &NativeCid) -> Option<ClientOrderId> {
36 Uuid::parse_str(native.as_str()).ok()
37 }
38}
39
40pub fn cid_from_link_id(link_id: Option<&str>) -> Option<ClientOrderId> {
43 Uuid::parse_str(link_id?.trim()).ok()
44}
45
46const FOREIGN_NS: Uuid = Uuid::from_u128(0x3d19_c0b2_6e7f_4a58_8c41_b7a2_5e90_0002);
48
49pub fn foreign_cid(order_id: &str) -> ClientOrderId {
52 Uuid::new_v5(&FOREIGN_NS, order_id.as_bytes())
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58 use proptest::prelude::*;
59
60 #[test]
61 fn foreign_cids_are_stable_and_distinct() {
62 assert_eq!(
63 foreign_cid("1616024329462743808"),
64 foreign_cid("1616024329462743808")
65 );
66 assert_ne!(
67 foreign_cid("1616024329462743808"),
68 foreign_cid("1616024329462743809")
69 );
70 assert_eq!(foreign_cid("x").get_version_num(), 5);
71 }
72
73 #[test]
74 fn only_a_whole_uuid_in_the_link_id_is_a_cid() {
75 let cid = Uuid::new_v4();
76 assert_eq!(cid_from_link_id(Some(&cid.to_string())), Some(cid));
77 assert_eq!(cid_from_link_id(Some("my-first-order")), None);
78 assert_eq!(cid_from_link_id(None), None);
79 }
80
81 proptest! {
82 #[test]
83 fn bybit_cid_roundtrip(bytes in any::<[u8; 16]>()) {
84 let cid = ClientOrderId::from_bytes(bytes);
85 let native = BybitCidCodec.encode(cid).expect("encode");
86 prop_assert_eq!(BybitCidCodec.decode(&native), Some(cid));
87 }
88 }
89}