1pub use crate::context::{
2 Context, InfrastructureConfig, MinioConfig, MlflowConfig, PostgresConfig, Receiver, RedisConfig,
3};
4
5#[cfg(feature = "messaging")]
6pub use crate::stream::ReceiverExt;
7
8#[cfg(feature = "messaging")]
9pub use crate::publish::ContextPublishExt;
10
11#[cfg(feature = "messaging")]
12pub use crate::rpc::{Request, Response, RpcExt};
13
14#[cfg(feature = "messaging")]
15pub use crate::rpc_streaming::{
16 StreamError, StreamMessage, StreamPayload, StreamReceiver, StreamSender, StreamingRpcExt,
17};
18
19#[cfg(feature = "messaging")]
20pub use crate::discovery::DiscoveryExt;
21pub use crate::discovery::{NodeMetadata, NodeStatus};
22
23pub use crate::schema::{
24 CompatibilityCheck, CompatibilityMode, MessageSchema, MessageVersionInfo, SchemaEvolution, SchemaField,
25 SchemaRegistryExt, SchemaVersioningExt,
26};
27
28pub use crate::auth::{AuthExt, Permission, Policy, Rule};
29
30pub use crate::secrets::{
31 EnvironmentBackend, RedisBackend, SecretBackend, SecretsConfig, SecretsManager, VaultBackend,
32};
33
34#[cfg(feature = "messaging")]
35pub use crate::health::{
36 HealthMonitor, HealthReport, HealthReporter, HealthReportingExt, NodeHealthInfo, SystemHealth,
37};
38
39#[cfg(feature = "messaging")]
40pub use crate::behavior_interrupt::{
41 BehaviorControl, BehaviorInterruptConfig, BehaviorInterruptTrigger, InterruptMode,
42};
43
44#[cfg(feature = "messaging")]
45pub use crate::on_demand::subscribe_on_demand;
46pub use crate::on_demand::OnDemandRequest;
47
48pub use crate::metrics::{Counter, Gauge, Histogram, MetricsRegistry, NodeMetrics};
49
50pub use crate::rate_limit::{MultiKeyRateLimiter, RateLimit, RateLimiter, RateLimiterManager};
51
52pub use crate::circuit_breaker::{CircuitBreaker, CircuitBreakerConfig, CircuitState};
53
54#[cfg(feature = "messaging")]
55pub use crate::dead_letter::{DeadLetterConfig, DeadLetterQueue, FailedMessage};
56
57#[cfg(feature = "schema-validation")]
58pub use crate::schema_validation::{
59 export_project_schema, get_project_schema, load_and_validate_project, validate_config_file,
60 validate_node_config_file, validate_node_config_json, validate_project_config, validate_project_json,
61};
62
63pub use crate::dependency::{create_graph, DependencyGraph, ShutdownPlan, StartupPlan};
64
65#[cfg(feature = "messaging")]
66pub use crate::lifecycle::{
67 get_timestamp_ms, topic_node_lifecycle, topic_node_status, topic_node_trigger, ModeChanged, ModeError, ModeRequest,
68 NodeLifecycleAction, NodeLifecycleEvent, NodeLifecycleStatus, NodeStatusEvent, NodeTriggerEvent,
69 TOPIC_MODE_CHANGED, TOPIC_MODE_ERROR, TOPIC_MODE_REQUEST,
70};
71
72pub use crate::topic_registry::{TopicInfo, TopicRegistry, TopologyStats};
73
74pub use crate::topic_utils::{extract_category, format_elapsed_time, TopicMetadata, Topics};
75
76pub use crate::state::{
77 FilesystemStateManager, MemoryStateManager, StateManager, StateOptions, StateSnapshot, VersionedState,
78};
79
80pub use crate::recovery::{
81 BackoffStrategy, DegradationMode, RecoveryAction, RecoveryAttempt, RecoveryHistory, RecoveryManager,
82 RecoveryPolicy, RecoveryPolicyBuilder, RecoveryStats,
83};
84
85#[cfg(feature = "messaging")]
86pub use crate::tracing_otel::{
87 JaegerExporter, SpanBuilder, SpanData, TraceContext, TraceExporter, TracedMessage, TracingConfig, TracingExt,
88 TracingHandle, ZipkinExporter,
89};
90
91pub use crate::profiling::{
92 measure_latency, ExecutionStats, LatencyStats, MemoryStats, PerformanceMetrics, PerformanceReport, ProfilingExt,
93 ThroughputStats, TimerGuard,
94};
95
96pub use crate::service::Service;
97#[cfg(feature = "messaging")]
98pub use crate::service_manager::ServiceManager;
99
100pub use crate::node::{
101 fmt_node_with_controller, run_node, HealthPriority, HealthReportingConfig, HealthStatusExt, Node, NodeImpl,
102 NodeTag, NodeType, RateConfig,
103};
104
105pub use crate::config::{
106 env_or_default, load_and_validate, load_config, load_config_from_env, substitute_env_vars, Validate,
107};
108
109pub use crate::model::{load_labels, load_model_config, CustomLabelsConfig, ModelConfig, PreprocessingConfig};
110
111pub use crate::builder_setter;
112pub use crate::enum_str_method;
113
114pub use crate::messages::Message;
115
116pub use crate::messages::Timestamped;
117
118pub use crate::types::*;
119
120pub use crate::error::{Mecha10Error, Result};
121
122pub use async_trait::async_trait;
123
124pub use tokio::{
125 self, select,
126 time::{interval, sleep, timeout, Duration, Instant},
127};
128
129pub use tokio::task::JoinHandle;
130
131pub use serde::{Deserialize, Serialize};
132pub use serde_json::{self, json, Value as JsonValue};
133
134pub use schemars::JsonSchema;
135
136pub use tracing::{debug, error, info, instrument, trace, warn, Level};
137
138#[cfg(feature = "macros")]
139pub use mecha10_macros::Node;
140
141#[cfg(feature = "macros")]
142pub use mecha10_macros::Config;
143
144#[cfg(feature = "macros")]
145pub use mecha10_macros::ConfigDefaults;
146
147fn since_epoch() -> std::time::Duration {
150 std::time::SystemTime::now()
151 .duration_since(std::time::UNIX_EPOCH)
152 .expect("Time went backwards")
153}
154
155pub fn now_micros() -> u64 {
157 since_epoch().as_micros() as u64
158}
159
160pub fn now_secs() -> u64 {
162 since_epoch().as_secs()
163}
164
165pub fn interval_at_hz(hz: f32) -> tokio::time::Interval {
200 let duration = std::time::Duration::from_secs_f32(1.0 / hz);
201 tokio::time::interval(duration)
202}
203
204pub fn init_logging() {
223 use tracing_subscriber::EnvFilter;
224
225 static SENTRY_GUARD: std::sync::OnceLock<sentry::ClientInitGuard> = std::sync::OnceLock::new();
226 if SENTRY_GUARD.get().is_none() && sentry::Hub::current().client().is_none() {
227 let sentry_dsn = std::env::var("SENTRY_DSN_NODES").unwrap_or_else(|_| {
228 "https://7ea967345cb382353a93ce31365e4790@o4506107376631808.ingest.us.sentry.io/4512334108229632"
229 .to_string()
230 });
231 let mut sentry_opts = sentry::ClientOptions::default();
232 sentry_opts.dsn = sentry_dsn.parse().ok();
233 sentry_opts.release = sentry::release_name!();
234 let _ = SENTRY_GUARD.set(sentry::init(sentry_opts));
235 }
236
237 let base = std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string());
238 let curated = format!("{base},ort=warn,rtc_dtls=error,rtc_ice=error,rtc::peer_connection::handler=error");
239 let env_filter = EnvFilter::try_new(&curated).unwrap_or_else(|_| {
240 EnvFilter::new("info,ort=warn,rtc_dtls=error,rtc_ice=error,rtc::peer_connection::handler=error")
241 });
242
243 #[cfg(feature = "messaging")]
244 {
245 use tracing_subscriber::prelude::*;
246
247 let _ = tracing_subscriber::registry()
248 .with(env_filter)
249 .with(tracing_subscriber::fmt::layer())
250 .with(crate::log_publisher::RedisLogLayer::new())
251 .try_init();
252 }
253
254 #[cfg(not(feature = "messaging"))]
255 {
256 let _ = tracing_subscriber::fmt().with_env_filter(env_filter).try_init();
257 }
258}
259
260#[macro_export]
261macro_rules! in_node_context {
262 ($ctx:expr, $body:block) => {{
263 let _span = tracing::info_span!("node", node_id = $ctx.node_id()).entered();
264 $body
265 }};
266}
267
268pub use in_node_context;
269
270#[macro_export]
271macro_rules! node_binary_main {
272 ($node_crate:ident) => {
273 #[tokio::main]
274 async fn main() -> anyhow::Result<()> {
275 $crate::prelude::init_logging();
276
277 $node_crate::run().await.map_err(|e| anyhow::anyhow!(e))
278 }
279 };
280}
281
282pub use node_binary_main;
283
284pub fn now_millis() -> u64 {
297 since_epoch().as_millis() as u64
298}
299
300pub fn elapsed_micros(timestamp_micros: u64) -> u64 {
321 now_micros().saturating_sub(timestamp_micros)
322}
323
324pub fn timestamp_as_secs(timestamp_micros: u64) -> f64 {
338 timestamp_micros as f64 / 1_000_000.0
339}
340
341pub fn duration_from_hz(hz: f32) -> Duration {
351 Duration::from_secs_f32(1.0 / hz)
352}
353
354pub fn normalize_angle(angle: f32) -> f32 {
367 use std::f32::consts::PI;
368 let mut normalized = angle;
369 while normalized > PI {
370 normalized -= 2.0 * PI;
371 }
372 while normalized < -PI {
373 normalized += 2.0 * PI;
374 }
375 normalized
376}
377
378pub fn normalize_angle_degrees(angle: f32) -> f32 {
388 let mut normalized = angle;
389 while normalized > 180.0 {
390 normalized -= 360.0;
391 }
392 while normalized < -180.0 {
393 normalized += 360.0;
394 }
395 normalized
396}
397
398pub fn angle_difference(from: f32, to: f32) -> f32 {
410 normalize_angle(to - from)
411}
412
413pub fn degrees_to_radians(degrees: f32) -> f32 {
423 degrees * std::f32::consts::PI / 180.0
424}
425
426pub fn radians_to_degrees(radians: f32) -> f32 {
436 radians * 180.0 / std::f32::consts::PI
437}
438
439pub fn safe_divide(numerator: f32, denominator: f32, default: f32) -> f32 {
450 if denominator.abs() < f32::EPSILON {
451 default
452 } else {
453 numerator / denominator
454 }
455}
456
457pub fn clamp<T: PartialOrd>(value: T, min: T, max: T) -> T {
469 if value < min {
470 min
471 } else if value > max {
472 max
473 } else {
474 value
475 }
476}
477
478pub fn lerp(a: f32, b: f32, t: f32) -> f32 {
494 a + (b - a) * t
495}
496
497pub use std::collections::{HashMap, HashSet};
498
499pub use std::sync::{Arc, Mutex};
500
501pub use std::path::{Path, PathBuf};
502
503pub type Shared<T> = Arc<T>;
504pub type SharedMut<T> = Arc<Mutex<T>>;
505
506#[cfg(test)]
507mod sentry_options_tests {
508 #[test]
515 fn client_options_builds_via_default_and_field_assignment() {
516 let dsn = "https://public@sentry.example.com/1";
517
518 let mut opts = sentry::ClientOptions::default();
519 opts.dsn = dsn.parse().ok();
520 opts.release = sentry::release_name!();
521
522 assert!(opts.dsn.is_some());
523 }
524
525 #[test]
526 fn invalid_dsn_parses_to_none_without_panicking() {
527 let mut opts = sentry::ClientOptions::default();
528 opts.dsn = "not-a-valid-dsn".parse().ok();
529
530 assert!(opts.dsn.is_none());
531 }
532}