diff --git a/Cargo.lock b/Cargo.lock index 4db18ebf..690940f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -311,6 +311,8 @@ dependencies = [ "aimdb-core", "aimdb-executor", "aimdb-tokio-adapter", + "serde", + "serde_json", "tokio", "tracing", ] diff --git a/aimdb-core/src/buffer/mod.rs b/aimdb-core/src/buffer/mod.rs index 7d4067fd..74591b9e 100644 --- a/aimdb-core/src/buffer/mod.rs +++ b/aimdb-core/src/buffer/mod.rs @@ -38,18 +38,21 @@ //! //! # Example //! +//! Illustrative (not compiled: `.buffer()` comes from your runtime adapter's +//! registrar extension trait, which `aimdb-core` cannot depend on): +//! //! ```rust,ignore //! use aimdb_core::buffer::BufferCfg; //! //! // High-frequency sensor data //! reg.buffer(BufferCfg::SpmcRing { capacity: 2048 }) -//! .source(|em, data| async { ... }) -//! .tap(|em, data| async { ... }); +//! .source(|ctx, producer| async move { /* … */ }) +//! .tap(|ctx, consumer| async move { /* … */ }); //! //! // Configuration updates //! reg.buffer(BufferCfg::SingleLatest) -//! .source(|em, cfg| async { ... }) -//! .tap(|em, cfg| async { ... }); +//! .source(|ctx, producer| async move { /* … */ }) +//! .tap(|ctx, consumer| async move { /* … */ }); //! ``` // Module structure diff --git a/aimdb-core/src/buffer/traits.rs b/aimdb-core/src/buffer/traits.rs index 5f324ad5..e49b5e1a 100644 --- a/aimdb-core/src/buffer/traits.rs +++ b/aimdb-core/src/buffer/traits.rs @@ -163,15 +163,6 @@ pub trait BufferReader: Send { /// # Requirements /// - Record must be configured with `.with_remote_access()` /// - Only available with the `remote-access` feature (requires serde_json) -/// -/// # Example -/// ```rust,ignore -/// // Internal use in remote access handler -/// let json_reader: Box = record.subscribe_json()?; -/// while let Ok(json_val) = json_reader.recv_json().await { -/// // Forward JSON value to remote client... -/// } -/// ``` #[cfg(feature = "remote-access")] pub trait JsonBufferReader: Send { /// Receive the next value as JSON (async) @@ -225,21 +216,6 @@ pub struct BufferMetricsSnapshot { /// /// Implemented by buffer types when the `metrics` feature is enabled. /// Provides counters for diagnosing producer-consumer imbalances. -/// -/// # Example -/// ```rust,ignore -/// use aimdb_core::buffer::BufferMetrics; -/// -/// // After enabling `metrics` feature -/// let metrics = buffer.metrics(); -/// if metrics.produced_count > metrics.consumed_count + 1000 { -/// println!("Warning: consumer is {} items behind", -/// metrics.produced_count - metrics.consumed_count); -/// } -/// if metrics.dropped_count > 0 { -/// println!("Warning: {} items dropped due to overflow", metrics.dropped_count); -/// } -/// ``` #[cfg(feature = "metrics")] pub trait BufferMetrics { /// Get a snapshot of current buffer metrics diff --git a/aimdb-core/src/builder.rs b/aimdb-core/src/builder.rs index f75d5b8d..09be8c2c 100644 --- a/aimdb-core/src/builder.rs +++ b/aimdb-core/src/builder.rs @@ -355,16 +355,6 @@ impl AimDbBuilder { /// must return a future that runs for as long as needed (e.g. an infinite /// cleanup loop). Tasks are spawned in registration order, after all /// record tasks and connectors have been started. - /// - /// # Example - /// ```rust,ignore - /// builder.on_start(|ctx| async move { - /// loop { - /// do_cleanup().await; - /// ctx.time().sleep_secs(3600).await; - /// } - /// }); - /// ``` pub fn on_start(&mut self, f: F) -> &mut Self where F: FnOnce(crate::RuntimeContext) -> Fut + Send + 'static, @@ -396,12 +386,17 @@ impl AimDbBuilder { /// /// # Examples /// + /// Illustrative (not compiled: the connector types live in downstream + /// crates `aimdb-core` cannot depend on): + /// /// ```rust,ignore /// // (1) data-plane link to an MQTT topic - /// AimDbBuilder::new().runtime(rt) - /// .with_connector(MqttConnector::new("mqtt://broker.local:1883")) - /// .configure::(|r| { r.link_from("mqtt://commands/temp"); }) - /// .build().await?; + /// let mut b = AimDbBuilder::new().runtime(rt) + /// .with_connector(MqttConnector::new("mqtt://broker.local:1883")); + /// b.configure::("commands.temp", |r| { + /// r.link_from("mqtt://commands/temp").with_deserializer_raw(parse).finish(); + /// }); + /// b.build().await?; /// /// // (2a) remote-access SERVER — no links, just expose this db over UDS /// AimDbBuilder::new().runtime(rt) @@ -409,10 +404,10 @@ impl AimDbBuilder { /// .build().await?; /// /// // (2b) remote-access CLIENT — mirror a record to a peer over UDS - /// AimDbBuilder::new().runtime(rt) - /// .with_connector(UdsClient::new("/run/aimdb.sock")) - /// .configure::(|r| { r.with_remote_access().link_to("uds://temp"); }) - /// .build().await?; + /// let mut b = AimDbBuilder::new().runtime(rt) + /// .with_connector(UdsClient::new("/run/aimdb.sock")); + /// b.configure::("temp", |r| { r.with_remote_access().link_to("uds://temp"); }); + /// b.build().await?; /// ``` pub fn with_connector( mut self, @@ -441,15 +436,6 @@ impl AimDbBuilder { /// * `key` - A unique identifier for this record. Can be a string literal, `StringKey`, /// or any type implementing `RecordKey` (including user-defined enum keys). /// * `f` - Configuration closure - /// - /// # Example - /// ```rust,ignore - /// // Using string literal - /// builder.configure::("sensor.temp.room1", |reg| { ... }); - /// - /// // Using compile-time safe enum key - /// builder.configure::(SensorKey::TempRoom1, |reg| { ... }); - /// ``` pub fn configure( &mut self, key: impl RecordKey, @@ -569,22 +555,6 @@ impl AimDbBuilder { /// # Returns /// `DbResult<()>` — Ok once the database starts; the call then blocks until /// every future the runner is driving has completed (typically forever). - /// - /// # Example - /// - /// ```rust,ignore - /// #[tokio::main] - /// async fn main() -> DbResult<()> { - /// AimDbBuilder::new() - /// .runtime(Arc::new(TokioAdapter::new()?)) - /// .configure::(|reg| { - /// reg.with_buffer(BufferCfg::SpmcRing { capacity: 100 }) - /// .with_source(my_producer) - /// .with_tap(my_consumer); - /// }) - /// .run().await // Runs forever - /// } - /// ``` pub async fn run(self) -> DbResult<()> { log_info!("Building database and spawning background tasks..."); @@ -626,19 +596,6 @@ impl AimDbBuilder { /// buffer, duplicate keys, dependency-graph cycles) is collected and /// returned as one [`DbError::InvalidConfiguration`] carrying **all** /// findings — one run surfaces every mistake. - /// - /// # Example - /// - /// ```rust,ignore - /// let (db, runner) = AimDbBuilder::new() - /// .runtime(runtime) - /// .configure::("temp", |reg| { /* … */ }) - /// .with_connector(mqtt_builder) - /// .build().await?; - /// - /// let handle = db.clone(); // clone freely before runner.run() - /// runner.run().await; // drives everything to completion - /// ``` pub async fn build(mut self) -> DbResult<(AimDb, AimDbRunner)> { use crate::error::ConfigError; @@ -868,6 +825,9 @@ impl Default for AimDbBuilder { /// /// # Examples /// +/// Illustrative (not compiled: the runtime adapter lives in a downstream +/// crate `aimdb-core` cannot depend on): +/// /// ```rust,ignore /// use aimdb_tokio_adapter::TokioAdapter; /// @@ -952,12 +912,6 @@ impl AimDb { /// /// External crates (e.g. `aimdb-persistence`) retrieve their typed state here /// to service query calls. The extensions are read-only on the live handle. - /// - /// # Example - /// ```rust,ignore - /// use aimdb_persistence::PersistenceState; - /// let state = db.extensions().get::().unwrap(); - /// ``` pub fn extensions(&self) -> &Extensions { &self.inner.extensions } @@ -989,13 +943,6 @@ impl AimDb { /// # Arguments /// * `key` - The record key (e.g., "sensor.temperature") /// * `value` - The value to produce - /// - /// # Example - /// - /// ```rust,ignore - /// db.produce::("sensors.indoor", indoor_temp)?; - /// db.produce::("sensors.outdoor", outdoor_temp)?; - /// ``` pub fn produce(&self, key: impl AsRef, value: T) -> DbResult<()> where T: Send + 'static + Debug + Clone, @@ -1013,15 +960,6 @@ impl AimDb { /// /// # Arguments /// * `key` - The record key (e.g., "sensor.temperature") - /// - /// # Example - /// - /// ```rust,ignore - /// let mut reader = db.subscribe::("sensors.indoor")?; - /// while let Ok(temp) = reader.recv().await { - /// println!("Indoor: {:.1}°C", temp.celsius); - /// } - /// ``` pub fn subscribe( &self, key: impl AsRef, @@ -1039,17 +977,6 @@ impl AimDb { /// /// # Arguments /// * `key` - The record key (e.g., "sensor.temperature") - /// - /// # Example - /// - /// ```rust,ignore - /// let indoor_producer = db.producer::("sensors.indoor"); - /// let outdoor_producer = db.producer::("sensors.outdoor"); - /// - /// // Each producer writes to its own record - /// indoor_producer.produce(indoor_temp); - /// outdoor_producer.produce(outdoor_temp); - /// ``` pub fn producer( &self, key: impl Into, @@ -1070,16 +997,6 @@ impl AimDb { /// /// # Arguments /// * `key` - The record key (e.g., "sensor.temperature") - /// - /// # Example - /// - /// ```rust,ignore - /// let indoor_consumer = db.consumer::("sensors.indoor"); - /// let outdoor_consumer = db.consumer::("sensors.outdoor"); - /// - /// // Each consumer reads from its own record - /// let mut rx = indoor_consumer.subscribe(); - /// ``` pub fn consumer( &self, key: impl Into, @@ -1102,14 +1019,6 @@ impl AimDb { /// Resolve a record key to its RecordId /// /// Useful for checking if a record exists before operations. - /// - /// # Example - /// - /// ```rust,ignore - /// if let Some(id) = db.resolve_key("sensors.temperature") { - /// println!("Record exists with ID: {}", id); - /// } - /// ``` pub fn resolve_key(&self, key: &str) -> Option { self.inner.resolve_str(key) } @@ -1118,13 +1027,6 @@ impl AimDb { /// /// Returns a slice of RecordIds for all records of type T. /// Useful for introspection when multiple records of the same type exist. - /// - /// # Example - /// - /// ```rust,ignore - /// let temp_ids = db.records_of_type::(); - /// println!("Found {} temperature records", temp_ids.len()); - /// ``` pub fn records_of_type(&self) -> &[crate::record_id::RecordId] { self.inner.records_of_type::() } @@ -1149,14 +1051,6 @@ impl AimDb { /// /// Returns metadata for all registered records, useful for remote access introspection. /// Available only when the `std` feature is enabled. - /// - /// # Example - /// ```rust,ignore - /// let records = db.list_records(); - /// for record in records { - /// println!("Record: {} ({})", record.name, record.type_id); - /// } - /// ``` #[cfg(feature = "remote-access")] pub fn list_records(&self) -> Vec { self.inner.list_records() @@ -1210,11 +1104,6 @@ impl AimDb { /// /// # Returns /// `Ok(())` on success, error if record not found, has producers, or deserialization fails - /// - /// # Example (internal use) - /// ```rust,ignore - /// db.set_record_from_json("AppConfig", json!({"debug": true}))?; - /// ``` #[cfg(feature = "remote-access")] pub fn set_record_from_json( &self, @@ -1238,14 +1127,6 @@ impl AimDb { /// /// The topic is resolved dynamically if a `TopicResolverFn` is configured, /// otherwise the static topic from the URL is used. - /// - /// # Example - /// ```rust,ignore - /// // In MqttConnector after db.build() - /// let routes = db.collect_inbound_routes("mqtt"); - /// let router = RouterBuilder::from_routes(routes).build(); - /// connector.set_router(router).await?; - /// ``` pub fn collect_inbound_routes( &self, scheme: &str, diff --git a/aimdb-core/src/connector.rs b/aimdb-core/src/connector.rs index d5630e4e..2d97b5f5 100644 --- a/aimdb-core/src/connector.rs +++ b/aimdb-core/src/connector.rs @@ -13,15 +13,17 @@ //! //! # Example //! -//! ```rust,ignore -//! use aimdb_core::BufferCfg; -//! +//! ```no_run +//! # use aimdb_core::AimDbBuilder; +//! # #[derive(Clone, Debug)] struct WeatherAlert { level: u8 } +//! # fn wire(builder: &mut AimDbBuilder) { //! builder.configure::("weather.alert", |reg| { -//! reg.buffer(BufferCfg::SingleLatest) -//! .link_to("mqtt://alerts/weather") -//! .with_serializer_raw(|alert: &WeatherAlert| Ok(alert.to_json_vec())) +//! // .buffer(BufferCfg::SingleLatest) — via your runtime adapter's ext trait +//! reg.link_to("mqtt://alerts/weather") +//! .with_serializer_raw(|alert: &WeatherAlert| Ok(vec![alert.level])) //! .finish(); //! }); +//! # } //! ``` use core::fmt::{self, Debug}; @@ -158,8 +160,9 @@ pub type SourceFactoryFn = Arc Box + Sen /// /// # Example /// -/// ```rust,ignore +/// ```rust /// use aimdb_core::connector::TopicProvider; +/// # #[derive(Clone, Debug)] struct Temperature { sensor_id: u32 } /// /// struct SensorTopicProvider; /// @@ -614,6 +617,9 @@ fn parse_connector_url(url: &str) -> DbResult { /// /// # Example /// +/// Illustrative sketch of a connector author's `build()` (not compiled: the +/// client types are fictional — see `aimdb-mqtt-connector` for a real one): +/// /// ```rust,ignore /// pub struct MqttConnectorBuilder { /// broker_url: String, diff --git a/aimdb-core/src/extensions.rs b/aimdb-core/src/extensions.rs index 174a68a8..d26beb5a 100644 --- a/aimdb-core/src/extensions.rs +++ b/aimdb-core/src/extensions.rs @@ -7,15 +7,21 @@ //! modifying `aimdb-core`. //! //! # Example -//! ```rust,ignore +//! ```no_run +//! # use aimdb_core::{AimDb, AimDbBuilder, RecordRegistrar}; +//! # struct MyState { flag: bool } +//! # fn at_configure_time(builder: &mut AimDbBuilder) { //! // Storing a value (e.g. from an external "with_persistence" builder ext): -//! builder.extensions_mut().insert(MyState { ... }); -//! +//! builder.extensions_mut().insert(MyState { flag: true }); +//! # } +//! # fn in_registrar(reg: &mut RecordRegistrar<'_, u32>) { //! // Retrieving it from a RecordRegistrar closure: //! let state = reg.extensions().get::().expect("MyState not configured"); -//! +//! # } +//! # fn at_query_time(db: &AimDb) { //! // Retrieving it from a live AimDb handle (query time): //! let state = db.extensions().get::().expect("MyState not configured"); +//! # } //! ``` use alloc::boxed::Box; diff --git a/aimdb-core/src/record_id.rs b/aimdb-core/src/record_id.rs index 71ccbf92..bb63be85 100644 --- a/aimdb-core/src/record_id.rs +++ b/aimdb-core/src/record_id.rs @@ -35,6 +35,9 @@ //! //! ## Enum Keys (compile-time safe, embedded) //! +//! Illustrative (not compiled: the derive macro lives in `aimdb-derive`, +//! behind the optional `derive` feature): +//! //! ```rust,ignore //! use aimdb_derive::RecordKey; //! @@ -47,7 +50,7 @@ //! } //! //! // Compile-time typo detection! -//! let producer = db.producer::(AppKey::TempIndoor); +//! builder.configure::(AppKey::TempIndoor, |reg| { /* … */ }); //! ``` use alloc::{boxed::Box, collections::BTreeSet, string::ToString}; @@ -105,7 +108,8 @@ pub use aimdb_derive::RecordKey; /// /// # Implementing RecordKey /// -/// The easiest way is to use the derive macro: +/// The easiest way is to use the derive macro (illustrative, not compiled: +/// the macro lives in `aimdb-derive`, behind the optional `derive` feature): /// /// ```rust,ignore /// #[derive(RecordKey, Clone, Copy, PartialEq, Eq)] @@ -172,7 +176,12 @@ pub use aimdb_derive::RecordKey; /// /// **Manual implementation:** Implement `Hash` by hashing `self.as_str()`: /// -/// ```rust,ignore +/// ```rust +/// use core::hash::{Hash, Hasher}; +/// # pub enum MyKey { Temperature } +/// # impl MyKey { +/// # fn as_str(&self) -> &'static str { "sensor.temp" } +/// # } /// impl Hash for MyKey { /// fn hash(&self, state: &mut H) { /// self.as_str().hash(state); @@ -189,23 +198,6 @@ pub trait RecordKey: /// /// Returns the URL/address to use with connectors (MQTT topics, KNX addresses, etc.). /// Use with `.link_to()` for outbound or `.link_from()` for inbound connections. - /// - /// # Example - /// - /// ```rust,ignore - /// #[derive(RecordKey)] - /// pub enum SensorKey { - /// #[key = "temp.indoor"] - /// #[link_address = "mqtt://sensors/temp/indoor"] - /// TempIndoor, - /// } - /// - /// // Use with link_to for outbound - /// reg.link_to(SensorKey::TempIndoor.link_address().unwrap()) - /// - /// // Or with link_from for inbound - /// reg.link_from(SensorKey::TempIndoor.link_address().unwrap()) - /// ``` #[inline] fn link_address(&self) -> Option<&str> { None diff --git a/aimdb-core/src/remote/config.rs b/aimdb-core/src/remote/config.rs index c189e3ed..c1d1f4c6 100644 --- a/aimdb-core/src/remote/config.rs +++ b/aimdb-core/src/remote/config.rs @@ -92,9 +92,10 @@ impl AimxConfig { /// Sets the socket file permissions (Unix only) /// /// # Example - /// ```rust,ignore - /// config.socket_permissions(0o600) // Owner only - /// config.socket_permissions(0o660) // Owner + group + /// ```rust + /// # use aimdb_core::remote::AimxConfig; + /// let config = AimxConfig::uds_default() + /// .socket_permissions(0o600); // Owner only (0o660: owner + group) /// ``` pub fn socket_permissions(mut self, mode: u32) -> Self { self.socket_permissions = Some(mode); diff --git a/aimdb-core/src/remote/mod.rs b/aimdb-core/src/remote/mod.rs index b4abd008..187ef103 100644 --- a/aimdb-core/src/remote/mod.rs +++ b/aimdb-core/src/remote/mod.rs @@ -24,7 +24,8 @@ //! //! Remote access is registered like any other connector — via `with_connector` //! using `aimdb_uds_connector::UdsServer` (this replaced the former -//! `AimDbBuilder::with_remote_access(config)`): +//! `AimDbBuilder::with_remote_access(config)`). Illustrative (not compiled: +//! the connector lives in a downstream crate `aimdb-core` cannot depend on): //! //! ```rust,ignore //! use aimdb_core::remote::{AimxConfig, SecurityPolicy}; diff --git a/aimdb-core/src/router.rs b/aimdb-core/src/router.rs index 694a3bcd..344646ab 100644 --- a/aimdb-core/src/router.rs +++ b/aimdb-core/src/router.rs @@ -167,19 +167,6 @@ impl Router { /// Builder for constructing routers /// /// Provides a fluent API for adding routes before creating the router. -/// -/// # Example -/// -/// ```rust,ignore -/// use aimdb_core::router::RouterBuilder; -/// -/// // Ingest callbacks are normally built by `InboundConnectorBuilder::finish()` -/// // and collected via `AimDb::collect_inbound_routes()`. -/// let router = RouterBuilder::new() -/// .add_route(Arc::from("sensors/temperature"), temperature_ingest) -/// .add_route(Arc::from("sensors/humidity"), humidity_ingest) -/// .build(); -/// ``` pub struct RouterBuilder { routes: Vec, } @@ -198,13 +185,6 @@ impl RouterBuilder { /// /// # Arguments /// * `routes` - Vector of (resource_id, ingest) tuples - /// - /// # Example - /// ```rust,ignore - /// let routes = db.collect_inbound_routes("mqtt"); - /// let router = RouterBuilder::from_routes(routes).build(); - /// connector.set_router(router).await?; - /// ``` pub fn from_routes(routes: Vec<(String, IngestFn)>) -> Self { let mut builder = Self::new(); for (resource_id, ingest) in routes { diff --git a/aimdb-core/src/session/pump.rs b/aimdb-core/src/session/pump.rs index e6942df1..ed55ee3d 100644 --- a/aimdb-core/src/session/pump.rs +++ b/aimdb-core/src/session/pump.rs @@ -3,7 +3,8 @@ //! Two free functions that own the boilerplate a data-plane connector used to //! hand-roll. The author writes only the pure I/O adapter — a //! [`Connector`](crate::transport::Connector) (outbound) and a [`Source`] -//! (inbound) — and composes the helpers in `build()`: +//! (inbound) — and composes the helpers in `build()` +//! (illustrative — `sink()`/`subscription()` are the author's own constructors): //! //! ```rust,ignore //! let mut f = pump_sink(db, "redis", self.sink().await?); // outbound diff --git a/aimdb-core/src/transform/join.rs b/aimdb-core/src/transform/join.rs index 9592c9f0..ec2ac448 100644 --- a/aimdb-core/src/transform/join.rs +++ b/aimdb-core/src/transform/join.rs @@ -89,23 +89,6 @@ impl JoinTrigger { /// Obtained as the first argument to the [`JoinBuilder::on_triggers`] closure. /// Call `.recv().await` in a loop to consume trigger events from all input forwarders. /// Returns `Err` when all input forwarders have exited and the channel is closed. -/// -/// ```rust,ignore -/// .on_triggers(|mut rx, producer| async move { -/// let mut last_a: Option = None; -/// let mut last_b: Option = None; -/// while let Ok(trigger) = rx.recv().await { -/// match trigger.index() { -/// 0 => last_a = trigger.as_input::().copied(), -/// 1 => last_b = trigger.as_input::().copied(), -/// _ => {} -/// } -/// if let (Some(a), Some(b)) = (last_a, last_b) { -/// producer.produce(compute(a, b)); -/// } -/// } -/// }) -/// ``` pub struct JoinEventRx { inner: async_channel::Receiver, } @@ -233,6 +216,9 @@ where /// /// The task runs until all input forwarders close (i.e., all upstream records stop producing). /// + /// Illustrative (not compiled: fragment of a `transform_join` pipeline with + /// user-defined input types): + /// /// ```rust,ignore /// .on_triggers(|mut rx, producer| async move { /// let mut last_a: Option = None; diff --git a/aimdb-core/src/transport.rs b/aimdb-core/src/transport.rs index dfab5b41..94aa31e2 100644 --- a/aimdb-core/src/transport.rs +++ b/aimdb-core/src/transport.rs @@ -132,6 +132,9 @@ impl std::error::Error for PublishError {} /// /// # Example Implementation /// +/// Illustrative sketch (not compiled: the MQTT client types are fictional — +/// see `aimdb-mqtt-connector` for a real implementation): +/// /// ```rust,ignore /// impl Connector for MqttConnector { /// fn publish( diff --git a/aimdb-core/src/typed_api.rs b/aimdb-core/src/typed_api.rs index 0fc282eb..dda6e3f7 100644 --- a/aimdb-core/src/typed_api.rs +++ b/aimdb-core/src/typed_api.rs @@ -8,7 +8,10 @@ //! //! # Producer Example //! -//! ```rust,ignore +//! ```no_run +//! # use aimdb_core::{Producer, RuntimeContext}; +//! # #[derive(Clone, Debug)] struct Temperature { celsius: f32 } +//! # async fn read_sensor() -> Temperature { Temperature { celsius: 21.0 } } //! async fn temperature_producer( //! ctx: RuntimeContext, //! producer: Producer, @@ -23,7 +26,9 @@ //! //! # Consumer Example //! -//! ```rust,ignore +//! ```no_run +//! # use aimdb_core::{Consumer, RuntimeContext}; +//! # #[derive(Clone, Debug)] struct Temperature { celsius: f32 } //! async fn temperature_monitor( //! ctx: RuntimeContext, //! consumer: Consumer, @@ -37,6 +42,9 @@ //! //! # Record Registration Example //! +//! Illustrative (not compiled: `.buffer()` comes from your runtime adapter's +//! registrar extension trait, which `aimdb-core` cannot depend on): +//! //! ```rust,ignore //! builder.configure::("sensors.outdoor", |reg| { //! reg.buffer(cfg) @@ -381,11 +389,6 @@ where /// /// The name shows up in stage profiling output. This method is always /// available; when the `profiling` feature is disabled it is a no-op. - /// - /// ```rust,ignore - /// reg.source(|ctx, producer| async move { /* ... */ }) - /// .with_name("sensor_reader"); - /// ``` pub fn with_name(&mut self, name: &str) -> &mut Self { #[cfg(feature = "profiling")] if let Some((kind, idx)) = self.last_stage { @@ -401,15 +404,6 @@ where /// The closure receives the [`RuntimeContext`](crate::RuntimeContext) /// (time + logging capabilities) and a pre-resolved [`Producer`]; it is /// collected at `build()` time and driven by the `AimDbRunner`. - /// - /// ```rust,ignore - /// reg.source(|ctx, producer| async move { - /// loop { - /// producer.produce(read_sensor().await); - /// ctx.time().sleep_secs(1).await; - /// } - /// }); - /// ``` pub fn source(&mut self, f: F) -> &mut Self where F: FnOnce(crate::RuntimeContext, crate::Producer) -> Fut + Send + 'static, @@ -433,15 +427,6 @@ where /// The closure receives the [`RuntimeContext`](crate::RuntimeContext) and a /// pre-resolved [`Consumer`]; it is collected at `build()` time and /// driven by the `AimDbRunner`. Multiple taps per record are allowed. - /// - /// ```rust,ignore - /// reg.tap(|ctx, consumer| async move { - /// let mut rx = consumer.subscribe(); - /// while let Ok(value) = rx.recv().await { - /// ctx.log().info("observed value"); - /// } - /// }); - /// ``` pub fn tap(&mut self, f: F) -> &mut Self where F: FnOnce(crate::RuntimeContext, crate::Consumer) -> Fut + Send + 'static, @@ -502,14 +487,6 @@ where /// / `set` / `subscribe` protocol. Requires `T: RemoteSerialize` /// (blanket-impl'd for every `Serialize + DeserializeOwned` type). Works on /// no_std + alloc. - /// - /// # Example - /// ```rust,ignore - /// builder.configure::(|reg| { - /// reg.buffer(BufferCfg::SingleLatest) - /// .with_remote_access(); // Enable remote queries - /// }); - /// ``` #[cfg(feature = "json-serialize")] pub fn with_remote_access(&mut self) -> &mut Self where @@ -568,17 +545,6 @@ where /// Link TO external system (outbound: AimDB → External) /// /// Subscribes to buffer updates and publishes them to an external system. - /// - /// # Example - /// - /// ```rust,ignore - /// builder.configure::(|reg| { - /// reg.buffer(BufferCfg::SingleLatest) - /// .link_to("mqtt://broker/sensors/temp") - /// .with_serializer_raw(|t| serde_json::to_vec(t).unwrap()) - /// .finish() - /// }); - /// ``` pub fn link_to(&mut self, url: &str) -> OutboundConnectorBuilder<'_, 'a, T> { OutboundConnectorBuilder { registrar: self, @@ -593,17 +559,6 @@ where /// Link FROM external system (inbound: External → AimDB) /// /// Subscribes to an external data source and produces values into this record's buffer. - /// - /// # Example - /// - /// ```rust,ignore - /// builder.configure::(|reg| { - /// reg.buffer(BufferCfg::SingleLatest) - /// .link_from("mqtt://broker/lights/+/state") - /// .with_deserializer(|_ctx, bytes: &[u8]| parse_light_state(bytes)) - /// .finish() - /// }); - /// ``` pub fn link_from(&mut self, url: &str) -> InboundConnectorBuilder<'_, 'a, T> { InboundConnectorBuilder { registrar: self, @@ -662,17 +617,6 @@ where /// The closure receives the [`RuntimeContext`](crate::RuntimeContext) for /// platform-independent timestamps and logging, plus the typed value being /// serialized. - /// - /// # Example - /// - /// ```rust,ignore - /// .link_to("mqtt://broker/sensors/temp") - /// .with_serializer(|ctx, value: &Temperature| { - /// ctx.log().debug("Serializing temperature for MQTT"); - /// value.to_bytes() - /// .map_err(|_| SerializeError::InvalidData) - /// }) - /// ``` pub fn with_serializer(mut self, f: F) -> Self where F: Fn(crate::RuntimeContext, &T) -> Result, crate::connector::SerializeError> @@ -709,25 +653,6 @@ where /// The provider is type-checked at compile time against `T` and stays /// typed end-to-end: it is fused into the link's serialized source and /// called with `&T` per value (design 036 W1). - /// - /// # Example - /// - /// ```rust,ignore - /// use aimdb_core::connector::TopicProvider; - /// - /// struct SensorTopicProvider; - /// - /// impl TopicProvider for SensorTopicProvider { - /// fn topic(&self, value: &Temperature) -> Option { - /// Some(format!("sensors/temp/{}", value.sensor_id)) - /// } - /// } - /// - /// reg.link_to("mqtt://sensors/default") - /// .with_topic_provider(SensorTopicProvider) - /// .with_serializer(...) - /// .finish(); - /// ``` pub fn with_topic_provider

