Skip to content

refactor(examples): use type-safe handlers and tag validation in 10-production-boilerplate. - #33

Open
hashi-divyansh wants to merge 3 commits into
coregx:mainfrom
hashi-divyansh:refactor/example-10-generic-handlers
Open

hashi-divyansh wants to merge 3 commits into
coregx:mainfrom
hashi-divyansh:refactor/example-10-generic-handlers

Conversation

@hashi-divyansh

@hashi-divyansh hashi-divyansh commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Modernizes examples/10-production-boilerplate to use fursy's type-safe generic handlers (fursy.Box[Req, Res]) and the built-in tag-based validation (plugins/validator), so the "production" example demonstrates fursy's recommended patterns. Business logic is unchanged.

Behavior changes (error responses only)

Case Before After
Invalid JSON body 400 Invalid Request: … 400 RFC 9457 Bad Request
Validation failure (user endpoints) 500 (plain error fell through MapError) 422 Validation Failed + errors map
Missing message on broadcast 400 Message is required 422 Validation Failed
Valid JSON sent with a non-JSON Content-Type (e.g. text/plain) accepted (201/200) 415 Unsupported Media Type
Password longer than 72 characters (register, change password) 500 (bcrypt error) 422 Validation Failed (max=72)

Success responses, routes, methods and status codes are unchanged.

Testing

  • go mod tidy && go build ./..., go vet ./..., gofmt, go test -race ./... (also verified merged with current main)
  • New TestAPI_Register cases: missing email → 422, password > 72 characters → 422, text/plain → 415, and an assertion that register sends no Location header
  • Manual run (SQLite + migrations):
    • register 201 (no Location header), login 200
    • password > 72 characters on register / change password → 422
    • SSE stream receives a broadcast with default type info
    • broadcast {} → 422
    • GET /api/chat/ws → 101 Switching Protocols
  • Compared against upstream/main: success responses are identical

@codecov

codecov Bot commented Nov 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@hashi-divyansh
hashi-divyansh force-pushed the refactor/example-10-generic-handlers branch from 3ce95bf to c6a4f57 Compare November 5, 2026 13:24
@hashi-divyansh
hashi-divyansh force-pushed the refactor/example-10-generic-handlers branch from c6a4f57 to e0c0b53 Compare November 5, 2026 13:30
@hashi-divyansh
hashi-divyansh marked this pull request as ready for review November 6, 2026 13:21
@hashi-divyansh

Copy link
Copy Markdown
Contributor Author

This line seems odd to me.
CORSOrigins should be of type string not []string. @kolkov

// CORS settings
CORSOrigins []string

@kolkov kolkov added documentation area: examples Changes to examples/ type: refactor Code cleanup, no behavior change status: review In code review and removed documentation labels Nov 11, 2026

@kolkov kolkov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks @hashi-divyansh — the conversion is right. I checked it merged with current main: the new 422/415 behavior measures as claimed (the table's "was" column is off for Content-Type, see below), success responses keep their statuses and bodies, and SSE (200, text/event-stream) and WebSocket (101) still upgrade through the generic handlers.

Needed before merge:

  1. internal/user/api.go:54: c.Created("/api/users/me", …) adds Location: /api/users/me, which main never sent; that path is the caller's profile, not the created user (the comment above already says "fix this dummy url"). Use c.Context.Created(ToResponse(user)) or a real URL.

Optional:

  • The table's "wrong Content-Type was 400" isn't what main does: valid JSON with text/plain returns 201/200. 415 is correct, just a bigger change than described; no test covers 422/415 yet.
  • Two inputs still return 500: a@b.c passes the email tag but fails emailRegex (2+ letter TLD), and a 100-byte password fails in bcrypt. max=72 (see examples/validation/02) and a tag backed by emailRegex, or mapping domain errors to 422, close them.

On CORSOrigins: yes, string — AllowOrigins is a comma-separated string (middleware/cors.go:36) split in buildAllowMap; config.go:41 wraps the whole env value in a one-element slice. It's unused today (main.go uses middleware.CORS()), so wiring it is a separate change.

- register: use Context.Created so no Location header is sent (the
  previous /api/users/me was not the created user's URL)
- Add max=72 to password fields (bcrypt limit), as in
  examples/validation/02
- Tests: cover 422 (missing email, password too long), 415
  (text/plain) and the absence of a Location header on register
@hashi-divyansh hashi-divyansh changed the title refactor examples/10-production-boilerplate refactor(examples): use type-safe handlers and tag validation in 10-production-boilerplate. Nov 11, 2026
@hashi-divyansh

hashi-divyansh commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor Author

Thanks for the review, Kolkov. Fixed:

  • register now uses c.Context.Created(...), so there’s no Location header (matches main).
  • Corrected the Content-Type row (main accepted any Content-Type; now 415).
  • Added max=72 on password fields as in examples/validation/02 (note that it counts characters, not bytes).
  • Added tests for 422, 415 and the missing Location header.

The a@b.c 500 already happens on main (the old Validate() errors also fell through MapError), so I’ll handle it in a follow-up that maps domain validation errors to 422. The CORSOrigins type change and wiring will be a separate PR #40 .

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: examples Changes to examples/ status: review In code review type: refactor Code cleanup, no behavior change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants