Skip to main content

longbridge/sharelist/
context.rs

1use std::sync::Arc;
2
3use longbridge_httpcli::{HttpClient, Json, Method};
4use serde::{Serialize, de::DeserializeOwned};
5use tracing::{Subscriber, dispatcher, instrument::WithSubscriber};
6
7use crate::{Config, Result, sharelist::types::*, utils::counter::symbol_to_counter_id};
8
9struct InnerSharelistContext {
10    http_cli: HttpClient,
11    log_subscriber: Arc<dyn Subscriber + Send + Sync>,
12}
13
14impl Drop for InnerSharelistContext {
15    fn drop(&mut self) {
16        dispatcher::with_default(&self.log_subscriber.clone().into(), || {
17            tracing::info!("sharelist context dropped");
18        });
19    }
20}
21
22/// Community sharelist management context.
23#[derive(Clone)]
24pub struct SharelistContext(Arc<InnerSharelistContext>);
25
26impl SharelistContext {
27    /// Create a [`SharelistContext`]
28    pub fn new(config: Arc<Config>) -> Self {
29        let log_subscriber = config.create_log_subscriber("sharelist");
30        dispatcher::with_default(&log_subscriber.clone().into(), || {
31            tracing::info!(language = ?config.language, "creating sharelist context");
32        });
33        let ctx = Self(Arc::new(InnerSharelistContext {
34            http_cli: config.create_http_client(),
35            log_subscriber,
36        }));
37        dispatcher::with_default(&ctx.0.log_subscriber.clone().into(), || {
38            tracing::info!("sharelist context created");
39        });
40        ctx
41    }
42
43    /// Returns the log subscriber
44    #[inline]
45    pub fn log_subscriber(&self) -> Arc<dyn Subscriber + Send + Sync> {
46        self.0.log_subscriber.clone()
47    }
48
49    async fn get<R, Q>(&self, path: &'static str, query: Q) -> Result<R>
50    where
51        R: DeserializeOwned + Send + Sync + 'static,
52        Q: Serialize + Send + Sync,
53    {
54        Ok(self
55            .0
56            .http_cli
57            .request(Method::GET, path)
58            .query_params(query)
59            .response::<Json<R>>()
60            .send()
61            .with_subscriber(self.0.log_subscriber.clone())
62            .await?
63            .0)
64    }
65
66    async fn post<R, B>(&self, path: &'static str, body: B) -> Result<R>
67    where
68        R: DeserializeOwned + Send + Sync + 'static,
69        B: std::fmt::Debug + Serialize + Send + Sync + 'static,
70    {
71        Ok(self
72            .0
73            .http_cli
74            .request(Method::POST, path)
75            .body(Json(body))
76            .response::<Json<R>>()
77            .send()
78            .with_subscriber(self.0.log_subscriber.clone())
79            .await?
80            .0)
81    }
82
83    async fn http_delete<R, B>(&self, path: String, body: B) -> Result<R>
84    where
85        R: DeserializeOwned + Send + Sync + 'static,
86        B: std::fmt::Debug + Serialize + Send + Sync + 'static,
87    {
88        Ok(self
89            .0
90            .http_cli
91            .request(Method::DELETE, path.leak())
92            .body(Json(body))
93            .response::<Json<R>>()
94            .send()
95            .with_subscriber(self.0.log_subscriber.clone())
96            .await?
97            .0)
98    }
99
100    /// List user's own and subscribed sharelists.
101    ///
102    /// Path: `GET /v1/sharelists`
103    pub async fn list(&self, count: u32) -> Result<SharelistList> {
104        #[derive(Serialize)]
105        struct Query {
106            size: u32,
107            #[serde(rename = "self")]
108            is_self: &'static str,
109            subscription: &'static str,
110        }
111        self.get(
112            "/v1/sharelists",
113            Query {
114                size: count,
115                is_self: "true",
116                subscription: "true",
117            },
118        )
119        .await
120    }
121
122    /// Get sharelist detail.
123    ///
124    /// Path: `GET /v1/sharelists/{id}`
125    pub async fn detail(&self, id: i64) -> Result<SharelistDetail> {
126        #[derive(Serialize)]
127        struct Query {
128            constituent: &'static str,
129            quote: &'static str,
130            subscription: &'static str,
131        }
132        let path = format!("/v1/sharelists/{id}");
133        Ok(self
134            .0
135            .http_cli
136            .request(Method::GET, path.leak())
137            .query_params(Query {
138                constituent: "true",
139                quote: "true",
140                subscription: "true",
141            })
142            .response::<Json<SharelistDetail>>()
143            .send()
144            .with_subscriber(self.0.log_subscriber.clone())
145            .await?
146            .0)
147    }
148
149    /// Get popular sharelists.
150    ///
151    /// Path: `GET /v1/sharelists/popular`
152    pub async fn popular(&self, count: u32) -> Result<SharelistList> {
153        #[derive(Serialize)]
154        struct Query {
155            size: u32,
156        }
157        self.get("/v1/sharelists/popular", Query { size: count })
158            .await
159    }
160
161    /// Create a new sharelist.
162    ///
163    /// Path: `POST /v1/sharelists`
164    pub async fn create(&self, name: impl Into<String>, description: Option<String>) -> Result<()> {
165        let name_str = name.into();
166        let desc = description.unwrap_or_else(|| name_str.clone());
167        self.post::<serde_json::Value, _>("/v1/sharelists", serde_json::json!({ "name": name_str, "description": desc, "cover": "https://pub.pbkrs.com/files/202107/kaJSk6BsvPt6NJ3Q/sharelist_v1.png" })).await?;
168        Ok(())
169    }
170
171    /// Delete a sharelist.
172    ///
173    /// Path: `DELETE /v1/sharelists/{id}`
174    pub async fn delete(&self, id: i64) -> Result<serde_json::Value> {
175        self.http_delete(format!("/v1/sharelists/{id}"), serde_json::json!({}))
176            .await
177    }
178
179    /// Add securities to a sharelist.
180    ///
181    /// Path: `POST /v1/sharelists/{id}/items`
182    pub async fn add_securities(&self, id: i64, symbols: Vec<String>) -> Result<serde_json::Value> {
183        let counter_ids = symbols
184            .iter()
185            .map(|s| symbol_to_counter_id(s))
186            .collect::<Vec<_>>()
187            .join(",");
188        let path = format!("/v1/sharelists/{id}/items");
189        self.post(
190            path.leak(),
191            serde_json::json!({ "counter_ids": counter_ids }),
192        )
193        .await
194    }
195
196    /// Remove securities from a sharelist.
197    ///
198    /// Path: `DELETE /v1/sharelists/{id}/items`
199    pub async fn remove_securities(
200        &self,
201        id: i64,
202        symbols: Vec<String>,
203    ) -> Result<serde_json::Value> {
204        let counter_ids = symbols
205            .iter()
206            .map(|s| symbol_to_counter_id(s))
207            .collect::<Vec<_>>()
208            .join(",");
209        self.http_delete(
210            format!("/v1/sharelists/{id}/items"),
211            serde_json::json!({ "counter_ids": counter_ids }),
212        )
213        .await
214    }
215
216    /// Reorder securities in a sharelist.
217    ///
218    /// Path: `POST /v1/sharelists/{id}/items/sort`
219    pub async fn sort_securities(
220        &self,
221        id: i64,
222        symbols: Vec<String>,
223    ) -> Result<serde_json::Value> {
224        let counter_ids = symbols
225            .iter()
226            .map(|s| symbol_to_counter_id(s))
227            .collect::<Vec<_>>()
228            .join(",");
229        let path = format!("/v1/sharelists/{id}/items/sort");
230        self.post(
231            path.leak(),
232            serde_json::json!({ "counter_ids": counter_ids }),
233        )
234        .await
235    }
236}