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