Skip to content

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

@msyyc

Description

@msyyc

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

  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 [http-client-python] for no specific route http ops can we not put "/" in _url? #10010)

Reproduction

TypeSpec Playground Link

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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions