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 utils::build_request,
40};
41use http::Method;
42use serde::{Deserialize, Serialize};
43use tracing::debug;
44
45/// 短链接
46///
47/// # 示例
48///
49/// ```no_run
50/// use wechat_minapp::client::WechatMinapp;
51/// use wechat_minapp::Link::{ShortLinkArgs,Link};
52///
53/// #[tokio::main]
54/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
55/// // 初始化客户端
56/// let app_id = "your_app_id";
57/// let secret = "your_app_secret";
58/// let client = WechatMinapp::new(app_id, secret);
59/// let link = Link::new(client);
60///
61/// let args = ShortLinkArgs::builder()
62/// .path("pages/index/index")
63/// .build()
64/// .unwrap();
65/// // 生成短链接
66/// let short_link = link.short_link(args).await?;
67///
68///
69/// Ok(())
70/// }
71/// ```
72#[derive(Debug, Serialize, Deserialize, Clone)]
73pub struct ShortLink {
74 link: String,
75}
76
77/// 短链接生成参数
78///
79/// 用于配置短链接的生成选项,通过 [`ShortLinkArgs::builder()`] 方法创建。
80#[derive(Debug, Deserialize, Serialize)]
81pub struct ShortLinkArgs {
82 #[serde(rename = "page_url")]
83 path: String,
84 #[serde(skip_serializing_if = "Option::is_none")]
85 page_title: Option<String>,
86 is_permanent: bool,
87}
88
89/// 短链接参数构建器
90///
91/// 提供链式调用的方式构建短链接参数,确保参数的正确性。
92///
93/// # 示例
94///
95/// ```
96/// use wechat_minapp::Link::ShortLinkArgs;
97///
98/// let args = ShortLinkArgs::builder()
99/// .path("pages/index/index")
100/// .page_title("page title")
101/// .with_permanent()
102/// .build()
103/// .unwrap();
104/// ```
105#[derive(Debug, Deserialize)]
106pub struct ShortLinkArgsBuilder {
107 path: Option<String>,
108 page_title: Option<String>,
109 is_permanent: Option<bool>,
110}
111
112impl ShortLinkArgs {
113 pub fn builder() -> ShortLinkArgsBuilder {
114 ShortLinkArgsBuilder::new()
115 }
116
117 pub fn path(&self) -> String {
118 self.path.clone()
119 }
120
121 pub fn page_title(&self) -> Option<String> {
122 self.page_title.clone()
123 }
124
125 pub fn is_permanent(&self) -> bool {
126 self.is_permanent
127 }
128}
129
130impl ShortLinkArgsBuilder {
131 pub fn new() -> Self {
132 ShortLinkArgsBuilder {
133 path: None,
134 page_title: None,
135 is_permanent: None,
136 }
137 }
138
139 pub fn path(mut self, path: impl Into<String>) -> Self {
140 self.path = Some(path.into());
141 self
142 }
143
144 pub fn page_title(mut self, title: impl Into<String>) -> Self {
145 self.page_title = Some(title.into());
146 self
147 }
148 pub fn with_permanent(mut self) -> Self {
149 self.is_permanent = Some(true);
150 self
151 }
152
153 pub fn build(self) -> Result<ShortLinkArgs> {
154 let path = self.path.map_or_else(
155 || {
156 Err(Error::InvalidParameter(
157 "小程序页面路径不能为空".to_string(),
158 ))
159 },
160 |v| {
161 if v.len() > 1024 {
162 return Err(Error::InvalidParameter(
163 "页面路径最大长度 1024 个字符".to_string(),
164 ));
165 }
166 Ok(v)
167 },
168 )?;
169
170 Ok(ShortLinkArgs {
171 path,
172 page_title: self.page_title,
173 is_permanent: self.is_permanent.unwrap_or(false),
174 })
175 }
176}
177
178impl Default for ShortLinkArgsBuilder {
179 fn default() -> Self {
180 Self::new()
181 }
182}
183
184impl Link {
185 /// 生成短链接
186 ///
187 /// 调用微信小程序短链接生成接口,返回短链接`ShortLink`。
188 ///
189 /// # 参数
190 ///
191 /// - `args`: 短链接生成参数
192 ///
193 /// # 返回
194 ///
195 /// 成功返回 `Ok(ShortLink)`,失败返回错误信息。
196 ///
197 /// # 示例
198 ///
199 /// ```no_run
200 /// use wechat_minapp::client::WechatMinapp;
201 /// use wechat_minapp::Link::{ShortLinkArgs,Link};
202 ///
203 /// #[tokio::main]
204 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
205 /// // 初始化客户端
206 /// let app_id = "your_app_id";
207 /// let secret = "your_app_secret";
208 /// let client = WechatMinapp::new(app_id, secret);
209 /// let link = Link::new(client);
210 ///
211 /// let args = ShortLinkArgs::builder()
212 /// .path("pages/index/index")
213 /// .build()
214 /// .unwrap();
215 /// // 生成短链接
216 /// let short_link = link.short_link(args).await?;
217 ///
218 ///
219 /// Ok(())
220 /// }
221 /// ```
222 ///
223 /// # 错误
224 ///
225 /// - 网络错误
226 /// - 认证错误(access_token 无效)
227 /// - 微信 API 返回错误
228 /// - 参数序列化错误
229 pub async fn short_link(&self, args: ShortLinkArgs) -> Result<ShortLink> {
230 debug!("get qr code args {:?}", &args);
231
232 let query = serde_json::json!({
233 "access_token":self.client.token().await?
234 });
235
236 let body = serde_json::to_value(ShortLinkArgs {
237 path: args.path.clone(),
238 page_title: args.page_title,
239 is_permanent: args.is_permanent,
240 })?;
241
242 let request = build_request(
243 constants::SHORT_LINK_END_POINT,
244 Method::POST,
245 None,
246 Some(query),
247 Some(body),
248 )?;
249
250 let client = &self.client.client;
251
252 let response = client.execute(request).await?;
253
254 debug!("response: {:#?}", response);
255
256 if response.status().is_success() {
257 let (_parts, body) = response.into_parts();
258 debug!("short link response body: {:?}", &body);
259 eprintln!(
260 "short link response body: {:?}",
261 String::from_utf8_lossy(&body)
262 );
263 let json = serde_json::from_slice::<ShortLink>(&body)?;
264
265 debug!("short link: {:#?}", &json);
266
267 Ok(json)
268 } else {
269 let (_parts, body) = response.into_parts();
270 let message = String::from_utf8_lossy(&body).to_string();
271 Err(InternalServer(message))
272 }
273 }
274}