🔥 feat: unify internal and custom constraints into single interface#4397
🔥 feat: unify internal and custom constraints into single interface#4397pageton wants to merge 7 commits into
Conversation
Draft PR for constraint system rework per gofiber#4395. - Unify built-in and custom constraints under a single Constraint interface - Add optional Analyse() phase for precomputation at registration time - Remove TypeConstraint bitmask and large switch statement - Pre-parse integer args, compile regex, parse time layouts once at registration
Refactor the constraint system to treat built-in and custom constraints uniformly through a single ConstraintHandler interface with an optional Analyze() phase for precomputation at registration time. Key changes: - Add ConstraintHandler interface (Name, Execute) and optional ConstraintAnalyzer interface (Analyze) for precomputation - Convert each built-in constraint (int, bool, float, alpha, guid, minLen, maxLen, len, betweenLen, min, max, range, datetime, regex) into its own type implementing ConstraintHandler - Remove TypeConstraint bitmask, getParamConstraintType switch, needOneData/needTwoData constants, and the ~130-line switch in CheckConstraint - Pre-parse integer args, compile regex, and parse time layouts once at registration via Analyze() instead of on every request - Wrap existing CustomConstraint implementations automatically via customConstraintWrapper for backward compatibility - Preserve CheckConstraint method for external callers Closes gofiber#4395
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Code Review
This pull request refactors route constraints handling by introducing the ConstraintHandler and ConstraintAnalyzer interfaces, extracting built-in constraints into separate types, and simplifying the Constraint struct. The review feedback highlights two critical issues: a potential nil pointer dereference panic in CheckConstraint when a Constraint is manually constructed by external packages, and a bug where custom regexHandler results are discarded if they return a standard *regexp.Regexp.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4397 +/- ##
==========================================
- Coverage 91.40% 91.39% -0.02%
==========================================
Files 132 133 +1
Lines 13120 13266 +146
==========================================
+ Hits 11992 12124 +132
- Misses 711 724 +13
- Partials 417 418 +1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
- Resolve handler on the fly in matchConstraint if nil (prevents panic on manually constructed Constraint structs from external packages) - Update precompiled with custom regexHandler result when it returns *regexp.Regexp, instead of discarding it
|
@ReneWerner87 as discussed, this draft PR implements the constraint system rework we talked about. The key idea:
The pattern is: All 3460 tests pass, 0 lint issues. Still draft — would love your thoughts on the approach before we finalize. |
Cover nil-handler fallback in matchConstraint, missing-args Analyze branches for all constraint types, nil-precompiled Execute branches, and custom constraint wrapper integration.
|
@pageton can you share some before and after benchmarks |
|
Good first draft. Can you make sure that i.e., during analysis, if an int constraint is used, data should be an int so that it already matches for the comparison if there is a regex constraint in the pattern, it should already be available in the routing segment for the match as precompiled in the constraints, so that it can then simply be used (of course, using the configured regex compiler that can be configured in the app) In the end, there should be no more special cases for regex or int constraints, because they all work the same way and the data from the routing pattern is in the format used for matching the actual route or segment, ensuring that the routing match is as fast as possible |
|
In other words, there are two goals:
|
|
@ReneWerner87 here are the before/after benchmarks: Key improvements (precomputed
Slight regressions in simple constraints that don't benefit from precomputation ( The main value of this rework is architectural: unified interface, extensibility, and precomputation for multi-arg constraints. The 0-allocation guarantee is preserved. |
Benchmark all 14 constraint types for before/after comparison.
Restructure constraint system so Data holds pre-typed values: - Data is now []any with values converted at registration time (int for minLen/maxLen/len/betweenLen/min/max/range, *regexp.Regexp for regex, string for datetime layout) - Remove separate precompiled field — Execute uses Data directly - No special cases per constraint type in match path - bool uses strconv.ParseBool like original implementation - Custom constraints wrapped via customConstraintWrapper Benchstat vs main (geomean -3.45%): betweenLen: -21.38% range: -17.81% maxLen: -8.65% max: -8.87% minLen: -7.65% len: -7.27% min: -3.67%
|
@ReneWerner87 updated the implementation per your feedback.
All 3499 tests pass, 0 lint issues. |
|
@pageton why is this regex related extra code still needed ?
|
|
@ReneWerner87 you're right, that regex-specific code was no longer needed and conflicted with the goal of keeping the parser/match flow uniform. I removed the regex-only handling from
The parser now only resolves the handler, creates the constraint, and appends it like every other constraint. Regex-specific behavior is handled inside Pushed in |
|
@efectn can you check |




Summary
Unifies the internal constraint system (TypeConstraint bitmask + switch) and the custom constraint interface (
CustomConstraint) into a singleConstraintHandlerinterface with an optionalAnalyze()phase for precomputation at registration time.Closes #4395
Changes
New file:
constraint.goConstraintHandlerinterface —Name() string+Execute(param, args, precompiled)ConstraintAnalyzerinterface (optional) —Analyze(args) -> (any, error)for precomputationintConstraintType,minLenConstraintType,regexConstraintType, etc.customConstraintWrapperadapts existingCustomConstraintimplementations automaticallyfindConstraintHandler()merges custom + built-in constraints (custom priority)newConstraint()creates aConstraintwith automaticAnalyze()callModified:
path.goTypeConstraintuint16 type, 15 iota constants,needOneData/needTwoDatabitmasksgetParamConstraintType()(33-line switch)CheckConstraint()(~130-line switch) — replaced by singlehandler.Execute()dispatchanalyseParameterPart()usesfindConstraintHandler()+newConstraint()checkConstraint()delegates tohandler.Execute()viamatchConstraint()CheckConstraint()method as thin wrapper for backward compatModified:
path_test.gonewConstraint()instead of direct struct literalsPerformance impact
strconv.Atoi,time.Parselayout, regex compilation all happen once at registration viaAnalyze()Execute()callany: Type-asserted at match time (no interface overhead for simple constraints)Backward compatibility
CustomConstraintinterface preserved — auto-wrapped viacustomConstraintWrapperCheckConstraint()method preserved for external callersRegisterCustomConstraint()API unchanged:param<constraint(data)>) unchangedChecks