Skip to content

extern_path only matches package prefixes — per-type FQN mappings are silently ignored #99

@iainmcgin

Description

@iainmcgin

Problem

extern_path only matches at the proto package prefix level — a mapping for a specific type FQN is silently ignored. This is the same gap as anthropics/buffa#111, surfacing through connectrpc-codegen because it reuses buffa-codegen's type resolution.

This came up while working with Buf on the BSR Cargo SDK plugins. BSR's SDK assembly injects extern_path mappings to point a downstream plugin's generated code at the upstream module's SDK crate. Module-level (package-prefix) injection works today; per-type injection does not, which prost/tonic support and which BSR could rely on if a single proto package's files were ever split across more than one BSR module.

Where the limitation lives

TypeResolver (connectrpc-codegen/src/codegen.rs) wraps buffa_codegen::context::CodeGenContext::for_generate() so that service-stub input/output paths are byte-identical to what buffa emits for message fields:

struct TypeResolver<'a> {
    ctx: buffa_codegen::context::CodeGenContext<'a>,
    require_extern: bool,
}

impl<'a> TypeResolver<'a> {
    fn new(...) -> Self {
        Self {
            ctx: buffa_codegen::context::CodeGenContext::for_generate(proto_file, file_to_generate, config),
            require_extern,
        }
    }
}

CodeGenContext matches extern_paths against each file's package, never against a type FQN, so a per-type mapping like extern_path=.google.protobuf.Timestamp=::other_crate::Ts is dead — see anthropics/buffa#111 for the details and the prost comparison.

What this needs in connect-rust

The fix lands in buffa-codegen and connectrpc-codegen should inherit it for free via for_generate() — that's the whole point of the TypeResolver design. But there is connect-rust-specific surface to verify once buffa supports per-type matching:

  • TypeResolver::resolve_path / rust_type — service method input/output owned types under a per-type override.
  • TypeResolver::rust_view_type — view paths use CodeGenContext::rust_type_relative_split to find the package boundary and insert the __buffa::view:: sentinel; a per-type override changes where that boundary falls.
  • The require_extern mode used by generate_services — it currently rejects any path that isn't ::/crate::-rooted with an error message that suggests extern_path=.=<…>. The error text and the checks should still hold up when a per-type override is present.
  • Tests: extend wkt_types_use_buffa_types_extern_path and extern_catchall_with_wkt_longest_wins with a per-type override case; add a nested-type case.

Status

Tracking only. Blocked on anthropics/buffa#111. No implementation planned until Buf confirms the per-type case is something BSR's SDK assembly actually emits — module-level extern_path injection has been sufficient for the BSR plugin work so far.

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