-
Notifications
You must be signed in to change notification settings - Fork 344
Description
Bug Report
Summary
When an operation has no explicit @route decorator and no parent route segments, the buildPath() function in @typespec/http defaults the path to "/". This causes downstream consumers (TCGC, language emitters) to generate an unnecessary "/" in the request URL, which can cause mismatches with actual service endpoints.
Related issue: #10010 (reports the impact on Python SDK generation for Azure Storage)
Root Cause
The issue is in packages/http/src/route.ts, lines 59-67:
function buildPath(pathFragments: string[]) {
// Join all fragments with leading and trailing slashes trimmed
const path = pathFragments.length === 0 ? "/" : joinPathSegments(pathFragments);
// The final path must start with a '/', {/ (path expansion), or an allowed segment separator
return AllowedSegmentSeparators.includes(path[0]) || (path[0] === "{" && path[1] === "/")
? path
: /;
}Line 61: When pathFragments is empty (no route segments at all), buildPath unconditionally returns "/".
How It Happens
getUriTemplateAndParameters()(line 197) callsbuildPath([result.uriTemplate])DefaultRouteProducer(lines 212-215) constructs the URI template from parent segments + route path- If there are no segments and no
@routedecorator, the joined path is empty buildPathdefaults empty path to"/"- The
HttpOperation.pathis then"/"and passed through to TCGC and emitters as-is
Impact
- TCGC (
getSdkHttpOperationinhttp.ts) passeshttpOperation.paththrough without any normalization — it trusts the value from@typespec/http - Language emitters (Python, Java, C#, etc.) generate
_url = "/"in the request builder, which can cause URL mismatches with actual service endpoints - Azure Storage and similar services that define operations at the root level (no explicit route) are affected — the generated SDK appends
"/"to the base URL, causing test failures (see [http-client-python] for no specific route http ops can we not put "/" in _url? #10010)
Reproduction
import "@typespec/http";
using Http;
@service(#{ title: "Widget Service" })
namespace DemoService;
// These operations have no @route, so they get path: "/"
/** List widgets */
op list(): Widget[] | Error;
/** Create a widget */
@post op create(@body widget: Widget): Widget | Error;In the generated OpenAPI spec, both list and create get path "/".
Expected Behavior
When no route segments exist, buildPath() should return "" (empty string) instead of "/", allowing downstream consumers to handle the empty path appropriately rather than injecting a spurious "/".
Actual Behavior
buildPath() returns "/" when pathFragments is empty, and this "/" propagates through TCGC to all language emitters.
Existing Tests That Assert Current Behavior
The following tests in packages/http/test/routes.test.ts explicitly assert the current "/" behavior and would need updating:
- "join empty route segments correctly" (line ~275): asserts
@route("") + @route("") → path: "/" - "interface at the document root are included" (line ~39): asserts no-route interface →
"/"
Affected Packages
@typespec/http(root cause)@azure-tools/typespec-client-generator-core(passes through)- All language emitters (Python, Java, C#, JS)