Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Ferra and the Rust ecosystem

Ferra is a thin layer over a small set of upstream Rust crates that already solve specific concerns well. This page explains what is Ferra-defined and what is re-exported, so that you know where to look for documentation and which surface to import when you build an application.

If you are looking for “five-minute zero-to-API,” see Getting started. This page is about the long-term shape of the dependency graph you inherit when you depend on ferra.


The principle: cohabitation, not encapsulation

Ferra does not wrap the upstream crates it integrates. Instead, it does one of two things:

  1. Direct re-export — when an upstream crate already offers a stable, namespaced surface, Ferra simply re-exports it under ferra::*. You import ferra::sea_orm::DatabaseConnection, ferra::biscuit_quote::check, and you call them with the API the upstream crate documents. There is no Ferra-renamed equivalent.
  2. Default-locking + opt-in feature — when an upstream crate is correct for some applications but not all, Ferra locks the upstream-default configuration explicitly behind a Cargo feature, so the dependency weight only lands when you opt in.

Ferra introduces a wrapper or a Ferra-defined trait only when one of three things is true:

  • There is concrete behaviour to inject at the call site (logging, validation, span propagation).
  • A typed Ferra newtype prevents misuse that the upstream type does not catch (Email, UserId).
  • A Ferra trait abstracts over multiple backends with build-time or runtime swap-out (FerraAuthProvider, RateLimitStore).

If none of those apply, the upstream surface is exposed verbatim and the upstream documentation is the canonical reference.


What lives under ferra::*

Ferra-defined surface

The ferra:: namespace defines a small, stable user-facing API:

NamespaceWhat it is
ferra::FoundryThe router builder — Foundry::new(conn).mount::<M>().build().
ferra::IdThe framework’s identifier type used by #[id] fields.
ferra::RouterThe composed Axum router returned by Foundry::build().
ferra::FerraErrorThe framework-wide error enum surfaced in HTTP responses.
ferra::FerraLayerThe Tower layer composition entry point.
#[derive(FerraModel)]The proc-macro that turns a struct into a Ferra resource.
#[ferra(...)]The attribute namespace for Ferra-specific concerns.
#[id], #[field(...)]Field-level attributes for primary keys and projection rules.
#[authorize(...)]Operation-level authorization rules.

The Ingot, ferra-forge, ferra-anvil thematic names belong to internal documentation. You will not see them in your application code.

Re-exported upstream surface

The rest of ferra::* is re-exports. The biggest namespaces:

NamespaceRe-exportsDocumentation
ferra::sea_ormAll of sea_ormsea_orm docs.rs
ferra::biscuit_quoteAll of biscuit-quotebiscuit-quote docs.rs
ferra::tracingAll of tracingtracing docs.rs

When you write:

#![allow(unused)]
fn main() {
use ferra::sea_orm::Database;

let conn = Database::connect("postgres://...").await?;
}

…you are calling sea_orm::Database::connect. The upstream documentation applies verbatim. There is no Ferra-side wrapping.

Crate graph: modules pub(crate), items glob-exported at root

Every framework implementation crate (ferra-core, ferra-db, ferra-forge, ferra-http, ferra-openapi) keeps its modules pub(crate) and re-exports its public items at its crate root. The facade then writes pub use ferra_<crate>::*; once per implementation crate. A consequence is that two implementation crates can share a module name (emit, routes, schema, …) without collisions: only the items at each crate’s root reach the facade, and the items themselves are uniquely named across the workspace.

This rule is enforced mechanically. The cargo clippy --workspace -- -D ambiguous_glob_reexports lint fires the moment two glob re-exports in the facade name the same item from different sources; CI gates the lint as a release-blocker (FR-024). A future regression that exposes a sub-module from any implementation crate as pub would be caught the next time the facade picks up a sibling-crate item with the same name.

If you are reading the source code: prefer the absence of pub mod on every implementation crate’s lib.rs. The single exception is ferra::prelude on the facade itself — the prelude is intended to be opened directly by consumers, so its module visibility is pub (FR-025).


Cargo feature surface

Ferra’s default dependency graph is intentionally lean. Heavy or audience-partial features are opt-in.

Feature flagWhat it enablesWhen you want it
(default)Core framework + tracing + structured stderr JSON loggingAlways.
otelOpenTelemetry SDK + OTLP/HTTP exporter (locked to upstream-default transport)You run an OTel collector and want OTLP exports.
otel-grpcotel + gRPC transport (instead of HTTP/protobuf)Your collector requires gRPC and your ingress preserves HTTP/2.
otel-fipsotel + runtime-installed FIPS-compliant rustls CryptoProviderFIPS-compliant deployments.

Activating a feature

[dependencies]
ferra = { package = "ferra-rs", version = "0.x", features = ["otel"] }

The package = "ferra-rs" alias is required because the ferra name on crates.io is owned by an unrelated project; the framework’s facade is published as ferra-rs. See Getting Started for full details.

Adding the feature pulls the locked OTel SDK pin into your build; without the feature, none of those crates compile. There is no runtime check — the choice is at compile time.


Where to look for documentation

QuestionSource
How do I declare a model?This user guide → ferra-forge.md
What does Foundry::new(...) do?This user guide → ferra-http.md
How do I run a database query?Upstream — sea_orm docs.rs (Ferra re-exports verbatim)
What are valid Datalog rules in #[authorize]?Upstream — biscuit documentation + the rule grammar reference
How do I configure tracing subscribers?Upstream — tracing-subscriber docs.rs (Ferra re-exports verbatim)

When you ask an AI coding assistant about a Ferra application, the assistant should reach for upstream documentation for re-exported surfaces and for this user guide for Ferra-defined surface. The distinction matters because upstream training corpora are large and Ferra-specific corpora are small — the cohabitation pattern is what makes a Ferra application legible to AI agents trained on the broader Rust ecosystem.


A worked example

A complete Ferra application demonstrating both halves of the principle:

// Cargo.toml:
//   ferra = { package = "ferra-rs", version = "0.x", features = ["otel"] }

// Direct re-export (Half A): upstream `sea_orm` reached without renaming.
use ferra::sea_orm::Database;

// Direct re-export (Half A): upstream `biscuit-quote::check` for compile-time
// Datalog. A typo here is a `cargo build` error, not a runtime panic.
use ferra::biscuit_quote::check;

// Ferra-defined surface: the model derive, the router builder.
use ferra::{Foundry, FerraModel, Id};

#[derive(FerraModel)]
struct Film {
    #[id]
    id: Id,
    title: String,
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Upstream API (sea_orm): no Ferra-side wrapper.
    let conn = Database::connect("postgres://localhost/films").await?;

    // Ferra-defined: the router composition surface.
    let app = Foundry::new(conn).mount::<Film>().build();

    // Upstream API (axum / hyper): Ferra returns a standard Axum Router.
    axum::serve(tokio::net::TcpListener::bind("0.0.0.0:3000").await?, app).await?;
    Ok(())
}

The same application without the otel feature compiles to a smaller binary; the OTel SDK is not in the dependency graph at all.


When you need a Ferra wrapper that does not yet exist

If you find yourself wanting a Ferra-side wrapper around an upstream type — a Ferra newtype, a Ferra trait abstracting multiple backends, or a Ferra macro that does something the upstream macro does not — the wrapper is a feature request worth filing. The maintainers will ask which of the three justifications applies (concrete behaviour to inject, type-narrowing, swap-out insurance) and ship the wrapper at that point. Until then, the upstream surface is the documented path.