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// Parse-path depth guard, shared by the transport codec (JSON/MsgPack) and the
93// worker engine parse stage. Compiled whenever either consumer is enabled.
94#[cfg(any(feature = "transport", feature = "worker-batch"))]
95pub(crate) mod parse_guard;
96
97#[cfg(feature = "runtime")]
98#[cfg_attr(docsrs, doc(cfg(feature = "runtime")))]
99pub mod runtime;
100
101#[cfg(feature = "shutdown")]
102#[cfg_attr(docsrs, doc(cfg(feature = "shutdown")))]
103pub mod shutdown;
104
105#[cfg(feature = "health")]
106#[cfg_attr(docsrs, doc(cfg(feature = "health")))]
107pub mod health;
108
109#[cfg(feature = "config")]
110#[cfg_attr(docsrs, doc(cfg(feature = "config")))]
111pub mod config;
112
113#[cfg(feature = "logger")]
114#[cfg_attr(docsrs, doc(cfg(feature = "logger")))]
115pub mod logger;
116
117#[cfg(any(feature = "metrics", feature = "otel-metrics"))]
118#[cfg_attr(docsrs, doc(cfg(any(feature = "metrics", feature = "otel-metrics"))))]
119pub mod metrics;
120
121#[cfg(feature = "otel-tracing")]
122#[cfg_attr(docsrs, doc(cfg(feature = "otel-tracing")))]
123pub mod otel_tracing;
124
125#[cfg(feature = "transport")]
126#[cfg_attr(docsrs, doc(cfg(feature = "transport")))]
127pub mod transport;
128
129#[cfg(feature = "http")]
130#[cfg_attr(docsrs, doc(cfg(feature = "http")))]
131pub mod http_client;
132
133#[cfg(feature = "http-server")]
134#[cfg_attr(docsrs, doc(cfg(feature = "http-server")))]
135pub mod http_server;
136
137#[cfg(feature = "database")]
138#[cfg_attr(docsrs, doc(cfg(feature = "database")))]
139pub mod database;
140
141#[cfg(feature = "cache")]
142#[cfg_attr(docsrs, doc(cfg(feature = "cache")))]
143pub mod cache;
144
145#[cfg(feature = "spool")]
146#[cfg_attr(docsrs, doc(cfg(feature = "spool")))]
147pub mod spool;
148
149#[cfg(feature = "tiered-sink")]
150#[cfg_attr(docsrs, doc(cfg(feature = "tiered-sink")))]
151pub mod tiered_sink;
152
153#[cfg(feature = "secrets")]
154#[cfg_attr(docsrs, doc(cfg(feature = "secrets")))]
155pub mod secrets;
156
157#[cfg(feature = "credential")]
158#[cfg_attr(docsrs, doc(cfg(feature = "credential")))]
159pub mod credential;
160
161#[cfg(feature = "directory-config")]
162#[cfg_attr(docsrs, doc(cfg(feature = "directory-config")))]
163pub mod directory_config;
164
165#[cfg(feature = "memory")]
166#[cfg_attr(docsrs, doc(cfg(feature = "memory")))]
167pub mod memory;
168
169#[cfg(feature = "tls")]
170#[cfg_attr(docsrs, doc(cfg(feature = "tls")))]
171pub mod tls;
172
173#[cfg(feature = "scaling")]
174#[cfg_attr(docsrs, doc(cfg(feature = "scaling")))]
175pub mod scaling;
176
177#[cfg(feature = "governor")]
178#[cfg_attr(docsrs, doc(cfg(feature = "governor")))]
179pub mod governor;
180
181#[cfg(any(feature = "worker-pool", feature = "worker-batch", feature = "worker"))]
182#[cfg_attr(
183    docsrs,
184    doc(cfg(any(feature = "worker-pool", feature = "worker-batch", feature = "worker")))
185)]
186pub mod worker;
187
188#[cfg(feature = "cli")]
189#[cfg_attr(docsrs, doc(cfg(feature = "cli")))]
190pub mod cli;
191
192#[cfg(feature = "top")]
193#[cfg_attr(docsrs, doc(cfg(feature = "top")))]
194pub mod top;
195
196#[cfg(feature = "io")]
197#[cfg_attr(docsrs, doc(cfg(feature = "io")))]
198pub mod io;
199
200#[cfg(feature = "dlq")]
201#[cfg_attr(docsrs, doc(cfg(feature = "dlq")))]
202pub mod dlq;
203
204#[cfg(feature = "output-file")]
205#[cfg_attr(docsrs, doc(cfg(feature = "output-file")))]
206pub mod output;
207
208#[cfg(feature = "expression")]
209#[cfg_attr(docsrs, doc(cfg(feature = "expression")))]
210pub mod expression;
211
212#[cfg(feature = "deployment")]
213#[cfg_attr(docsrs, doc(cfg(feature = "deployment")))]
214pub mod deployment;
215
216#[cfg(feature = "version-check")]
217#[cfg_attr(docsrs, doc(cfg(feature = "version-check")))]
218pub mod version_check;
219
220#[cfg(feature = "concurrency")]
221#[cfg_attr(docsrs, doc(cfg(feature = "concurrency")))]
222pub mod concurrency;
223
224#[cfg(feature = "strmatch")]
225#[cfg_attr(docsrs, doc(cfg(feature = "strmatch")))]
226pub mod strmatch;
227
228// Re-export common types at crate root
229pub use env::{Environment, RuntimeContext, runtime_context};
230pub use kafka_config::{
231    DfeSource, KafkaConfigError, KafkaConfigResult, ServiceRole, TOPIC_SUFFIX_LAND,
232    TOPIC_SUFFIX_LOAD, config_from_file, config_from_properties_str,
233};
234pub use sensitive::{SensitiveString, expose_during};
235
236#[cfg(feature = "runtime")]
237#[cfg_attr(docsrs, doc(cfg(feature = "runtime")))]
238pub use runtime::RuntimePaths;
239
240#[cfg(feature = "health")]
241#[cfg_attr(docsrs, doc(cfg(feature = "health")))]
242pub use health::{HealthRegistry, HealthStatus};
243
244#[cfg(feature = "config")]
245#[cfg_attr(docsrs, doc(cfg(feature = "config")))]
246pub use config::{Config, ConfigError, ConfigOptions};
247
248#[cfg(feature = "config")]
249#[cfg_attr(docsrs, doc(cfg(feature = "config")))]
250pub use config::flat_env::{ApplyFlatEnv, EnvVarDoc, Normalize};
251
252#[cfg(feature = "config-reload")]
253#[cfg_attr(docsrs, doc(cfg(feature = "config-reload")))]
254pub use config::reloader::{ConfigReloader, ReloaderConfig};
255
256#[cfg(feature = "config-reload")]
257#[cfg_attr(docsrs, doc(cfg(feature = "config-reload")))]
258pub use config::shared::SharedConfig;
259
260#[cfg(feature = "config-postgres")]
261#[cfg_attr(docsrs, doc(cfg(feature = "config-postgres")))]
262pub use config::postgres::{
263    FallbackMode, PostgresConfig, PostgresConfigError, PostgresConfigSource,
264};
265
266#[cfg(feature = "logger")]
267#[cfg_attr(docsrs, doc(cfg(feature = "logger")))]
268pub use logger::{
269    LogFormat, LoggerError, LoggerOptions, SecurityEvent, SecurityOutcome, ThrottleConfig,
270};
271
272#[cfg(any(feature = "metrics", feature = "otel-metrics"))]
273#[cfg_attr(docsrs, doc(cfg(any(feature = "metrics", feature = "otel-metrics"))))]
274pub use metrics::{DfeMetrics, MetricsConfig, MetricsError, MetricsManager};
275
276#[cfg(feature = "otel-metrics")]
277#[cfg_attr(docsrs, doc(cfg(feature = "otel-metrics")))]
278pub use metrics::{OtelMetricsConfig, OtelProtocol};
279
280#[cfg(feature = "transport")]
281#[cfg_attr(docsrs, doc(cfg(feature = "transport")))]
282pub use transport::{
283    CommitToken, Message, PayloadFormat, Record, RecordMeta, SendResult, Transport,
284    TransportConfig, TransportError, TransportResult, TransportType, WorkBatch,
285};
286
287#[cfg(feature = "http-server")]
288#[cfg_attr(docsrs, doc(cfg(feature = "http-server")))]
289pub use http_server::{HttpServer, HttpServerConfig, HttpServerError};
290
291#[cfg(feature = "spool")]
292#[cfg_attr(docsrs, doc(cfg(feature = "spool")))]
293pub use spool::{Spool, SpoolConfig, SpoolError};
294
295#[cfg(feature = "tiered-sink")]
296#[cfg_attr(docsrs, doc(cfg(feature = "tiered-sink")))]
297pub use tiered_sink::{
298    CircuitBreaker, CircuitState, CompressionCodec, DrainStrategy, OrderingMode, Sink, SinkError,
299    TieredSink, TieredSinkConfig, TieredSinkError,
300};
301
302#[cfg(feature = "secrets")]
303#[cfg_attr(docsrs, doc(cfg(feature = "secrets")))]
304pub use secrets::{
305    CacheConfig, FileProvider, RotationEvent, SecretMetadata, SecretProvider, SecretSource,
306    SecretValue, SecretsConfig, SecretsError, SecretsManager, SecretsResult,
307};
308
309#[cfg(feature = "credential")]
310#[cfg_attr(docsrs, doc(cfg(feature = "credential")))]
311pub use credential::{CredentialError, resolve, resolve_optional};
312
313#[cfg(feature = "secrets-vault")]
314#[cfg_attr(docsrs, doc(cfg(feature = "secrets-vault")))]
315pub use secrets::{OpenBaoAuth, OpenBaoConfig, OpenBaoProvider};
316
317#[cfg(feature = "secrets-aws")]
318#[cfg_attr(docsrs, doc(cfg(feature = "secrets-aws")))]
319pub use secrets::{AwsConfig, AwsProvider};
320
321#[cfg(feature = "directory-config")]
322#[cfg_attr(docsrs, doc(cfg(feature = "directory-config")))]
323pub use directory_config::{
324    ChangeEvent, ChangeOperation, DirectoryConfigError, DirectoryConfigResult,
325    DirectoryConfigStore, DirectoryConfigStoreConfig, WriteMode, WriteResult,
326};
327
328#[cfg(feature = "memory")]
329#[cfg_attr(docsrs, doc(cfg(feature = "memory")))]
330pub use memory::{MemoryGuard, MemoryGuardConfig, MemoryPressure, detect_memory_limit};
331
332#[cfg(feature = "scaling")]
333#[cfg_attr(docsrs, doc(cfg(feature = "scaling")))]
334pub use scaling::{
335    ComponentSnapshot, GateType, PressureSnapshot, RateWindow, ScalingComponent, ScalingPressure,
336    ScalingPressureConfig,
337};
338
339#[cfg(feature = "governor")]
340#[cfg_attr(docsrs, doc(cfg(feature = "governor")))]
341pub use governor::{
342    Admit, ByteBudgetConfig, ByteBudgetController, GateActuator, Hysteresis, InboundGate,
343    MemoryPressureSource, NoopActuator, ObservingActuator, Pressure, PressureSource,
344    SelfRegulationConfig, SelfRegulationGovernor, SelfRegulationProfile, UnifiedPressure,
345    UnifiedPressureSnapshot,
346};
347
348#[cfg(any(feature = "worker-pool", feature = "worker-batch", feature = "worker"))]
349#[cfg_attr(
350    docsrs,
351    doc(cfg(any(feature = "worker-pool", feature = "worker-batch", feature = "worker")))
352)]
353pub use worker::{
354    AccumulatorConfig, AccumulatorFull, AdaptiveWorkerPool, BatchAccumulator, BatchDrainer,
355    BatchPipeline, BatchProcessor, PipelineStats, PipelineStatsSnapshot, ScalingDecision,
356    ScalingInput, WorkerPoolConfig,
357};
358
359#[cfg(feature = "cli")]
360#[cfg_attr(docsrs, doc(cfg(feature = "cli")))]
361pub use cli::{CliError, CommonArgs, StandardCommand, VersionInfo};
362
363#[cfg(feature = "cli-service")]
364#[cfg_attr(docsrs, doc(cfg(feature = "cli-service")))]
365pub use cli::{DfeApp, ServiceRuntime};
366
367#[cfg(feature = "io")]
368#[cfg_attr(docsrs, doc(cfg(feature = "io")))]
369pub use io::{AsyncNdjsonWriter, FileWriterConfig, NdjsonWriter, RotationPeriod};
370
371#[cfg(feature = "dlq")]
372#[cfg_attr(docsrs, doc(cfg(feature = "dlq")))]
373pub use dlq::{Dlq, DlqBackend, DlqConfig, DlqEntry, DlqError, DlqMode, DlqSource, FileDlqConfig};
374
375#[cfg(feature = "dlq-kafka")]
376#[cfg_attr(docsrs, doc(cfg(feature = "dlq-kafka")))]
377pub use dlq::{DlqRouting, KafkaDlqConfig};
378
379#[cfg(feature = "dlq-http")]
380#[cfg_attr(docsrs, doc(cfg(feature = "dlq-http")))]
381pub use dlq::HttpDlqConfig;
382
383#[cfg(feature = "dlq-redis")]
384#[cfg_attr(docsrs, doc(cfg(feature = "dlq-redis")))]
385pub use dlq::RedisDlqConfig;
386
387#[cfg(feature = "output-file")]
388#[cfg_attr(docsrs, doc(cfg(feature = "output-file")))]
389pub use output::{FileOutput, FileOutputConfig, OutputError};
390
391#[cfg(feature = "expression")]
392#[cfg_attr(docsrs, doc(cfg(feature = "expression")))]
393pub use expression::{
394    ALLOWED_FUNCTIONS, DISALLOWED_FUNCTIONS, ExpressionError, ExpressionResult, build_context,
395    compile, evaluate, evaluate_condition, validate,
396};
397
398#[cfg(feature = "deployment")]
399#[cfg_attr(docsrs, doc(cfg(feature = "deployment")))]
400pub use deployment::{
401    ContractMismatch, DeploymentContract, DeploymentError, HealthContract, KedaConfig, KedaContract,
402};
403
404#[cfg(feature = "version-check")]
405#[cfg_attr(docsrs, doc(cfg(feature = "version-check")))]
406pub use version_check::{VersionCheck, VersionCheckConfig, VersionCheckResponse};
407
408#[cfg(feature = "concurrency")]
409#[cfg_attr(docsrs, doc(cfg(feature = "concurrency")))]
410pub use concurrency::{
411    Actor, ActorConfig, ActorError, ActorHandle, ActorJoinHandle, BackgroundSink,
412    BackgroundSinkConfig, BackgroundSinkHandle, DrainError, Overflow, PeriodicTask, PeriodicWorker,
413    SinkDrain, TickError,
414};
415
416/// Library version
417pub const VERSION: &str = env!("CARGO_PKG_VERSION");
418
419/// Initialise all library components with default settings.
420///
421/// This is a convenience function that:
422/// 1. Detects the runtime environment
423/// 2. Sets up the logger with auto-detection
424/// 3. Loads configuration with the given env prefix
425///
426/// # Errors
427///
428/// Returns an error if logger or config initialisation fails.
429#[cfg(all(feature = "config", feature = "logger"))]
430pub fn init(env_prefix: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
431    logger::setup_default()?;
432    config::setup(config::ConfigOptions {
433        env_prefix: env_prefix.to_string(),
434        ..Default::default()
435    })?;
436    Ok(())
437}