From 2e1fd7e18cb405465fec21b2e63bce6a7583ef51 Mon Sep 17 00:00:00 2001 From: Thiago Teixeira Date: Thu, 11 Jun 2026 11:03:05 -0300 Subject: [PATCH] fix(http-client-js): preserve falsy params --- .../http-request-parameters-expression.tsx | 2 +- .../test/scenarios/encoding/header_bytes.md | 2 +- .../test/scenarios/encoding/header_date.md | 4 +- .../test/scenarios/encoding/query_date.md | 4 +- .../http-operations/basic-request.md | 52 +++++++++++++++++++ .../test/scenarios/http-operations/paging.md | 6 ++- .../http-operations/query-parameter.md | 2 +- .../body_root_anonymous.md | 2 +- .../with_body_property.md | 2 +- .../operation-parameters/with_body_root.md | 2 +- 10 files changed, 68 insertions(+), 10 deletions(-) diff --git a/packages/http-client-js/src/components/http-request-parameters-expression.tsx b/packages/http-client-js/src/components/http-request-parameters-expression.tsx index f38da9ec411..dc248bffb58 100644 --- a/packages/http-client-js/src/components/http-request-parameters-expression.tsx +++ b/packages/http-client-js/src/components/http-request-parameters-expression.tsx @@ -53,7 +53,7 @@ export function HttpRequestParametersExpression(props: HttpRequestParametersExpr if (propertyExpression.isNullish) { return code` - ...(${propertyExpression.fullExpression} && {${()}}) + ...(${propertyExpression.fullExpression} != undefined && {${()}}) `; } else { return ( diff --git a/packages/http-client-js/test/scenarios/encoding/header_bytes.md b/packages/http-client-js/test/scenarios/encoding/header_bytes.md index 3bc1c915bd3..982d6ced1a2 100644 --- a/packages/http-client-js/test/scenarios/encoding/header_bytes.md +++ b/packages/http-client-js/test/scenarios/encoding/header_bytes.md @@ -68,7 +68,7 @@ export async function defaultEncoding( const path = parse("/default").expand({}); const httpRequestOptions = { headers: { - ...(options?.value && { + ...(options?.value != undefined && { value: encodeUint8Array(options.value, "base64")!, }), }, diff --git a/packages/http-client-js/test/scenarios/encoding/header_date.md b/packages/http-client-js/test/scenarios/encoding/header_date.md index f7ace86237f..b6be9b50be3 100644 --- a/packages/http-client-js/test/scenarios/encoding/header_date.md +++ b/packages/http-client-js/test/scenarios/encoding/header_date.md @@ -68,7 +68,9 @@ export async function defaultEncoding( const path = parse("/default").expand({}); const httpRequestOptions = { headers: { - ...(options?.value && { value: dateRfc7231Serializer(options.value) }), + ...(options?.value != undefined && { + value: dateRfc7231Serializer(options.value), + }), }, }; const response = await client.pathUnchecked(path).get(httpRequestOptions); diff --git a/packages/http-client-js/test/scenarios/encoding/query_date.md b/packages/http-client-js/test/scenarios/encoding/query_date.md index bf3087b6951..284476a182f 100644 --- a/packages/http-client-js/test/scenarios/encoding/query_date.md +++ b/packages/http-client-js/test/scenarios/encoding/query_date.md @@ -66,7 +66,9 @@ export async function defaultEncoding( options?: DefaultEncodingOptions, ): Promise { const path = parse("/default{?value}").expand({ - ...(options?.value && { value: dateRfc3339Serializer(options.value) }), + ...(options?.value != undefined && { + value: dateRfc3339Serializer(options.value), + }), }); const httpRequestOptions = { headers: {}, diff --git a/packages/http-client-js/test/scenarios/http-operations/basic-request.md b/packages/http-client-js/test/scenarios/http-operations/basic-request.md index 939accf720b..0aabf26489b 100644 --- a/packages/http-client-js/test/scenarios/http-operations/basic-request.md +++ b/packages/http-client-js/test/scenarios/http-operations/basic-request.md @@ -142,3 +142,55 @@ export async function read( throw createRestError(response); } ``` + +# Should preserve falsy optional HTTP parameters + +This test verifies optional query and header parameters are emitted when their value is falsy but present. + +## TypeSpec + +```tsp +@service(#{ title: "Widget Service" }) +namespace DemoService; + +@route("/widgets") +@tag("Widgets") +interface Widgets { + @test + @get + read( + @query isActive?: boolean, + @query count?: int32, + @query filter?: string, + @header ifMatch?: string, + ): void; +} +``` + +## TypeScript + +### Request + +```ts src/api/widgetsClient/widgetsClientOperations.ts function read +export async function read(client: WidgetsClientContext, options?: ReadOptions): Promise { + const path = parse("/widgets{?isActive,count,filter}").expand({ + ...(options?.isActive != undefined && { isActive: options.isActive }), + ...(options?.count != undefined && { count: options.count }), + ...(options?.filter != undefined && { filter: options.filter }), + }); + const httpRequestOptions = { + headers: { + ...(options?.ifMatch != undefined && { "if-match": options.ifMatch }), + }, + }; + const response = await client.pathUnchecked(path).get(httpRequestOptions); + + if (typeof options?.operationOptions?.onResponse === "function") { + options?.operationOptions?.onResponse(response); + } + if (+response.status === 204 && !response.body) { + return; + } + throw createRestError(response); +} +``` diff --git a/packages/http-client-js/test/scenarios/http-operations/paging.md b/packages/http-client-js/test/scenarios/http-operations/paging.md index 4d763b04685..374b659a0a9 100644 --- a/packages/http-client-js/test/scenarios/http-operations/paging.md +++ b/packages/http-client-js/test/scenarios/http-operations/paging.md @@ -53,7 +53,7 @@ export function link( async function linkSend(client: TestClientContext, filter: string, options?: Record) { const path = parse("/link{?filter,nextToken}").expand({ filter: filter, - ...(options?.nextToken && { nextToken: options.nextToken }), + ...(options?.nextToken != undefined && { nextToken: options.nextToken }), }); const httpRequestOptions = { headers: {}, @@ -174,7 +174,9 @@ export function link( async function linkSend(client: TestClientContext, filter: string, options?: Record) { const path = parse("/link{?filter,maxPageSize}").expand({ filter: filter, - ...(options?.maxPageSize && { maxPageSize: options.maxPageSize }), + ...(options?.maxPageSize != undefined && { + maxPageSize: options.maxPageSize, + }), }); const httpRequestOptions = { headers: {}, diff --git a/packages/http-client-js/test/scenarios/http-operations/query-parameter.md b/packages/http-client-js/test/scenarios/http-operations/query-parameter.md index c29d575ff08..9cd81aadb04 100644 --- a/packages/http-client-js/test/scenarios/http-operations/query-parameter.md +++ b/packages/http-client-js/test/scenarios/http-operations/query-parameter.md @@ -24,7 +24,7 @@ export async function headModel( const path = parse("/").expand({}); const httpRequestOptions = { headers: { - ...(input.foo && { foo: input.foo }), + ...(input.foo != undefined && { foo: input.foo }), }, body: jsonVisibilityModelToTransportTransform(input), }; diff --git a/packages/http-client-js/test/scenarios/operation-parameters/body_root_anonymous.md b/packages/http-client-js/test/scenarios/operation-parameters/body_root_anonymous.md index a33507c5ff6..20a306a324d 100644 --- a/packages/http-client-js/test/scenarios/operation-parameters/body_root_anonymous.md +++ b/packages/http-client-js/test/scenarios/operation-parameters/body_root_anonymous.md @@ -32,7 +32,7 @@ export async function create( const path = parse("/").expand({}); const httpRequestOptions = { headers: { - ...(widget.foo && { foo: widget.foo }), + ...(widget.foo != undefined && { foo: widget.foo }), }, body: { id: widget.id, diff --git a/packages/http-client-js/test/scenarios/operation-parameters/with_body_property.md b/packages/http-client-js/test/scenarios/operation-parameters/with_body_property.md index ff0787efd73..1434a52e982 100644 --- a/packages/http-client-js/test/scenarios/operation-parameters/with_body_property.md +++ b/packages/http-client-js/test/scenarios/operation-parameters/with_body_property.md @@ -26,7 +26,7 @@ export async function create( const path = parse("/").expand({}); const httpRequestOptions = { headers: { - ...(options?.foo && { foo: options.foo }), + ...(options?.foo != undefined && { foo: options.foo }), }, body: jsonWidgetToTransportTransform(widget), }; diff --git a/packages/http-client-js/test/scenarios/operation-parameters/with_body_root.md b/packages/http-client-js/test/scenarios/operation-parameters/with_body_root.md index a9f651a40cf..a2a6604ec89 100644 --- a/packages/http-client-js/test/scenarios/operation-parameters/with_body_root.md +++ b/packages/http-client-js/test/scenarios/operation-parameters/with_body_root.md @@ -27,7 +27,7 @@ export async function create( const path = parse("/").expand({}); const httpRequestOptions = { headers: { - ...(widget.foo && { foo: widget.foo }), + ...(widget.foo != undefined && { foo: widget.foo }), }, body: jsonWidgetToTransportTransform(widget), };