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