Skip to content

zeph-vault: repeated Pin<Box<dyn Future<...>>> return-type boilerplate across all 4 VaultProvider backends #6484

Description

@bug-ops

Finding

All four VaultProvider implementations in zeph-vault (AgeVaultProvider in age.rs:492-498, ArcAgeVaultProvider in arc.rs:50-62, EnvVaultProvider in env.rs:39-46, MockVaultProvider in mock.rs:111-117) repeat the identical get_secret return-type signature verbatim:

fn get_secret(
    &self,
    key: &str,
) -> Pin<Box<dyn Future<Output = Result<Option<String>, VaultError>> + Send + '_>>

This is the standard workaround for returning a dyn-compatible future from a trait method (native async fn in traits, per this project's Edition 2024 convention, is not object-safe, and the async-trait crate is disallowed per .claude/rules/rust-code.md). The boilerplate is unavoidable in each impl block, but the return type itself is repeated 4 times letter-for-letter, with no shared alias to name it once.

The same unaliased pattern exists workspace-wide (11 files under crates/, e.g. zeph-llm/src/provider.rs, zeph-scheduler/src/task.rs), so a full fix is out of scope for a single-crate finding — this issue is scoped to zeph-vault only, where it is cheap and self-contained (one crate, one trait, 4 call sites).

Location

  • crates/zeph-vault/src/lib.rs:97-100 (trait definition)
  • crates/zeph-vault/src/age.rs:492-498
  • crates/zeph-vault/src/arc.rs:50-62
  • crates/zeph-vault/src/env.rs:39-46
  • crates/zeph-vault/src/mock.rs:111-117

Before

pub trait VaultProvider: Send + Sync {
    fn get_secret(
        &self,
        key: &str,
    ) -> Pin<Box<dyn Future<Output = Result<Option<String>, VaultError>> + Send + '_>>;
    ...
}

// repeated identically in age.rs, arc.rs, env.rs, mock.rs:
fn get_secret(
    &self,
    key: &str,
) -> Pin<Box<dyn Future<Output = Result<Option<String>, VaultError>> + Send + '_>> {
    ...
}

After

// lib.rs
/// A boxed, `Send` future resolving to a secret lookup result, borrowed for the
/// lifetime of the `&self` receiver that produced it.
pub type SecretFuture<'a> = Pin<Box<dyn Future<Output = Result<Option<String>, VaultError>> + Send + 'a>>;

pub trait VaultProvider: Send + Sync {
    fn get_secret(&self, key: &str) -> SecretFuture<'_>;
    ...
}

// age.rs / arc.rs / env.rs / mock.rs:
fn get_secret(&self, key: &str) -> SecretFuture<'_> {
    ...
}

Why

A named type alias documents intent once (what the future represents) instead of four separate anonymous repetitions of the same 90-character type, and shrinks each impl signature to a single line. It is a pure textual/readability change — the type is identical (Pin<Box<dyn Future<...> + Send + '_>> is what SecretFuture<'_> expands to), so no behavior, no call-site, and no downstream consumer of Box<dyn VaultProvider> is affected. Low priority since the crate already builds and passes rustdoc/clippy cleanly — this is a small ergonomics win, not a defect.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P4Long-term / exploratoryarchitectureArchitecture improvementstech-debtTechnical debt

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions