1use crate::comment::list::{
2 CommentCountParams, CommentHotParams, CommentListData, CommentListParams, CommentRepliesParams,
3 CountData, HotCommentData,
4};
5use crate::{BilibiliRequest, BpiClient, BpiResult};
6
7const LIST_ENDPOINT: &str = "https://api.bilibili.com/x/v2/reply";
8const REPLIES_ENDPOINT: &str = "https://api.bilibili.com/x/v2/reply/reply";
9const HOT_ENDPOINT: &str = "https://api.bilibili.com/x/v2/reply/hot";
10const COUNT_ENDPOINT: &str = "https://api.bilibili.com/x/v2/reply/count";
11
12#[derive(Clone, Copy)]
14pub struct CommentClient<'a> {
15 pub(crate) client: &'a BpiClient,
16}
17
18impl<'a> CommentClient<'a> {
19 pub(crate) fn new(client: &'a BpiClient) -> Self {
20 Self { client }
21 }
22
23 #[cfg(test)]
24 pub(crate) fn list_endpoint(&self) -> &'static str {
25 LIST_ENDPOINT
26 }
27
28 #[cfg(test)]
29 pub(crate) fn replies_endpoint(&self) -> &'static str {
30 REPLIES_ENDPOINT
31 }
32
33 #[cfg(test)]
34 pub(crate) fn hot_endpoint(&self) -> &'static str {
35 HOT_ENDPOINT
36 }
37
38 #[cfg(test)]
39 pub(crate) fn count_endpoint(&self) -> &'static str {
40 COUNT_ENDPOINT
41 }
42
43 pub async fn list(&self, params: CommentListParams) -> BpiResult<CommentListData> {
45 self.client
46 .get(LIST_ENDPOINT)
47 .query(¶ms.query_pairs())
48 .send_bpi_payload("comment.read.list")
49 .await
50 }
51
52 pub async fn replies(&self, params: CommentRepliesParams) -> BpiResult<CommentListData> {
54 self.client
55 .get(REPLIES_ENDPOINT)
56 .query(¶ms.query_pairs())
57 .send_bpi_payload("comment.read.replies")
58 .await
59 }
60
61 pub async fn hot(&self, params: CommentHotParams) -> BpiResult<Option<HotCommentData>> {
63 self.client
64 .get(HOT_ENDPOINT)
65 .query(¶ms.query_pairs())
66 .send_bpi_optional_payload("comment.read.hot")
67 .await
68 }
69
70 pub async fn count(&self, params: CommentCountParams) -> BpiResult<CountData> {
72 self.client
73 .get(COUNT_ENDPOINT)
74 .query(¶ms.query_pairs())
75 .send_bpi_payload("comment.read.count")
76 .await
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 use std::future::Future;
83
84 use crate::comment::list::{
85 CommentCountParams, CommentHotParams, CommentListData, CommentListParams,
86 CommentRepliesParams, CommentSort, CommentTarget, CountData, HotCommentData,
87 };
88 use crate::probe::contract::HttpMethod;
89 use crate::probe::endpoint_contract::EndpointContract;
90 use crate::{BpiClient, BpiResult};
91
92 const TEST_TYPE: i32 = 1;
93 const TEST_OID: i64 = 23199;
94 const TEST_ROOT_RPID: i64 = 2554491176;
95
96 fn target() -> BpiResult<CommentTarget> {
97 CommentTarget::new(TEST_TYPE, TEST_OID)
98 }
99
100 fn list_params() -> BpiResult<CommentListParams> {
101 Ok(CommentListParams::new(target()?)
102 .with_page(1)?
103 .with_page_size(5)?
104 .with_sort(CommentSort::Time)
105 .without_hot(false))
106 }
107
108 fn replies_params() -> BpiResult<CommentRepliesParams> {
109 CommentRepliesParams::new(target()?, TEST_ROOT_RPID)?
110 .with_page(1)?
111 .with_page_size(5)
112 }
113
114 fn hot_params() -> BpiResult<CommentHotParams> {
115 CommentHotParams::new(target()?, TEST_ROOT_RPID)?
116 .with_page(1)?
117 .with_page_size(5)
118 }
119
120 fn assert_list_future<F>(_future: F)
121 where
122 F: Future<Output = BpiResult<CommentListData>>,
123 {
124 }
125
126 fn assert_hot_future<F>(_future: F)
127 where
128 F: Future<Output = BpiResult<Option<HotCommentData>>>,
129 {
130 }
131
132 fn assert_count_future<F>(_future: F)
133 where
134 F: Future<Output = BpiResult<CountData>>,
135 {
136 }
137
138 fn contract(endpoint: &str) -> BpiResult<EndpointContract> {
139 let bytes = match endpoint {
140 "list" => {
141 include_bytes!("../../tests/contracts/comment/read/list/contract.json").as_slice()
142 }
143 "replies" => include_bytes!("../../tests/contracts/comment/read/replies/contract.json")
144 .as_slice(),
145 "hot" => {
146 include_bytes!("../../tests/contracts/comment/read/hot/contract.json").as_slice()
147 }
148 "count" => {
149 include_bytes!("../../tests/contracts/comment/read/count/contract.json").as_slice()
150 }
151 _ => unreachable!("unknown comment read contract"),
152 };
153 EndpointContract::from_slice(bytes)
154 }
155
156 #[test]
157 fn comment_client_exposes_promoted_endpoint_urls() -> BpiResult<()> {
158 let client = BpiClient::new()?;
159 let comment = client.comment();
160
161 assert_eq!(
162 comment.list_endpoint(),
163 "https://api.bilibili.com/x/v2/reply"
164 );
165 assert_eq!(
166 comment.replies_endpoint(),
167 "https://api.bilibili.com/x/v2/reply/reply"
168 );
169 assert_eq!(
170 comment.hot_endpoint(),
171 "https://api.bilibili.com/x/v2/reply/hot"
172 );
173 assert_eq!(
174 comment.count_endpoint(),
175 "https://api.bilibili.com/x/v2/reply/count"
176 );
177 Ok(())
178 }
179
180 #[test]
181 fn comment_methods_return_payload_futures() -> BpiResult<()> {
182 let client = BpiClient::new()?;
183 let comment = client.comment();
184
185 assert_list_future(comment.list(list_params()?));
186 assert_list_future(comment.replies(replies_params()?));
187 assert_hot_future(comment.hot(hot_params()?));
188 assert_count_future(comment.count(CommentCountParams::new(target()?)));
189 Ok(())
190 }
191
192 #[test]
193 fn comment_contracts_match_module_client_endpoints() -> BpiResult<()> {
194 let client = BpiClient::new()?;
195 let comment = client.comment();
196 let list = contract("list")?;
197 let replies = contract("replies")?;
198 let hot = contract("hot")?;
199 let count = contract("count")?;
200
201 assert_eq!(list.name, "comment.read.list");
202 assert_eq!(list.request.method, HttpMethod::Get);
203 assert_eq!(list.request.url.as_str(), comment.list_endpoint());
204 assert_eq!(
205 list.request.query.get("sort").map(String::as_str),
206 Some("0")
207 );
208
209 assert_eq!(replies.name, "comment.read.replies");
210 assert_eq!(replies.request.method, HttpMethod::Get);
211 assert_eq!(replies.request.url.as_str(), comment.replies_endpoint());
212 assert_eq!(
213 replies.request.query.get("root").map(String::as_str),
214 Some("2554491176")
215 );
216
217 assert_eq!(hot.name, "comment.read.hot");
218 assert_eq!(hot.request.method, HttpMethod::Get);
219 assert_eq!(hot.request.url.as_str(), comment.hot_endpoint());
220 assert!(
221 hot.cases
222 .iter()
223 .all(|case| case.response.rust_model.is_none())
224 );
225
226 assert_eq!(count.name, "comment.read.count");
227 assert_eq!(count.request.method, HttpMethod::Get);
228 assert_eq!(count.request.url.as_str(), comment.count_endpoint());
229 assert_eq!(
230 count.request.query.get("oid").map(String::as_str),
231 Some("23199")
232 );
233 Ok(())
234 }
235}