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}