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}