qcs_api_client_common/configuration/
settings.rs1use std::collections::HashMap;
3use std::path::PathBuf;
4
5use figment::providers::Format;
6use figment::{providers::Toml, Figment};
7use serde::{Deserialize, Serialize};
8
9use super::{
10 env_or_default_quilc_url, env_or_default_qvm_url, expand_path_from_env_or_default, LoadError,
11 DEFAULT_API_URL, DEFAULT_GRPC_API_URL, DEFAULT_PROFILE_NAME, DEFAULT_QUILC_URL,
12 DEFAULT_QVM_URL,
13};
14
15pub const SETTINGS_PATH_VAR: &str = "QCS_SETTINGS_FILE_PATH";
17pub const DEFAULT_SETTINGS_PATH: &str = "~/.qcs/settings.toml";
19
20#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
22pub struct Settings {
23 #[serde(default = "default_profile_name")]
25 pub default_profile_name: String,
26
27 #[serde(default = "default_profiles")]
29 pub profiles: HashMap<String, Profile>,
30
31 #[serde(default = "default_auth_servers")]
33 pub auth_servers: HashMap<String, AuthServer>,
34
35 #[serde(skip)]
38 pub file_path: Option<PathBuf>,
39}
40
41impl Settings {
42 pub fn load() -> Result<Self, LoadError> {
49 let path = expand_path_from_env_or_default(SETTINGS_PATH_VAR, DEFAULT_SETTINGS_PATH)?;
50 #[cfg(feature = "tracing")]
51 tracing::debug!("loading QCS settings from {path:?}");
52 Self::load_from_path(&path)
53 }
54
55 pub fn load_from_path(path: &PathBuf) -> Result<Self, LoadError> {
61 let mut settings: Self = Figment::from(Toml::file(path)).extract()?;
62 settings.file_path = Some(path.into());
63 Ok(settings)
64 }
65}
66
67impl Default for Settings {
68 fn default() -> Self {
69 Self {
70 default_profile_name: default_profile_name(),
71 profiles: default_profiles(),
72 auth_servers: default_auth_servers(),
73 file_path: None,
74 }
75 }
76}
77
78fn default_profile_name() -> String {
79 DEFAULT_PROFILE_NAME.to_string()
80}
81
82fn default_profiles() -> HashMap<String, Profile> {
83 HashMap::from([(DEFAULT_PROFILE_NAME.to_string(), Profile::default())])
84}
85
86fn default_auth_servers() -> HashMap<String, AuthServer> {
87 HashMap::from([(DEFAULT_PROFILE_NAME.to_string(), AuthServer::default())])
88}
89
90#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
93pub struct Profile {
94 #[serde(default = "default_api_url")]
96 pub api_url: String,
97 #[serde(default = "default_grpc_api_url")]
99 pub grpc_api_url: String,
100 #[serde(default = "default_profile_name")]
102 pub auth_server_name: String,
103 #[serde(default = "default_profile_name")]
105 pub credentials_name: String,
106 #[serde(default)]
108 pub applications: Applications,
109}
110
111impl Default for Profile {
112 fn default() -> Self {
113 Self {
114 api_url: DEFAULT_API_URL.to_string(),
115 grpc_api_url: DEFAULT_GRPC_API_URL.to_string(),
116 auth_server_name: DEFAULT_PROFILE_NAME.to_string(),
117 credentials_name: DEFAULT_PROFILE_NAME.to_string(),
118 applications: Applications::default(),
119 }
120 }
121}
122
123fn default_api_url() -> String {
124 DEFAULT_API_URL.to_string()
125}
126
127fn default_grpc_api_url() -> String {
128 DEFAULT_GRPC_API_URL.to_string()
129}
130
131pub(crate) const QCS_DEFAULT_CLIENT_ID_PRODUCTION: &str = "0oa3ykoirzDKpkfzk357";
132pub(crate) const QCS_DEFAULT_AUTH_ISSUER_PRODUCTION: &str =
133 "https://auth.qcs.rigetti.com/oauth2/aus8jcovzG0gW2TUG355";
134
135#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
137#[cfg_attr(feature = "python", pyo3::pyclass)]
138pub struct AuthServer {
139 pub client_id: String,
141 pub issuer: String,
152}
153
154impl Default for AuthServer {
155 fn default() -> Self {
156 Self {
157 client_id: QCS_DEFAULT_CLIENT_ID_PRODUCTION.to_string(),
158 issuer: QCS_DEFAULT_AUTH_ISSUER_PRODUCTION.to_string(),
159 }
160 }
161}
162
163impl AuthServer {
164 #[must_use]
166 pub const fn new(client_id: String, issuer: String) -> Self {
167 Self { client_id, issuer }
168 }
169}
170
171#[derive(Deserialize, Clone, Debug, Default, PartialEq, Eq, Serialize)]
173pub struct Applications {
174 #[serde(default)]
176 pub pyquil: Pyquil,
177}
178
179#[derive(Deserialize, Clone, Debug, PartialEq, Eq, Serialize)]
181pub struct Pyquil {
182 #[serde(default = "env_or_default_qvm_url")]
184 pub qvm_url: String,
185
186 #[serde(default = "env_or_default_quilc_url")]
188 pub quilc_url: String,
189}
190
191impl Default for Pyquil {
192 fn default() -> Self {
193 Self {
194 quilc_url: DEFAULT_QUILC_URL.to_string(),
195 qvm_url: DEFAULT_QVM_URL.to_string(),
196 }
197 }
198}
199
200#[cfg(test)]
201mod test {
202 use std::path::PathBuf;
203
204 use super::{Settings, SETTINGS_PATH_VAR};
205
206 #[test]
207 fn returns_err_if_invalid_path_env() {
208 figment::Jail::expect_with(|jail| {
209 jail.set_env(SETTINGS_PATH_VAR, "/blah/doesnt_exist.toml");
210 Settings::load().expect_err("Should return error when a file cannot be found.");
211 Ok(())
212 });
213 }
214
215 #[test]
216 fn test_uses_defaults_incomplete_settings() {
217 figment::Jail::expect_with(|jail| {
218 let _ = jail.create_file("settings.toml", r#"default_profile_name = "TEST""#)?;
219 jail.set_env(SETTINGS_PATH_VAR, "settings.toml");
220 let loaded = Settings::load().expect("should load settings");
221 let expected = Settings {
222 default_profile_name: "TEST".to_string(),
223 file_path: Some(PathBuf::from("settings.toml")),
224 ..Settings::default()
225 };
226
227 assert_eq!(loaded, expected);
228
229 Ok(())
230 });
231 }
232
233 #[test]
234 fn loads_from_env_var_path() {
235 figment::Jail::expect_with(|jail| {
236 let settings = Settings {
237 default_profile_name: "TEST".to_string(),
238 file_path: Some(PathBuf::from("secrets.toml")),
239 ..Settings::default()
240 };
241 let settings_string =
242 toml::to_string(&settings).expect("Should be able to serialize settings");
243
244 _ = jail.create_file("secrets.toml", &settings_string)?;
245 jail.set_env(SETTINGS_PATH_VAR, "secrets.toml");
246
247 assert_eq!(settings, Settings::load().unwrap());
248
249 Ok(())
250 });
251 }
252}