Skip to content

chore(echo): upgrade echo to v5#190

Merged
TheDarthMole merged 20 commits intomainfrom
chore/update-echo-v5
Feb 14, 2026
Merged

chore(echo): upgrade echo to v5#190
TheDarthMole merged 20 commits intomainfrom
chore/update-echo-v5

Conversation

@TheDarthMole
Copy link
Copy Markdown
Owner

@TheDarthMole TheDarthMole commented Jan 21, 2026

Dependent on swaggo/echo-swagger#140

Summary by CodeRabbit

  • New Features

    • Graceful shutdown support for improved service management
  • Refactor

    • Web framework upgraded to the latest major release
    • Logging modernised to structured format
  • Changes

    • API documentation (Swagger) route disabled
  • Tests

    • Tests updated for structured logging, route enumeration changes and in-memory TLS certificate handling
  • Chores

    • IDE (.idea/) added to ignore rules
    • Dependency versions updated in module manifest

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jan 21, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR upgrades Echo v4→v5, adds an afero filesystem to server Start, introduces a cancellable server context, changes handler signatures to use *echo.Context, switches logging to structured slog, updates tests for in-memory TLS certs, and adds .idea/ to .gitignore. (49 words)

Changes

Cohort / File(s) Summary
Framework & handler signature migration
internal/api/handlers/root.go, internal/api/handlers/root_test.go, internal/api/handlers/servers.go, internal/api/handlers/servers_test.go, internal/api/handlers/upswake.go, internal/api/handlers/upswake_test.go
Replace Echo v4 imports with echo/v5; change handler method signatures to accept *echo.Context; update tests to use e.Router().Routes() where applicable.
Server lifecycle, Start API & FS integration
internal/api/server.go, cmd/upswake/serve.go, cmd/upswake/root_test.go, internal/api/server_test.go
Add cancellable context and cancel to Server; change Start signature to accept afero.Fs (IOFS-backed cert loading); update Start/Stop flows and call sites; tests refactored to use in-memory FS and const refactor in test.
Structured logging & logger API updates
internal/api/handlers/servers.go, internal/api/handlers/upswake.go, internal/api/handlers/root.go, cmd/upswake/serve_test.go
Replace printf-style logger calls with structured slog and non-format c.Logger().Error/Info/Debug; log messages converted to structured fields; tests updated to assert JSON/structured output.
Tests: in-memory TLS certs & afero usage
internal/api/server_test.go, cmd/upswake/*_test.go
Tests generate RSA/ECC certs in-memory, write them to an afero.MemMapFs, and pass that FS to Server.Start; file paths and TLS test scaffolding updated accordingly.
Dependencies & repo config
go.mod, .gitignore
Bump github.com/labstack/echo v4→v5 and adjust several indirect module versions; add .idea/ to .gitignore.

Sequence Diagram(s)

sequenceDiagram
  participant CLI as "CLI / serve.go"
  participant Server as "internal/api/server.go\nServer"
  participant FS as "afero.Fs (IOFS)"
  participant Echo as "Echo v5 Engine"

  CLI->>Server: Start(fs, address, useSSL, certFile, keyFile)
  Server->>FS: open certFile / keyFile via IOFS
  Server->>Echo: configure Start/StartTLS with StartConfig + ctx
  Echo-->>Server: server started / running
  CLI->>Server: Stop()
  Server->>Server: invoke cancel()
  Echo-->>Server: graceful shutdown
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 I hopped from v4 into v5 tonight,
tucked certs in memory, cosy and tight.
Logs now speak in fields, tidy and bright,
a cancel call whispers the server goodnight.

🚥 Pre-merge checks | ✅ 2 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (21 files):

⚔️ .github/workflows/build.yml (content)
⚔️ .github/workflows/dependency-review.yml (content)
⚔️ .github/workflows/golangci-lint.yml (content)
⚔️ .github/workflows/lint-swagger.yml (content)
⚔️ .github/workflows/scorecard.yml (content)
⚔️ .github/workflows/test.yml (content)
⚔️ .gitignore (content)
⚔️ Containerfile (content)
⚔️ cmd/upswake/root_test.go (content)
⚔️ cmd/upswake/serve.go (content)
⚔️ cmd/upswake/serve_test.go (content)
⚔️ go.mod (content)
⚔️ go.sum (content)
⚔️ internal/api/handlers/root.go (content)
⚔️ internal/api/handlers/root_test.go (content)
⚔️ internal/api/handlers/servers.go (content)
⚔️ internal/api/handlers/servers_test.go (content)
⚔️ internal/api/handlers/upswake.go (content)
⚔️ internal/api/handlers/upswake_test.go (content)
⚔️ internal/api/server.go (content)
⚔️ internal/api/server_test.go (content)

These conflicts must be resolved before merging into main.
Resolve conflicts locally and push changes to this branch.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately describes the primary change: upgrading the Echo framework from v4 to v5, which is reflected throughout multiple files in the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/update-echo-v5

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
internal/api/handlers/root_test.go (1)

152-164: Assertion message is out of sync with the expected count.

The message says “Expected 2 routes” while lenExpectedRoutes is 3. Using Lenf keeps the message accurate.

🛠️ Suggested fix
-	assert.Len(t, e.Router().Routes(), lenExpectedRoutes, "Expected 2 routes to be registered")
+	assert.Lenf(t, e.Router().Routes(), lenExpectedRoutes, "Expected %d routes to be registered", lenExpectedRoutes)
internal/api/handlers/root.go (1)

40-56: Swagger route is disabled while Root still redirects to a missing endpoint.

The commented-out /swagger/* registration means tests expecting this route will fail. The Root handler redirects to /swagger/index.html, but that endpoint is never registered. Additionally, echo-swagger (swaggo/echo-swagger) targets Echo v4 and lacks official v5 support—your dependencies include both v4 and v5, suggesting a migration in progress. You must either:

  • Re-enable the swagger handler and resolve the v5 compatibility (fork or wait for community port), or
  • Update Root and tests to remove the swagger redirect

Also, if the comment line remains, add a space after // to satisfy lint rules:

Lint fix for the commented line
-	//g.GET("/swagger/*", echoSwagger.WrapHandler)
+	// g.GET("/swagger/*", echoSwagger.WrapHandler)
🤖 Fix all issues with AI agents
In `@cmd/upswake/root_test.go`:
- Around line 208-210: The test uses the literal "http(s) server started" in the
wantOutput slice which will never match actual Echo logs ("http server started"
or "https server started"); update the assertions that reference wantOutput (the
test's wantOutput slice used in the test function in root_test.go) to perform a
scheme‑agnostic match—either replace the literal with a regex like `^https?
server started` and use assert.Regexp (or similar) to check the log line, or
split the expectation into two explicit strings and assert the actual output
equals one of them depending on useSSL; ensure you update both occurrences that
currently contain "http(s) server started".

In `@go.mod`:
- Around line 9-10: Remove the unused Echo v4 module entry from go.mod by
deleting the line declaring github.com/labstack/echo/v4 v4.9.0, then run go mod
tidy to update module graph and vendor files; verify that all imports reference
labstack/echo/v5 (e.g., in server.go and handler packages) and run the
test/build to ensure no references remain to Echo v4.

In `@internal/api/handlers/servers.go`:
- Around line 80-84: Echo v5 removed Context.Logger(), so replace uses of
c.Logger() in handlers (e.g., WakeServer and other methods on ServerHandler)
with the supported pattern: use c.Echo().Logger (or retrieve a logger stored in
context via c.Get) and update log calls like c.Logger().Error(...) to
c.Echo().Logger.Error(...) (or the context logger retrieval) and adjust any
structured logging helpers to use slog.Any/fields as before; ensure all
occurrences across the handlers are changed consistently.

In `@internal/api/server_test.go`:
- Around line 274-286: Remove the noisy certificate print and stale TODO: delete
the fmt.Println(string(publicRSAKey)) call and remove any associated TODO
comment referencing use of an in-memory FS; the cert setup using certFs,
generateEncodedRSAKeys, generateEncodedECCKeys and the afero.WriteFile calls
should remain untouched so tests continue writing rsa.key/rsa.cert and
ecc.key/ecc.cert to the memory FS without printing their contents (also apply
same removal at the other occurrence around lines 405-406).
- Around line 200-266: The RSA PEM block in generateEncodedRSAKeys uses
x509.MarshalPKCS1PrivateKey (PKCS#1) but labels the PEM as "PRIVATE KEY", which
implies PKCS#8; update generateEncodedRSAKeys to either change the PEM block
Type to "RSA PRIVATE KEY" to match PKCS#1 (recommended) or convert the key to
PKCS#8 via x509.MarshalPKCS8 and keep "PRIVATE KEY"; ensure the change is
applied where x509.MarshalPKCS1PrivateKey and pem.Encode are used so TLS loaders
accept the key.

In `@internal/api/server.go`:
- Around line 84-103: The GracefulTimeout in Server.Start is set to the bare
integer 5 (nanoseconds) causing improper shutdown; update the echo.StartConfig
initialization in Start (the start variable) to use an explicit time.Duration
such as 5 * time.Second for the GracefulTimeout field and add the time package
import; ensure you modify the Start function and import list so GracefulTimeout:
5 * time.Second is used instead of 5.
🧹 Nitpick comments (2)
internal/api/handlers/upswake.go (1)

74-135: Echo logger may stringify slog.Attr, losing structure.

If you want structured logs, consider logging via slog.Logger directly or ensure Echo’s logger is wired to a slog handler so attributes remain structured; otherwise they’ll likely be rendered as plain strings.

internal/api/handlers/servers.go (1)

150-154: Inconsistent error logging - consider adding context.

This error log lacks structured context compared to other error logs in this file. Since broadcast is available in scope, including it would aid debugging.

🔧 Suggested improvement
 	if broadcast == nil {
-		c.Logger().Error("invalid broadcast address")
+		c.Logger().Error("invalid broadcast address encountered", slog.Int("index", /* loop index */), slog.Any("broadcast", broadcast))
 		return c.JSON(http.StatusInternalServerError, Response{Message: ErrorBroadcastAddress.Error()})
 	}

