wechat_minapp/link/short_link.rs
1//! 微信小程序短链接生成模块
2//!
3//! 获取小程序 Short Link,适用于微信内拉起小程序的业务场景。
4//! 目前只开放给电商类目(具体包含以下一级类目:电商平台、商家自营、跨境电商)。通过该接口,可以选择生成到期失效和永久有效的小程序短链。
5//! [官方文档](https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/qrcode-link/short-link/generateShortLink.html)。
6//!
7//!
8//! ## 示例
9//!
10//! ```no_run
11//! use wechat_minapp::client::WechatMinapp;
12//! use wechat_minapp::Link::{ShortLinkArgs,Link};
13//!
14//! #[tokio::main]
15//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
16//! // 初始化客户端
17//! let app_id = "your_app_id";
18//! let secret = "your_app_secret";
19//! let client = WechatMinapp::new(app_id, secret);
20//! let link = Link::new(client);
21//!
22//! let args = ShortLinkArgs::builder()
23//! .path("pages/index/index")
24//! .build()
25//! .unwrap();
26//! // 生成短链接
27//! let short_link = link.short_link(args).await?;
28//!
29//!
30//! Ok(())
31//! }
32//! ```
33//!
34
35use super::Link;
36use crate::{
37 Result, constants,
38 error::Error::{self, InternalServer},
39};
40use http::header::{CONTENT_TYPE, HeaderValue};
41use http::{Method, Request};
42use serde::{Deserialize, Serialize};
43use std::collections::HashMap;
44use tracing::debug;
45
46/// 短链接
47///
48/// # 示例
49///
50/// ```no_run
51/// use wechat_minapp::client::WechatMinapp;
52/// use wechat_minapp::Link::{ShortLinkArgs,Link};
53///
54/// #[tokio::main]
55/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
56/// // 初始化客户端
57/// let app_id = "your_app_id";
58/// let secret = "your_app_secret";
59/// let client = WechatMinapp::new(app_id, secret);
60/// let link = Link::new(client);
61///
62/// let args = ShortLinkArgs::builder()
63/// .path("pages/index/index")
64/// .build()
65/// .unwrap();
66/// // 生成短链接
67/// let short_link = link.short_link(args).await?;
68///
69///
70/// Ok(())
71/// }
72/// ```
73#[derive(Debug, Serialize, Deserialize, Clone)]
74pub struct ShortLink {
75 link: String,
76}
77
78/// 短链接生成参数
79///
80/// 用于配置短链接的生成选项,通过 [`ShortLinkArgs::builder()`] 方法创建。
81#[derive(Debug, Deserialize)]
82pub struct ShortLinkArgs {
83 path: String,
84 page_title: Option<String>,
85 is_permanent: bool,
86}
87
88/// 短链接参数构建器
89///
90/// 提供链式调用的方式构建短链接参数,确保参数的正确性。
91///
92/// # 示例
93///
94/// ```
95/// use wechat_minapp::Link::ShortLinkArgs;
96///
97/// let args = ShortLinkArgs::builder()
98/// .path("pages/index/index")
99/// .page_title("page title")
100/// .with_permanent()
101/// .build()
102/// .unwrap();
103/// ```
104#[derive(Debug, Deserialize)]
105pub struct ShortLinkArgsBuilder {
106 path: Option<String>,
107 page_title: Option<String>,
108 is_permanent: Option<bool>,
109}
110
111impl ShortLinkArgs {
112 pub fn builder() -> ShortLinkArgsBuilder {
113 ShortLinkArgsBuilder::new()
114 }
115
116 pub fn path(&self) -> String {
117 self.path.clone()
118 }
119
120 pub fn page_title(&self) -> Option<String> {
121 self.page_title.clone()
122 }
123
124 pub fn is_permanent(&self) -> bool {
125 self.is_permanent
126 }
127}
128
129impl ShortLinkArgsBuilder {
130 pub fn new() -> Self {
131 ShortLinkArgsBuilder {
132 path: None,
133 page_title: None,
134 is_permanent: None,
135 }
136 }
137
138 pub fn path(mut self, path: impl Into<String>) -> Self {
139 self.path = Some(path.into());
140 self
141 }
142
143 pub fn page_title(mut self, title: impl Into<String>) -> Self {
144 self.page_title = Some(title.into());
145 self
146 }
147 pub fn with_permanent(mut self) -> Self {
148 self.is_permanent = Some(true);
149 self
150 }
151
152 pub fn build(self) -> Result<ShortLinkArgs> {
153 let path = self.path.map_or_else(
154 || {
155 Err(Error::InvalidParameter(
156 "小程序页面路径不能为空".to_string(),
157 ))
158 },
159 |v| {
160 if v.len() > 1024 {
161 return Err(Error::InvalidParameter(
162 "页面路径最大长度 1024 个字符".to_string(),
163 ));
164 }
165 Ok(v)
166 },
167 )?;
168
169 Ok(ShortLinkArgs {
170 path,
171 page_title: self.page_title,
172 is_permanent: self.is_permanent.unwrap_or(false),
173 })
174 }
175}
176
177impl Default for ShortLinkArgsBuilder {
178 fn default() -> Self {
179 Self::new()
180 }
181}
182
183impl Link {
184 /// 生成短链接
185 ///
186 /// 调用微信小程序短链接生成接口,返回短链接`ShortLink`。
187 ///
188 /// # 参数
189 ///
190 /// - `args`: 短链接生成参数
191 ///
192 /// # 返回
193 ///
194 /// 成功返回 `Ok(ShortLink)`,失败返回错误信息。
195 ///
196 /// # 示例
197 ///
198 /// ```no_run
199 /// use wechat_minapp::client::WechatMinapp;
200 /// use wechat_minapp::Link::{ShortLinkArgs,Link};
201 ///
202 /// #[tokio::main]
203 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
204 /// // 初始化客户端
205 /// let app_id = "your_app_id";
206 /// let secret = "your_app_secret";
207 /// let client = WechatMinapp::new(app_id, secret);
208 /// let link = Link::new(client);
209 ///
210 /// let args = ShortLinkArgs::builder()
211 /// .path("pages/index/index")
212 /// .build()
213 /// .unwrap();
214 /// // 生成短链接
215 /// let short_link = link.short_link(args).await?;
216 ///
217 ///
218 /// Ok(())
219 /// }
220 /// ```
221 ///
222 /// # 错误
223 ///
224 /// - 网络错误
225 /// - 认证错误(access_token 无效)
226 /// - 微信 API 返回错误
227 /// - 参数序列化错误
228 pub async fn short_link(&self, args: ShortLinkArgs) -> Result<ShortLink> {
229 debug!("get qr code args {:?}", &args);
230 let token = format!("access_token={}", self.client.token().await?);
231 let mut url = url::Url::parse(constants::SHORT_LINK_END_POINT)?;
232 url.set_query(Some(&token));
233
234 let mut body = HashMap::new();
235
236 body.insert("page_url", args.path);
237
238 if let Some(page_title) = args.page_title {
239 body.insert("page_title", page_title.to_string());
240 }
241
242 if args.is_permanent {
243 body.insert("is_permanent", args.is_permanent.to_string());
244 }
245
246 let client = &self.client.client;
247 let req_body = serde_json::to_vec(&body)?;
248 let request = Request::builder()
249 .uri(url.as_str())
250 .method(Method::POST)
251 .header(CONTENT_TYPE, HeaderValue::from_static("application/json"))
252 .header(
253 "User-Agent",
254 HeaderValue::from_static(constants::HTTP_CLIENT_USER_AGENT),
255 )
256 .body(req_body)?;
257
258 let response = client.execute(request).await?;
259
260 debug!("response: {:#?}", response);
261
262 if response.status().is_success() {
263 let (_parts, body) = response.into_parts();
264 debug!("short link response body: {:?}", &body);
265 eprintln!(
266 "short link response body: {:?}",
267 String::from_utf8_lossy(&body)
268 );
269 let json = serde_json::from_slice::<ShortLink>(&body)?;
270
271 debug!("short link: {:#?}", &json);
272
273 Ok(json)
274 } else {
275 let (_parts, body) = response.into_parts();
276 let message = String::from_utf8_lossy(&body).to_string();
277 Err(InternalServer(message))
278 }
279 }
280}