detached 6070679

fix: reduce cyclomatic complexity in admin handler routing

Refactor handleAdminRoutes to use a route matching helper instead of
a large switch statement, reducing cyclomatic complexity from 16 to
acceptable levels.
commit
60706798a905c9a07a4f3e903ef7f3df1f39c558
author
brice · · 2026-01-17 15:30 UTC
parent
d4d1fa4
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +41 -23
M internal/adapters/in/http/admin/handler.go +41 -23
@@ -122,30 +122,48 @@ func (h *Handler) handleAdminRoutes(w http.ResponseWriter, r *http.Request) {
122122 path := strings.TrimPrefix(r.URL.Path, "/admin")
123123
124124 // Route to appropriate handler
125- switch {
126- case path == "/attachments" || strings.HasPrefix(path, "/attachments/"):
127- h.handleAttachmentsConfig(w, r, path)
128- case path == "/routes" || strings.HasPrefix(path, "/routes/"):
129- h.handleRoutes(w, r, path)
130- case path == "/networks":
131- h.handleNetworks(w, r)
132- case path == "/secrets" || strings.HasPrefix(path, "/secrets/"):
133- h.handleSecrets(w, r, path)
134- case path == "/deploy" || strings.HasPrefix(path, "/deploy/"):
135- h.handleDeploy(w, r, path)
136- case path == "/logs" || strings.HasPrefix(path, "/logs/"):
137- h.handleLogs(w, r, path)
138- case path == "/status":
139- h.handleStatus(w, r)
140- case path == "/health":
141- h.handleHealth(w, r)
142- case path == "/reload":
143- h.handleReload(w, r)
144- case path == "/config":
145- h.handleConfig(w, r)
146- default:
147- h.sendError(w, http.StatusNotFound, "route not found")
125+ if handler, ok := h.matchRoute(path); ok {
126+ handler(w, r, path)
127+ return
128+ }
129+ h.sendError(w, http.StatusNotFound, "route not found")
130+}
131+
132+// routeHandler is the signature for path-based route handlers.
133+type routeHandler func(w http.ResponseWriter, r *http.Request, path string)
134+
135+// matchRoute returns the handler for a given path, or false if not found.
136+func (h *Handler) matchRoute(path string) (routeHandler, bool) {
137+ // Exact match routes
138+ exactRoutes := map[string]routeHandler{
139+ "/networks": func(w http.ResponseWriter, r *http.Request, _ string) { h.handleNetworks(w, r) },
140+ "/status": func(w http.ResponseWriter, r *http.Request, _ string) { h.handleStatus(w, r) },
141+ "/health": func(w http.ResponseWriter, r *http.Request, _ string) { h.handleHealth(w, r) },
142+ "/reload": func(w http.ResponseWriter, r *http.Request, _ string) { h.handleReload(w, r) },
143+ "/config": func(w http.ResponseWriter, r *http.Request, _ string) { h.handleConfig(w, r) },
144+ }
145+ if handler, ok := exactRoutes[path]; ok {
146+ return handler, true
147+ }
148+
149+ // Prefix match routes
150+ prefixRoutes := []struct {
151+ prefix string
152+ handler routeHandler
153+ }{
154+ {"/attachments", h.handleAttachmentsConfig},
155+ {"/routes", h.handleRoutes},
156+ {"/secrets", h.handleSecrets},
157+ {"/deploy", h.handleDeploy},
158+ {"/logs", h.handleLogs},
159+ }
160+ for _, route := range prefixRoutes {
161+ if path == route.prefix || strings.HasPrefix(path, route.prefix+"/") {
162+ return route.handler, true
163+ }
148164 }
165+
166+ return nil, false
149167 }
150168
151169 // sendJSON sends a JSON response.
@@ -122,30 +122,48 @@ func (h *Handler) handleAdminRoutes(w http.ResponseWriter, r *http.Request) {
122 path := strings.TrimPrefix(r.URL.Path, "/admin")122 path := strings.TrimPrefix(r.URL.Path, "/admin")
123 123
124 // Route to appropriate handler124 // Route to appropriate handler
125- switch {125+ if handler, ok := h.matchRoute(path); ok {
126- case path == "/attachments" || strings.HasPrefix(path, "/attachments/"):126+ handler(w, r, path)
127- h.handleAttachmentsConfig(w, r, path)127+ return
128- case path == "/routes" || strings.HasPrefix(path, "/routes/"):128+ }
129- h.handleRoutes(w, r, path)129+ h.sendError(w, http.StatusNotFound, "route not found")
130- case path == "/networks":130+}
131- h.handleNetworks(w, r)131+
132- case path == "/secrets" || strings.HasPrefix(path, "/secrets/"):132+// routeHandler is the signature for path-based route handlers.
133- h.handleSecrets(w, r, path)133+type routeHandler func(w http.ResponseWriter, r *http.Request, path string)
134- case path == "/deploy" || strings.HasPrefix(path, "/deploy/"):134+
135- h.handleDeploy(w, r, path)135+// matchRoute returns the handler for a given path, or false if not found.
136- case path == "/logs" || strings.HasPrefix(path, "/logs/"):136+func (h *Handler) matchRoute(path string) (routeHandler, bool) {
137- h.handleLogs(w, r, path)137+ // Exact match routes
138- case path == "/status":138+ exactRoutes := map[string]routeHandler{
139- h.handleStatus(w, r)139+ "/networks": func(w http.ResponseWriter, r *http.Request, _ string) { h.handleNetworks(w, r) },
140- case path == "/health":140+ "/status": func(w http.ResponseWriter, r *http.Request, _ string) { h.handleStatus(w, r) },
141- h.handleHealth(w, r)141+ "/health": func(w http.ResponseWriter, r *http.Request, _ string) { h.handleHealth(w, r) },
142- case path == "/reload":142+ "/reload": func(w http.ResponseWriter, r *http.Request, _ string) { h.handleReload(w, r) },
143- h.handleReload(w, r)143+ "/config": func(w http.ResponseWriter, r *http.Request, _ string) { h.handleConfig(w, r) },
144- case path == "/config":144+ }
145- h.handleConfig(w, r)145+ if handler, ok := exactRoutes[path]; ok {
146- default:146+ return handler, true
147- h.sendError(w, http.StatusNotFound, "route not found")147+ }
148+
149+ // Prefix match routes
150+ prefixRoutes := []struct {
151+ prefix string
152+ handler routeHandler
153+ }{
154+ {"/attachments", h.handleAttachmentsConfig},
155+ {"/routes", h.handleRoutes},
156+ {"/secrets", h.handleSecrets},
157+ {"/deploy", h.handleDeploy},
158+ {"/logs", h.handleLogs},
159+ }
160+ for _, route := range prefixRoutes {
161+ if path == route.prefix || strings.HasPrefix(path, route.prefix+"/") {
162+ return route.handler, true
163+ }
148 }164 }
165+
166+ return nil, false
149 }167 }
150 168
151 // sendJSON sends a JSON response.169 // sendJSON sends a JSON response.

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close