Skip to main content

Crate mapepire

Crate mapepire 

Source
Expand description

§mapepire

CI Audit (daily) deps.rs MSRV License: MIT OR Apache-2.0

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:

ExampleDemonstrates
examples/one_shot.rsPool::builderpool.execute(sql) → iterate rows
examples/prepared.rsJob::prepare + Query::execute_with reused across calls
examples/transaction.rspool.acquire().rollback_on_drop() + v0.4 typed begin/commit
examples/streaming.rsRows::stream_typed::<T> with a serde::Deserialize row struct
examples/with_tracing.rstracing-subscriber registration + per-execute span output
examples/cl_command.rsJob::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

FeatureDefaultPurpose
rustls-tlsonPure-Rust TLS via rustls (target for v0.2 transport)
native-tlsoffOS-platform TLS via native-tls (alternate v0.2 backend)
insecure-tlsoffCompile-time gate for TlsConfig::Insecure (skip server-cert validation; never use in production)
serde-configoffDaemonServerSpec DTO for loading from config files (TOML/YAML/JSON via consumer’s choice of parser)
tracingofftracing span instrumentation on every public dispatch entry point (Job::execute, Pool::execute, Reserved::*). Per-pool ParameterLogging governs whether parameter values appear on spans.
metricsoffmetrics 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.3Pool with deadpool, Reserved for transactions, public Executor / FromRow traits, diagnostic methods carried over from v0.2 (done).
  • v0.4tracing and metrics feature flags; idle_timeout enforcement; rollback_on_drop tightened 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 PR
  • SECURITY.md — vulnerability reporting
  • Makefilemake help lists all dev tasks

§License

Dual-licensed under either of:

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-config
pub use crate::config::SpecError;serde-config
pub use crate::config::TlsConfigSpec;serde-config
pub 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
Executor trait — common SQL dispatch surface.
from_row
FromRow trait — typed materialization for one row.
job
Single connection to a Mapepire daemon.
observabilitymetrics
Metric name constants emitted by the metrics feature.
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§

PoolStatus
Snapshot of pool state — re-exported from deadpool::Status.