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::*};
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, Q>(&self, path: String, query: Q) -> Result<R>
84    where
85        R: DeserializeOwned + Send + Sync + 'static,
86        Q: Serialize + Send + Sync,
87    {
88        Ok(self
89            .0
90            .http_cli
91            .request(Method::DELETE, path.leak())
92            .query_params(query)
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<()> {
175        let path = format!("/v1/sharelists/{id}");
176        self.0
177            .http_cli
178            .request(Method::DELETE, path.leak())
179            .response::<Json<serde_json::Value>>()
180            .send()
181            .with_subscriber(self.0.log_subscriber.clone())
182            .await?;
183        Ok(())
184    }
185
186    /// Add securities to a sharelist.
187    ///
188    /// Path: `POST /v1/sharelists/{id}/items`
189    pub async fn add_securities(&self, id: i64, symbols: Vec<String>) -> Result<()> {
190        let syms = symbols.join(",");
191        let path = format!("/v1/sharelists/{id}/items");
192        self.post::<serde_json::Value, _>(path.leak(), serde_json::json!({ "symbols": syms }))
193            .await?;
194        Ok(())
195    }
196
197    /// Remove securities from a sharelist.
198    ///
199    /// Path: `DELETE /v1/sharelists/{id}/items`
200    pub async fn remove_securities(&self, id: i64, symbols: Vec<String>) -> Result<()> {
201        #[derive(Serialize)]
202        struct Query {
203            symbols: String,
204        }
205        self.http_delete::<serde_json::Value, _>(
206            format!("/v1/sharelists/{id}/items"),
207            Query {
208                symbols: symbols.join(","),
209            },
210        )
211        .await?;
212        Ok(())
213    }
214
215    /// Reorder securities in a sharelist.
216    ///
217    /// Path: `POST /v1/sharelists/{id}/items/sort`
218    pub async fn sort_securities(&self, id: i64, symbols: Vec<String>) -> Result<()> {
219        let syms = symbols.join(",");
220        let path = format!("/v1/sharelists/{id}/items/sort");
221        self.post::<serde_json::Value, _>(path.leak(), serde_json::json!({ "symbols": syms }))
222            .await?;
223        Ok(())
224    }
225}