use std::fmt;
use std::str::FromStr;
use serde::{Deserialize, Serialize};
#[derive(Serialize)]
pub(crate) struct EmptyRequest {}
pub(crate) fn deserialize_vec_or_empty_string<'de, D, T>(
deserializer: D,
) -> Result<Vec<T>, D::Error>
where
D: serde::Deserializer<'de>,
T: Deserialize<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum WireValue<T> {
Sequence(Vec<T>),
String(String),
Null(()),
}
match WireValue::<T>::deserialize(deserializer)? {
WireValue::Sequence(values) => Ok(values),
WireValue::String(value) if value.is_empty() => Ok(Vec::new()),
WireValue::String(value) => Err(serde::de::Error::custom(format!(
"expected an array or empty string, got {value:?}"
))),
WireValue::Null(()) => Ok(Vec::new()),
}
}
#[derive(Debug, Clone, Deserialize)]
pub(crate) struct OkxResponse<D> {
pub code: String,
pub msg: String,
pub data: D,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct NumberString(String);
impl NumberString {
pub fn as_str(&self) -> &str {
&self.0
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn parse<T: FromStr>(&self) -> Result<T, T::Err> {
self.0.parse()
}
pub fn into_string(self) -> String {
self.0
}
#[cfg(feature = "rust-decimal")]
pub fn to_decimal(&self) -> Result<rust_decimal::Decimal, rust_decimal::Error> {
self.0.parse()
}
}
impl From<String> for NumberString {
fn from(s: String) -> Self {
NumberString(s)
}
}
impl From<&str> for NumberString {
fn from(s: &str) -> Self {
NumberString(s.to_owned())
}
}
impl AsRef<str> for NumberString {
fn as_ref(&self) -> &str {
&self.0
}
}
impl fmt::Display for NumberString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
macro_rules! string_enum {
(
$(#[$meta:meta])*
$vis:vis enum $name:ident {
$( $(#[$vmeta:meta])* $variant:ident = $wire:literal ),* $(,)?
}
) => {
$(#[$meta])*
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
$vis enum $name {
$( $(#[$vmeta])* $variant, )*
Unknown(String),
}
impl $name {
pub fn as_str(&self) -> &str {
match self {
$( $name::$variant => $wire, )*
$name::Unknown(s) => s.as_str(),
}
}
}
impl ::core::convert::From<&str> for $name {
fn from(s: &str) -> Self {
match s {
$( $wire => $name::$variant, )*
other => $name::Unknown(other.to_owned()),
}
}
}
impl ::core::fmt::Display for $name {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
f.write_str(self.as_str())
}
}
impl ::serde::Serialize for $name {
fn serialize<S: ::serde::Serializer>(&self, ser: S) -> ::core::result::Result<S::Ok, S::Error> {
ser.serialize_str(self.as_str())
}
}
impl<'de> ::serde::Deserialize<'de> for $name {
fn deserialize<D: ::serde::Deserializer<'de>>(de: D) -> ::core::result::Result<Self, D::Error> {
let s = <::std::string::String as ::serde::Deserialize>::deserialize(de)?;
::core::result::Result::Ok($name::from(s.as_str()))
}
}
};
}
string_enum! {
pub enum InstType {
Spot = "SPOT",
Margin = "MARGIN",
Swap = "SWAP",
Futures = "FUTURES",
Option = "OPTION",
Events = "EVENTS",
}
}
string_enum! {
pub enum OrderSide {
Buy = "buy",
Sell = "sell",
}
}
string_enum! {
pub enum OrderType {
Market = "market",
Limit = "limit",
PostOnly = "post_only",
Fok = "fok",
Ioc = "ioc",
OptimalLimitIoc = "optimal_limit_ioc",
}
}
string_enum! {
pub enum TradeMode {
Cash = "cash",
Cross = "cross",
Isolated = "isolated",
SpotIsolated = "spot_isolated",
}
}
string_enum! {
pub enum PositionSide {
Long = "long",
Short = "short",
Net = "net",
}
}
string_enum! {
pub enum OrderState {
Live = "live",
PartiallyFilled = "partially_filled",
Filled = "filled",
Canceled = "canceled",
MmpCanceled = "mmp_canceled",
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn number_string_parses_and_preserves() {
let n = NumberString::from("1.005");
assert_eq!(n.as_str(), "1.005");
assert_eq!(n.parse::<f64>().unwrap(), 1.005);
assert_eq!(n.into_string(), "1.005");
}
#[test]
fn known_enum_value_round_trips() {
let v: InstType = serde_json::from_str("\"SWAP\"").unwrap();
assert_eq!(v, InstType::Swap);
assert_eq!(serde_json::to_string(&v).unwrap(), "\"SWAP\"");
}
#[test]
fn unknown_enum_value_is_preserved_not_an_error() {
let v: InstType = serde_json::from_str("\"FUTURE_THING\"").unwrap();
assert_eq!(v, InstType::Unknown("FUTURE_THING".to_owned()));
assert_eq!(serde_json::to_string(&v).unwrap(), "\"FUTURE_THING\"");
}
}