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