1mod client;
2mod config;
3mod dispatcher;
4mod sys;
5
6use std::{panic::PanicHookInfo, sync::Arc, time::Duration};
7
8pub use client::{AptabaseClient, new_session_id};
9use config::Config;
10use serde_json::json;
11
12#[derive(Default, Debug, Clone)]
13pub struct InitOptions {
14 pub host: Option<String>,
15 pub flush_interval: Option<Duration>,
16}
17
18pub struct Builder {
20 app_key: String,
21 app_version: String,
22 session_id: Option<String>,
23 enable_polling: bool,
24 panic_hook: Option<PanicHook>,
25 options: InitOptions,
26}
27
28pub type PanicHook =
29 Box<dyn Fn(&AptabaseClient, &PanicHookInfo<'_>, String) + 'static + Sync + Send>;
30
31fn get_panic_message(info: &PanicHookInfo) -> String {
32 let payload = info.payload();
33 if let Some(s) = payload.downcast_ref::<&str>() {
34 return s.to_string();
35 } else if let Some(s) = payload.downcast_ref::<String>() {
36 return s.to_string();
37 }
38
39 format!("{:?}", payload)
40}
41
42impl Builder {
43 pub fn new(app_key: &str, app_version: &str) -> Self {
45 Self {
46 app_key: app_key.into(),
47 app_version: app_version.into(),
48 session_id: None,
49 enable_polling: false,
50 panic_hook: None,
51 options: Default::default(),
52 }
53 }
54
55 pub fn with_options(mut self, opts: InitOptions) -> Self {
57 self.options = opts;
58 self
59 }
60
61 pub fn with_session_id(mut self, session_id: impl Into<String>) -> Self {
63 self.session_id = Some(session_id.into());
64 self
65 }
66
67 pub fn with_polling(mut self, enable: bool) -> Self {
69 self.enable_polling = enable;
70 self
71 }
72
73 pub fn with_panic_hook(mut self, hook: PanicHook) -> Self {
75 self.panic_hook = Some(hook);
76 self
77 }
78
79 pub fn with_default_panic_hook(self) -> Self {
81 self.with_panic_hook(Box::new(|client, info, message| {
82 let location = info
83 .location()
84 .map(|loc| format!("{}:{}:{}", loc.file(), loc.line(), loc.column()))
85 .unwrap_or_default();
86 let _ = client.track_event(
87 "panic",
88 Some(json!({
89 "info": format!("{} ({})", message, location),
90 })),
91 );
92 }))
93 }
94
95 pub fn build(self) -> Arc<AptabaseClient> {
97 let cfg = Config::new(self.app_key, self.session_id, self.options);
98 let client = Arc::new(AptabaseClient::new(&cfg, self.app_version));
99
100 if self.enable_polling {
101 client.start_polling(cfg.flush_interval);
102 }
103
104 if let Some(hook) = self.panic_hook {
105 let default_panic = std::panic::take_hook();
106 let hook_client = client.clone();
107 std::panic::set_hook(Box::new(move |info| {
108 let msg = get_panic_message(info);
109 hook(&hook_client, info, msg);
110 hook_client.flush_blocking();
111 default_panic(info);
112 }));
113 }
114
115 client
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn builder_uses_supplied_session_id() {
125 let client = Builder::new("A-DEV-123", "test")
127 .with_session_id("persisted-session")
128 .build();
129
130 assert_eq!(client.eval_session_id(), "persisted-session");
132 }
133
134 #[test]
135 fn builder_treats_empty_session_id_as_absent() {
136 let client = Builder::new("A-DEV-123", "test")
138 .with_session_id("")
139 .build();
140
141 assert!(!client.eval_session_id().is_empty());
143 }
144}