headless_lms_utils/
lib.rs

1//! Commonly used utils.
2
3pub 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;
28use url::Url;
29
30#[derive(Clone, PartialEq)]
31pub struct ApplicationConfiguration {
32    pub base_url: String,
33    pub test_mode: bool,
34    pub development_uuid_login: bool,
35    pub azure_configuration: Option<AzureConfiguration>,
36    pub tmc_account_creation_origin: Option<String>,
37}
38
39impl ApplicationConfiguration {
40    /// Attempts to create an ApplicationConfiguration from environment variables.
41    pub fn try_from_env() -> anyhow::Result<Self> {
42        let base_url = env::var("BASE_URL").context("BASE_URL must be defined")?;
43        let test_mode = env::var("TEST_MODE").is_ok();
44        let development_uuid_login = env::var("DEVELOPMENT_UUID_LOGIN").is_ok();
45
46        let azure_configuration = AzureConfiguration::try_from_env()?;
47
48        let tmc_account_creation_origin = Some(
49            env::var("TMC_ACCOUNT_CREATION_ORIGIN")
50                .context("TMC_ACCOUNT_CREATION_ORIGIN must be defined")?,
51        );
52
53        Ok(Self {
54            base_url,
55            test_mode,
56            development_uuid_login,
57            azure_configuration,
58            tmc_account_creation_origin,
59        })
60    }
61}
62
63#[derive(Clone, PartialEq)]
64pub struct AzureChatbotConfiguration {
65    pub api_key: String,
66    pub api_endpoint: Url,
67}
68
69impl AzureChatbotConfiguration {
70    /// Attempts to create an AzureChatbotConfiguration from environment variables.
71    /// Returns `Ok(Some(AzureChatbotConfiguration))` if both environment variables are set.
72    /// Returns `Ok(None)` if no environment variables are set for chatbot.
73    /// Returns an error if set environment variables fail to parse.
74    pub fn try_from_env() -> anyhow::Result<Option<Self>> {
75        let api_key = env::var("AZURE_CHATBOT_API_KEY").ok();
76        let api_endpoint_str = env::var("AZURE_CHATBOT_API_ENDPOINT").ok();
77
78        if let (Some(api_key), Some(api_endpoint_str)) = (api_key, api_endpoint_str) {
79            let api_endpoint = Url::parse(&api_endpoint_str)
80                .context("Invalid URL in AZURE_CHATBOT_API_ENDPOINT")?;
81            Ok(Some(AzureChatbotConfiguration {
82                api_key,
83                api_endpoint,
84            }))
85        } else {
86            Ok(None)
87        }
88    }
89}
90
91#[derive(Clone, PartialEq)]
92pub struct AzureSearchConfiguration {
93    pub vectorizer_resource_uri: String,
94    pub vectorizer_deployment_id: String,
95    pub vectorizer_api_key: String,
96    pub vectorizer_model_name: String,
97    pub search_endpoint: Url,
98    pub search_api_key: String,
99}
100
101impl AzureSearchConfiguration {
102    /// Attempts to create an AzureSearchConfiguration from environment variables.
103    /// Returns `Ok(Some(AzureSearchConfiguration))` if all related environment variables are set.
104    /// Returns `Ok(None)` if no environment variables are set for search and vectorizer.
105    /// Returns an error if set environment variables fail to parse.
106    pub fn try_from_env() -> anyhow::Result<Option<Self>> {
107        let vectorizer_resource_uri = env::var("AZURE_VECTORIZER_RESOURCE_URI").ok();
108        let vectorizer_deployment_id = env::var("AZURE_VECTORIZER_DEPLOYMENT_ID").ok();
109        let vectorizer_api_key = env::var("AZURE_VECTORIZER_API_KEY").ok();
110        let vectorizer_model_name = env::var("AZURE_VECTORIZER_MODEL_NAME").ok();
111        let search_endpoint_str = env::var("AZURE_SEARCH_ENDPOINT").ok();
112        let search_api_key = env::var("AZURE_SEARCH_API_KEY").ok();
113
114        if let (
115            Some(vectorizer_resource_uri),
116            Some(vectorizer_deployment_id),
117            Some(vectorizer_api_key),
118            Some(vectorizer_model_name),
119            Some(search_endpoint_str),
120            Some(search_api_key),
121        ) = (
122            vectorizer_resource_uri,
123            vectorizer_deployment_id,
124            vectorizer_api_key,
125            vectorizer_model_name,
126            search_endpoint_str,
127            search_api_key,
128        ) {
129            let search_endpoint =
130                Url::parse(&search_endpoint_str).context("Invalid URL in AZURE_SEARCH_ENDPOINT")?;
131            Ok(Some(AzureSearchConfiguration {
132                vectorizer_resource_uri,
133                vectorizer_deployment_id,
134                vectorizer_api_key,
135                vectorizer_model_name,
136                search_endpoint,
137                search_api_key,
138            }))
139        } else {
140            Ok(None)
141        }
142    }
143}
144
145#[derive(Clone, PartialEq)]
146pub struct AzureBlobStorageConfiguration {
147    pub storage_account: String,
148    pub access_key: String,
149}
150
151impl AzureBlobStorageConfiguration {
152    /// Attempts to create an AzureBlobStorageConfiguration from environment variables.
153    /// Returns `Ok(Some(AzureBlobStorageConfiguration))` if both environment variables are set.
154    /// Returns `Ok(None)` if no environment variables are set for blob storage.
155    pub fn try_from_env() -> anyhow::Result<Option<Self>> {
156        let storage_account = env::var("AZURE_BLOB_STORAGE_ACCOUNT").ok();
157        let access_key = env::var("AZURE_BLOB_STORAGE_ACCESS_KEY").ok();
158
159        if let (Some(storage_account), Some(access_key)) = (storage_account, access_key) {
160            Ok(Some(AzureBlobStorageConfiguration {
161                storage_account,
162                access_key,
163            }))
164        } else {
165            Ok(None)
166        }
167    }
168
169    pub fn connection_string(&self) -> anyhow::Result<String> {
170        Ok(format!(
171            "DefaultEndpointsProtocol=https;AccountName={};AccountKey={};EndpointSuffix=core.windows.net",
172            self.storage_account, self.access_key
173        ))
174    }
175}
176
177#[derive(Clone, PartialEq)]
178pub struct AzureConfiguration {
179    pub chatbot_config: Option<AzureChatbotConfiguration>,
180    pub search_config: Option<AzureSearchConfiguration>,
181    pub blob_storage_config: Option<AzureBlobStorageConfiguration>,
182}
183
184impl AzureConfiguration {
185    /// Attempts to create an AzureConfiguration by calling the individual try_from_env functions.
186    /// Returns `Ok(Some(AzureConfiguration))` if any of the configurations are set.
187    /// Returns `Ok(None)` if no relevant environment variables are set.
188    pub fn try_from_env() -> anyhow::Result<Option<Self>> {
189        let chatbot = AzureChatbotConfiguration::try_from_env()?;
190        let search_config = AzureSearchConfiguration::try_from_env()?;
191        let blob_storage_config = AzureBlobStorageConfiguration::try_from_env()?;
192
193        if chatbot.is_some() || search_config.is_some() || blob_storage_config.is_some() {
194            Ok(Some(AzureConfiguration {
195                chatbot_config: chatbot,
196                search_config,
197                blob_storage_config,
198            }))
199        } else {
200            Ok(None)
201        }
202    }
203}