Alternatively, if you want to include the loop index, change to an indexed loop:

-for _, broadcast := range broadcasts {
+for i, broadcast := range broadcasts {

Comment thread cmd/upswake/root_test.go
Comment thread go.mod Outdated
Comment thread internal/api/handlers/servers.go
Comment thread internal/api/server_test.go
Comment thread internal/api/server_test.go Outdated
Comment thread internal/api/server.go
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@internal/api/handlers/root.go`:
- Around line 40-44: The Register function currently comments out the Swagger
route causing RootHandler.Root's redirect to /swagger/index.html to 404; either
re-enable the Swagger route in RootHandler.Register by uncommenting or restoring
the g.GET("/swagger/*", echoSwagger.WrapHandler) line so the /swagger/* handler
is registered, or update RootHandler.Root to redirect/serve a path that exists;
locate RootHandler.Register and RootHandler.Root in
internal/api/handlers/root.go and make the change so the route referenced by
RootHandler.Root is actually registered (tests expect the /swagger/* route to be
present).

In `@internal/api/server_test.go`:
- Around line 32-34: The pingHandler currently uses the wrong Echo v5 signature
(func pingHandler(c *echo.Context) error); change it to the correct interface
type by updating the signature to use echo.Context (func pingHandler(c
echo.Context) error) and update any local references inside pingHandler that
assume a pointer receiver to use the interface methods instead so it compiles
against Echo v5.
♻️ Duplicate comments (3)
internal/api/server_test.go (3)

236-239: RSA PEM label mismatches the encoded key format.

The key is encoded using x509.MarshalPKCS1PrivateKey (PKCS#1 format), but the PEM block type is "PRIVATE KEY" which indicates PKCS#8 format. This mismatch may cause TLS loaders to fail. Use "RSA PRIVATE KEY" for PKCS#1.

Proposed fix
-	err = pem.Encode(&pemKey, &pem.Block{Type: "PRIVATE KEY", Bytes: b})
+	err = pem.Encode(&pemKey, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: b})

286-286: Remove debug print statement.

Printing certificate material clutters test logs and serves no purpose in automated tests.

Proposed fix
-	fmt.Println(string(publicRSAKey))

405-406: Remove stale TODO comment.

The TODO states "We should use a memory fs here" but the code already uses certFs (an in-memory filesystem). This comment is now misleading.

Proposed fix
-			// TODO: We should use a memory fs here with certificatse loaded, not depending on actual certs on disk
 			err := srv.Start(certFs, tt.args.address, tt.args.useSSL, tt.args.certFile, tt.args.keyFile)

Comment thread internal/api/handlers/root.go
Comment thread internal/api/server_test.go
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@internal/api/server.go`:
- Around line 42-45: The validator is being created with the parent ctx instead
of the server's cancellable context newCtx, so change the NewServer function to
pass newCtx into NewCustomValidator (so app.Validator =
NewCustomValidator(newCtx)) ensuring the CustomValidator used by app.Validator
and its Validate method (which calls cv.validator.StructCtx(cv.ctx, i)) is tied
to the server's cancellation lifecycle.
🧹 Nitpick comments (6)
internal/api/handlers/root.go (1)

76-76: Consider using structured logging instead of string concatenation.

Concatenating err.Error() into the message string loses the ability to filter/parse the error field independently in structured log aggregators. Echo v5's Logger interface is minimal, so this may be acceptable, but if you have access to slog or zap here, structured fields would be preferable.

-		c.Logger().Error("Error getting broadcast addresses: " + err.Error())
+		// If a richer logger is available via dependency injection:
+		// logger.Error("Error getting broadcast addresses", "error", err)

Also applies to: 83-83

internal/api/server_test.go (1)

390-410: Parallel tests sharing randomised ports carry a small collision risk.

Each test case generates a random port via rand.IntN(65535-49152)+49152. Since tests run in parallel (line 392), two tests could theoretically pick the same port. The probability is low given the range (~16k ports), but for robustness you could use :0 to let the OS assign a free port, or retry on EADDRINUSE.

This is a minor observation — unlikely to cause flakes in practice.

internal/api/server.go (2)

94-96: HTTPSRedirect middleware is registered on every Start call with SSL.

If Start were called multiple times (unlikely but possible), the Pre(middleware.HTTPSRedirect()) middleware would be registered repeatedly. Since Start is effectively called once, this is fine in practice, but registering middleware in NewServer (conditionally) or guarding against double-registration would be more defensive.


102-105: Stop() always returns nil — consider whether callers expect meaningful errors.

Currently Stop only cancels the context and returns nil. The caller in serve.go (line 121-123) checks the error from Stop() and logs a warning. Since Stop can never return an error, that check is dead code. If Stop is never expected to fail, consider changing the signature to not return an error, or document the contract.

internal/api/handlers/servers.go (2)

12-12: Advisory: Echo v5 is in a pre-stability period until 2026-03-31.

Echo recommends waiting until after 2026-03-31 before upgrading to v5 in production, as breaking API changes may still occur during this period, even if it violates semantic versioning. Ensure this timeline aligns with your release plans, or pin to a specific v5 tag to avoid surprise breakage.


112-113: Consider logging port and broadcast for consistency with BroadcastWakeServer.

The BroadcastWakeServer handler (lines 174–177) logs mac, port, and broadcast fields on success, whereas WakeServer only logs mac (line 112). Since WakeServerRequest contains all three fields, adding port and broadcast to the success log would improve debuggability and maintain consistent observability across both handlers.

Suggested improvement
-	c.Logger().Info("wake on lan packet sent", slog.String("mac", sanitizeString(wsRequest.Mac)))
+	c.Logger().Info("wake on lan packet sent",
+		slog.String("mac", sanitizeString(wsRequest.Mac)),
+		slog.Int("port", wsRequest.Port),
+		slog.String("broadcast", wsRequest.Broadcast))

Comment thread internal/api/server.go Outdated
@codecov
Copy link
Copy Markdown

codecov bot commented Feb 14, 2026

Codecov Report

❌ Patch coverage is 90.74074% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/api/handlers/servers.go 88.88% 2 Missing ⚠️
internal/api/handlers/upswake.go 77.77% 2 Missing ⚠️
internal/api/handlers/root.go 80.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@TheDarthMole TheDarthMole merged commit 5ab567c into main Feb 14, 2026
13 of 14 checks passed
@TheDarthMole TheDarthMole deleted the chore/update-echo-v5 branch February 14, 2026 13:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant