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