Skip to content

HTTP handlers/middlewares are mixed up when package/type names match across different import paths (type identification collision) #361

@damnedest

Description

@damnedest

Summary

When registering handlers and middlewares via fxhttpserver, the framework builds identifiers based on reflect.Type.String(). This representation omits the full import path and only returns something like "pkg.Type". If two packages in different paths use the same package and type names (e.g., both define package web_auth_login_v1 with a Handler type), they collide. As a result, the registry may resolve a wrong implementation for a route, e.g. the public login handler gets called for an admin route.

Environment

  • Framework: github.com/ankorstore/yokai/fxhttpserver (vendored)
  • Router: labstack/echo v4
  • Project structure includes duplicate package/type names under different roots, e.g.:
    • internal/ports/http/public/endpoints/web_auth_login_v1.Handler
    • internal/ports/http/admin/endpoints/web_auth_login_v1.Handler

How to reproduce

  1. Create two handlers with the same package name and type name under different import paths, e.g.:
    • internal/ports/http/public/endpoints/web_auth_login_v1/handler.go (type Handler)
    • internal/ports/http/admin/endpoints/web_auth_login_v1/handler.go (type Handler)
  2. Register them via fxhttpserver group registrations in public/register_gen.go and admin/register_gen.go.
  3. Start the server and call the admin login route: POST /admin/web/auth/login/v1.
  4. Observe that the function bound to the route can be from the public handler, visible in logs/stack like:
    <module>/internal/ports/http/public/endpoints/web_auth_login_v1.(*Handler).Handle.func1

Expected behavior

Each route should resolve and invoke the correct handler associated with its distinct package path (admin vs public), even if the package and type names are identical.

Actual behavior

Handlers (and potentially middlewares) with matching package and type names get mixed up due to a collision in type identification. The registry returns the first matching entry based on a non-unique string identity, and the wrong handler is invoked.

Root cause analysis

  • fxhttpserver currently identifies types using reflect.Type.String() (and for constructors, similarly based on function types). String() produces a short name like "*web_auth_login_v1.Handler" that lacks the full import path.
  • When the same package and type names appear under different import paths (admin/public), their Type.String() values are identical, causing collisions.
  • Additionally, attempts to use PkgPath() directly may return an empty string for non-named types (pointers, functions). For example:
    • reflect.Type.PkgPath() is empty for pointer and func kinds.
    • Constructors (func) require inspecting the return type (Out(0)), and pointers must be unwrapped to reach the named type.

Proposed fix

Build stable, unique identifiers using the named type’s full import path and name: PkgPath() + "." + Name(). To handle pointers and constructors:

  • Unwrap pointers via Elem() until reaching the base type.
  • For constructors, inspect the return type via Out(0) and then apply the same unwrapping.
  • If the type is still unnamed (e.g., slice/map/func), fall back to t.String().

Why this works

  • Admin and Public handlers now produce distinct identifiers, e.g.:
    • <module>/internal/ports/http/admin/endpoints/web_auth_login_v1.Handler
    • <module>/internal/ports/http/public/endpoints/web_auth_login_v1.Handler
  • The registry (lookupRegisteredHandler / lookupRegisteredMiddleware) will match the correct concrete implementation without collisions.

Backward compatibility

  • This change only affects internal identifier strings used inside the registry. It does not alter public APIs.
  • If you have persisted or compared the old reflect.Type.String() values elsewhere (unlikely), adjust those usages.

Alternatives considered

  • Renaming packages/types to avoid collisions (works but invasive and brittle).
  • Registering only concrete echo handlers/middleware (avoids DI benefits and flexibility provided by fx).
  • Comparing reflect.Type values directly in the registry (also viable), but string IDs with full import paths are sufficient and easier for logging and diagnostics.

Additional notes

  • For types defined in package main, PkgPath() will be "main" — expected and acceptable.
  • For generic types, if a named concrete type is used, PkgPath()+Name() works; otherwise, the fallback to t.String() keeps a readable identity.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions