Skip to content

Conversation

@duhnnie
Copy link
Contributor

@duhnnie duhnnie commented Nov 30, 2025

This adds a necessary util function (which adds a trailing slash / to a string if it doesn't have one) for fixing an issue in fiber V3.

Summary by CodeRabbit

  • New Features

    • Added utility functions to automatically append trailing slashes to strings and byte arrays when missing, improving path normalization.
  • Tests

    • Comprehensive test suite added for trailing slash functionality, covering edge cases, immutability validation, and performance benchmarks.

✏️ Tip: You can customize this high-level summary in your review settings.

@duhnnie duhnnie requested a review from a team as a code owner November 30, 2025 15:58
@duhnnie duhnnie requested review from ReneWerner87, efectn, gaby and sixcolors and removed request for a team November 30, 2025 15:58
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @duhnnie, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a new generic utility function, AddTrailingSlash, designed to append a trailing slash to string or byte sequence inputs if one is not already present. This addition is critical for resolving a specific issue identified in Fiber V3, ensuring consistent and correct path formatting. The implementation is thoroughly tested with dedicated unit tests for both string and byte slice inputs, and includes performance benchmarks to validate its efficiency.

Highlights

  • New Utility Function: Introduced a new generic utility function, AddTrailingSlash, which ensures that a given string or byte sequence ends with a forward slash ('/').
  • Type Handling: The AddTrailingSlash function is generic and can operate on both string and []byte types, adapting its behavior accordingly.
  • Comprehensive Testing: Extensive unit tests have been added for AddTrailingSlash covering various scenarios for both string and byte slice inputs, including edge cases like empty inputs or inputs already ending with a slash.
  • Performance Benchmarking: Benchmarks are included to measure the performance of AddTrailingSlash across different input sizes and types, ensuring efficiency.
  • Purpose: This function is specifically added to address and fix an issue within the Fiber V3 framework, improving path handling consistency.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new utility function, AddTrailingSlash, along with its unit tests and benchmarks. The implementation is solid and the tests provide good coverage. My review includes a few suggestions to improve code clarity and maintainability, such as correcting typos in comments and test logs, removing a redundant type assertion, and a recommendation to document a potential side-effect in the []byte handling to prevent misuse.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 30, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

This change introduces two new utility functions—AddTrailingSlashString and AddTrailingSlashBytes—to append trailing slashes to strings and byte slices respectively. The Makefile's modernize target is updated to apply fixes via the -fix flag. Comprehensive tests and benchmarks validate both functions across various input scenarios.

Changes

Cohort / File(s) Change Summary
Makefile update
Makefile
Modified modernize target to include -fix flag alongside -test=false, enabling fixes to be applied rather than only checked
String utility function
strings.go
Added AddTrailingSlashString(s string) string function to append trailing slash when absent, returning "/" for empty input and preserving strings already ending with "/"
Bytes utility function
bytes.go
Added AddTrailingSlashBytes(b []byte) []byte function delegating to AddTrailingSlashString for consistency, converting byte slice to string, appending slash if needed, and converting back
String function tests
strings_test.go
Added Test_AddTrailingSlashString with multiple test cases covering empty, slash-only, plain, path-like, double-slash, and Unicode paths; added Benchmark_AddTrailingSlashString for performance measurement
Bytes function tests
bytes_test.go
Added Test_AddTrailingSlashBytes, Test_AddTrailingSlashBytes_NoMutation, Test_AddTrailingSlashBytes_ReturnsSame, and Benchmark_AddTrailingSlashBytes with comprehensive edge case coverage

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~15 minutes

  • Straightforward utility function additions with minimal logic complexity
  • Test cases are thorough but follow a repetitive pattern
  • Makefile change is trivial (single flag addition)
  • Primary review focus: verify test coverage adequacy, performance characteristics, and absence of mutation in input slices

Poem

🐰 A hop through slashes, swift and neat,
String to bytes, a trailing treat!
With tests galore and benchmarks true,
These paths now wear their "/" shoe! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feature: add AddTrailingSlash' accurately describes the main change: adding two new exported utility functions (AddTrailingSlashString and AddTrailingSlashBytes) along with comprehensive test coverage and a minor Makefile update.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
Contributor

@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: 4

🧹 Nitpick comments (2)
byteseq.go (1)

93-95: Remove unnecessary type assertion.

When v already ends with '/', the type assertion any(v).(T) is redundant since v is already of type T.

Apply this diff:

 	if len(v) > 0 && v[len(v)-1] == '/' {
-		return any(v).(T)
+		return v
 	}
byteseq_test.go (1)

349-385: Add allocation reporting to benchmark for consistency.

All other benchmarks in this file call b.ReportAllocs() to track memory allocations, which is important for performance analysis. This benchmark should follow the same pattern.

Apply this diff:

 	for _, tt := range tests {
 		b.Run(tt.name, func(b *testing.B) {
+			b.ReportAllocs()
 			switch v := tt.in.(type) {
 
 			case string:
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d531ec6 and 3865bdf.

📒 Files selected for processing (2)
  • byteseq.go (1 hunks)
  • byteseq_test.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
byteseq_test.go (1)
byteseq.go (1)
  • AddTrailingSlash (92-106)

@duhnnie duhnnie force-pushed the feature/addtrailingslash branch from 3865bdf to df3a88e Compare November 30, 2025 16:18
@gaby
Copy link
Member

gaby commented Nov 30, 2025

@ReneWerner87 isn't the trailing slash matching handled by fasthttp?

@ReneWerner87
Copy link
Member

@gaby Yes, that's right, but this helper is used to compare the paths that we have stored in the routing tree of the various apps (mount paths) and thus prioritise which error handler is used by the mounted app.
I don't know yet whether the helper is useful here, only if it really has a speed advantage and is used multiple times.

@duhnnie
Copy link
Contributor Author

duhnnie commented Nov 30, 2025

@ReneWerner87 @gaby I made corrections to the initial solutions (docs, linting, typos, improvements, etc)

Copy link
Contributor

@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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between df3a88e and 5f61c4b.

📒 Files selected for processing (2)
  • byteseq.go (1 hunks)
  • byteseq_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • byteseq_test.go
🔇 Additional comments (1)
byteseq.go (1)

104-116: LGTM: Type switch implementation correctly addresses mutation concerns.

The implementation properly handles both string and []byte cases:

  • String concatenation is idiomatic and creates a new string
  • []byte allocation with explicit make and copy avoids the mutation issues raised in past reviews
  • The nolint directives are appropriate since the type assertions are guaranteed by the constraint

@codecov
Copy link

codecov bot commented Nov 30, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 84.81%. Comparing base (f1fb607) to head (2a657ad).

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #162      +/-   ##
==========================================
+ Coverage   84.62%   84.81%   +0.19%     
==========================================
  Files          12       12              
  Lines         956      968      +12     
==========================================
+ Hits          809      821      +12     
  Misses        126      126              
  Partials       21       21              
Flag Coverage Δ
unittests 84.81% <100.00%> (+0.19%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@duhnnie
Copy link
Contributor Author

duhnnie commented Nov 30, 2025

Waiting for your decision about this: #162 (comment)

@gaby
Copy link
Member

gaby commented Dec 2, 2025

@duhnnie The benchmarks are showing that when the string doesn't have a trailing slash, it's very inefficient adding one at the end (your switch case)

Benchmark_AddTrailingSlash/StringSmallNoSlash-4         	49275459	        23.59 ns/op	       4 B/op	       1 allocs/op
Benchmark_AddTrailingSlash/StringSmallWithSlash-4       	1000000000	         0.5775 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlash/StringLargeNoSlash-4         	  801898	      1432 ns/op	   10240 B/op	       1 allocs/op
Benchmark_AddTrailingSlash/StringLargeWithSlash-4       	1000000000	         0.5786 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlash/BytesSmallNoSlash-4          	75852127	        15.17 ns/op	       4 B/op	       1 allocs/op
Benchmark_AddTrailingSlash/BytesSmallWithSlash-4        	1000000000	         0.4331 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlash/BytesLargeNoSlash-4          	  812870	      1385 ns/op	   10240 B/op	       1 allocs/op
Benchmark_AddTrailingSlash/BytesLargeWithSlash-4        	1000000000	         0.4333 ns/op	       0 B/op	       0 allocs/op

@duhnnie duhnnie force-pushed the feature/addtrailingslash branch from 5f61c4b to 46680cd Compare December 3, 2025 13:51
Copy link
Contributor

@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: 0

🧹 Nitpick comments (1)
byteseq_test.go (1)

343-378: Consider adding baseline comparisons for performance context.

The benchmark comprehensively tests various input sizes and conditions. However, it might be valuable to add comparison benchmarks against:

  • Naive string concatenation (e.g., if !strings.HasSuffix(s, "/") { s += "/" })
  • Direct type-specific implementations without generics

This would help quantify the performance trade-off of the generic approach and inform the decision about whether to keep the current implementation or switch to specialized functions.

Example addition:

func Benchmark_AddTrailingSlash_Comparison(b *testing.B) {
    input := "abc"
    
    b.Run("Generic", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            _ = AddTrailingSlash(input)
        }
    })
    
    b.Run("DirectString", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            s := input
            if len(s) > 0 && s[len(s)-1] != '/' {
                s = s + "/"
            }
            _ = s
        }
    })
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5f61c4b and 46680cd.

📒 Files selected for processing (2)
  • byteseq.go (1 hunks)
  • byteseq_test.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
byteseq_test.go (1)
byteseq.go (1)
  • AddTrailingSlash (101-118)
🔇 Additional comments (4)
byteseq.go (2)

91-100: Documentation is clear and accurate.

The documentation accurately describes the behavior for both string and []byte inputs, including the optimization that returns the original input when no modification is needed.


106-118: Reconsider the type switch approach if benchmarks show significant performance degradation.

The generic type conversions using any() do introduce measurable overhead—this is well-documented in Go: type assertions on interface values are slower than concrete types or properly constrained generics, with observed slowdowns of tens to hundreds of percent in tight loops.

If benchmarks confirm material performance impact for this code path, consider whether:

  • Separate type-specific functions (AddTrailingSlashString, AddTrailingSlashBytes) are worth the API trade-off
  • The constraint design allows a more efficient generic approach
  • This code path is performance-critical for typical use cases

However, decide based on actual benchmark data from this codebase rather than theoretical concerns alone.

byteseq_test.go (2)

307-323: Test coverage is comprehensive and correct.

The test cases cover all important scenarios: empty input, input without trailing slash, input with trailing slash, and the root case. The use of require.Equal is consistent with the rest of the file.


325-341: Byte slice test is correct and mirrors string test coverage.

This test appropriately validates the []byte variant with the same comprehensive test cases as the string version.

@duhnnie duhnnie force-pushed the feature/addtrailingslash branch from 46680cd to bb03b21 Compare December 3, 2025 13:56
@duhnnie
Copy link
Contributor Author

duhnnie commented Dec 3, 2025

@gaby

I see, you're right, the main issue is the allocation cost for large strings. However, since strings in Go are immutable, adding a trailing slash always requires allocating a new string. This is expected and unavoidable.

For the []byte case, we could check the slice capacity and append the slash without allocating, but that would mutate the caller’s buffer. Because this function is intended to be non-mutating and behave consistently for both string and []byte inputs, we intentionally allocate a new slice instead.

About the use of the switch, I left this comment above: #162 (comment)

Thoughts?

@duhnnie duhnnie requested a review from gaby December 3, 2025 14:48
@gaby
Copy link
Member

gaby commented Dec 3, 2025

@duhnnie See if it works using utils.UnsafeString and utils.UnsafeBytes, since in fiber we use those

@ReneWerner87
Copy link
Member

/gemini review

@ReneWerner87
Copy link
Member

@gaby can you check again

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new utility function, AddTrailingSlash, and its associated tests. The core logic in AddTrailingSlash has a critical flaw in its handling of generic types, which I've detailed in my comments. Specifically, it doesn't correctly process named types that have string or []byte as their underlying type. I've also identified a couple of areas for improvement in the tests to make them more robust, including testing for named types and ensuring slice identity is checked correctly.

@ReneWerner87
Copy link
Member

bench output from the bench action

Benchmark_AddTrailingSlash/string/empty-4              	68699278	        17.43 ns/op	       1 B/op	       1 allocs/op
Benchmark_AddTrailingSlash/string/slash-only-4         	501702490	         2.391 ns/op	 418.15 MB/s	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlash/string/short-no-slash-4     	58627597	        18.93 ns/op	 158.48 MB/s	       4 B/op	       1 allocs/op
Benchmark_AddTrailingSlash/string/short-with-slash-4   	501861198	         2.391 ns/op	1672.99 MB/s	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlash/string/path-no-slash-4      	41579101	        27.43 ns/op	 474.01 MB/s	      16 B/op	       1 allocs/op
Benchmark_AddTrailingSlash/string/path-with-slash-4    	502002145	         2.391 ns/op	5854.90 MB/s	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlash/string/long-no-slash-4      	34721426	        32.61 ns/op	1349.08 MB/s	      48 B/op	       1 allocs/op
Benchmark_AddTrailingSlash/string/long-with-slash-4    	501992530	         2.390 ns/op	18826.44 MB/s	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/bytes/empty-4          	47151802	        23.87 ns/op	       8 B/op	       1 allocs/op
Benchmark_AddTrailingSlashBytes/bytes/slash-only-4     	501587596	         2.394 ns/op	 417.78 MB/s	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/bytes/short-no-slash-4 	350623000	         3.426 ns/op	 875.54 MB/s	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/bytes/short-with-slash-4         	501496095	         2.396 ns/op	1669.56 MB/s	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/bytes/path-no-slash-4            	350578702	         3.428 ns/op	3792.37 MB/s	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/bytes/path-with-slash-4          	501639562	         2.398 ns/op	5837.43 MB/s	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/bytes/long-no-slash-4            	350547199	         3.424 ns/op	12848.77 MB/s	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/bytes/long-with-slash-4          	499894323	         2.394 ns/op	18794.15 MB/s	       0 B/op	       0 allocs/op

@gaby
Copy link
Member

gaby commented Dec 7, 2025

bench output from the bench action

Benchmark_AddTrailingSlash/string/empty-4              	68699278	        17.43 ns/op	       1 B/op	       1 allocs/op
Benchmark_AddTrailingSlash/string/slash-only-4         	501702490	         2.391 ns/op	 418.15 MB/s	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlash/string/short-no-slash-4     	58627597	        18.93 ns/op	 158.48 MB/s	       4 B/op	       1 allocs/op
Benchmark_AddTrailingSlash/string/short-with-slash-4   	501861198	         2.391 ns/op	1672.99 MB/s	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlash/string/path-no-slash-4      	41579101	        27.43 ns/op	 474.01 MB/s	      16 B/op	       1 allocs/op
Benchmark_AddTrailingSlash/string/path-with-slash-4    	502002145	         2.391 ns/op	5854.90 MB/s	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlash/string/long-no-slash-4      	34721426	        32.61 ns/op	1349.08 MB/s	      48 B/op	       1 allocs/op
Benchmark_AddTrailingSlash/string/long-with-slash-4    	501992530	         2.390 ns/op	18826.44 MB/s	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/bytes/empty-4          	47151802	        23.87 ns/op	       8 B/op	       1 allocs/op
Benchmark_AddTrailingSlashBytes/bytes/slash-only-4     	501587596	         2.394 ns/op	 417.78 MB/s	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/bytes/short-no-slash-4 	350623000	         3.426 ns/op	 875.54 MB/s	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/bytes/short-with-slash-4         	501496095	         2.396 ns/op	1669.56 MB/s	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/bytes/path-no-slash-4            	350578702	         3.428 ns/op	3792.37 MB/s	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/bytes/path-with-slash-4          	501639562	         2.398 ns/op	5837.43 MB/s	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/bytes/long-no-slash-4            	350547199	         3.424 ns/op	12848.77 MB/s	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/bytes/long-with-slash-4          	499894323	         2.394 ns/op	18794.15 MB/s	       0 B/op	       0 allocs/op

This is bad, why is appending a slash taking 18ns?

@ReneWerner87
Copy link
Member

Benchmark_AddTrailingSlashBytes
Benchmark_AddTrailingSlashBytes/empty
Benchmark_AddTrailingSlashBytes/empty-12         	1000000000	         0.5934 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/empty-12         	1000000000	         0.6035 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/empty-12         	1000000000	         0.6054 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/empty-12         	1000000000	         0.6011 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/empty-12         	1000000000	         0.5932 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/slash-only
Benchmark_AddTrailingSlashBytes/slash-only-12    	1000000000	         0.8688 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/slash-only-12    	1000000000	         0.8784 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/slash-only-12    	1000000000	         0.8779 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/slash-only-12    	1000000000	         0.8898 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/slash-only-12    	1000000000	         0.8919 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/short-no-slash
Benchmark_AddTrailingSlashBytes/short-no-slash-12         	100000000	        11.22 ns/op	       4 B/op	       1 allocs/op
Benchmark_AddTrailingSlashBytes/short-no-slash-12         	100000000	        11.39 ns/op	       4 B/op	       1 allocs/op
Benchmark_AddTrailingSlashBytes/short-no-slash-12         	100000000	        11.02 ns/op	       4 B/op	       1 allocs/op
Benchmark_AddTrailingSlashBytes/short-no-slash-12         	100000000	        11.32 ns/op	       4 B/op	       1 allocs/op
Benchmark_AddTrailingSlashBytes/short-no-slash-12         	100000000	        11.10 ns/op	       4 B/op	       1 allocs/op
Benchmark_AddTrailingSlashBytes/short-with-slash
Benchmark_AddTrailingSlashBytes/short-with-slash-12       	1000000000	         0.8768 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/short-with-slash-12       	1000000000	         0.8781 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/short-with-slash-12       	1000000000	         0.8670 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/short-with-slash-12       	1000000000	         0.8755 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/short-with-slash-12       	1000000000	         0.8831 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/path-no-slash
Benchmark_AddTrailingSlashBytes/path-no-slash-12          	91264586	        12.74 ns/op	      16 B/op	       1 allocs/op
Benchmark_AddTrailingSlashBytes/path-no-slash-12          	96365540	        12.58 ns/op	      16 B/op	       1 allocs/op
Benchmark_AddTrailingSlashBytes/path-no-slash-12          	95957455	        12.71 ns/op	      16 B/op	       1 allocs/op
Benchmark_AddTrailingSlashBytes/path-no-slash-12          	94733720	        12.87 ns/op	      16 B/op	       1 allocs/op
Benchmark_AddTrailingSlashBytes/path-no-slash-12          	93020551	        12.79 ns/op	      16 B/op	       1 allocs/op
Benchmark_AddTrailingSlashBytes/path-with-slash
Benchmark_AddTrailingSlashBytes/path-with-slash-12        	1000000000	         0.8892 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/path-with-slash-12        	1000000000	         0.8913 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/path-with-slash-12        	1000000000	         0.8770 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/path-with-slash-12        	1000000000	         0.8698 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashBytes/path-with-slash-12        	1000000000	         0.8697 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashString
Benchmark_AddTrailingSlashString/empty
Benchmark_AddTrailingSlashString/empty-12                 	1000000000	         0.2882 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashString/empty-12                 	1000000000	         0.2891 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashString/empty-12                 	1000000000	         0.2883 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashString/empty-12                 	1000000000	         0.2873 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashString/empty-12                 	1000000000	         0.2897 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashString/slash-only
Benchmark_AddTrailingSlashString/slash-only-12            	1000000000	         0.4371 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashString/slash-only-12            	1000000000	         0.4360 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashString/slash-only-12            	1000000000	         0.4387 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashString/slash-only-12            	1000000000	         0.4472 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashString/slash-only-12            	1000000000	         0.4364 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashString/short-no-slash
Benchmark_AddTrailingSlashString/short-no-slash-12        	100000000	        10.75 ns/op	       4 B/op	       1 allocs/op
Benchmark_AddTrailingSlashString/short-no-slash-12        	100000000	        10.66 ns/op	       4 B/op	       1 allocs/op
Benchmark_AddTrailingSlashString/short-no-slash-12        	100000000	        10.73 ns/op	       4 B/op	       1 allocs/op
Benchmark_AddTrailingSlashString/short-no-slash-12        	100000000	        10.67 ns/op	       4 B/op	       1 allocs/op
Benchmark_AddTrailingSlashString/short-no-slash-12        	100000000	        10.89 ns/op	       4 B/op	       1 allocs/op
Benchmark_AddTrailingSlashString/short-with-slash
Benchmark_AddTrailingSlashString/short-with-slash-12      	1000000000	         0.4475 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashString/short-with-slash-12      	1000000000	         0.4394 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashString/short-with-slash-12      	1000000000	         0.4340 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashString/short-with-slash-12      	1000000000	         0.4347 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashString/short-with-slash-12      	1000000000	         0.4342 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashString/path-no-slash
Benchmark_AddTrailingSlashString/path-no-slash-12         	97934535	        12.39 ns/op	      16 B/op	       1 allocs/op
Benchmark_AddTrailingSlashString/path-no-slash-12         	96036816	        12.41 ns/op	      16 B/op	       1 allocs/op
Benchmark_AddTrailingSlashString/path-no-slash-12         	99526560	        12.30 ns/op	      16 B/op	       1 allocs/op
Benchmark_AddTrailingSlashString/path-no-slash-12         	97432910	        12.28 ns/op	      16 B/op	       1 allocs/op
Benchmark_AddTrailingSlashString/path-no-slash-12         	96607342	        12.28 ns/op	      16 B/op	       1 allocs/op
Benchmark_AddTrailingSlashString/path-with-slash
Benchmark_AddTrailingSlashString/path-with-slash-12       	1000000000	         0.4345 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashString/path-with-slash-12       	1000000000	         0.4342 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashString/path-with-slash-12       	1000000000	         0.4334 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashString/path-with-slash-12       	1000000000	         0.4336 ns/op	       0 B/op	       0 allocs/op
Benchmark_AddTrailingSlashString/path-with-slash-12       	1000000000	         0.4328 ns/op	       0 B/op	       0 allocs/op

Copy link
Contributor

@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

🧹 Nitpick comments (4)
strings_test.go (2)

197-222: Parallel subtests capture range variable tc; consider rebinding for pre‑Go 1.22 compatibility.

With t.Parallel() inside the subtests, capturing the range variable directly (for _, tc := range testCases) relies on Go 1.22’s new per‑iteration loop semantics. If this module is ever tested with Go < 1.22 (or with loopvar disabled), all subtests may see the same tc value.

To make the tests robust across Go versions, you can rebind tc inside the loop:

-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			t.Parallel()
-			result := AddTrailingSlashString(tc.in)
-			require.Equal(t, tc.want, result)
-		})
-	}
+	for _, tc := range testCases {
+		tc := tc // avoid capturing the loop variable in parallel subtests on Go < 1.22
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+			result := AddTrailingSlashString(tc.in)
+			require.Equal(t, tc.want, result)
+		})
+	}

If you’ve standardized on Go 1.22+ with the new semantics, this becomes optional but still makes intent explicit.


224-247: Optional: set per‑case byte size in Benchmark_AddTrailingSlashString.

The benchmark looks good and will surface allocs clearly. For consistency with the existing case-conversion benchmarks (which call b.SetBytes), you could also set the processed byte count per iteration:

-	for _, tc := range cases {
-		b.Run(tc.name, func(b *testing.B) {
-			b.ReportAllocs()
+	for _, tc := range cases {
+		b.Run(tc.name, func(b *testing.B) {
+			b.ReportAllocs()
+			b.SetBytes(int64(len(tc.input)))

Not required, but it makes throughput numbers more interpretable in go test -benchmem output.

bytes_test.go (2)

109-133: As with strings, parallel subtests capture range variable tc; consider a local copy.

Test_AddTrailingSlashBytes uses t.Parallel() inside subtests and captures tc from the for _, tc := range testCases loop. This is only automatically safe under Go 1.22+ loop semantics; on earlier Go versions it can lead to all subtests observing the final tc value.

To make the test robust across Go versions, you can rebind tc inside the loop:

-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			t.Parallel()
-			result := AddTrailingSlashBytes(tc.in)
-			require.Equal(t, tc.want, result)
-		})
-	}
+	for _, tc := range testCases {
+		tc := tc // avoid capturing loop variable in parallel subtests on Go < 1.22
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+			result := AddTrailingSlashBytes(tc.in)
+			require.Equal(t, tc.want, result)
+		})
+	}

If the module is guaranteed to be built and tested only with Go 1.22+ (and go 1.22+ in go.mod), this is an optional safety/clarity improvement.


157-180: Optional: call SetBytes in Benchmark_AddTrailingSlashBytes for clearer throughput metrics.

Benchmark correctly reports allocations and exercises multiple input shapes. For consistency with other benchmarks in this package, you could also set the bytes processed per iteration:

-	for _, tc := range cases {
-		b.Run(tc.name, func(b *testing.B) {
-			b.ReportAllocs()
+	for _, tc := range cases {
+		b.Run(tc.name, func(b *testing.B) {
+			b.ReportAllocs()
+			b.SetBytes(int64(len(tc.input)))

This is purely for nicer -benchmem output; behavior-wise the benchmark is already fine.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bb03b21 and 2a657ad.

📒 Files selected for processing (5)
  • Makefile (1 hunks)
  • bytes.go (1 hunks)
  • bytes_test.go (1 hunks)
  • strings.go (1 hunks)
  • strings_test.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
strings_test.go (1)
strings.go (1)
  • AddTrailingSlashString (55-67)
bytes.go (2)
convert.go (2)
  • UnsafeBytes (26-29)
  • UnsafeString (18-23)
strings.go (1)
  • AddTrailingSlashString (55-67)
bytes_test.go (1)
bytes.go (1)
  • AddTrailingSlashBytes (70-72)
🔇 Additional comments (3)
strings.go (1)

52-67: AddTrailingSlashString implementation and allocation behavior look solid.

Logic is straightforward and efficient:

  • """/" (special-cased).
  • If last byte is '/', returns the original string (no alloc).
  • Otherwise allocates n+1 bytes, copies, appends '/', and returns via UnsafeString on a fresh buffer.

This matches the documented behavior and the new tests, while ensuring no mutation of the input and allocations only when a slash is actually appended.

bytes.go (1)

67-72: AddTrailingSlashBytes correctly reuses string helper and keeps input immutable.

AddTrailingSlashBytes cleanly delegates all logic to AddTrailingSlashString via UnsafeString/UnsafeBytes:

  • If a slash is already present, it returns a slice backed by the original array with zero allocations.
  • If a slash must be appended, the only allocation is from AddTrailingSlashString’s internal buffer; the original slice is untouched.

This matches the doc comment and the new tests around non-mutation and “same backing slice when already slashed”.

bytes_test.go (1)

136-155: Non-mutation and same-backing-slice tests clearly encode the intended API contract.

Test_AddTrailingSlashBytes_NoMutation and Test_AddTrailingSlashBytes_ReturnsSame nicely pin down two important behaviors:

  • The original input slice must not be mutated when a slash is appended.
  • When a slash is already present, the returned slice shares the same backing array (require.Same(t, &input[0], &result[0])), which aligns with the current AddTrailingSlashBytes implementation using unsafe conversions.

This is a good level of specification for the helper’s behavior.

Copy link
Member

@gaby gaby left a comment

Choose a reason for hiding this comment

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

LGTM

@ReneWerner87 ReneWerner87 merged commit 99f0c38 into gofiber:master Dec 7, 2025
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants