Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: feature
packages:
- "@typespec/http-client-java"
---

Emitter support for constant optional Accept header with `@clientDefaultValue`. When an Accept header is optional with a constant type and has `@Legacy.clientDefaultValue`, the emitter now treats it as a required constant, ensuring the value is always sent and hidden from the public API.
12 changes: 11 additions & 1 deletion packages/http-client-java/emitter/src/code-model-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1522,12 +1522,22 @@ export class CodeModelBuilder {
}

const nullable = param.type.kind === "nullable";

// When Accept header is optional with constant type and has clientDefaultValue,
// treat as required constant — always sent, hidden from public API.
const isAcceptConstantWithDefault =
param.kind === "header" &&
param.serializedName.toLowerCase() === "accept" &&
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this specific to "accept" header only? We should make this generic and clientDefaultValue should work the same way everywhere.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is kind like a "hack" to support Search case before we support @clientDefaultValue in general.
A bit concern from me is that we are expanding our support for @clientDefaultValue a bit too far.
Or do you think we should support @clientDefaultValue in general?

Copy link
Copy Markdown
Contributor

@weidongxu-microsoft weidongxu-microsoft Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a normal e.g. query parameter

@query
@clientDefaultValue("instanceView")
expand?: "instanceView";

I think emitter would generate API like

Paged<> list() {
  Expand expand = Expand.INSTANCE_VIEW;
  RequestOptions requestOptions = new RequestOptions();
  if (expand != null) {
    requestOptions.addQuery("expand", expand);
  }
  return list(requestOptions);
}

Paged<> list(Expand expand) {
  RequestOptions requestOptions = new RequestOptions();
  if (expand != null) {
    requestOptions.addQuery("expand", expand);
  }
  return list(requestOptions);
}

So if user calls list((Expand) null), they can send the request without expand parameter.


Hence in this Accept case, we are doing special process here. Given that TCGC would always give us a generated Accept parameter (if op has response body and TypeSpec does not define the header), it seems OK for us to use the only non-null Accept value.

But this seems not applicable to other header/query param.

param.optional &&
sdkType.kind === "constant" &&
param.clientDefaultValue !== undefined;

