Skip to content

[http-client-csharp] Preserve error response model names and fix the ErrorResult helper keep-key collision #12048

Description

Summary

The Response → Result model naming change in commit d5ef03c (#11999) exposes a pre-existing type-identity mismatch in the generator's keep-set handling.

An otherwise-unreferenced service model named ErrorResponse is renamed to ErrorResult and then incorrectly matches the keep entry intended for the internal generic helper ErrorResult<T>. Consequently, error models that were previously pruned are emitted, often as new public API.

Observed impact

Azure/azure-sdk-for-net#63407 regenerates SDKs with @azure-typespec/http-client-csharp 1.0.0-alpha.20260925.2, which depends on @typespec/http-client-csharp 1.0.0-alpha.20260924.3.

The PR introduces 22 new non-generic ErrorResult models: 19 public and 3 internal, across libraries including Content Safety, Content Understanding, Communication, Language, and others. Public additions also include model-factory methods and serialization APIs.

For example, Content Safety now contains both:

// Existing generator-owned helper in Generated/Internal/ErrorResult.cs
internal partial class ErrorResult<T> : Response<T>

// Newly retained service model in Generated/Models/ErrorResult.cs
public partial class ErrorResult
{
    public ResponseError Error { get; }
    public string ErrorCode { get; }
}

The non-generic Content Safety model has no client-operation references; its generated references are its serialization implementation, model factory, and model-reader/writer context.

Separately, Anomaly Detector and Defender EASM each rename an already-emitted ErrorResponse model to ErrorResult. Those two renames are distinct from the 22 newly retained models described here.

Root-cause trace

  1. ModelProvider.NormalizeTypeName changes the model suffix from Response to Result, subject to its exact-name/customization/last-contract preservation rules.
  2. ErrorResultDefinition defines the generic helper. Its InternalHelperProvider base constructor calls AddTypeToKeep(this, isRoot: false).
  3. CodeModelGenerator.MaterializeKeepSet stores provider.Type.FullyQualifiedName. That property omits generic arity.
  4. Reference-map provider identities, however, include generic arity. The stored keep key therefore exactly matches the non-generic model, rather than identifying only the intended generic helper.
  5. AddKeptNonRootNames and RemoveKeptNonRootNames preserve the matching model against removal and, where applicable, internalization.

For Content Safety, the identities are:

Intended helper metadata identity: Azure.AI.ContentSafety.ErrorResult`1
Stored helper keep key:            Azure.AI.ContentSafety.ErrorResult
New service model identity:        Azure.AI.ContentSafety.ErrorResult

This repeats independently in each affected library's namespace. The suffix rename exposes the collision; it does not introduce a new service error contract.

Reproduction

Use the Content Safety package and pinned spec from Azure/azure-sdk-for-net#63407:

  1. Compare generation using @azure-typespec/http-client-csharp 1.0.0-alpha.20260924.2 and 1.0.0-alpha.20260925.2.
  2. Inspect sdk/contentsafety/Azure.AI.ContentSafety/src/Generated/Models/ErrorResult.cs and the exported API.
  3. Observe that the newer generation emits the previously-pruned error wrapper and adds ContentSafetyModelFactory.ErrorResult(...), while the unrelated generic helper already existed before the update.

The diagnosis above is based on the regeneration diff and the generator source at the linked commit; a standalone generator regression test has not yet been added.

Expected behavior / suggested fixes

There are two independent requirements to address:

1. Exempt error response models from automatic Response-to-Result renaming

Error response models should retain their names rather than having the automatic suffix normalization rename them from ErrorResponse to ErrorResult. This applies independently of whether a model is pruned or retained, and should not depend on a previous contract or a hand-written customization being present.

The exclusion should apply to models identified as error responses, not just a special case for the literal name ErrorResponse. It should not disable the intended suffix normalization for ordinary non-error response models. Explicit user naming customizations should continue to be honored.

Fixing the keep-key collision alone would stop unintended model emission, but would not address undesired renaming of error response models that are legitimately retained. Naming and reachability need to be handled separately.

2. Fix the generic/non-generic keep-key collision

Use consistent, generic-arity-aware identities when materializing provider keep registrations and matching them against reference-map nodes. Preserving ErrorResult<T> must not preserve ErrorResult merely because their namespace and simple name match.

Exempting error response models from renaming avoids this particular trigger, but is not a substitute for fixing the underlying identity mismatch: a non-generic model already named ErrorResult can still collide with the helper.

Suggested regression coverage

  • An error response model named ErrorResponse retains that name, including when no previous contract or customization exists.
  • A differently named error response model ending in Response also retains its name.
  • A legitimately referenced error response model is retained without automatic suffix renaming; an unreferenced error response model remains eligible for normal pruning.
  • Ordinary non-error response models continue to receive the intended Response-to-Result normalization, subject to existing naming-preservation rules.
  • Explicit naming customizations remain honored.
  • An unreferenced non-generic model already named ErrorResult is still pruned when the internal ErrorResult<T> helper is present.
  • The generic helper remains available where required and remains internal.
  • No public non-generic ErrorResult, model-factory method, or context registration is introduced solely by the helper's keep registration.
  • A genuinely referenced non-generic ErrorResult is retained normally.
  • Generic and non-generic providers sharing a namespace and simple name cannot preserve one another accidentally.

Both changes should be made in the generator rather than adding per-library naming customizations or suppressions in the regenerated SDKs.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

bugSomething isn't workingemitter:client:csharpIssue for the C# client emitter: @typespec/http-client-csharp

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions