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