headless_lms_utils/
lib.rs1pub mod cache;
4pub mod document_schema_processor;
5pub mod email_processor;
6pub mod error;
7pub mod file_store;
8pub mod folder_checksum;
9pub mod futures;
10pub mod http;
11pub mod icu4x;
12pub mod ip_to_country;
13pub mod language_tag_to_name;
14pub mod merge_edits;
15pub mod numbers;
16pub mod page_visit_hasher;
17pub mod pagination;
18pub mod prelude;
19pub mod strings;
20pub mod tmc;
21pub mod url_to_oembed_endpoint;
22
23#[macro_use]
24extern crate tracing;
25
26use anyhow::Context;
27use std::{env, str::FromStr};
28use url::Url;
29
30#[derive(Clone, PartialEq)]
31pub struct ApplicationConfiguration {
32 pub base_url: String,
33 pub test_mode: bool,
34 pub test_chatbot: bool,
35 pub development_uuid_login: bool,
36 pub azure_configuration: Option<AzureConfiguration>,
37 pub tmc_account_creation_origin: Option<String>,
38}
39
40impl ApplicationConfiguration {
41 pub fn try_from_env() -> anyhow::Result<Self> {
43 let base_url = env::var("BASE_URL").context("BASE_URL must be defined")?;
44 let test_mode = env::var("TEST_MODE").is_ok();
45 let development_uuid_login = env::var("DEVELOPMENT_UUID_LOGIN").is_ok();
46 let test_chatbot = test_mode
47 && (env::var("USE_MOCK_AZURE_CONFIGURATION").is_ok_and(|v| v.as_str() != "false")
48 || env::var("AZURE_CHATBOT_API_KEY").is_err());
49
50 let azure_configuration = if test_chatbot {
51 AzureConfiguration::mock_conf()?
52 } else {
53 AzureConfiguration::try_from_env()?
54 };
55
56 let tmc_account_creation_origin = Some(
57 env::var("TMC_ACCOUNT_CREATION_ORIGIN")
58 .context("TMC_ACCOUNT_CREATION_ORIGIN must be defined")?,
59 );
60
61 Ok(Self {
62 base_url,
63 test_mode,
64 test_chatbot,
65 development_uuid_login,
66 azure_configuration,
67 tmc_account_creation_origin,
68 })
69 }
70}
71
72#[derive(Clone, PartialEq)]
73pub struct AzureChatbotConfiguration {
74 pub api_key: String,
75 pub api_endpoint: Url,
76}
77
78impl AzureChatbotConfiguration {
79 pub fn try_from_env() -> anyhow::Result<Option<Self>> {
84 let api_key = env::var("AZURE_CHATBOT_API_KEY").ok();
85 let api_endpoint_str = env::var("AZURE_CHATBOT_API_ENDPOINT").ok();
86
87 if let (Some(api_key), Some(api_endpoint_str)) = (api_key, api_endpoint_str) {
88 let api_endpoint = Url::parse(&api_endpoint_str)
89 .context("Invalid URL in AZURE_CHATBOT_API_ENDPOINT")?;
90 Ok(Some(AzureChatbotConfiguration {
91 api_key,
92 api_endpoint,
93 }))
94 } else {
95 Ok(None)
96 }
97 }
98}
99
100#[derive(Clone, PartialEq)]
101pub struct AzureSearchConfiguration {
102 pub vectorizer_resource_uri: String,
103 pub vectorizer_deployment_id: String,
104 pub vectorizer_api_key: String,
105 pub vectorizer_model_name: String,
106 pub search_endpoint: Url,
107 pub search_api_key: String,
108}
109
110impl AzureSearchConfiguration {
111 pub fn try_from_env() -> anyhow::Result<Option<Self>> {
116 let vectorizer_resource_uri = env::var("AZURE_VECTORIZER_RESOURCE_URI").ok();
117 let vectorizer_deployment_id = env::var("AZURE_VECTORIZER_DEPLOYMENT_ID").ok();
118 let vectorizer_api_key = env::var("AZURE_VECTORIZER_API_KEY").ok();
119 let vectorizer_model_name = env::var("AZURE_VECTORIZER_MODEL_NAME").ok();
120 let search_endpoint_str = env::var("AZURE_SEARCH_ENDPOINT").ok();
121 let search_api_key = env::var("AZURE_SEARCH_API_KEY").ok();
122
123 if let (
124 Some(vectorizer_resource_uri),
125 Some(vectorizer_deployment_id),
126 Some(vectorizer_api_key),
127 Some(vectorizer_model_name),
128 Some(search_endpoint_str),
129 Some(search_api_key),
130 ) = (
131 vectorizer_resource_uri,
132 vectorizer_deployment_id,
133 vectorizer_api_key,
134 vectorizer_model_name,
135 search_endpoint_str,
136 search_api_key,
137 ) {
138 let search_endpoint =
139 Url::parse(&search_endpoint_str).context("Invalid URL in AZURE_SEARCH_ENDPOINT")?;
140 Ok(Some(AzureSearchConfiguration {
141 vectorizer_resource_uri,
142 vectorizer_deployment_id,
143 vectorizer_api_key,
144 vectorizer_model_name,
145 search_endpoint,
146 search_api_key,
147 }))
148 } else {
149 Ok(None)
150 }
151 }
152}
153
154#[derive(Clone, PartialEq)]
155pub struct AzureBlobStorageConfiguration {
156 pub storage_account: String,
157 pub access_key: String,
158}
159
160impl AzureBlobStorageConfiguration {
161 pub fn try_from_env() -> anyhow::Result<Option<Self>> {
165 let storage_account = env::var("AZURE_BLOB_STORAGE_ACCOUNT").ok();
166 let access_key = env::var("AZURE_BLOB_STORAGE_ACCESS_KEY").ok();
167
168 if let (Some(storage_account), Some(access_key)) = (storage_account, access_key) {
169 Ok(Some(AzureBlobStorageConfiguration {
170 storage_account,
171 access_key,
172 }))
173 } else {
174 Ok(None)
175 }
176 }
177
178 pub fn connection_string(&self) -> anyhow::Result<String> {
179 Ok(format!(
180 "DefaultEndpointsProtocol=https;AccountName={};AccountKey={};EndpointSuffix=core.windows.net",
181 self.storage_account, self.access_key
182 ))
183 }
184}
185
186#[derive(Clone, PartialEq)]
187pub struct AzureConfiguration {
188 pub chatbot_config: Option<AzureChatbotConfiguration>,
189 pub search_config: Option<AzureSearchConfiguration>,
190 pub blob_storage_config: Option<AzureBlobStorageConfiguration>,
191}
192
193impl AzureConfiguration {
194 pub fn try_from_env() -> anyhow::Result<Option<Self>> {
198 let chatbot = AzureChatbotConfiguration::try_from_env()?;
199 let search_config = AzureSearchConfiguration::try_from_env()?;
200 let blob_storage_config = AzureBlobStorageConfiguration::try_from_env()?;
201
202 if chatbot.is_some() || search_config.is_some() || blob_storage_config.is_some() {
203 Ok(Some(AzureConfiguration {
204 chatbot_config: chatbot,
205 search_config,
206 blob_storage_config,
207 }))
208 } else {
209 Ok(None)
210 }
211 }
212
213 pub fn mock_conf() -> anyhow::Result<Option<Self>> {
218 let base_url = env::var("BASE_URL").context("BASE_URL must be defined")?;
219 let chatbot_config = Some(AzureChatbotConfiguration {
220 api_key: "".to_string(),
221 api_endpoint: Url::from_str(&base_url)?.join("/api/v0/mock-azure/test")?,
222 });
223 let search_config = Some(AzureSearchConfiguration {
224 vectorizer_resource_uri: "".to_string(),
225 vectorizer_deployment_id: "".to_string(),
226 vectorizer_api_key: "".to_string(),
227 vectorizer_model_name: "".to_string(),
228 search_api_key: "".to_string(),
229 search_endpoint: Url::from_str("https://example.com/does-not-exist/")?,
230 });
231 let blob_storage_config = Some(AzureBlobStorageConfiguration {
232 storage_account: "".to_string(),
233 access_key: "".to_string(),
234 });
235
236 Ok(Some(AzureConfiguration {
237 chatbot_config,
238 search_config,
239 blob_storage_config,
240 }))
241 }
242}