Expand description
§mapepire
Async Rust client SDK for Mapepire —
a cloud-friendly access layer for Db2 for IBM i that exposes the
database over TLS-secured WebSockets.
Status: v0.4 ready (observability + cleanup). Not yet on crates.io. The full v1.0 surface (real-IBM-i CI, examples) lands in v1.0.
Sibling SDKs exist for Node.js, Python, Java, Go, PHP, and C#/.NET. This crate fills the Rust gap with a parity-first design.
§Quick look
use mapepire::{DaemonServer, Pool, TlsConfig};
let server = DaemonServer::builder()
.host("ibmi.example.com")
.user("DCURTIS")
.password(std::env::var("MAPEPIRE_PASSWORD").unwrap())
.tls(TlsConfig::Verified)
.build()
.expect("missing required field");
let pool = Pool::builder(server).max_size(8).build().await?;
// One-shot SQL via the routed pool.
let _rows = pool.execute("SELECT 1 FROM SYSIBM.SYSDUMMY1").await?;
// Transactional work via Reserved (BEGIN / DML / COMMIT all on one socket).
let conn = pool.acquire().await?.rollback_on_drop();
conn.execute("BEGIN").await?;
conn.execute_with(
"UPDATE ORDERS SET STATUS = ? WHERE ID = ?",
&[serde_json::json!("paid"), serde_json::json!(42)],
).await?;
conn.execute("COMMIT").await?;Pool::builder(server).max_size(8).build().await? warms a deadpool-backed
connection pool of Job handles. pool.execute(...) runs one-shot SQL via
the v0.3 §7.3 three-tier routing scan (idle → least-busy → fair-queue).
pool.acquire() returns a Reserved handle that pins one socket for
BEGIN / DML / COMMIT so the entire transaction lands on the same
connection. The opt-in .rollback_on_drop() fires a best-effort ROLLBACK
if the handle drops without an explicit COMMIT / ROLLBACK.
The password setter takes ownership of a String and immediately moves
it into a zeroizing buffer (Password). DaemonServer is not Clone —
the Pool::builder constructor takes impl Into<Arc<DaemonServer>> so the
single config is shared across every pooled connection.
§Runnable examples
The examples/ directory ships six runnable demos covering
the common patterns:
| Example | Demonstrates |
|---|---|
examples/one_shot.rs | Pool::builder → pool.execute(sql) → iterate rows |
examples/prepared.rs | Job::prepare + Query::execute_with reused across calls |
examples/transaction.rs | pool.acquire().rollback_on_drop() + v0.4 typed begin/commit |
examples/streaming.rs | Rows::stream_typed::<T> with a serde::Deserialize row struct |
examples/with_tracing.rs | tracing-subscriber registration + per-execute span output |
examples/cl_command.rs | Job::cl(...) + ClMessage walkthrough |
Each example reads MAPEPIRE_HOST, MAPEPIRE_USER, and MAPEPIRE_PASSWORD
from the environment. Run with cargo run --example <name> (or
cargo run --example with_tracing --features tracing for the tracing demo).
§Single-connection alternative
If you only need one connection (e.g. a CLI tool or a one-shot script),
Job::connect skips the pool entirely:
use mapepire::{DaemonServer, Job, TlsConfig};
let server = DaemonServer::builder()
.host("daemon.example.com")
.port(8076)
.user("USER")
.password(std::env::var("MAPEPIRE_PASSWORD").unwrap())
.tls(TlsConfig::Verified)
.build()
.expect("missing required field");
let job = Job::connect(&server).await?;
let rows = job.execute("SELECT NAME, COUNT FROM SCHEMA.STATS").await?;
let dynamic = rows.into_dynamic().await?;
for row in dynamic {
let name: String = row.get("NAME")?;
let count: i64 = row.get("COUNT")?;
println!("{name}: {count}");
}Job::connect performs the full TCP → TLS → WebSocket Upgrade →
Connect handshake and resolves once the daemon confirms the session.
§Cargo features
| Feature | Default | Purpose |
|---|---|---|
rustls-tls | on | Pure-Rust TLS via rustls (target for v0.2 transport) |
native-tls | off | OS-platform TLS via native-tls (alternate v0.2 backend) |
insecure-tls | off | Compile-time gate for TlsConfig::Insecure (skip server-cert validation; never use in production) |
serde-config | off | DaemonServerSpec DTO for loading from config files (TOML/YAML/JSON via consumer’s choice of parser) |
tracing | off | tracing span instrumentation on every public dispatch entry point (Job::execute, Pool::execute, Reserved::*). Per-pool ParameterLogging governs whether parameter values appear on spans. |
metrics | off | metrics facade integration. Counters / gauges / histograms documented at mapepire::observability. |
A compile_error! guard fires when neither rustls-tls nor native-tls
is enabled — disabling default features requires an explicit alternate
TLS backend selection.
§Observability (optional)
Enable tracing and/or metrics features for production observability:
[dependencies]
mapepire = { version = "0.4", features = ["rustls-tls", "tracing", "metrics"] }
tracing-subscriber = "0.3"
metrics-exporter-prometheus = "0.15"// Tracing — fmt subscriber to stderr.
tracing_subscriber::fmt::init();
// Metrics — Prometheus exporter on :9000/metrics.
metrics_exporter_prometheus::PrometheusBuilder::new().install()?;Once installed, every Job::execute / Pool::execute / Pool::acquire /
Reserved::* call emits the relevant spans and metrics. Both features are
zero-cost when disabled.
The metric-name contract is documented at
mapepire::observability
and is SemVer-stable — names won’t be renamed without a major bump.
§Roadmap
- v0.1 — protocol foundation (done).
- v0.2 — transport,
Job::connect, integration tests (done). - v0.3 —
Poolwithdeadpool,Reservedfor transactions, publicExecutor/FromRowtraits, diagnostic methods carried over from v0.2 (done). - v0.4 —
tracingandmetricsfeature flags;idle_timeoutenforcement;rollback_on_droptightened to only-if-in-tx; registry-backed routing fast path (done). - v1.0 — examples, real-IBM-i CI, donation proposal to the Mapepire-IBMi GitHub org.
§Documentation
AGENTS.md— contributor and AI-assistant guide (architecture, coding standards, security invariants, MSRV policy)CONTRIBUTING.md— how to open a PRSECURITY.md— vulnerability reportingMakefile—make helplists all dev tasks
§License
Dual-licensed under either of:
- MIT license (https://opensource.org/licenses/MIT)
- Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0)
at your option. By contributing, you agree your contribution will be dual-licensed as above.
§Building a DaemonServer
use mapepire::{DaemonServer, TlsConfig};
let server = DaemonServer::builder()
.host("ibmi.example.com")
.user("DCURTIS")
.password("hunter2".to_string())
.tls(TlsConfig::Verified)
.build()
.expect("missing required field");
assert_eq!(server.port, DaemonServer::DEFAULT_PORT);§Encoding a request
use mapepire::protocol::request::Request;
let r = Request::Sql {
id: "1".into(),
sql: "SELECT 1 FROM SYSIBM.SYSDUMMY1".into(),
rows: None,
parameters: None,
};
let json = serde_json::to_string(&r).expect("Request serializes to JSON");
assert!(json.contains(r#""type":"sql""#));Re-exports§
pub use crate::config::BuilderError;pub use crate::config::DaemonServer;pub use crate::config::DaemonServerBuilder;pub use crate::config::TlsConfig;pub use crate::config::DaemonServerSpec;serde-configpub use crate::config::SpecError;serde-configpub use crate::config::TlsConfigSpec;serde-configpub use crate::error::DecodeError;pub use crate::error::DiagnosticItem;pub use crate::error::Error;pub use crate::error::ProtocolError;pub use crate::error::Result;pub use crate::error::ServerError;pub use crate::error::TransportError;pub use crate::executor::Executor;pub use crate::from_row::FromRow;pub use crate::job::Job;pub use crate::job::TraceLevel;pub use crate::password::Password;pub use crate::pool::ParameterLogging;pub use crate::pool::Pool;pub use crate::pool::PoolBuilder;pub use crate::pool::RecyclingMethod;pub use crate::pool::Reserved;pub use crate::protocol::ClMessage;pub use crate::protocol::Column;pub use crate::protocol::ErrorResponse;pub use crate::protocol::IdAllocator;pub use crate::protocol::QueryMetaData;pub use crate::protocol::QueryResult;pub use crate::protocol::Request;pub use crate::protocol::RequestId;pub use crate::protocol::Response;pub use crate::query::Query;pub use crate::query::Row;pub use crate::query::Rows;
Modules§
- config
- Daemon connection configuration.
- error
- Crate-wide error types.
- executor
Executortrait — common SQL dispatch surface.- from_
row FromRowtrait — typed materialization for one row.- job
- Single connection to a Mapepire daemon.
- observability
metrics - Metric name constants emitted by the
metricsfeature. - password
- Password handling — zeroize on drop, no Clone, no Serialize.
- pool
- Connection pool for
crate::Job(v0.3 §4.6, §7). - protocol
- Mapepire wire protocol.
- query
- Prepared-statement handle (
Query) + result-set types (Rows,Row).
Structs§
- Pool
Status - Snapshot of pool state — re-exported from
deadpool::Status.