Skip to content

ClientConfig / CallOptions: #[non_exhaustive] + pub fields blocks all construction outside the crate #90

@iainmcgin

Description

@iainmcgin

ClientConfig and CallOptions are simultaneously #[non_exhaustive] and have pub fields. This combination is the worst of both worlds for a downstream crate.

A user reading the rustdoc sees pub base_uri: Uri, pub protocol: Protocol, ..., reaches for the obvious construction, and finds neither form compiles outside the defining crate:

// E0639: cannot create non-exhaustive structs using struct expression
let config = ClientConfig { base_uri: uri, protocol: Protocol::Connect, ..Default::default() };

// also rejected — struct update syntax is forbidden too
let config = ClientConfig { base_uri: uri, ..ClientConfig::default() };

The only legal path is the builder methods (ClientConfig::new(uri).protocol(...).default_timeout(...)), but the pub fields suggest otherwise, and the compiler error doesn't point at the builder.

#[non_exhaustive] is the right call (we will add fields), but it should be paired with pub(crate) fields and read accessors so the rustdoc and the construction story agree:

#[non_exhaustive]
pub struct ClientConfig {
    pub(crate) base_uri: Uri,
    pub(crate) protocol: Protocol,
    // ...
}

impl ClientConfig {
    pub fn new(base_uri: Uri) -> Self { ... }
    pub fn protocol(mut self, p: Protocol) -> Self { ... }   // existing builder methods
    pub fn base_uri(&self) -> &Uri { ... }                    // new read accessors
    pub fn protocol_ref(&self) -> Protocol { ... }
}

Same change for CallOptions.

While here, also unify the invalid-header policy: Response::with_header panics on bad input while ClientConfig::default_header and CallOptions::with_header silently drop it. A user who passes a malformed dynamic value to default_header gets no error, no log, and no header — a hard-to-debug production gap. Both sides should either panic (acceptable for compile-time-constant header names) or return Result (Response already has try_with_header to copy from).

Scope

Small, but a SemVer-major change for anyone reading the pub fields directly (which is currently allowed). Best landed alongside the RequestContext evolution decision.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions