Skip to main content

hyperi_rustlib/
lib.rs

1// Project:   hyperi-rustlib
2// File:      src/lib.rs
3// Purpose:   Main library entry point and public API exports
4// Language:  Rust
5//
6// License:   BUSL-1.1
7// Copyright: (c) 2026 HYPERI PTY LIMITED
8
9//! # hyperi-rustlib
10//!
11//! There's plenty of sage advice out there about how to run Rust services in production at scale -- config cascades, structured logging, masking secrets, multi-backend secrets management, Prometheus, OpenTelemetry, Kafka transports, tiered disk-spillover sinks, adaptive worker pools, graceful shutdown -- but almost none of it as code you can just install and use.
12//!
13//! This is that code.
14//!
15//! Opinionated, drop-in, working out of the box. The patterns from blog posts, watercooler chats and beers with your Google mates as actual library -- not a framework you assemble from twenty crates and 8 weeks of munging.
16//!
17//! Built as the foundation for HyperI's PB/hr data services. Generic enough
18//! that you don't need to be at HyperI to use it.
19//!
20//! Full reference docs live under [`docs/`](https://github.com/hyperi-io/hyperi-rustlib/tree/main/docs).
21//! Start at [`docs/README.md`](https://github.com/hyperi-io/hyperi-rustlib/blob/main/docs/README.md)
22//! for the entry-point index.
23//!
24//! ## Quick Start
25//!
26//! ```rust,no_run
27//! use hyperi_rustlib::{env, config, logger, metrics};
28//!
29//! fn main() -> Result<(), Box<dyn std::error::Error>> {
30//!     // Detect runtime environment
31//!     let environment = env::Environment::detect();
32//!     println!("Running in: {:?}", environment);
33//!
34//!     // Initialise logger (respects LOG_LEVEL env var)
35//!     logger::setup_default()?;
36//!
37//!     // Load configuration with 7-layer cascade
38//!     config::setup(config::ConfigOptions {
39//!         env_prefix: "MYAPP".into(),
40//!         ..Default::default()
41//!     })?;
42//!
43//!     // Access config
44//!     let cfg = config::get();
45//!     let db_host = cfg.get_string("database.host").unwrap_or_default();
46//!
47//!     // Create metrics
48//!     let metrics_mgr = metrics::MetricsManager::new("myapp");
49//!     let _counter = metrics_mgr.counter("requests_total", "Total requests processed");
50//!
51//!     tracing::info!(db_host = %db_host, "Application started");
52//!     Ok(())
53//! }
54//! ```
55//!
56//! See `docs/CORE-PILLARS.md` in the repository for the auto-wiring architecture.
57
58#![deny(unsafe_code)]
59#![cfg_attr(docsrs, feature(doc_cfg))]
60#![warn(clippy::pedantic)]
61// Crate-wide hard-deny for the most pernicious async footgun. Holding a
62// sync `Mutex`/`RwLock` guard across `.await` is the canonical way to
63// deadlock a tokio runtime under contention. The lint catches this at
64// compile time. Integration tests that legitimately serialise via a
65// sync mutex opt out file-locally with `#![allow(...)]` and a comment
66// explaining the safety reasoning.
67#![deny(clippy::await_holding_lock)]
68#![warn(rustdoc::broken_intra_doc_links)]
69#![warn(rustdoc::private_intra_doc_links)]
70#![warn(rustdoc::invalid_codeblock_attributes)]
71#![warn(rustdoc::bare_urls)]
72#![allow(clippy::module_name_repetitions)]
73#![allow(clippy::doc_markdown)] // Allow brand names without backticks
74#![allow(clippy::cast_precision_loss)] // Metrics values are fine with f64 precision
75#![allow(clippy::missing_panics_doc)] // MVP does not require exhaustive docs
76#![allow(clippy::missing_errors_doc)] // MVP does not require exhaustive docs
77#![allow(clippy::double_must_use)] // Return types already marked must_use
78#![allow(clippy::unused_async)] // Async for future compatibility
79#![allow(clippy::redundant_closure_for_method_calls)] // Clearer with explicit closure
80#![allow(clippy::result_large_err)] // figment::Error is large by design
81#![allow(clippy::needless_pass_by_value)]
82// API cleaner with owned values
83// Test code allowances - unwrap is acceptable in tests for cleaner assertions
84#![cfg_attr(test, allow(clippy::unwrap_used))]
85#![cfg_attr(test, allow(clippy::field_reassign_with_default))]
86
87// Core modules (always available)
88pub mod env;
89pub mod kafka_config;
90pub mod sensitive;
91
92#[cfg(feature = "runtime")]
93#[cfg_attr(docsrs, doc(cfg(feature = "runtime")))]
94pub mod runtime;
95
96#[cfg(feature = "shutdown")]
97#[cfg_attr(docsrs, doc(cfg(feature = "shutdown")))]
98pub mod shutdown;
99
100#[cfg(feature = "health")]
101#[cfg_attr(docsrs, doc(cfg(feature = "health")))]
102pub mod health;
103
104#[cfg(feature = "config")]
105#[cfg_attr(docsrs, doc(cfg(feature = "config")))]
106pub mod config;
107
108#[cfg(feature = "logger")]
109#[cfg_attr(docsrs, doc(cfg(feature = "logger")))]
110pub mod logger;
111
112#[cfg(any(feature = "metrics", feature = "otel-metrics"))]
113#[cfg_attr(docsrs, doc(cfg(any(feature = "metrics", feature = "otel-metrics"))))]
114pub mod metrics;
115
116#[cfg(feature = "otel-tracing")]
117#[cfg_attr(docsrs, doc(cfg(feature = "otel-tracing")))]
118pub mod otel_tracing;
119
120#[cfg(feature = "transport")]
121#[cfg_attr(docsrs, doc(cfg(feature = "transport")))]
122pub mod transport;
123
124#[cfg(feature = "http")]
125#[cfg_attr(docsrs, doc(cfg(feature = "http")))]
126pub mod http_client;
127
128#[cfg(feature = "http-server")]
129#[cfg_attr(docsrs, doc(cfg(feature = "http-server")))]
130pub mod http_server;
131
132#[cfg(feature = "database")]
133#[cfg_attr(docsrs, doc(cfg(feature = "database")))]
134pub mod database;
135
136#[cfg(feature = "cache")]
137#[cfg_attr(docsrs, doc(cfg(feature = "cache")))]
138pub mod cache;
139
140#[cfg(feature = "spool")]
141#[cfg_attr(docsrs, doc(cfg(feature = "spool")))]
142pub mod spool;
143
144#[cfg(feature = "tiered-sink")]
145#[cfg_attr(docsrs, doc(cfg(feature = "tiered-sink")))]
146pub mod tiered_sink;
147
148#[cfg(feature = "secrets")]
149#[cfg_attr(docsrs, doc(cfg(feature = "secrets")))]
150pub mod secrets;
151
152#[cfg(feature = "directory-config")]
153#[cfg_attr(docsrs, doc(cfg(feature = "directory-config")))]
154pub mod directory_config;
155
156#[cfg(feature = "memory")]
157#[cfg_attr(docsrs, doc(cfg(feature = "memory")))]
158pub mod memory;
159
160#[cfg(feature = "tls")]
161#[cfg_attr(docsrs, doc(cfg(feature = "tls")))]
162pub mod tls;
163
164#[cfg(feature = "scaling")]
165#[cfg_attr(docsrs, doc(cfg(feature = "scaling")))]
166pub mod scaling;
167
168#[cfg(feature = "governor")]
169#[cfg_attr(docsrs, doc(cfg(feature = "governor")))]
170pub mod governor;
171
172#[cfg(any(feature = "worker-pool", feature = "worker-batch", feature = "worker"))]
173#[cfg_attr(
174    docsrs,
175    doc(cfg(any(feature = "worker-pool", feature = "worker-batch", feature = "worker")))
176)]
177pub mod worker;
178
179#[cfg(feature = "cli")]
180#[cfg_attr(docsrs, doc(cfg(feature = "cli")))]
181pub mod cli;
182
183#[cfg(feature = "top")]
184#[cfg_attr(docsrs, doc(cfg(feature = "top")))]
185pub mod top;
186
187#[cfg(feature = "io")]
188#[cfg_attr(docsrs, doc(cfg(feature = "io")))]
189pub mod io;
190
191#[cfg(feature = "dlq")]
192#[cfg_attr(docsrs, doc(cfg(feature = "dlq")))]
193pub mod dlq;
194
195#[cfg(feature = "output-file")]
196#[cfg_attr(docsrs, doc(cfg(feature = "output-file")))]
197pub mod output;
198
199#[cfg(feature = "expression")]
200#[cfg_attr(docsrs, doc(cfg(feature = "expression")))]
201pub mod expression;
202
203#[cfg(feature = "deployment")]
204#[cfg_attr(docsrs, doc(cfg(feature = "deployment")))]
205pub mod deployment;
206
207#[cfg(feature = "version-check")]
208#[cfg_attr(docsrs, doc(cfg(feature = "version-check")))]
209pub mod version_check;
210
211#[cfg(feature = "concurrency")]
212#[cfg_attr(docsrs, doc(cfg(feature = "concurrency")))]
213pub mod concurrency;
214
215#[cfg(feature = "strmatch")]
216#[cfg_attr(docsrs, doc(cfg(feature = "strmatch")))]
217pub mod strmatch;
218
219// Re-export common types at crate root
220pub use env::{Environment, RuntimeContext, runtime_context};
221pub use kafka_config::{
222    DfeSource, KafkaConfigError, KafkaConfigResult, ServiceRole, TOPIC_SUFFIX_LAND,
223    TOPIC_SUFFIX_LOAD, config_from_file, config_from_properties_str,
224};
225pub use sensitive::{SensitiveString, expose_during};
226
227#[cfg(feature = "runtime")]
228#[cfg_attr(docsrs, doc(cfg(feature = "runtime")))]
229pub use runtime::RuntimePaths;
230
231#[cfg(feature = "health")]
232#[cfg_attr(docsrs, doc(cfg(feature = "health")))]
233pub use health::{HealthRegistry, HealthStatus};
234
235#[cfg(feature = "config")]
236#[cfg_attr(docsrs, doc(cfg(feature = "config")))]
237pub use config::{Config, ConfigError, ConfigOptions};
238
239#[cfg(feature = "config")]
240#[cfg_attr(docsrs, doc(cfg(feature = "config")))]
241pub use config::flat_env::{ApplyFlatEnv, EnvVarDoc, Normalize};
242
243#[cfg(feature = "config-reload")]
244#[cfg_attr(docsrs, doc(cfg(feature = "config-reload")))]
245pub use config::reloader::{ConfigReloader, ReloaderConfig};
246
247#[cfg(feature = "config-reload")]
248#[cfg_attr(docsrs, doc(cfg(feature = "config-reload")))]
249pub use config::shared::SharedConfig;
250
251#[cfg(feature = "config-postgres")]
252#[cfg_attr(docsrs, doc(cfg(feature = "config-postgres")))]
253pub use config::postgres::{
254    FallbackMode, PostgresConfig, PostgresConfigError, PostgresConfigSource,
255};
256
257#[cfg(feature = "logger")]
258#[cfg_attr(docsrs, doc(cfg(feature = "logger")))]
259pub use logger::{
260    LogFormat, LoggerError, LoggerOptions, SecurityEvent, SecurityOutcome, ThrottleConfig,
261};
262
263#[cfg(any(feature = "metrics", feature = "otel-metrics"))]
264#[cfg_attr(docsrs, doc(cfg(any(feature = "metrics", feature = "otel-metrics"))))]
265pub use metrics::{DfeMetrics, MetricsConfig, MetricsError, MetricsManager};
266
267#[cfg(feature = "otel-metrics")]
268#[cfg_attr(docsrs, doc(cfg(feature = "otel-metrics")))]
269pub use metrics::{OtelMetricsConfig, OtelProtocol};
270
271#[cfg(feature = "transport")]
272#[cfg_attr(docsrs, doc(cfg(feature = "transport")))]
273pub use transport::{
274    CommitToken, Message, PayloadFormat, Record, RecordMeta, SendResult, Transport,
275    TransportConfig, TransportError, TransportResult, TransportType, WorkBatch,
276};
277
278#[cfg(feature = "http-server")]
279#[cfg_attr(docsrs, doc(cfg(feature = "http-server")))]
280pub use http_server::{HttpServer, HttpServerConfig, HttpServerError};
281
282#[cfg(feature = "spool")]
283#[cfg_attr(docsrs, doc(cfg(feature = "spool")))]
284pub use spool::{Spool, SpoolConfig, SpoolError};
285
286#[cfg(feature = "tiered-sink")]
287#[cfg_attr(docsrs, doc(cfg(feature = "tiered-sink")))]
288pub use tiered_sink::{
289    CircuitBreaker, CircuitState, CompressionCodec, DrainStrategy, OrderingMode, Sink, SinkError,
290    TieredSink, TieredSinkConfig, TieredSinkError,
291};
292
293#[cfg(feature = "secrets")]
294#[cfg_attr(docsrs, doc(cfg(feature = "secrets")))]
295pub use secrets::{
296    CacheConfig, FileProvider, RotationEvent, SecretMetadata, SecretProvider, SecretSource,
297    SecretValue, SecretsConfig, SecretsError, SecretsManager, SecretsResult,
298};
299
300#[cfg(feature = "secrets-vault")]
301#[cfg_attr(docsrs, doc(cfg(feature = "secrets-vault")))]
302pub use secrets::{OpenBaoAuth, OpenBaoConfig, OpenBaoProvider};
303
304#[cfg(feature = "secrets-aws")]
305#[cfg_attr(docsrs, doc(cfg(feature = "secrets-aws")))]
306pub use secrets::{AwsConfig, AwsProvider};
307
308#[cfg(feature = "directory-config")]
309#[cfg_attr(docsrs, doc(cfg(feature = "directory-config")))]
310pub use directory_config::{
311    ChangeEvent, ChangeOperation, DirectoryConfigError, DirectoryConfigResult,
312    DirectoryConfigStore, DirectoryConfigStoreConfig, WriteMode, WriteResult,
313};
314
315#[cfg(feature = "memory")]
316#[cfg_attr(docsrs, doc(cfg(feature = "memory")))]
317pub use memory::{MemoryGuard, MemoryGuardConfig, MemoryPressure, detect_memory_limit};
318
319#[cfg(feature = "scaling")]
320#[cfg_attr(docsrs, doc(cfg(feature = "scaling")))]
321pub use scaling::{
322    ComponentSnapshot, GateType, PressureSnapshot, RateWindow, ScalingComponent, ScalingPressure,
323    ScalingPressureConfig,
324};
325
326#[cfg(feature = "governor")]
327#[cfg_attr(docsrs, doc(cfg(feature = "governor")))]
328pub use governor::{
329    Admit, ByteBudgetConfig, ByteBudgetController, GateActuator, Hysteresis, InboundGate,
330    MemoryPressureSource, NoopActuator, ObservingActuator, Pressure, PressureSource,
331    SelfRegulationConfig, SelfRegulationGovernor, SelfRegulationProfile, UnifiedPressure,
332    UnifiedPressureSnapshot,
333};
334
335#[cfg(any(feature = "worker-pool", feature = "worker-batch", feature = "worker"))]
336#[cfg_attr(
337    docsrs,
338    doc(cfg(any(feature = "worker-pool", feature = "worker-batch", feature = "worker")))
339)]
340pub use worker::{
341    AccumulatorConfig, AccumulatorFull, AdaptiveWorkerPool, BatchAccumulator, BatchDrainer,
342    BatchPipeline, BatchProcessor, PipelineStats, PipelineStatsSnapshot, ScalingDecision,
343    ScalingInput, WorkerPoolConfig,
344};
345
346#[cfg(feature = "cli")]
347#[cfg_attr(docsrs, doc(cfg(feature = "cli")))]
348pub use cli::{CliError, CommonArgs, StandardCommand, VersionInfo};
349
350#[cfg(feature = "cli-service")]
351#[cfg_attr(docsrs, doc(cfg(feature = "cli-service")))]
352pub use cli::{DfeApp, ServiceRuntime};
353
354#[cfg(feature = "io")]
355#[cfg_attr(docsrs, doc(cfg(feature = "io")))]
356pub use io::{AsyncNdjsonWriter, FileWriterConfig, NdjsonWriter, RotationPeriod};
357
358#[cfg(feature = "dlq")]
359#[cfg_attr(docsrs, doc(cfg(feature = "dlq")))]
360pub use dlq::{Dlq, DlqBackend, DlqConfig, DlqEntry, DlqError, DlqMode, DlqSource, FileDlqConfig};
361
362#[cfg(feature = "dlq-kafka")]
363#[cfg_attr(docsrs, doc(cfg(feature = "dlq-kafka")))]
364pub use dlq::{DlqRouting, KafkaDlqConfig};
365
366#[cfg(feature = "dlq-http")]
367#[cfg_attr(docsrs, doc(cfg(feature = "dlq-http")))]
368pub use dlq::HttpDlqConfig;
369
370#[cfg(feature = "dlq-redis")]
371#[cfg_attr(docsrs, doc(cfg(feature = "dlq-redis")))]
372pub use dlq::RedisDlqConfig;
373
374#[cfg(feature = "output-file")]
375#[cfg_attr(docsrs, doc(cfg(feature = "output-file")))]
376pub use output::{FileOutput, FileOutputConfig, OutputError};
377
378#[cfg(feature = "expression")]
379#[cfg_attr(docsrs, doc(cfg(feature = "expression")))]
380pub use expression::{
381    ALLOWED_FUNCTIONS, DISALLOWED_FUNCTIONS, ExpressionError, ExpressionResult, build_context,
382    compile, evaluate, evaluate_condition, validate,
383};
384
385#[cfg(feature = "deployment")]
386#[cfg_attr(docsrs, doc(cfg(feature = "deployment")))]
387pub use deployment::{
388    ContractMismatch, DeploymentContract, DeploymentError, HealthContract, KedaConfig, KedaContract,
389};
390
391#[cfg(feature = "version-check")]
392#[cfg_attr(docsrs, doc(cfg(feature = "version-check")))]
393pub use version_check::{VersionCheck, VersionCheckConfig, VersionCheckResponse};
394
395#[cfg(feature = "concurrency")]
396#[cfg_attr(docsrs, doc(cfg(feature = "concurrency")))]
397pub use concurrency::{
398    Actor, ActorConfig, ActorError, ActorHandle, ActorJoinHandle, BackgroundSink,
399    BackgroundSinkConfig, BackgroundSinkHandle, DrainError, Overflow, PeriodicTask, PeriodicWorker,
400    SinkDrain, TickError,
401};
402
403/// Library version
404pub const VERSION: &str = env!("CARGO_PKG_VERSION");
405
406/// Initialise all library components with default settings.
407///
408/// This is a convenience function that:
409/// 1. Detects the runtime environment
410/// 2. Sets up the logger with auto-detection
411/// 3. Loads configuration with the given env prefix
412///
413/// # Errors
414///
415/// Returns an error if logger or config initialisation fails.
416#[cfg(all(feature = "config", feature = "logger"))]
417pub fn init(env_prefix: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
418    logger::setup_default()?;
419    config::setup(config::ConfigOptions {
420        env_prefix: env_prefix.to_string(),
421        ..Default::default()
422    })?;
423    Ok(())
424}