polyoxide-binance 0.45.0

Rust client library for Binance USDⓈ-M futures and spot market data and streams
Documentation
//! What every route builder sends through: core's request builder, with the
//! route's weight as its cost.

use std::marker::PhantomData;

use polyoxide_core::{HttpClient, QueryBuilder, Request, RequestError};
use serde::de::DeserializeOwned;

/// A product's route, as [`Routed`] reads it: where the request goes and what
/// it costs.
pub(crate) trait Priced: Copy {
    /// What a request costs, in the product's own terms.
    type Cost: Copy + Into<polyoxide_core::Cost>;

    /// The route's path.
    fn path(self) -> &'static str;

    /// What sending the route costs.
    fn cost(self) -> Self::Cost;
}

/// A route and its query, sent as core's [`Request`] with the route's cost.
///
/// The route builders wrap one. Setting a query parameter twice replaces it,
/// and a parameter that changes the weight replaces the route with it.
pub(crate) struct Routed<T, R, E> {
    http: HttpClient,
    route: R,
    query: Vec<(&'static str, String)>,
    _marker: PhantomData<fn() -> (T, E)>,
}

impl<T, R, E> Routed<T, R, E> {
    pub(crate) fn new(http: &HttpClient, route: R) -> Self {
        Self {
            http: http.clone(),
            route,
            query: Vec::new(),
            _marker: PhantomData,
        }
    }

    /// Sets a query parameter, replacing an earlier value for the same key.
    pub(crate) fn query(mut self, key: &'static str, value: impl ToString) -> Self {
        let value = value.to_string();
        match self.query.iter_mut().find(|(k, _)| *k == key) {
            Some(slot) => slot.1 = value,
            None => self.query.push((key, value)),
        }
        self
    }

    /// Replaces the route, for a parameter that changes the weight.
    pub(crate) fn route(mut self, route: R) -> Self {
        self.route = route;
        self
    }
}

impl<T, R: Priced, E> Routed<T, R, E> {
    /// What sending this request will cost.
    pub(crate) fn cost(&self) -> R::Cost {
        self.route.cost()
    }
}

impl<T: DeserializeOwned, R: Priced, E: RequestError> Routed<T, R, E> {
    /// Sends on the client's send loop, which waits for the budget, and
    /// decodes the response.
    pub(crate) async fn send(self) -> Result<T, E> {
        let mut request =
            Request::<T, E>::new(self.http, self.route.path()).with_cost(self.route.cost().into());
        for (key, value) in self.query {
            request = request.query(key, value);
        }
        request.send().await
    }
}

/// A route builder with no parameters of its own beyond those its namespace
/// method sets: `cost` and `send`, like the builders with setters. A product
/// invokes it through its own `route_builder!`, which names its module and
/// error type.
macro_rules! route_builder {
    (
        $(#[$doc:meta])* $name:ident => $output:ty;
        module = $module:ident, error = $error:ident
    ) => {
        $(#[$doc])*
        #[must_use = "a request does nothing until it is sent"]
        pub struct $name {
            pub(crate) request: $crate::$module::request::Routed<$output>,
        }

        impl $name {
            /// What sending this request will cost.
            pub fn cost(&self) -> $crate::$module::weight::Cost {
                self.request.cost()
            }

            /// Waits for the budget, sends, and decodes the response.
            ///
            /// A `429` holds every request on the budget, then this request
            /// is retried on the client's schedule; with no retry left and no
            /// `Retry-After`, the hold runs to the next UTC minute. A `418`
            /// holds every request for its `Retry-After`, or 2 minutes, and
            /// is not retried.
            ///
            /// The wait has no deadline of its own: a request can wait out a
            /// minute's spent budget or a ban, which Binance documents at up
            /// to 3 days. Wrap the call in `tokio::time::timeout` where a
            /// caller needs a deadline.
            pub async fn send(self) -> Result<$output, $crate::$module::$error> {
                self.request.send().await
            }
        }
    };
}

pub(crate) use route_builder;