nautilus_databento/
common.rs1use std::{fmt::Debug, fs, path::Path, sync::LazyLock};
19
20use databento::{dbn, historical::DateTimeRange};
21use indexmap::IndexMap;
22use nautilus_core::{UnixNanos, string::secret::REDACTED};
23use nautilus_model::{
24 enums::BarAggregation,
25 identifiers::{ClientId, Venue},
26};
27use time::OffsetDateTime;
28use ustr::Ustr;
29use zeroize::ZeroizeOnDrop;
30
31use crate::types::{DatabentoPublisher, PublisherId};
32
33pub const DATABENTO: &str = "DATABENTO";
35
36pub static DATABENTO_VENUE: LazyLock<Venue> = LazyLock::new(|| Venue::new(Ustr::from(DATABENTO)));
38
39pub static DATABENTO_CLIENT_ID: LazyLock<ClientId> =
41 LazyLock::new(|| ClientId::new(Ustr::from(DATABENTO)));
42
43pub const ALL_SYMBOLS: &str = "ALL_SYMBOLS";
44
45pub fn load_publishers(filepath: impl AsRef<Path>) -> anyhow::Result<Vec<DatabentoPublisher>> {
51 let file_content = fs::read_to_string(filepath)?;
52 Ok(serde_json::from_str(&file_content)?)
53}
54
55#[must_use]
57pub fn build_publisher_venue_map(
58 publishers: &[DatabentoPublisher],
59) -> IndexMap<PublisherId, Venue> {
60 publishers
61 .iter()
62 .map(|p| (p.publisher_id, Venue::from(p.venue.as_str())))
63 .collect()
64}
65
66#[derive(Clone, ZeroizeOnDrop)]
68pub struct Credential {
69 api_key: Box<[u8]>,
70}
71
72impl Debug for Credential {
73 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74 f.debug_struct(stringify!(Credential))
75 .field("api_key", &REDACTED)
76 .finish()
77 }
78}
79
80impl Credential {
81 #[must_use]
83 pub fn new(api_key: impl Into<String>) -> Self {
84 let api_key_bytes = api_key.into().into_bytes();
85
86 Self {
87 api_key: api_key_bytes.into_boxed_slice(),
88 }
89 }
90
91 #[allow(
95 clippy::missing_panics_doc,
96 reason = "the API key is created from a UTF-8 String"
97 )]
98 #[must_use]
99 pub fn api_key(&self) -> &str {
100 std::str::from_utf8(&self.api_key).expect("API key is valid UTF-8")
101 }
102
103 #[must_use]
108 pub fn api_key_masked(&self) -> String {
109 nautilus_core::string::secret::mask_api_key(self.api_key())
110 }
111}
112
113pub fn get_date_time_range(start: UnixNanos, end: UnixNanos) -> anyhow::Result<DateTimeRange> {
117 Ok(DateTimeRange::from((
118 OffsetDateTime::from_unix_timestamp_nanos(i128::from(start.as_u64()))?,
119 OffsetDateTime::from_unix_timestamp_nanos(i128::from(end.as_u64()))?,
120 )))
121}
122
123pub fn ohlcv_schema_from_aggregation(aggregation: BarAggregation) -> anyhow::Result<dbn::Schema> {
129 match aggregation {
130 BarAggregation::Second => Ok(dbn::Schema::Ohlcv1S),
131 BarAggregation::Minute => Ok(dbn::Schema::Ohlcv1M),
132 BarAggregation::Hour => Ok(dbn::Schema::Ohlcv1H),
133 BarAggregation::Day => Ok(dbn::Schema::Ohlcv1D),
134 _ => anyhow::bail!("Unsupported bar aggregation for Databento OHLCV: {aggregation:?}"),
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use rstest::*;
141
142 use super::*;
143
144 #[rstest]
145 #[case(
146 UnixNanos::default(),
147 UnixNanos::default(),
148 "DateTimeRange { start: 1970-01-01 0:00:00.0 +00:00:00, end: 1970-01-01 0:00:00.0 +00:00:00 }"
149 )]
150 #[case(UnixNanos::default(), 1_000_000_000.into(), "DateTimeRange { start: 1970-01-01 0:00:00.0 +00:00:00, end: 1970-01-01 0:00:01.0 +00:00:00 }")]
151 fn test_get_date_time_range(
152 #[case] start: UnixNanos,
153 #[case] end: UnixNanos,
154 #[case] range_str: &str,
155 ) {
156 let range = get_date_time_range(start, end).unwrap();
157 assert_eq!(format!("{range:?}"), range_str);
158 }
159
160 #[rstest]
161 fn test_credential_api_key_masked_short() {
162 let credential = Credential::new("short");
163 assert_eq!(credential.api_key_masked(), "*****");
164 }
165
166 #[rstest]
167 fn test_credential_api_key_masked_long() {
168 let credential = Credential::new("abcdefghijklmnop");
169 assert_eq!(credential.api_key_masked(), "abcd...mnop");
170 }
171
172 #[rstest]
173 fn test_credential_debug_redaction() {
174 let credential = Credential::new("test_api_key");
175 let debug_str = format!("{credential:?}");
176 assert!(debug_str.contains(REDACTED));
177 assert!(!debug_str.contains("test_api_key"));
178 }
179}