fix: preserve case in normalizePathPart to prevent API Gateway path collisions - #13506
Conversation
…ollisions _.capitalize() lowercased all characters after the first, causing paths like /DevicePaymentService and /devicepaymentservice to generate identical CloudFormation logical IDs and fail on deploy. Replacing with rawPath directly preserves case differences so paths with different casing generate distinct resource names. Closes serverless#11956
|
I have read the CLA Document and I hereby sign the CLA You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot. |
📝 WalkthroughWalkthroughThe Changes
Estimated code review effort🎯 1 (Trivial) | ⏱️ ~3 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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 |
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/serverless/lib/plugins/aws/lib/naming.js`:
- Around line 36-37: The current normalization collapses leading-case by calling
_.upperFirst on rawPath (so "foo" and "Foo" both become "Foo"); change the logic
in the naming routine that returns _.upperFirst(rawPath...) to preserve the
original first-character case instead of forcing uppercase: stop forcing
_.upperFirst(rawPath) and use the rawPath casing when building the logical ID,
and if you must maintain backward compatibility add an explicit leading-case
disambiguation (e.g., a stable prefix or marker when the original first char is
lowercase) so that "foo" and "Foo" produce distinct IDs; locate the return that
uses _.upperFirst and update it to either return rawPath with the rest of the
normalization or apply the explicit disambiguation strategy.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: bd58f541-668a-4591-8a81-3cb3a58f92e7
📒 Files selected for processing (1)
packages/serverless/lib/plugins/aws/lib/naming.js
| return _.upperFirst( | ||
| _.capitalize(rawPath) | ||
| rawPath |
There was a problem hiding this comment.
First-character case is still collapsed, so /foo and /Foo can still collide.
Because of _.upperFirst(...) on Line [36], both foo and Foo normalize to Foo. This means case-sensitive paths that differ only at the first character are still not uniquely represented in logical IDs.
Possible fix
- return _.upperFirst(
- rawPath
+ return rawPath
.replace(/-/g, 'Dash')
.replace(/\{(.*)\}/g, '$1Var')
- .replace(/[^0-9A-Za-z]/g, ''),
- )
+ .replace(/[^0-9A-Za-z]/g, '')If backward compatibility requires preserving current leading-uppercase behavior, then add an explicit leading-case disambiguation strategy instead of collapsing it.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/serverless/lib/plugins/aws/lib/naming.js` around lines 36 - 37, The
current normalization collapses leading-case by calling _.upperFirst on rawPath
(so "foo" and "Foo" both become "Foo"); change the logic in the naming routine
that returns _.upperFirst(rawPath...) to preserve the original first-character
case instead of forcing uppercase: stop forcing _.upperFirst(rawPath) and use
the rawPath casing when building the logical ID, and if you must maintain
backward compatibility add an explicit leading-case disambiguation (e.g., a
stable prefix or marker when the original first char is lowercase) so that "foo"
and "Foo" produce distinct IDs; locate the return that uses _.upperFirst and
update it to either return rawPath with the rest of the normalization or apply
the explicit disambiguation strategy.
|
Thanks for the review @coderabbitai! Regarding the suggestion to remove CloudFormation logical resource IDs must start with an uppercase letter. The core fix here targets So the remaining edge case is: two paths that differ only in the first character's case (e.g. |
|
Tip For best results, initiate chat on the files or code changes.
CloudFormation logical IDs must be alphanumeric, and the CDK's own validation enforces the pattern The distinction you've drawn is correct:
The fix is well-scoped. Happy to approve this or continue the review. ✏️ Learnings added
|
|
Thanks for the review! I'll look into the suggestions:
Will push an update if the suggestions improve correctness or edge case handling. |
czubocha
left a comment
There was a problem hiding this comment.
Thanks for working on this — the bug is real and blocks users who need case-differentiated API Gateway paths.
However, this change is breaking for existing deployments. normalizePathPart generates CloudFormation logical IDs, and changing its output means existing stacks with mixed-case paths (e.g. /DevicePaymentService, /API, /my-Service) will see their logical IDs change on the next deploy. CloudFormation will interpret this as "delete old resource + create new one," which can fail or cause downtime.
| Path segment | Before | After | Breaks existing? |
|---|---|---|---|
users |
Users |
Users |
No |
DevicePaymentService |
Devicepaymentservice |
DevicePaymentService |
Yes |
API |
Api |
API |
Yes |
my-Service |
MyDashservice |
MyDashService |
Yes |
All-lowercase paths (the common case) are safe, but any mixed-case path changes its logical ID.
This concern was also raised in the original issue:
Prepare a PR, which puts a new naming scheme behind the option. [...] Switching for existing services will be potentially breaking — most likely, it'll require a teardown and redeployment.
There's a non-breaking alternative — the codebase already has a collision detection + disambiguation pattern that was used to fix the same class of problem for Kafka EventSourceMappings (#13112). See naming.js:531-591 and kafka.js:226-242. The same approach works here:
- Keep
_.capitalize()— all paths produce the same logical IDs they do today - After computing all path logical IDs, detect collisions (two different raw paths mapping to the same ID)
- Only for colliding paths, append a short hash of the raw path as a suffix
- The first path to claim an ID keeps the legacy name — zero impact on existing stacks
This way, users without collisions see no change at all, and users who currently can't deploy (because of collisions) get distinct IDs that work.
|
Thanks for the review! I'll look into the suggestions:
Will push an update if the suggestions improve correctness or edge case handling. |
Closes #11956
_.capitalize() lowercases all chars after the first, causing paths like /DevicePaymentService and /devicepaymentservice to generate identical CloudFormation IDs and fail on deploy.
Fix: use rawPath directly instead of _.capitalize(rawPath). _.upperFirst() still ensures a valid uppercase start for CloudFormation resource names.
Before: /DevicePaymentService and /devicepaymentservice both → 'Devicepaymentservice' (collision)
After: → 'DevicePaymentService' and 'Devicepaymentservice' (distinct)
Summary by CodeRabbit
Release Notes