const parameter = new Parameter(parameterName, param.doc ?? "", schema, {
summary: param.summary,
implementation: parameterOnClient
? ImplementationLocation.Client
: ImplementationLocation.Method,
required: !param.optional,
required: isAcceptConstantWithDefault || !param.optional,
nullable: nullable,
protocol: {
http: new HttpParameter(param.kind, {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
// Code generated by Microsoft (R) TypeSpec Code Generator.

package tsptest.specialheaders;

import com.azure.core.annotation.Generated;
import com.azure.core.annotation.ReturnType;
import com.azure.core.annotation.ServiceClient;
import com.azure.core.annotation.ServiceMethod;
import com.azure.core.exception.ClientAuthenticationException;
import com.azure.core.exception.HttpResponseException;
import com.azure.core.exception.ResourceModifiedException;
import com.azure.core.exception.ResourceNotFoundException;
import com.azure.core.http.rest.RequestOptions;
import com.azure.core.http.rest.Response;
import com.azure.core.util.FluxUtil;
import reactor.core.publisher.Mono;
import tsptest.specialheaders.implementation.ConstantAcceptHeaderWithDefaultValueAsRequiredsImpl;

/**
* Initializes a new instance of the asynchronous SpecialHeadersClient type.
*/
@ServiceClient(builder = SpecialHeadersClientBuilder.class, isAsync = true)
public final class ConstantAcceptHeaderWithDefaultValueAsRequiredAsyncClient {
@Generated
private final ConstantAcceptHeaderWithDefaultValueAsRequiredsImpl serviceClient;

/**
* Initializes an instance of ConstantAcceptHeaderWithDefaultValueAsRequiredAsyncClient class.
*
* @param serviceClient the service client implementation.
*/
@Generated
ConstantAcceptHeaderWithDefaultValueAsRequiredAsyncClient(
ConstantAcceptHeaderWithDefaultValueAsRequiredsImpl serviceClient) {
this.serviceClient = serviceClient;
}

/**
* Accept header with constant type and default value should be marked as required, before we support
* clientDefaultValue. Issue: https://github.com/microsoft/typespec/issues/10178.
*
* @param requestOptions The options to configure the HTTP request before HTTP client sends it.
* @throws HttpResponseException thrown if the request is rejected by server.
* @throws ClientAuthenticationException thrown if the request is rejected by server on status code 401.
* @throws ResourceNotFoundException thrown if the request is rejected by server on status code 404.
* @throws ResourceModifiedException thrown if the request is rejected by server on status code 409.
* @return the {@link Response} on successful completion of {@link Mono}.
*/
@Generated
@ServiceMethod(returns = ReturnType.SINGLE)
public Mono<Response<Void>> retrieveWithResponse(RequestOptions requestOptions) {
return this.serviceClient.retrieveWithResponseAsync(requestOptions);
}

/**
* Accept header with constant type and default value should be marked as required, before we support
* clientDefaultValue. Issue: https://github.com/microsoft/typespec/issues/10178.
*
* @throws HttpResponseException thrown if the request is rejected by server.
* @throws ClientAuthenticationException thrown if the request is rejected by server on status code 401.
* @throws ResourceNotFoundException thrown if the request is rejected by server on status code 404.
* @throws ResourceModifiedException thrown if the request is rejected by server on status code 409.
* @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent.
* @return A {@link Mono} that completes when a successful response is received.
*/
@Generated
@ServiceMethod(returns = ReturnType.SINGLE)
public Mono<Void> retrieve() {
// Generated convenience method for retrieveWithResponse
RequestOptions requestOptions = new RequestOptions();
return retrieveWithResponse(requestOptions).flatMap(FluxUtil::toMono);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
// Code generated by Microsoft (R) TypeSpec Code Generator.

package tsptest.specialheaders;

import com.azure.core.annotation.Generated;
import com.azure.core.annotation.ReturnType;
import com.azure.core.annotation.ServiceClient;
import com.azure.core.annotation.ServiceMethod;
import com.azure.core.exception.ClientAuthenticationException;
import com.azure.core.exception.HttpResponseException;
import com.azure.core.exception.ResourceModifiedException;
import com.azure.core.exception.ResourceNotFoundException;
import com.azure.core.http.rest.RequestOptions;
import com.azure.core.http.rest.Response;
import tsptest.specialheaders.implementation.ConstantAcceptHeaderWithDefaultValueAsRequiredsImpl;

/**
* Initializes a new instance of the synchronous SpecialHeadersClient type.
*/
@ServiceClient(builder = SpecialHeadersClientBuilder.class)
public final class ConstantAcceptHeaderWithDefaultValueAsRequiredClient {
@Generated
private final ConstantAcceptHeaderWithDefaultValueAsRequiredsImpl serviceClient;

/**
* Initializes an instance of ConstantAcceptHeaderWithDefaultValueAsRequiredClient class.
*
* @param serviceClient the service client implementation.
*/
@Generated
ConstantAcceptHeaderWithDefaultValueAsRequiredClient(
ConstantAcceptHeaderWithDefaultValueAsRequiredsImpl serviceClient) {
this.serviceClient = serviceClient;
}

/**
* Accept header with constant type and default value should be marked as required, before we support
* clientDefaultValue. Issue: https://github.com/microsoft/typespec/issues/10178.
*
* @param requestOptions The options to configure the HTTP request before HTTP client sends it.
* @throws HttpResponseException thrown if the request is rejected by server.
* @throws ClientAuthenticationException thrown if the request is rejected by server on status code 401.
* @throws ResourceNotFoundException thrown if the request is rejected by server on status code 404.
* @throws ResourceModifiedException thrown if the request is rejected by server on status code 409.
* @return the {@link Response}.
*/
@Generated
@ServiceMethod(returns = ReturnType.SINGLE)
public Response<Void> retrieveWithResponse(RequestOptions requestOptions) {
return this.serviceClient.retrieveWithResponse(requestOptions);
}

/**
* Accept header with constant type and default value should be marked as required, before we support
* clientDefaultValue. Issue: https://github.com/microsoft/typespec/issues/10178.
*
* @throws HttpResponseException thrown if the request is rejected by server.
* @throws ClientAuthenticationException thrown if the request is rejected by server on status code 401.
* @throws ResourceNotFoundException thrown if the request is rejected by server on status code 404.
* @throws ResourceModifiedException thrown if the request is rejected by server on status code 409.
* @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent.
*/
@Generated
@ServiceMethod(returns = ReturnType.SINGLE)
public void retrieve() {
// Generated convenience method for retrieveWithResponse
RequestOptions requestOptions = new RequestOptions();
retrieveWithResponse(requestOptions).getValue();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,12 @@
EtagHeadersClient.class,
EtagHeadersOptionalBodyClient.class,
SkipSpecialHeadersClient.class,
ConstantAcceptHeaderWithDefaultValueAsRequiredClient.class,
RepeatabilityHeadersAsyncClient.class,
EtagHeadersAsyncClient.class,
EtagHeadersOptionalBodyAsyncClient.class,
SkipSpecialHeadersAsyncClient.class })
SkipSpecialHeadersAsyncClient.class,
ConstantAcceptHeaderWithDefaultValueAsRequiredAsyncClient.class })
public final class SpecialHeadersClientBuilder implements HttpTrait<SpecialHeadersClientBuilder>,
ConfigurationTrait<SpecialHeadersClientBuilder>, EndpointTrait<SpecialHeadersClientBuilder> {
@Generated
Expand Down Expand Up @@ -332,6 +334,18 @@ public SkipSpecialHeadersAsyncClient buildSkipSpecialHeadersAsyncClient() {
return new SkipSpecialHeadersAsyncClient(buildInnerClient().getSkipSpecialHeaders());
}

/**
* Builds an instance of ConstantAcceptHeaderWithDefaultValueAsRequiredAsyncClient class.
*
* @return an instance of ConstantAcceptHeaderWithDefaultValueAsRequiredAsyncClient.
*/
@Generated
public ConstantAcceptHeaderWithDefaultValueAsRequiredAsyncClient
buildConstantAcceptHeaderWithDefaultValueAsRequiredAsyncClient() {
return new ConstantAcceptHeaderWithDefaultValueAsRequiredAsyncClient(
buildInnerClient().getConstantAcceptHeaderWithDefaultValueAsRequireds());
}

/**
* Builds an instance of RepeatabilityHeadersClient class.
*
Expand Down Expand Up @@ -372,5 +386,17 @@ public SkipSpecialHeadersClient buildSkipSpecialHeadersClient() {
return new SkipSpecialHeadersClient(buildInnerClient().getSkipSpecialHeaders());
}

/**
* Builds an instance of ConstantAcceptHeaderWithDefaultValueAsRequiredClient class.
*
* @return an instance of ConstantAcceptHeaderWithDefaultValueAsRequiredClient.
*/
@Generated
public ConstantAcceptHeaderWithDefaultValueAsRequiredClient
buildConstantAcceptHeaderWithDefaultValueAsRequiredClient() {
return new ConstantAcceptHeaderWithDefaultValueAsRequiredClient(
buildInnerClient().getConstantAcceptHeaderWithDefaultValueAsRequireds());
}

private static final ClientLogger LOGGER = new ClientLogger(SpecialHeadersClientBuilder.class);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
// Code generated by Microsoft (R) TypeSpec Code Generator.

package tsptest.specialheaders.implementation;

import com.azure.core.annotation.ExpectedResponses;
import com.azure.core.annotation.Get;
import com.azure.core.annotation.HeaderParam;
import com.azure.core.annotation.Host;
import com.azure.core.annotation.HostParam;
import com.azure.core.annotation.ReturnType;
import com.azure.core.annotation.ServiceInterface;
import com.azure.core.annotation.ServiceMethod;
import com.azure.core.annotation.UnexpectedResponseExceptionType;
import com.azure.core.exception.ClientAuthenticationException;
import com.azure.core.exception.HttpResponseException;
import com.azure.core.exception.ResourceModifiedException;
import com.azure.core.exception.ResourceNotFoundException;
import com.azure.core.http.rest.RequestOptions;
import com.azure.core.http.rest.Response;
import com.azure.core.http.rest.RestProxy;
import com.azure.core.util.Context;
import com.azure.core.util.FluxUtil;
import reactor.core.publisher.Mono;
import tsptest.specialheaders.SpecialHeadersServiceVersion;

/**
* An instance of this class provides access to all the operations defined in
* ConstantAcceptHeaderWithDefaultValueAsRequireds.
*/
public final class ConstantAcceptHeaderWithDefaultValueAsRequiredsImpl {
/**
* The proxy service used to perform REST calls.
*/
private final ConstantAcceptHeaderWithDefaultValueAsRequiredsService service;

/**
* The service client containing this operation class.
*/
private final SpecialHeadersClientImpl client;

/**
* Initializes an instance of ConstantAcceptHeaderWithDefaultValueAsRequiredsImpl.
*
* @param client the instance of the service client containing this operation class.
*/
ConstantAcceptHeaderWithDefaultValueAsRequiredsImpl(SpecialHeadersClientImpl client) {
this.service = RestProxy.create(ConstantAcceptHeaderWithDefaultValueAsRequiredsService.class,
client.getHttpPipeline(), client.getSerializerAdapter());
this.client = client;
}

/**
* Gets Service version.
*
* @return the serviceVersion value.
*/
public SpecialHeadersServiceVersion getServiceVersion() {
return client.getServiceVersion();
}

/**
* The interface defining all the services for SpecialHeadersClientConstantAcceptHeaderWithDefaultValueAsRequireds
* to be used by the proxy service to perform REST calls.
*/
@Host("{endpoint}")
@ServiceInterface(name = "SpecialHeadersClientConstantAcceptHeaderWithDefaultValueAsRequireds")
public interface ConstantAcceptHeaderWithDefaultValueAsRequiredsService {
@Get("/constant-optional-accept-header-with-default-value")
@ExpectedResponses({ 200 })
@UnexpectedResponseExceptionType(value = ClientAuthenticationException.class, code = { 401 })
@UnexpectedResponseExceptionType(value = ResourceNotFoundException.class, code = { 404 })
@UnexpectedResponseExceptionType(value = ResourceModifiedException.class, code = { 409 })
@UnexpectedResponseExceptionType(HttpResponseException.class)
Mono<Response<Void>> retrieve(@HostParam("endpoint") String endpoint, @HeaderParam("accept") String accept,
RequestOptions requestOptions, Context context);

@Get("/constant-optional-accept-header-with-default-value")
@ExpectedResponses({ 200 })
@UnexpectedResponseExceptionType(value = ClientAuthenticationException.class, code = { 401 })
@UnexpectedResponseExceptionType(value = ResourceNotFoundException.class, code = { 404 })
@UnexpectedResponseExceptionType(value = ResourceModifiedException.class, code = { 409 })
@UnexpectedResponseExceptionType(HttpResponseException.class)
Response<Void> retrieveSync(@HostParam("endpoint") String endpoint, @HeaderParam("accept") String accept,
RequestOptions requestOptions, Context context);
}

/**
* Accept header with constant type and default value should be marked as required, before we support
* clientDefaultValue. Issue: https://github.com/microsoft/typespec/issues/10178.
*
* @param requestOptions The options to configure the HTTP request before HTTP client sends it.
* @throws HttpResponseException thrown if the request is rejected by server.
* @throws ClientAuthenticationException thrown if the request is rejected by server on status code 401.
* @throws ResourceNotFoundException thrown if the request is rejected by server on status code 404.
* @throws ResourceModifiedException thrown if the request is rejected by server on status code 409.
* @return the {@link Response} on successful completion of {@link Mono}.
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public Mono<Response<Void>> retrieveWithResponseAsync(RequestOptions requestOptions) {
final String accept = "application/json;odata.metadata=minimal";
return FluxUtil
.withContext(context -> service.retrieve(this.client.getEndpoint(), accept, requestOptions, context));
}

/**
* Accept header with constant type and default value should be marked as required, before we support
* clientDefaultValue. Issue: https://github.com/microsoft/typespec/issues/10178.
*
* @param requestOptions The options to configure the HTTP request before HTTP client sends it.
* @throws HttpResponseException thrown if the request is rejected by server.
* @throws ClientAuthenticationException thrown if the request is rejected by server on status code 401.
* @throws ResourceNotFoundException thrown if the request is rejected by server on status code 404.
* @throws ResourceModifiedException thrown if the request is rejected by server on status code 409.
* @return the {@link Response}.
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public Response<Void> retrieveWithResponse(RequestOptions requestOptions) {
final String accept = "application/json;odata.metadata=minimal";
return service.retrieveSync(this.client.getEndpoint(), accept, requestOptions, Context.NONE);
}
}
Loading
Loading