1#[macro_use]
2extern crate log;
3
4#[macro_use]
5extern crate serde;
6
7use std::{
8 collections::{BTreeSet, HashMap},
9 sync::Arc,
10};
11
12use anyhow::Result;
13use async_trait::async_trait;
14use secstr::SecUtf8;
15use std::sync::Mutex;
16use strum::EnumString;
17
18pub mod accounts;
19pub mod alerts;
20pub mod options;
21pub mod orders;
22mod session;
23pub mod transactions;
24
25#[cfg(all(feature = "keychain", target_os = "linux"))]
26mod linux;
27#[cfg(all(feature = "keychain", target_os = "linux"))]
28pub use linux::KeychainStore;
29
30#[cfg(all(feature = "keychain", target_os = "macos"))]
31mod macos;
32#[cfg(all(feature = "keychain", target_os = "macos"))]
33pub use macos::KeychainStore;
34
35#[cfg(all(feature = "keychain", target_os = "windows"))]
36mod windows;
37#[cfg(all(feature = "keychain", target_os = "windows"))]
38pub use windows::KeychainStore;
39
40pub use accounts::Api as Accounts;
41pub use session::CallbackProvider;
42pub use session::Session;
43pub use session::OOB;
44
45const SANDBOX_URL: &str = "https://apisb.etrade.com";
47
48const LIVE_URL: &str = "https://api.etrade.com";
50
51fn qs_params<'a, T: serde::Serialize + serde::Deserialize<'a>>(
52 params: &T,
53) -> Result<Option<BTreeSet<(String, String)>>> {
54 let qss = serde_urlencoded::to_string(params)?;
55 let qs: BTreeSet<(String, String)> = serde_urlencoded::from_str(&qss)?;
56 if qs.is_empty() {
57 Ok(None)
58 } else {
59 Ok(Some(qs))
60 }
61}
62
63fn empty_body() -> Option<()> {
64 None
65}
66
67#[derive(Debug, Clone, Copy, Deserialize, Serialize, EnumString, strum::Display)]
68pub enum Mode {
69 Sandbox,
70 Live,
71}
72
73#[derive(Debug, Clone, Deserialize, Serialize, Default)]
74#[serde(rename_all = "camelCase", default)]
75pub struct Messages {
76 #[serde(rename = "Message", skip_serializing_if = "Vec::is_empty")]
77 pub message: Vec<Message>,
78}
79
80impl Messages {
81 pub fn is_empty(&self) -> bool {
82 self.message.is_empty()
83 }
84}
85
86#[derive(Debug, Clone, Deserialize, Serialize, Default)]
87#[serde(rename_all = "camelCase", default)]
88pub struct Message {
89 pub description: String,
90 pub code: i32,
91 #[serde(rename = "type")]
92 pub tpe: MessageType,
93}
94
95#[derive(Debug, Clone, Copy, Deserialize, Serialize, EnumString)]
96#[strum(serialize_all = "lowercase")]
97pub enum MessageType {
98 #[serde(rename = "WARNING")]
99 Warning,
100 #[serde(rename = "INFO")]
101 Info,
102 #[serde(rename = "INFO_HOLD")]
103 InfoHold,
104 #[serde(rename = "ERROR")]
105 Error,
106}
107
108impl Default for MessageType {
109 fn default() -> Self {
110 MessageType::Info
111 }
112}
113
114#[derive(Debug, Deserialize, Serialize, Default, Clone)]
115#[serde(rename_all = "camelCase", default)]
116pub struct Product {
117 pub symbol: String,
118 #[serde(skip_serializing_if = "Option::is_none")]
119 pub security_type: Option<SecurityType>,
120 #[serde(skip_serializing_if = "Option::is_none")]
121 pub security_sub_type: Option<String>,
122 pub call_put: String,
123 pub expiry_year: i32,
124 pub expiry_month: i32,
125 pub expiry_day: i32,
126 pub strike_price: f64,
127 pub expiry_type: String,
128}
129
130#[derive(Debug, Clone, Copy, Deserialize, Serialize, EnumString)]
131pub enum SecurityType {
132 #[serde(rename = "EQ")]
133 Eq,
134 #[serde(rename = "OPTN")]
135 Optn,
136 #[serde(rename = "MF")]
137 Mf,
138 #[serde(rename = "MMF")]
139 Mmf,
140}
141
142#[derive(Debug, Clone, Copy, Deserialize, Serialize, EnumString)]
143#[strum(serialize_all = "lowercase")]
144pub enum MarketSession {
145 #[serde(rename = "REGULAR")]
146 Regular,
147 #[serde(rename = "EXTENDED")]
148 Extended,
149}
150
151#[derive(Debug, Clone, Copy, Deserialize, Serialize, EnumString)]
152#[strum(serialize_all = "lowercase")]
153pub enum OptionType {
154 #[serde(rename = "CALL")]
155 Call,
156 #[serde(rename = "PUT")]
157 Put,
158}
159
160#[derive(Debug, Clone, Copy, Deserialize, Serialize, EnumString)]
161#[strum(serialize_all = "lowercase")]
162pub enum SortOrder {
163 #[serde(rename = "ASC")]
164 Asc,
165 #[serde(rename = "DESC")]
166 Desc,
167}
168
169#[derive(Debug, Clone)]
170pub struct Credentials {
171 pub key: SecUtf8,
172 pub secret: SecUtf8,
173}
174
175impl Credentials {
176 pub fn new(key: SecUtf8, secret: SecUtf8) -> Credentials {
177 Credentials { key, secret }
178 }
179}
180
181impl Into<oauth::Credentials> for Credentials {
182 fn into(self) -> oauth::Credentials {
183 oauth::Credentials::new(self.key.into_unsecure(), self.secret.into_unsecure())
184 }
185}
186
187impl<T> From<oauth::Credentials<T>> for Credentials
188where
189 T: Into<SecUtf8>,
190{
191 fn from(input: oauth::Credentials<T>) -> Self {
192 Credentials {
193 key: input.identifier.into(),
194 secret: input.secret.into(),
195 }
196 }
197}
198
199#[async_trait]
200pub trait Store {
201 async fn put(
202 &self,
203 namespace: impl Into<String> + Send,
204 key: impl Into<String> + Send,
205 value: impl Into<SecUtf8> + Send,
206 ) -> Result<()>;
207 async fn del(&self, namespace: impl AsRef<str> + Send, key: impl AsRef<str> + Send) -> Result<()>;
208 async fn get(&self, namespace: impl AsRef<str> + Send, key: impl AsRef<str> + Send) -> Result<Option<SecUtf8>>;
209}
210
211#[derive(Debug)]
212pub struct Memstore {
213 data: Arc<Mutex<HashMap<String, HashMap<String, SecUtf8>>>>,
214}
215
216impl Memstore {
217 pub fn new() -> Self {
218 Memstore {
219 data: Arc::new(Mutex::new(HashMap::new())),
220 }
221 }
222}
223
224impl Default for Memstore {
225 fn default() -> Self {
226 Memstore::new()
227 }
228}
229
230#[async_trait]
231impl Store for Memstore {
232 async fn put(
233 &self,
234 namespace: impl Into<String> + Send,
235 key: impl Into<String> + Send,
236 value: impl Into<SecUtf8> + Send,
237 ) -> Result<()> {
238 let mut data = self.data.lock().unwrap();
239
240 let svc_state = data.entry(namespace.into()).or_insert_with(HashMap::new);
241 svc_state.insert(key.into(), value.into());
242 Ok(())
243 }
244
245 async fn del(&self, namespace: impl AsRef<str> + Send, key: impl AsRef<str> + Send) -> Result<()> {
246 let mut data = self.data.lock().unwrap();
247
248 if let Some(st) = data.get_mut(namespace.as_ref()) {
249 st.remove(key.as_ref());
250 }
251 Ok(())
252 }
253
254 async fn get(&self, namespace: impl AsRef<str> + Send, key: impl AsRef<str> + Send) -> Result<Option<SecUtf8>> {
255 let data = self.data.lock().unwrap();
256 Ok(data.get(namespace.as_ref()).and_then(|r| r.get(key.as_ref()).cloned()))
257 }
258}
259
260#[cfg(test)]
261pub mod tests {
262
263 use super::{Memstore, Store};
264 use anyhow::Result;
265 use secstr::SecUtf8;
266 pub(crate) fn init() {
267 std::env::set_var("RUST_LOG", "debug");
268 let _ = pretty_env_logger::try_init();
269 }
270 #[tokio::test]
271 async fn test_mem_store() {
272 verify_token_store(Memstore::new()).await;
273 }
274
275 pub async fn verify_token_store(token_store: impl Store) {
276 let expected: Result<SecUtf8> = Ok("hello".into());
277 token_store.put("my_svc", "api_key", "hello").await.unwrap();
278 assert_eq!(token_store.get("my_svc", "api_key").await.ok(), Some(expected.ok()));
279 assert!(token_store.del("my_svc", "api_key").await.is_ok());
280 assert!(token_store.get("my_svc", "api_key").await.unwrap().is_none());
281 }
282}