microsoft / microsoft/typespec

[http] buildPath() should not default empty path to '/' when no route segments exist

Open
#10,037 5 comments 0 reactions 0 assignees View on GitHub
design:needed lib:http triaged:core
Dominant language
Java
Stars
5.9k
Forks
394
Avg merge
1d 23h
Merged PRs (30d)
104

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:

```typescript
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

1. `getUriTemplateAndParameters()` (line 197) calls `buildPath([result.uriTemplate])`
2. `DefaultRouteProducer` (lines 212-215) constructs the URI template from parent segments + route path
3. If there are no segments and no `@route` decorator, the joined path is empty
4. `buildPath` defaults empty path to `"/"`
5. The `HttpOperation.path` is then `"/"` and passed through to TCGC and emitters as-is

### Impact

- **TCGC** (`getSdkHttpOperation` in `http.ts`) passes `httpOperation.path` through 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 #10010)

### Reproduction

[TypeSpec Playground Link](https://typespec.io/playground/?options=%7B%22linterRuleSet%22%3A%7B%22extends%22%3A%5B%22%40typespec%2Fhttp%2Fall%22%5D%7D%7D&c=aW1wb3J0ICJAdHlwZXNwZWMvaHR0cCI7Cgp1c2luZyBIdHRwOwpAc2VydmljZSgjeyB0aXRsZTogIldpZGdldCBTxhoiIH0pCm5hbWVzcGFjZSBEZW1vxxo7Cgptb2RlbCDHMXsKICBAdmlzaWJpbGl0eShMaWZlY3ljbGUuUmVhZCkKICBpZDogc3RyaW5nOwoKICB3ZWlnaHQ6IGludDMyOwogIGNvbG9yOiAicmVkIiB8ICJibHVlIjsKfQoKQGVycm9yx3BFxAzFb2NvZGXLQG1lc3NhZ2XKZcQ6dGFnKOcA1HMiKQppbnRlcmbkAMvHFMVJLyoqIExpc3Qgd8cVKi%2FkAM7kANdsaXN0KCk6xy9bXSB85gCB5QDDxDhDcmVhdGUgYcc8xztwb3N0IGPFGyhAYm9keccfyEnJUtFQVXBk1FBhdGNoIHXFHCjEDmjlAU%2FGRC5pZCwgzmZNZXJnZVDENcZQPMYqPtp4RGVsZdJ4ZMYWxgfUeSk6IHZvaWTKT8ZOQW5hbHl60U9yb3V0ZSgie2lkfS9hxiMiKecBLscQ12XmAdTKZ30K&e=%40typespec%2Fopenapi3&vs=%7B%7D)

```typespec
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)

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.