(mut self, provider: P) -> Self where P: crate::connector::TopicProvider + 'static, @@ -912,16 +837,6 @@ where /// Prefer `.with_deserializer(|ctx, data| ...)` for access to /// `RuntimeContext` (timestamps, logging). Use this raw variant /// only when context is unnecessary. - /// - /// # Example - /// - /// ```rust,ignore - /// .link_from("mqtt://broker/sensors/temp") - /// .with_deserializer_raw(|bytes| { - /// serde_json::from_slice::(bytes) - /// .map_err(|e| e.to_string()) - /// }) - /// ``` pub fn with_deserializer_raw(mut self, f: F) -> Self where F: Fn(&[u8]) -> Result + Send + Sync + 'static, @@ -936,17 +851,6 @@ where /// The closure receives the [`RuntimeContext`](crate::RuntimeContext) for /// platform-independent timestamps and logging, plus the raw bytes from /// the external system. - /// - /// # Example - /// - /// ```rust,ignore - /// .link_from("knx://gateway/9/1/0") - /// .with_deserializer(|ctx, data: &[u8]| { - /// let mut temp = from_knx(data, "9/1/0")?; - /// temp.timestamp = ctx.time().now(); - /// Ok(temp) - /// }) - /// ``` pub fn with_deserializer(mut self, f: F) -> Self where F: Fn(crate::RuntimeContext, &[u8]) -> Result + Send + Sync + 'static, @@ -980,19 +884,6 @@ where /// - Topics determined from smart contracts at runtime /// - Service discovery integration /// - Environment-specific topic configuration - /// - /// # Example - /// - /// ```rust,ignore - /// reg.link_from("mqtt://mesh/default/data") // Fallback topic - /// .with_topic_resolver(|| { - /// // Read from smart contract, config service, etc. - /// let node_id = smart_contract.get_producer_node_id()?; - /// Some(format!("mesh/{}/data", node_id)) - /// }) - /// .with_deserializer(|_ctx, bytes: &[u8]| parse_sensor_data(bytes)) - /// .finish(); - /// ``` pub fn with_topic_resolver(mut self, resolver: F) -> Self where F: Fn() -> Option + Send + Sync + 'static, diff --git a/aimdb-core/src/typed_record.rs b/aimdb-core/src/typed_record.rs index 08aa8605..d50d4e39 100644 --- a/aimdb-core/src/typed_record.rs +++ b/aimdb-core/src/typed_record.rs @@ -343,16 +343,6 @@ pub trait JsonRecordAccess { /// Returns error if: /// - Record not configured with `.with_remote_access()` /// - Buffer subscription fails (shouldn't happen in practice) - /// - /// # Example (internal use) - /// ```rust,ignore - /// let record: &Box = db.storage(id)?; - /// let mut json_reader = record.json_access().unwrap().subscribe_json()?; - /// - /// while let Ok(json_val) = json_reader.recv_json().await { - /// // Forward to remote client... - /// } - /// ``` fn subscribe_json(&self) -> crate::DbResult>; /// Sets a record value from JSON @@ -378,14 +368,6 @@ pub trait JsonRecordAccess { /// - JSON deserialization fails (schema mismatch) /// - Record not configured with buffer /// - Record not configured with `.with_remote_access()` - /// - /// # Example (internal use) - /// ```rust,ignore - /// let record: &Box = db.storage(id)?; - /// let json_val = serde_json::json!({"log_level": "debug"}); - /// // Only works if producer_count == 0 - /// record.json_access().unwrap().set_from_json(json_val)?; - /// ``` fn set_from_json(&self, json_value: serde_json::Value) -> crate::DbResult<()>; } @@ -670,17 +652,6 @@ impl TypedRecord { /// /// # Arguments /// * `f` - A function that takes the `RuntimeContext` and a `Consumer`, and returns a Future - /// - /// # Example - /// - /// ```rust,ignore - /// record.add_consumer(|ctx, consumer| async move { - /// let mut rx = consumer.subscribe(); - /// while let Ok(value) = rx.recv().await { - /// println!("Consumer: {:?}", value); - /// } - /// }); - /// ``` pub fn add_consumer(&mut self, f: F) where F: FnOnce(crate::RuntimeContext, crate::Consumer) -> Fut + Send + 'static, @@ -1114,19 +1085,6 @@ impl TypedRecord { /// **Both std and no_std**: Direct access via `Deref`, `.get()`, `.into_inner()` /// /// **std only**: `.as_json()` (if `.with_remote_access()` configured) - /// - /// # Examples - /// ```rust,ignore - /// // Direct access (std and no_std) - /// if let Some(value) = record.latest() { - /// println!("Temp: {:.1}°C", value.celsius); - /// } - /// - /// // JSON serialization (std only) - /// if let Some(json) = record.latest()?.as_json() { - /// println!("{}", json); - /// } - /// ``` pub fn latest(&self) -> Option> { // Read buffer-native storage via peek() (design 031). Records without // a buffer return None — see Breaking Changes in design 031. diff --git a/aimdb-data-contracts/src/lib.rs b/aimdb-data-contracts/src/lib.rs index 9befa636..b569cf75 100644 --- a/aimdb-data-contracts/src/lib.rs +++ b/aimdb-data-contracts/src/lib.rs @@ -213,6 +213,10 @@ pub trait Observable: SchemaType { /// /// # Example /// +/// Not compiled: the snippet needs `aimdb-core`'s builder, which this crate +/// only depends on under the `observable` feature — `linkable` alone has no +/// core dependency. +/// /// ```rust,ignore /// use aimdb_data_contracts::Linkable; /// use my_app::Temperature; // user-defined type implementing Linkable diff --git a/aimdb-data-contracts/src/migratable.rs b/aimdb-data-contracts/src/migratable.rs index 13dbd240..d778cd19 100644 --- a/aimdb-data-contracts/src/migratable.rs +++ b/aimdb-data-contracts/src/migratable.rs @@ -139,7 +139,10 @@ impl core::fmt::Display for MigrationError { /// /// # Example /// -/// ```rust,ignore +/// ```rust +/// # use aimdb_data_contracts::{MigrationError, MigrationStep}; +/// # struct TemperatureV1 { schema_version: u32, temp: f64, timestamp: u64, unit: String } +/// # struct TemperatureV2 { schema_version: u32, celsius: f64, timestamp: u64 } /// struct TemperatureV1ToV2; /// impl MigrationStep for TemperatureV1ToV2 { /// type Older = TemperatureV1; @@ -212,6 +215,9 @@ pub trait MigrationChain: SchemaType + serde::de::DeserializeOwned + serde::Seri /// /// # Syntax /// +/// Grammar sketch with placeholder types (not compiled — see +/// `examples/weather-mesh-demo`'s temperature contract for a compiled chain): +/// /// ```rust,ignore /// migration_chain! { /// type Current = MyType; diff --git a/aimdb-data-contracts/src/observable.rs b/aimdb-data-contracts/src/observable.rs index 10743f27..105c35c2 100644 --- a/aimdb-data-contracts/src/observable.rs +++ b/aimdb-data-contracts/src/observable.rs @@ -18,14 +18,23 @@ use crate::Observable; /// /// # Example /// -/// ```ignore +/// ```no_run /// use aimdb_data_contracts::log_tap; -/// -/// builder.configure::(NodeKey::Alpha, |reg| { -/// reg.buffer(BufferCfg::SingleLatest) -/// .tap(|ctx, consumer| log_tap(ctx, consumer, "alpha")) -/// .finish(); +/// # use aimdb_core::AimDbBuilder; +/// # use aimdb_data_contracts::{Observable, SchemaType}; +/// # #[derive(Clone, Debug)] +/// # struct Temperature { celsius: f32 } +/// # impl SchemaType for Temperature { const NAME: &'static str = "temperature"; } +/// # impl Observable for Temperature { +/// # type Signal = f32; +/// # fn signal(&self) -> f32 { self.celsius } +/// # } +/// # fn wire(builder: &mut AimDbBuilder) { +/// builder.configure::("node.alpha", |reg| { +/// // .buffer(BufferCfg::SingleLatest) — via your runtime adapter's ext trait +/// reg.tap(|ctx, consumer| log_tap(ctx, consumer, "alpha")); /// }); +/// # } /// ``` #[cfg(feature = "observable")] pub async fn log_tap( diff --git a/aimdb-derive/src/lib.rs b/aimdb-derive/src/lib.rs index 282642cf..c160453e 100644 --- a/aimdb-derive/src/lib.rs +++ b/aimdb-derive/src/lib.rs @@ -5,6 +5,10 @@ //! //! # Example //! +//! Illustrative (not compiled: the generated impl targets `aimdb_core`'s +//! `RecordKey` trait, a circular dev-dependency for this proc-macro crate — +//! compiled integration tests live in `aimdb-core`): +//! //! ```rust,ignore //! use aimdb_derive::RecordKey; //! @@ -38,6 +42,9 @@ use syn::{parse_macro_input, Data, DeriveInput, Error, Fields, Lit, Meta}; /// /// # Example /// +/// Illustrative (not compiled: see the crate-level note — compiled +/// integration tests live in `aimdb-core`): +/// /// ```rust,ignore /// // Note: Hash is auto-generated to satisfy the Borrow contract /// #[derive(RecordKey, Clone, Copy, PartialEq, Eq)] diff --git a/aimdb-embassy-adapter/src/buffer.rs b/aimdb-embassy-adapter/src/buffer.rs index e70c8485..8a79f151 100644 --- a/aimdb-embassy-adapter/src/buffer.rs +++ b/aimdb-embassy-adapter/src/buffer.rs @@ -332,6 +332,9 @@ impl< /// An async closure that can be passed to `embassy_executor::Spawner::spawn()` /// /// # Example + /// + /// Illustrative (not compiled: an `embassy_executor` task on a thumb target): + /// /// ```rust,ignore /// // In your Embassy application: /// #[embassy_executor::task] diff --git a/aimdb-embassy-adapter/src/lib.rs b/aimdb-embassy-adapter/src/lib.rs index 1abf8ae9..7ff5bb58 100644 --- a/aimdb-embassy-adapter/src/lib.rs +++ b/aimdb-embassy-adapter/src/lib.rs @@ -289,26 +289,12 @@ where /// /// # Recommended Values /// - /// **For SPMC Ring Buffer:** - /// ```ignore - /// // Small buffer: 16 items, 2 consumers - /// reg.buffer_sized::<16, 2>(EmbassyBufferType::SpmcRing) - /// - /// // Large buffer: 64 items, 4 consumers - /// reg.buffer_sized::<64, 4>(EmbassyBufferType::SpmcRing) - /// ``` - /// - /// **For SingleLatest (only latest value stored):** - /// ```ignore - /// // 4 consumers watching the latest value - /// reg.buffer_sized::<1, 4>(EmbassyBufferType::SingleLatest) - /// ``` - /// - /// **For Mailbox (single-slot overwrite):** - /// ```ignore - /// // Parameters are ignored, single slot - /// reg.buffer_sized::<1, 4>(EmbassyBufferType::Mailbox) - /// ``` + /// - SPMC ring: `buffer_sized::<16, 2>(EmbassyBufferType::SpmcRing)` for a + /// small buffer (16 items, 2 consumers); `::<64, 4>` for a large one. + /// - SingleLatest: `buffer_sized::<1, 4>(EmbassyBufferType::SingleLatest)` + /// — only the latest value is stored, 4 consumers watch it. + /// - Mailbox: `buffer_sized::<1, 4>(EmbassyBufferType::Mailbox)` — single + /// slot with overwrite; the parameters are ignored. /// /// # Counting CONSUMERS /// @@ -336,6 +322,9 @@ where /// - `F`: Closure that takes (RuntimeContext, Producer, Context) and returns a Future /// /// # Example + /// + /// Illustrative (not compiled: uses device peripherals on a thumb target): + /// /// ```ignore /// use embassy_stm32::exti::ExtiInput; /// diff --git a/aimdb-knx-connector/src/embassy_client.rs b/aimdb-knx-connector/src/embassy_client.rs index 196a7d2a..52cbed2e 100644 --- a/aimdb-knx-connector/src/embassy_client.rs +++ b/aimdb-knx-connector/src/embassy_client.rs @@ -23,6 +23,9 @@ //! //! # Usage //! +//! Illustrative (not compiled: requires the `embassy-runtime` feature and a +//! device network stack): +//! //! ```rust,ignore //! use aimdb_knx_connector::KnxConnectorBuilder; //! use aimdb_core::AimDbBuilder; diff --git a/aimdb-knx-connector/src/lib.rs b/aimdb-knx-connector/src/lib.rs index d42c6f3a..4956bc8d 100644 --- a/aimdb-knx-connector/src/lib.rs +++ b/aimdb-knx-connector/src/lib.rs @@ -31,10 +31,11 @@ //! //! ## Tokio Usage (Standard Library) //! -//! ```rust,ignore +//! ```no_run +//! use aimdb_core::buffer::BufferCfg; //! use aimdb_core::AimDbBuilder; -//! use aimdb_tokio_adapter::TokioAdapter; //! use aimdb_knx_connector::KnxConnector; +//! use aimdb_tokio_adapter::{TokioAdapter, TokioRecordRegistrarExt}; //! use std::sync::Arc; //! //! #[derive(Debug, Clone)] @@ -42,32 +43,38 @@ //! is_on: bool, //! } //! +//! # async fn demo() -> Result<(), Box> { //! let runtime = Arc::new(TokioAdapter::new()?); //! -//! let db = AimDbBuilder::new() +//! let mut builder = AimDbBuilder::new() //! .runtime(runtime) -//! .with_connector(KnxConnector::new("knx://192.168.1.19:3671")) -//! .configure::(|reg| { -//! reg.buffer(BufferCfg::SingleLatest) -//! // Inbound: Monitor KNX bus -//! .link_from("knx://1/0/7") -//! .with_deserializer_raw(|data: &[u8]| { -//! let is_on = data.get(0).map(|&b| b != 0).unwrap_or(false); -//! Ok(Box::new(LightState { is_on })) -//! }) -//! .finish() -//! // Outbound: Send commands to KNX -//! .link_to("knx://1/0/8") -//! .with_serializer_raw(|state: &LightState| { -//! Ok(vec![if state.is_on { 1 } else { 0 }]) -//! }) -//! .finish(); -//! }) -//! .build().await?; +//! .with_connector(KnxConnector::new("knx://192.168.1.19:3671")); +//! builder.configure::("light.state", |reg| { +//! reg.buffer(BufferCfg::SingleLatest) +//! // Inbound: Monitor KNX bus +//! .link_from("knx://1/0/7") +//! .with_deserializer_raw(|data: &[u8]| { +//! let is_on = data.first().map(|&b| b != 0).unwrap_or(false); +//! Ok(LightState { is_on }) +//! }) +//! .finish() +//! // Outbound: Send commands to KNX +//! .link_to("knx://1/0/8") +//! .with_serializer_raw(|state: &LightState| { +//! Ok(vec![if state.is_on { 1 } else { 0 }]) +//! }) +//! .finish(); +//! }); +//! let (db, runner) = builder.build().await?; +//! # Ok(()) +//! # } //! ``` //! //! ## Embassy Usage (Embedded) //! +//! Illustrative (not compiled: requires the `embassy-runtime` feature and a +//! device network stack): +//! //! ```rust,ignore //! use aimdb_core::AimDbBuilder; //! use aimdb_embassy_adapter::EmbassyAdapter; @@ -106,19 +113,6 @@ //! ## DPT Support //! //! This connector uses `knx-pico` for Data Point Type conversion: -//! -//! ```rust,ignore -//! use knx_pico::dpt::{Dpt1, Dpt5, Dpt9, DptDecode, DptEncode}; -//! -//! // DPT 1.001 - Boolean (switch) -//! let is_on = Dpt1::Switch.decode(data)?; -//! -//! // DPT 5.001 - 8-bit unsigned (0-100%) -//! let percentage = Dpt5::Percentage.decode(data)?; -//! -//! // DPT 9.001 - 2-byte float (temperature) -//! let temp = Dpt9::Temperature.decode(data)?; -//! ``` #![cfg_attr(not(feature = "std"), no_std)] diff --git a/aimdb-knx-connector/src/tokio_client.rs b/aimdb-knx-connector/src/tokio_client.rs index 193bd606..85d0d2e5 100644 --- a/aimdb-knx-connector/src/tokio_client.rs +++ b/aimdb-knx-connector/src/tokio_client.rs @@ -34,22 +34,6 @@ use tokio::sync::mpsc; /// /// # Usage Pattern /// -/// ```rust,ignore -/// use aimdb_knx_connector::KnxConnector; -/// -/// // Configure database with KNX links -/// let db = AimDbBuilder::new() -/// .runtime(runtime) -/// .with_connector(KnxConnector::new("knx://192.168.1.19:3671")) -/// .configure::(|reg| { -/// reg.link_from("knx://1/0/7") -/// .with_deserializer(deserialize_light) -/// .with_buffer(BufferCfg::SingleLatest) -/// .finish(); -/// }) -/// .build().await?; -/// ``` -/// /// The connector collects routes from the database during build() and /// automatically monitors all required KNX group addresses. pub struct KnxConnectorBuilder { @@ -64,12 +48,6 @@ impl KnxConnectorBuilder { /// /// # Arguments /// * `gateway_url` - Gateway URL (knx://host:port) - /// - /// # Example - /// - /// ```rust,ignore - /// let builder = KnxConnector::new("knx://192.168.1.19:3671"); - /// ``` pub fn new(gateway_url: impl Into) -> Self { Self { gateway_url: gateway_url.into(), diff --git a/aimdb-mqtt-connector/src/embassy_client.rs b/aimdb-mqtt-connector/src/embassy_client.rs index af2f71ee..713f4d84 100644 --- a/aimdb-mqtt-connector/src/embassy_client.rs +++ b/aimdb-mqtt-connector/src/embassy_client.rs @@ -19,6 +19,9 @@ //! //! # Usage //! +//! Illustrative (not compiled: requires the `embassy-runtime` feature and a +//! device network stack): +//! //! ```rust,ignore //! use aimdb_mqtt_connector::embassy_client::MqttConnectorBuilder; //! use aimdb_core::AimDbBuilder; diff --git a/aimdb-mqtt-connector/src/lib.rs b/aimdb-mqtt-connector/src/lib.rs index 2cb30ddf..411747b8 100644 --- a/aimdb-mqtt-connector/src/lib.rs +++ b/aimdb-mqtt-connector/src/lib.rs @@ -13,38 +13,55 @@ //! //! ## Tokio Usage (Standard Library) //! -//! ```rust,ignore +//! ```no_run //! use aimdb_core::AimDbBuilder; -//! use aimdb_tokio_adapter::TokioAdapter; //! use aimdb_mqtt_connector::{MqttConnector, MqttLinkExt, MqttOutboundLinkExt}; +//! use aimdb_tokio_adapter::TokioAdapter; //! use std::sync::Arc; //! +//! # #[derive(Clone, Debug)] struct Temperature { celsius: f32 } +//! # #[derive(Clone, Debug)] struct TempCommand { target: f32 } +//! # async fn temperature_producer( +//! # ctx: aimdb_core::RuntimeContext, +//! # producer: aimdb_core::Producer, +//! # ) {} +//! # async fn demo() -> Result<(), Box> { //! let runtime = Arc::new(TokioAdapter::new()?); //! -//! let db = AimDbBuilder::new() +//! let mut builder = AimDbBuilder::new() //! .runtime(runtime) -//! .with_connector(MqttConnector::new("mqtt://localhost:1883")) -//! .configure::(|reg| { -//! reg.source(temperature_producer) -//! // Outbound: Publish to MQTT (QoS/retain via MqttLinkExt traits) -//! .link_to("mqtt://sensors/temperature") -//! .with_qos(1) -//! .with_retain(false) -//! .with_serializer_raw(|t| { -//! serde_json::to_vec(t) -//! .map_err(|_| aimdb_core::connector::SerializeError::InvalidData) -//! }) -//! .finish() -//! // Inbound: Subscribe from MQTT -//! .link_from("mqtt://commands/temperature") -//! .with_deserializer_raw(|data| Temperature::from_json(data)) -//! .finish(); -//! }) -//! .build().await?; +//! .with_connector(MqttConnector::new("mqtt://localhost:1883")); +//! +//! // Outbound: publish to MQTT (QoS/retain via the MqttLinkExt traits) +//! builder.configure::("sensor.temp", |reg| { +//! reg.source(temperature_producer) +//! .link_to("mqtt://sensors/temperature") +//! .with_qos(1) +//! .with_retain(false) +//! .with_serializer_raw(|t: &Temperature| Ok(t.celsius.to_be_bytes().to_vec())) +//! .finish(); +//! }); +//! +//! // Inbound: subscribe from MQTT +//! builder.configure::("command.temp", |reg| { +//! reg.link_from("mqtt://commands/temperature") +//! .with_deserializer_raw(|data| match data.try_into() { +//! Ok(bytes) => Ok(TempCommand { target: f32::from_be_bytes(bytes) }), +//! Err(_) => Err("bad frame".to_string()), +//! }) +//! .finish(); +//! }); +//! +//! let (db, runner) = builder.build().await?; +//! # Ok(()) +//! # } //! ``` //! //! ## Embassy Usage (Embedded) //! +//! Illustrative (not compiled: requires the `embassy-runtime` feature and a +//! device network stack): +//! //! ```rust,ignore //! use aimdb_core::AimDbBuilder; //! use aimdb_embassy_adapter::EmbassyAdapter; diff --git a/aimdb-mqtt-connector/src/link_ext.rs b/aimdb-mqtt-connector/src/link_ext.rs index 90dd5b13..79126308 100644 --- a/aimdb-mqtt-connector/src/link_ext.rs +++ b/aimdb-mqtt-connector/src/link_ext.rs @@ -7,14 +7,17 @@ //! `("qos", …)` / `("retain", …)` option keys the MQTT clients have always //! read from `protocol_options` — wire behavior is unchanged. //! -//! ```rust,ignore +//! ```no_run //! use aimdb_mqtt_connector::{MqttLinkExt, MqttOutboundLinkExt}; +//! # #[derive(Clone, Debug)] struct Temperature { celsius: f32 } +//! # fn wire(reg: &mut aimdb_core::RecordRegistrar<'_, Temperature>) { //! //! reg.link_to("mqtt://sensors/temp") //! .with_qos(1) //! .with_retain(true) -//! .with_serializer_raw(serialize) +//! .with_serializer_raw(|t: &Temperature| Ok(t.celsius.to_be_bytes().to_vec())) //! .finish(); +//! # } //! ``` use aimdb_core::{InboundConnectorBuilder, OutboundConnectorBuilder}; diff --git a/aimdb-mqtt-connector/src/tokio_client.rs b/aimdb-mqtt-connector/src/tokio_client.rs index cafb1597..923e0ce7 100644 --- a/aimdb-mqtt-connector/src/tokio_client.rs +++ b/aimdb-mqtt-connector/src/tokio_client.rs @@ -23,22 +23,6 @@ use std::time::Duration; /// /// # Usage Pattern /// -/// ```rust,ignore -/// use aimdb_mqtt_connector::MqttConnector; -/// -/// // Configure database with MQTT links -/// let db = AimDbBuilder::new() -/// .runtime(runtime) -/// .with_connector(MqttConnector::new("mqtt://localhost:1883")) -/// .configure::(|reg| { -/// reg.link_from("mqtt://commands/temp") -/// .with_deserializer(deserialize_temp) -/// .with_buffer(BufferCfg::SingleLatest) -/// .with_remote_access(); -/// }) -/// .build().await?; -/// ``` -/// /// The connector collects routes from the database during build() and /// automatically subscribes to all required MQTT topics. pub struct MqttConnectorBuilder { @@ -55,12 +39,6 @@ impl MqttConnectorBuilder { /// /// # Arguments /// * `broker_url` - Broker URL (mqtt://host:port or mqtts://host:port) - /// - /// # Example - /// - /// ```rust,ignore - /// let builder = MqttConnector::new("mqtt://localhost:1883"); - /// ``` pub fn new(broker_url: impl Into) -> Self { Self { broker_url: broker_url.into(), @@ -77,13 +55,6 @@ impl MqttConnectorBuilder { /// /// # Arguments /// * `client_id` - Unique identifier for this client - /// - /// # Example - /// - /// ```rust,ignore - /// let builder = MqttConnector::new("mqtt://localhost:1883") - /// .with_client_id("my-app-001"); - /// ``` pub fn with_client_id(mut self, client_id: impl Into) -> Self { self.client_id = Some(client_id.into()); self diff --git a/aimdb-persistence-sqlite/src/lib.rs b/aimdb-persistence-sqlite/src/lib.rs index 4e16721a..528eda97 100644 --- a/aimdb-persistence-sqlite/src/lib.rs +++ b/aimdb-persistence-sqlite/src/lib.rs @@ -13,11 +13,14 @@ //! //! # Example //! -//! ```rust,ignore +//! ```no_run //! use aimdb_persistence_sqlite::SqliteBackend; //! use std::sync::Arc; //! +//! # fn demo() -> Result<(), Box> { //! let backend = Arc::new(SqliteBackend::new("./data/history.db")?); +//! # Ok(()) +//! # } //! ``` use std::path::Path; diff --git a/aimdb-persistence/src/lib.rs b/aimdb-persistence/src/lib.rs index 800ab91e..6e2db9b1 100644 --- a/aimdb-persistence/src/lib.rs +++ b/aimdb-persistence/src/lib.rs @@ -13,25 +13,48 @@ //! //! # Usage //! -//! ```rust,ignore +//! ```no_run //! use aimdb_persistence::{AimDbBuilderPersistExt, RecordRegistrarPersistExt, AimDbQueryExt}; -//! use aimdb_persistence_sqlite::SqliteBackend; -//! +//! // A real backend, e.g. `aimdb_persistence_sqlite::SqliteBackend`: +//! # use aimdb_persistence::{BoxFuture, PersistenceBackend, PersistenceError, QueryParams, StoredValue}; +//! # struct SqliteBackend; +//! # impl SqliteBackend { fn new(_p: &str) -> Result { Ok(Self) } } +//! # impl PersistenceBackend for SqliteBackend { +//! # fn store<'a>( +//! # &'a self, _n: &'a str, _v: &'a serde_json::Value, _t: u64, +//! # ) -> BoxFuture<'a, Result<(), PersistenceError>> { Box::pin(async { Ok(()) }) } +//! # fn query<'a>( +//! # &'a self, _p: &'a str, _q: QueryParams, +//! # ) -> BoxFuture<'a, Result, PersistenceError>> { Box::pin(async { Ok(vec![]) }) } +//! # fn cleanup(&self, _t: u64) -> BoxFuture<'_, Result> { +//! # Box::pin(async { Ok(0) }) +//! # } +//! # } +//! # use aimdb_core::buffer::BufferCfg; +//! # use aimdb_core::AimDbBuilder; +//! # use aimdb_tokio_adapter::{TokioAdapter, TokioRecordRegistrarExt}; +//! # use std::sync::Arc; +//! # use std::time::Duration; +//! # #[derive(Clone, Debug, serde::Serialize)] struct MyRecord { value: f32 } +//! # async fn demo() -> Result<(), Box> { +//! # let runtime = Arc::new(TokioAdapter::new()?); //! let backend = Arc::new(SqliteBackend::new("./data/history.db")?); //! //! let mut builder = AimDbBuilder::new() //! .runtime(runtime) //! .with_persistence(backend.clone(), Duration::from_secs(7 * 24 * 3600)); //! -//! builder.configure::(key, |reg| { +//! builder.configure::("my.record", |reg| { //! reg.buffer(BufferCfg::SpmcRing { capacity: 500 }) -//! .persist(key.to_string()); +//! .persist("my.record"); //! }); //! -//! let db = builder.build().await?; +//! let (db, runner) = builder.build().await?; //! -//! // Query historical data -//! let latest: Vec = db.query_latest("my_record::*", 1).await?; +//! // Query historical data (any `DeserializeOwned` shape; `Value` shown here) +//! let latest: Vec = db.query_latest("my_record::*", 1).await?; +//! # Ok(()) +//! # } //! ``` pub mod backend; diff --git a/aimdb-serial-connector/src/embassy_transport.rs b/aimdb-serial-connector/src/embassy_transport.rs index 96294ce8..e982371f 100644 --- a/aimdb-serial-connector/src/embassy_transport.rs +++ b/aimdb-serial-connector/src/embassy_transport.rs @@ -130,7 +130,8 @@ impl SerialClient { /// Serves the full AimX toolset over a serial UART, so a host (or another board) /// can `record.list`/`get`/`set`/`subscribe`/`drain` this db over the wire. -/// Register it directly with `with_connector`: +/// Register it directly with `with_connector` (illustrative — the UART halves +/// come from device init on a thumb target): /// /// ```ignore /// builder.with_connector( diff --git a/aimdb-sync/src/consumer.rs b/aimdb-sync/src/consumer.rs index ba5e6204..e54642f9 100644 --- a/aimdb-sync/src/consumer.rs +++ b/aimdb-sync/src/consumer.rs @@ -19,7 +19,7 @@ use std::time::Duration; /// /// # Example /// -/// ```rust,ignore +/// ```no_run /// # use aimdb_sync::*; /// # use serde::{Serialize, Deserialize}; /// # #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/aimdb-sync/src/handle.rs b/aimdb-sync/src/handle.rs index 62335bfb..8e8c139b 100644 --- a/aimdb-sync/src/handle.rs +++ b/aimdb-sync/src/handle.rs @@ -42,16 +42,17 @@ pub trait AimDbBuilderSyncExt { /// /// # Example /// - /// ```rust,ignore + /// ```no_run /// use aimdb_core::AimDbBuilder; /// use aimdb_tokio_adapter::TokioAdapter; /// use aimdb_sync::AimDbBuilderSyncExt; /// use std::sync::Arc; /// + /// # #[derive(Debug, Clone)] struct MyData { value: f32 } /// # fn main() -> Result<(), Box> { /// let mut builder = AimDbBuilder::new() - /// .runtime(Arc::new(TokioAdapter)); // Create adapter (it's just a marker) - /// builder.configure::(|reg| { + /// .runtime(Arc::new(TokioAdapter::new()?)); + /// builder.configure::("my.data", |reg| { /// // Configure buffer, sources, taps, etc. /// }); /// let handle = builder.attach()?; // Build happens in runtime thread @@ -83,17 +84,12 @@ pub trait AimDbSyncExt { /// /// # Example /// - /// ```rust,ignore - /// use aimdb_core::AimDbBuilder; - /// use aimdb_tokio_adapter::TokioAdapter; + /// ```no_run + /// use aimdb_core::AimDb; /// use aimdb_sync::AimDbSyncExt; - /// use std::sync::Arc; - /// - /// # fn main() -> Result<(), Box> { - /// let db = AimDbBuilder::new() - /// .runtime(Arc::new(TokioAdapter::new()?)) - /// .build()?; /// + /// // `db` comes out of an async `AimDbBuilder::build()` elsewhere + /// # fn demo(db: AimDb) -> Result<(), Box> { /// let handle = db.attach()?; /// # Ok(()) /// # } @@ -298,7 +294,7 @@ impl AimDbHandle { /// /// # Example /// - /// ```rust,ignore + /// ```no_run /// # use aimdb_sync::*; /// # use serde::{Serialize, Deserialize}; /// # #[derive(Debug, Clone, Serialize, Deserialize)] @@ -372,7 +368,7 @@ impl AimDbHandle { /// /// # Example /// - /// ```rust,ignore + /// ```no_run /// # use aimdb_sync::*; /// # use serde::{Serialize, Deserialize}; /// # #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/aimdb-sync/src/lib.rs b/aimdb-sync/src/lib.rs index 0bf04730..72cb989d 100644 --- a/aimdb-sync/src/lib.rs +++ b/aimdb-sync/src/lib.rs @@ -43,32 +43,31 @@ //! //! ## Quick Start //! -//! ```rust,ignore +//! ```no_run //! use aimdb_core::{AimDbBuilder, buffer::BufferCfg}; //! use aimdb_tokio_adapter::{TokioAdapter, TokioRecordRegistrarExt}; //! use aimdb_sync::AimDbBuilderSyncExt; -//! use serde::{Serialize, Deserialize}; //! use std::sync::Arc; //! -//! #[derive(Debug, Clone, Serialize, Deserialize)] +//! #[derive(Debug, Clone)] //! struct Temperature { //! celsius: f32, //! } //! //! # fn main() -> Result<(), Box> { //! // Build and attach database (NO #[tokio::main] NEEDED!) -//! let adapter = Arc::new(TokioAdapter); +//! let adapter = Arc::new(TokioAdapter::new()?); //! let mut builder = AimDbBuilder::new().runtime(adapter); //! -//! builder.configure::(|reg| { +//! builder.configure::("sensor.temp", |reg| { //! reg.buffer(BufferCfg::SpmcRing { capacity: 10 }); //! }); //! //! let handle = builder.attach()?; //! //! // Create producer and consumer -//! let producer = handle.producer::()?; -//! let consumer = handle.consumer::()?; +//! let producer = handle.producer::("sensor.temp")?; +//! let consumer = handle.consumer::("sensor.temp")?; //! //! // Producer: blocking operations //! producer.set(Temperature { celsius: 25.0 })?; @@ -87,8 +86,11 @@ //! //! Both `SyncProducer` and `SyncConsumer` can be cloned and shared across threads: //! -//! ```rust,ignore +//! ```no_run //! use std::thread; +//! # use aimdb_sync::{SyncConsumer, SyncProducer}; +//! # #[derive(Debug, Clone)] struct Temperature { celsius: f32 } +//! # fn demo(producer: SyncProducer, consumer: SyncConsumer) { //! //! // Clone for use in another thread //! let producer_clone = producer.clone(); @@ -103,6 +105,7 @@ //! println!("Got: {:.1}°C", temp.celsius); //! } //! }); +//! # } //! ``` //! //! ## Independent Subscriptions @@ -110,11 +113,16 @@ //! Note: Cloning a `SyncConsumer` shares the same channel, so only one thread //! will receive each value. For independent subscriptions, create multiple consumers: //! -//! ```rust,ignore -//! let consumer1 = handle.consumer::()?; -//! let consumer2 = handle.consumer::()?; +//! ```no_run +//! # use aimdb_sync::AimDbHandle; +//! # #[derive(Debug, Clone)] struct Temperature { celsius: f32 } +//! # fn demo(handle: &AimDbHandle) -> Result<(), Box> { +//! let consumer1 = handle.consumer::("sensor.temp")?; +//! let consumer2 = handle.consumer::("sensor.temp")?; //! //! // Both receive independent copies of all values +//! # Ok(()) +//! # } //! ``` //! //! ## Channel Capacity Configuration @@ -122,15 +130,22 @@ //! By default, both producers and consumers use a channel capacity of 100. //! You can customize this per record type using the `_with_capacity` methods: //! -//! ```rust,ignore +//! ```no_run +//! # use aimdb_sync::AimDbHandle; +//! # #[derive(Debug, Clone)] struct SensorData { value: f32 } +//! # #[derive(Debug, Clone)] struct RareEvent { code: u8 } +//! # #[derive(Debug, Clone)] struct LatestOnly { state: u8 } +//! # fn demo(handle: &AimDbHandle) -> Result<(), Box> { //! // High-frequency sensor data needs larger buffer -//! let producer = handle.producer_with_capacity::(1000)?; +//! let producer = handle.producer_with_capacity::("sensor.fast", 1000)?; //! //! // Rare events can use smaller buffer -//! let consumer = handle.consumer_with_capacity::(10)?; +//! let consumer = handle.consumer_with_capacity::("events.rare", 10)?; //! //! // SingleLatest-like behavior: use capacity=1 to minimize queueing -//! let consumer = handle.consumer_with_capacity::(1)?; +//! let consumer = handle.consumer_with_capacity::("state.latest", 1)?; +//! # Ok(()) +//! # } //! ``` //! //! **When to adjust capacity:** @@ -151,14 +166,22 @@ //! ### Solutions for SingleLatest Semantics //! //! 1. **Use `get_latest()`** - Drains the channel to get the most recent value: -//! ```rust,ignore +//! ```no_run +//! # #[derive(Debug, Clone)] struct Temperature { celsius: f32 } +//! # fn demo(consumer: &aimdb_sync::SyncConsumer) -> aimdb_core::DbResult<()> { //! // Always get the latest value, skipping queued intermediates //! let latest = consumer.get_latest()?; +//! # Ok(()) +//! # } //! ``` //! //! 2. **Use capacity=1** - Minimize queueing: -//! ```rust,ignore -//! let consumer = handle.consumer_with_capacity::(1)?; +//! ```no_run +//! # #[derive(Debug, Clone)] struct Temperature { celsius: f32 } +//! # fn demo(handle: &aimdb_sync::AimDbHandle) -> aimdb_core::DbResult<()> { +//! let consumer = handle.consumer_with_capacity::("sensor.temp", 1)?; +//! # Ok(()) +//! # } //! ``` //! //! 3. **Use the async API directly** - For perfect semantic preservation. @@ -197,13 +220,17 @@ //! and return any errors that occur in the async context //! - `try_set()` sends immediately without waiting for the produce result (fire-and-forget) //! -//! ```rust,ignore +//! ```no_run +//! # use aimdb_sync::{DbError, SyncProducer}; +//! # #[derive(Debug, Clone)] struct Temperature { celsius: f32 } +//! # fn demo(producer: &SyncProducer, data: Temperature) { //! // Errors are properly propagated to the caller //! match producer.set(data) { //! Ok(()) => println!("Successfully produced"), -//! Err(DbError::RecordNotFound { .. }) => eprintln!("Type not registered"), +//! Err(DbError::RecordKeyNotFound { .. }) => eprintln!("Record not registered"), //! Err(e) => eprintln!("Production failed: {}", e), //! } +//! # } //! ``` //! //! ## Safety diff --git a/aimdb-tokio-adapter/src/buffer.rs b/aimdb-tokio-adapter/src/buffer.rs index 67b24c40..b9934dfe 100644 --- a/aimdb-tokio-adapter/src/buffer.rs +++ b/aimdb-tokio-adapter/src/buffer.rs @@ -209,14 +209,6 @@ impl TokioBuffer { /// /// # Returns /// A `tokio::task::JoinHandle` that can be used to await task completion - /// - /// # Example - /// ```rust,ignore - /// let handle = buffer.spawn_dispatcher(|value| async move { - /// println!("Processing: {:?}", value); - /// // Call producer and consumers here - /// }); - /// ``` pub fn spawn_dispatcher(&self, handler: F) -> tokio::task::JoinHandle<()> where F: Fn(T) -> Fut + Send + Sync + 'static, diff --git a/aimdb-uds-connector/Cargo.toml b/aimdb-uds-connector/Cargo.toml index d6d9fe07..85d64e12 100644 --- a/aimdb-uds-connector/Cargo.toml +++ b/aimdb-uds-connector/Cargo.toml @@ -30,3 +30,6 @@ tracing = { version = "0.1", optional = true } [dev-dependencies] aimdb-tokio-adapter = { version = "0.6.0", path = "../aimdb-tokio-adapter" } tokio = { version = "1", features = ["rt-multi-thread", "macros", "time"] } +# For the crate-level doc example (a mirrored record needs a JSON shape) +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" diff --git a/aimdb-uds-connector/src/lib.rs b/aimdb-uds-connector/src/lib.rs index 1d9b9c8a..d89f6dbe 100644 --- a/aimdb-uds-connector/src/lib.rs +++ b/aimdb-uds-connector/src/lib.rs @@ -14,19 +14,35 @@ //! register it with `with_connector` to stand up remote access. Sugar over //! [`SessionServerConnector`]. //! -//! ```rust,ignore +//! ```no_run +//! use aimdb_core::buffer::BufferCfg; +//! use aimdb_core::AimDbBuilder; //! use aimdb_uds_connector::{UdsClient, UdsServer}; +//! # use aimdb_tokio_adapter::{TokioAdapter, TokioRecordRegistrarExt}; +//! # use std::sync::Arc; +//! # #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +//! # struct Temp { celsius: f32 } +//! # async fn demo() -> Result<(), Box> { +//! # let rt = Arc::new(TokioAdapter::new()?); //! //! // server: expose this db over a socket (no links) -//! AimDbBuilder::new().runtime(rt) +//! AimDbBuilder::new().runtime(rt.clone()) //! .with_connector(UdsServer::new("/run/aimdb.sock").max_connections(32)) //! .build().await?; //! //! // client: mirror a record to a peer over the socket -//! AimDbBuilder::new().runtime(rt) -//! .with_connector(UdsClient::new("/run/aimdb.sock")) -//! .configure::("temp", |r| { r.with_remote_access().link_to("uds://temp")...; }) -//! .build().await?; +//! let mut b = AimDbBuilder::new().runtime(rt) +//! .with_connector(UdsClient::new("/run/aimdb.sock")); +//! b.configure::("temp", |r| { +//! r.buffer(BufferCfg::SingleLatest) +//! .with_remote_access() +//! .link_to("uds://temp") +//! .with_serializer_raw(|t: &Temp| Ok(serde_json::to_vec(t).expect("serialize"))) +//! .finish(); +//! }); +//! b.build().await?; +//! # Ok(()) +//! # } //! ``` mod transport; @@ -83,8 +99,11 @@ impl UdsClient { /// Accepts AimX connections over a Unix-domain socket and serves the full AimX /// toolset. Register it via `with_connector` to stand up remote access: /// -/// ```rust,ignore -/// builder.with_connector(UdsServer::new("/run/aimdb.sock").max_connections(32)) +/// ```no_run +/// # use aimdb_uds_connector::UdsServer; +/// # fn demo(builder: aimdb_core::AimDbBuilder) { +/// builder.with_connector(UdsServer::new("/run/aimdb.sock").max_connections(32)); +/// # } /// ``` /// /// Unlike a data-plane connector, a server takes **no** `link_to`/`link_from` — diff --git a/aimdb-wasm-adapter/src/bindings.rs b/aimdb-wasm-adapter/src/bindings.rs index 4a0e41e4..bfe9c30e 100644 --- a/aimdb-wasm-adapter/src/bindings.rs +++ b/aimdb-wasm-adapter/src/bindings.rs @@ -322,6 +322,9 @@ impl WasmDb { /// generic type parameters cannot cross the WASM boundary. Typical usage /// is in a factory function that builds a pre-configured `WasmDb`: /// + /// Illustrative (not compiled: `#[wasm_bindgen]` exports only build for + /// the wasm32 target): + /// /// ```rust,ignore /// #[wasm_bindgen] /// pub fn create_db() -> WasmDb { diff --git a/aimdb-websocket-connector/src/client/builder.rs b/aimdb-websocket-connector/src/client/builder.rs index 1e304dc7..f40d053f 100644 --- a/aimdb-websocket-connector/src/client/builder.rs +++ b/aimdb-websocket-connector/src/client/builder.rs @@ -35,7 +35,7 @@ use crate::transport::WsDialer; /// /// # Example /// -/// ```rust,ignore +/// ```no_run /// use aimdb_websocket_connector::WsClientConnector; /// /// let connector = WsClientConnector::new("wss://cloud.example.com/ws") @@ -61,13 +61,6 @@ pub struct WsClientConnectorBuilder { impl WsClientConnectorBuilder { /// Create a new builder targeting the given WebSocket URL. - /// - /// # Examples - /// - /// ```rust,ignore - /// WsClientConnector::new("wss://cloud.example.com/ws") - /// WsClientConnector::new("ws://192.168.1.100:8080/ws") - /// ``` pub fn new(url: impl Into) -> Self { Self { url: url.into(), @@ -115,9 +108,10 @@ impl WsClientConnectorBuilder { /// /// # Example /// - /// ```rust,ignore + /// ```no_run + /// # use aimdb_websocket_connector::WsClientConnector; /// WsClientConnector::new("wss://cloud/ws") - /// .with_subscribe_topics(["sensors/#", "config/#"]) + /// .with_subscribe_topics(["sensors/#", "config/#"]); /// ``` pub fn with_subscribe_topics( mut self, diff --git a/aimdb-websocket-connector/src/lib.rs b/aimdb-websocket-connector/src/lib.rs index ab2a9ade..eb1053ab 100644 --- a/aimdb-websocket-connector/src/lib.rs +++ b/aimdb-websocket-connector/src/lib.rs @@ -17,47 +17,64 @@ //! //! ## Server Quick Start //! -//! ```rust,ignore -//! use aimdb_tokio_adapter::TokioAdapter; +//! ```no_run +//! use aimdb_core::buffer::BufferCfg; +//! use aimdb_core::AimDbBuilder; +//! use aimdb_tokio_adapter::{TokioAdapter, TokioRecordRegistrarExt}; //! use aimdb_websocket_connector::WebSocketConnector; -//! -//! let db = AimDbBuilder::new() -//! .runtime(TokioAdapter::new()) +//! # use std::sync::Arc; +//! # #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +//! # struct Temperature { celsius: f32 } +//! # async fn demo() -> Result<(), Box> { +//! let mut builder = AimDbBuilder::new() +//! .runtime(Arc::new(TokioAdapter::new()?)) //! .with_connector( //! WebSocketConnector::new() //! .bind("0.0.0.0:8080") //! .path("/ws") //! .with_late_join(true), -//! ) -//! .configure::(TempKey::Vienna, |reg| { -//! reg.buffer(BufferCfg::SpmcRing { capacity: 100 }) -//! .link_to("ws://sensors/temperature/vienna") -//! .with_serializer_raw(|t| serde_json::to_vec(t).map_err(Into::into)) -//! .finish(); -//! }) -//! .build().await?; +//! ); +//! builder.configure::("sensors.temp.vienna", |reg| { +//! reg.buffer(BufferCfg::SpmcRing { capacity: 100 }) +//! .link_to("ws://sensors/temperature/vienna") +//! .with_serializer_raw(|t: &Temperature| Ok(serde_json::to_vec(t).expect("serialize"))) +//! .finish(); +//! }); +//! let (db, runner) = builder.build().await?; +//! # Ok(()) +//! # } //! ``` //! //! ## Client Quick Start //! -//! ```rust,ignore +//! ```no_run +//! use aimdb_core::buffer::BufferCfg; +//! use aimdb_core::AimDbBuilder; +//! use aimdb_tokio_adapter::{TokioAdapter, TokioRecordRegistrarExt}; //! use aimdb_websocket_connector::WsClientConnector; -//! -//! let db = AimDbBuilder::new() -//! .runtime(TokioAdapter::new()) +//! # use std::sync::Arc; +//! # #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +//! # struct Temperature { celsius: f32 } +//! # async fn demo() -> Result<(), Box> { +//! let mut builder = AimDbBuilder::new() +//! .runtime(Arc::new(TokioAdapter::new()?)) //! .with_connector( //! WsClientConnector::new("wss://cloud.example.com/ws"), -//! ) -//! .configure::("sensors/temp", |reg| { -//! reg.buffer(BufferCfg::SpmcRing { capacity: 100 }) -//! .link_to("ws-client://sensors/temp") -//! .with_serializer_raw(|t| serde_json::to_vec(t).map_err(Into::into)) -//! .finish() -//! .link_from("ws-client://config/threshold") -//! .with_deserializer_raw(|data| serde_json::from_slice(data)) -//! .finish(); -//! }) -//! .build().await?; +//! ); +//! builder.configure::("sensors.temp", |reg| { +//! reg.buffer(BufferCfg::SpmcRing { capacity: 100 }) +//! .link_to("ws-client://sensors/temp") +//! .with_serializer_raw(|t: &Temperature| Ok(serde_json::to_vec(t).expect("serialize"))) +//! .finish() +//! .link_from("ws-client://config/threshold") +//! .with_deserializer_raw(|data| { +//! serde_json::from_slice::(data).map_err(|e| e.to_string()) +//! }) +//! .finish(); +//! }); +//! let (db, runner) = builder.build().await?; +//! # Ok(()) +//! # } //! ``` //! //! ## Wire Protocol diff --git a/aimdb-websocket-connector/src/server/auth.rs b/aimdb-websocket-connector/src/server/auth.rs index 9a56911b..03d59379 100644 --- a/aimdb-websocket-connector/src/server/auth.rs +++ b/aimdb-websocket-connector/src/server/auth.rs @@ -111,8 +111,10 @@ impl AuthError { /// /// # Example — Bearer token auth /// -/// ```rust,ignore -/// use aimdb_websocket_connector::auth::{AuthHandler, AuthRequest, AuthError, Permissions}; +/// ```no_run +/// use aimdb_websocket_connector::{AuthHandler, AuthRequest, AuthError, Permissions}; +/// # use core::future::Future; +/// # use core::pin::Pin; /// /// struct BearerAuth { valid_token: String } /// diff --git a/aimdb-websocket-connector/src/server/builder.rs b/aimdb-websocket-connector/src/server/builder.rs index 3a63116d..67e4ccfa 100644 --- a/aimdb-websocket-connector/src/server/builder.rs +++ b/aimdb-websocket-connector/src/server/builder.rs @@ -46,13 +46,16 @@ use aimdb_ws_protocol::TopicInfo; /// /// # Example /// -/// ```rust,ignore +/// ```no_run /// use aimdb_websocket_connector::WebSocketConnector; +/// # use aimdb_data_contracts::{SchemaType, Streamable}; +/// # #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +/// # struct Temperature { celsius: f32 } +/// # impl SchemaType for Temperature { const NAME: &'static str = "temperature"; } +/// # impl Streamable for Temperature {} /// /// let mut connector = WebSocketConnector::new(); /// connector.register::(); -/// connector.register::(); -/// connector.register::(); /// /// let connector = connector /// .bind("0.0.0.0:8080") @@ -119,13 +122,6 @@ impl WebSocketConnectorBuilder { } /// Set the TCP address to bind the WebSocket server to. - /// - /// # Examples - /// - /// ```rust,ignore - /// .bind("0.0.0.0:9090") - /// .bind(([127, 0, 0, 1], 8765)) - /// ``` pub fn bind(mut self, addr: impl ToSocketAddrs) -> Self { self.bind_addr = addr .to_socket_addrs() @@ -189,10 +185,11 @@ impl WebSocketConnectorBuilder { /// /// # Example /// - /// ```rust,ignore + /// ```no_run /// use axum::{routing::get, Router}; + /// # use aimdb_websocket_connector::WebSocketConnector; /// - /// let rest = Router::new().route("/api/status", get(status_handler)); + /// let rest = Router::new().route("/api/status", get(|| async { "ok" })); /// let connector = WebSocketConnector::new().with_additional_routes(rest); /// ``` pub fn with_additional_routes(mut self, router: AxumRouter) -> Self { @@ -207,10 +204,10 @@ impl WebSocketConnectorBuilder { /// /// # Example /// - /// ```rust,ignore + /// ```no_run + /// # use aimdb_websocket_connector::WebSocketConnector; /// WebSocketConnector::new() - /// .with_auto_subscribe(["#"]) // push everything - /// .with_auto_subscribe(["sensors/#"]) // only sensor topics + /// .with_auto_subscribe(["sensors/#"]); // or ["#"] to push everything /// ``` pub fn with_auto_subscribe( mut self, @@ -246,19 +243,6 @@ impl WebSocketConnectorBuilder { /// Each call monomorphizes closures that capture `T` for serialization, /// deserialization, and routing. The serializer performs a `downcast_ref` /// on `&dyn Any` to recover the concrete type at dispatch. - /// - /// # Example - /// - /// ```rust,ignore - /// use aimdb_websocket_connector::WebSocketConnector; - /// - /// let mut connector = WebSocketConnector::new(); - /// connector.register::(); - /// connector.register::(); - /// connector.register::(); // user's own type - /// - /// let connector = connector.bind("0.0.0.0:8080"); - /// ``` /// # Panics /// /// Panics if a *different* type has already been registered under the