async_dashscope/
config.rs

1// https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation - text-generation
2// https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation - image-generation
3// https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation - 音频理解、视觉理解
4// https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation  - 录音文件识别
5// https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation  - 语音合成
6// https://dashscope.aliyuncs.com/api/v1/services/aigc/text2image/image-synthesis - 创意海报生成API参考
7
8// https://dashscope-intl.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation  新加坡
9// https://dashscope.aliyuncs.com/api/v1/services/aigc/image2image/image-synthesis 图像翻译
10
11// https://dashscope.aliyuncs.com/api/v1/files 上传文件
12
13// todo: Qwen3-Coder 暂不支持 dashscope 的基于 Partial Mode 的代码补全功能
14
15use derive_builder::Builder;
16use reqwest::header::AUTHORIZATION;
17use secrecy::{ExposeSecret as _, SecretString};
18
19pub const DASHSCOPE_API_BASE: &str = "https://dashscope.aliyuncs.com/api/v1";
20
21/// # Config
22///
23/// ```rust
24/// use async_dashscope::config::ConfigBuilder;
25/// use async_dashscope::Client;
26/// 
27/// let conf = ConfigBuilder::default()
28///         // optional, default is: https://dashscope.aliyuncs.com/api/v1
29///         .api_base("http://localhost:8080")
30///         .api_key("test")
31///         .build()
32///         .unwrap();
33/// let  client = Client::with_config(conf);
34/// ```
35#[derive(Debug, Builder, Clone)]
36#[builder(setter(into))]
37pub struct Config {
38    #[builder(setter(into, strip_option))]
39    #[builder(default = "self.default_base_url()")]
40    api_base: Option<String>,
41    api_key: SecretString,
42}
43
44impl ConfigBuilder {
45    fn default_base_url(&self) -> Option<String> {
46        Some(DASHSCOPE_API_BASE.to_string())
47    }
48}
49
50impl Config {
51    pub fn url(&self, path: &str) -> String {
52        let n_url = format!(
53            "{}/{}",
54            self.api_base.clone()
55                .unwrap_or(DASHSCOPE_API_BASE.to_string())
56                .trim_end_matches('/'),
57            path.trim_start_matches('/')
58        );
59        n_url.trim_end_matches('/').to_string()
60    }
61    pub fn headers(&self) -> reqwest::header::HeaderMap {
62        let mut headers = reqwest::header::HeaderMap::new();
63        headers.insert("Content-Type", "application/json".parse().unwrap());
64        headers.insert(
65            "X-DashScope-OssResourceResolve",
66            "enable".parse().unwrap(),
67        );
68        headers.insert(
69            AUTHORIZATION,
70            format!("Bearer {}", self.api_key.expose_secret())
71                .parse()
72                .unwrap(),
73        );
74        headers
75    }
76
77    pub fn set_api_key(&mut self, api_key: SecretString) {
78        self.api_key = api_key;
79    }
80    
81    pub fn api_key(&self) -> &SecretString {
82        &self.api_key
83    }
84}
85
86impl Default for Config {
87    fn default() -> Self {
88        Self {
89            api_base: Some(DASHSCOPE_API_BASE.to_string()),
90            api_key: std::env::var("DASHSCOPE_API_KEY")
91                .unwrap_or_else(|_| "".to_string())
92                .into(),
93        }
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    #[test]
102    fn test_url_normal_case() {
103        let instance = ConfigBuilder::default()
104            .api_base("https://example.com")
105            .api_key("test")
106            .build()
107            .unwrap();
108        assert_eq!(instance.url("/v1"), "https://example.com/v1");
109    }
110
111    #[test]
112    fn test_url_empty_path() {
113        let instance = ConfigBuilder::default()
114            .api_base("http://localhost:8080")
115            .api_key("test")
116            .build()
117            .unwrap();
118        assert_eq!(instance.url(""), "http://localhost:8080");
119    }
120
121    #[test]
122    fn test_url_empty_api_base() {
123        let instance = ConfigBuilder::default().api_key("test").build().unwrap();
124        assert_eq!(
125            instance.url("/test"),
126            format!("{DASHSCOPE_API_BASE}/test").as_str()
127        );
128    }
129
130    #[test]
131    fn test_url_slash_in_both_parts() {
132        let instance = ConfigBuilder::default()
133            .api_base("https://a.com/")
134            .api_key("test")
135            .build()
136            .unwrap(); //Config {
137        assert_eq!(instance.url("/b"), "https://a.com/b");
138    }
139
140    #[test]
141    fn test_url_no_slash_in_path() {
142        let instance = ConfigBuilder::default()
143            .api_base("https://a.com")
144            .api_key("test")
145            .build()
146            .unwrap();
147        assert_eq!(instance.url("b"), "https://a.com/b");
148    }
149
150    #[test]
151    fn test_api_key() {
152        let instance = ConfigBuilder::default()
153            .api_base("https://example.com")
154            .api_key("test")
155            .build()
156            .unwrap();
157        assert_eq!(
158            instance.headers().get("Authorization").unwrap(),
159            "Bearer test"
160        );
161    }
162}