longbridge/sharelist/
context.rs1use 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#[derive(Clone)]
24pub struct SharelistContext(Arc<InnerSharelistContext>);
25
26impl SharelistContext {
27 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 #[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 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 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 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 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 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 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 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 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}