Skip to main content

polyester/services/
mod.rs

1//! Service wrappers over generated Connect clients.
2
3mod api_keys;
4mod auth;
5mod balances;
6mod correlation_id;
7mod deposit_withdraw;
8mod market_data;
9mod orders;
10mod profile;
11mod scope;
12mod thin;
13mod triggers;
14mod unary;
15
16pub use api_keys::ApiKeysService;
17pub use auth::AuthService;
18pub use balances::BalancesService;
19pub use deposit_withdraw::{
20    DepositService, PreparedTradingWithdraw, WithdrawService, ZipperService,
21    new_trading_withdraw_idempotency_key, new_trading_withdraw_nonce,
22};
23pub use market_data::{
24    CreateSubscriptionOptions, ListMarketOverviewOptions, MarketDataService,
25    MarketOverviewCreateSubscriptionOptions, MarketOverviewService, OrderbookService,
26};
27pub use orders::{OrdersService, TradesService};
28pub use profile::ProfileService;
29pub use thin::*;
30pub use triggers::TriggersService;
31
32use crate::catalogs::Manager as CatalogManager;
33use crate::errors::{Error, Result};
34use crate::transport::Factory;
35use std::sync::Arc;
36use std::time::Duration;
37use tokio::sync::OnceCell;
38
39use crate::realtime::Client as RealtimeClient;
40
41/// Shared dependencies for service constructors.
42#[derive(Clone)]
43pub struct ServiceContext {
44    pub factory: Factory,
45    pub catalogs: Arc<CatalogManager>,
46    pub default_sub_account_id: Option<String>,
47    pub default_account_id: Option<String>,
48    pub realtime: RealtimeClient,
49    pub catalog_ready: Arc<OnceCell<Result<()>>>,
50    pub hydrate_catalogs_enabled: bool,
51}
52
53impl ServiceContext {
54    /// Wait for construction-time catalog hydration when enabled.
55    ///
56    /// Propagates hydration failure so order paths do not proceed with empty catalogs.
57    pub async fn wait_for_catalogs(&self) -> Result<()> {
58        if !self.hydrate_catalogs_enabled {
59            return Ok(());
60        }
61        if self.catalogs.is_ready() {
62            return Ok(());
63        }
64        if let Some(result) = self.catalog_ready.get() {
65            return result.clone();
66        }
67        let deadline = tokio::time::Instant::now() + Duration::from_secs(30);
68        while self.catalog_ready.get().is_none() {
69            if self.catalogs.is_ready() {
70                return Ok(());
71            }
72            if tokio::time::Instant::now() >= deadline {
73                return Err(Error::validation(
74                    "catalogs are not ready; await client.wait_for_catalogs() before placing orders",
75                ));
76            }
77            tokio::time::sleep(Duration::from_millis(5)).await;
78        }
79        if self.catalogs.is_ready() {
80            return Ok(());
81        }
82        self.catalog_ready.get().cloned().unwrap_or_else(|| {
83            Err(Error::validation(
84                "catalogs are not ready; await client.wait_for_catalogs() before placing orders",
85            ))
86        })
87    }
88}