-
Notifications
You must be signed in to change notification settings - Fork 24
Description
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
- 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
(typeHandler
)internal/ports/http/admin/endpoints/web_auth_login_v1/handler.go
(typeHandler
)
- Register them via fxhttpserver group registrations in
public/register_gen.go
andadmin/register_gen.go
. - Start the server and call the admin login route:
POST /admin/web/auth/login/v1
. - 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.