-
Couldn't load subscription status.
- Fork 317
Create documentation for backward compatibility supported scenarios #8809
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
jorgerangel-msft
merged 9 commits into
main
from
copilot/create-back-compat-documentation
Oct 27, 2025
Merged
Changes from 3 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
eb52af3
Initial plan
Copilot 1ac17bd
Add comprehensive backward compatibility documentation
Copilot bd6df7d
Complete backward compatibility documentation
Copilot eb56853
Address PR feedback: simplify documentation and fix inaccuracies
Copilot dfaed6b
Revert removal of Scenario 3 and fix typo
Copilot cc1015c
Run prettier to fix markdown formatting
Copilot 9c2d956
Remove Scenario 3 (Model Factory Method Renamed)
Copilot 0bb1320
Remove scenario numbers from sub-headers
Copilot 01678e3
Add 'Scenario:' prefix back to sub-headers
Copilot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
301 changes: 301 additions & 0 deletions
301
packages/http-client-csharp/generator/docs/backward-compatibility.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,301 @@ | ||
| # Backward Compatibility Support | ||
|
|
||
| ## Table of Contents | ||
|
|
||
| - [Overview](#overview) | ||
| - [How It Works](#how-it-works) | ||
| - [Supported Scenarios](#supported-scenarios) | ||
| - [Model Factory Methods](#model-factory-methods) | ||
| - [Model Properties](#model-properties) | ||
| - [API Version Enum](#api-version-enum) | ||
| - [Technical Implementation](#technical-implementation) | ||
| - [Best Practices](#best-practices) | ||
|
|
||
| ## Overview | ||
|
|
||
| The TypeSpec C# generator supports backward compatibility (back-compat) to ensure that existing client code continues to work when the service API evolves. This is achieved by comparing the current generated code against a previous contract (assembly) and automatically generating compatibility shims where needed. | ||
|
|
||
| The backward compatibility system uses the `LastContractView` mechanism to access information about previously generated types, methods, and properties from the last released version of the library. | ||
|
|
||
| ## How It Works | ||
|
|
||
| When generating code, the generator can optionally receive a compiled assembly from the previous version of the library (the "last contract"). The generator: | ||
|
|
||
| 1. Analyzes the current TypeSpec specification and generates types based on the current API | ||
| 2. Compares the generated types against the types in the last contract | ||
| 3. Automatically generates compatibility methods, properties, or enum members where differences are detected | ||
| 4. Marks compatibility members with `[EditorBrowsable(EditorBrowsableState.Never)]` to hide them from IntelliSense while keeping them available for existing code | ||
jorgerangel-msft marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ## Supported Scenarios | ||
|
|
||
| ### Model Factory Methods | ||
|
|
||
| Model factory methods are public static methods that enable creating model instances for mocking and testing purposes. The generator maintains backward compatibility for these methods across API changes. | ||
jorgerangel-msft marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| #### Scenario 1: New Model Property Added | ||
jorgerangel-msft marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
jorgerangel-msft marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| **Description:** When a new property is added to a model, the generator creates a backward-compatible factory method overload that excludes the new parameter. | ||
|
|
||
| **Example:** | ||
|
|
||
| Previous version had a model with three properties: | ||
| ```csharp | ||
| public static PublicModel1 PublicModel1( | ||
| string stringProp = default, | ||
| Thing modelProp = default, | ||
| IEnumerable<string> listProp = default) | ||
| ``` | ||
|
|
||
| Current version adds a new property (`dictProp`): | ||
| ```csharp | ||
| public static PublicModel1 PublicModel1( | ||
| string stringProp = default, | ||
| Thing modelProp = default, | ||
| IEnumerable<string> listProp = default, | ||
| IDictionary<string, string> dictProp = default) | ||
| ``` | ||
|
|
||
| **Generated Compatibility Method:** | ||
| ```csharp | ||
| [EditorBrowsable(EditorBrowsableState.Never)] | ||
| public static PublicModel1 PublicModel1( | ||
| string stringProp, | ||
| Thing modelProp, | ||
| IEnumerable<string> listProp) | ||
| { | ||
| return PublicModel1(stringProp, modelProp, listProp, dictProp: default); | ||
| } | ||
| ``` | ||
|
|
||
| **Key Points:** | ||
| - The old method signature is preserved | ||
| - It delegates to the new method with default value for the new parameter | ||
| - Parameters in the compatibility method have no default values to avoid ambiguous call sites | ||
| - The method is marked with `[EditorBrowsable(EditorBrowsableState.Never)]` to hide it from IntelliSense | ||
|
|
||
| #### Scenario 2: Parameter Ordering Changed | ||
|
|
||
| **Description:** When only the parameter ordering changes in a factory method (same parameters, different order), the generator replaces the current method with the previous method signature. | ||
|
|
||
| **Example:** | ||
|
|
||
| Previous version: | ||
| ```csharp | ||
| public static PublicModel1 PublicModel1( | ||
| Thing modelProp = default, | ||
| string stringProp = default, | ||
| IEnumerable<string> listProp = default, | ||
| IDictionary<string, string> dictProp = default) | ||
| ``` | ||
|
|
||
| Current version would generate different ordering: | ||
| ```csharp | ||
| public static PublicModel1 PublicModel1( | ||
| string stringProp = default, | ||
| Thing modelProp = default, | ||
| IEnumerable<string> listProp = default, | ||
| IDictionary<string, string> dictProp = default) | ||
| ``` | ||
|
|
||
| **Result:** The generator keeps the previous parameter ordering to maintain compatibility. | ||
|
|
||
| #### Scenario 3: Model Factory Method Renamed | ||
jorgerangel-msft marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
jorgerangel-msft marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| **Description:** When a model or factory method is renamed, the generator creates a compatibility method with the old name that instantiates the new model type. | ||
|
|
||
| **Example:** | ||
|
|
||
| Previous version: | ||
| ```csharp | ||
| public static PublicModel1 PublicModel1OldName(string stringProp = default) | ||
| ``` | ||
|
|
||
| Current version: | ||
| ```csharp | ||
| public static PublicModel1 PublicModel1( | ||
| string stringProp = default, | ||
| Thing modelProp = default, | ||
| IEnumerable<string> listProp = default, | ||
| IDictionary<string, string> dictProp = default) | ||
| ``` | ||
|
|
||
| **Generated Compatibility Method:** | ||
| ```csharp | ||
| [EditorBrowsable(EditorBrowsableState.Never)] | ||
| public static PublicModel1 PublicModel1OldName(string stringProp) | ||
| { | ||
| return new PublicModel1(stringProp, default, default, default); | ||
| } | ||
| ``` | ||
|
|
||
| **Key Points:** | ||
| - The old method name is preserved | ||
| - The method creates an instance of the new model type | ||
| - Missing parameters are filled with default values | ||
|
|
||
| ### Model Properties | ||
|
|
||
| The generator maintains backward compatibility for model property types, particularly for collection types. | ||
|
|
||
| #### Scenario: Collection Property Type Changed | ||
|
|
||
| **Description:** When a property type changes from a read-only collection to a read-write collection (or vice versa), the generator preserves the previous property type to avoid breaking changes. | ||
jorgerangel-msft marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| **Example:** | ||
|
|
||
| Previous version: | ||
| ```csharp | ||
| public IReadOnlyList<string> Items { get; } | ||
| ``` | ||
|
|
||
| Current TypeSpec would generate: | ||
| ```csharp | ||
| public IList<string> Items { get; set; } | ||
| ``` | ||
|
|
||
| **Result:** The generator detects the type mismatch and preserves the previous type: | ||
| ```csharp | ||
| public IReadOnlyList<string> Items { get; } | ||
| ``` | ||
|
|
||
| **Implementation Details:** | ||
| - The generator compares property types against the `LastContractView` | ||
| - For read-write lists and dictionaries, if the previous type was different, the previous type is retained | ||
| - A diagnostic message is logged: `"Changed property {ModelName}.{PropertyName} type to {LastContractType} to match last contract."` | ||
|
|
||
| ### API Version Enum | ||
|
|
||
| Service version enums maintain backward compatibility by preserving version values from previous releases. | ||
|
|
||
| #### Scenario: API Version Removed or Changed | ||
|
|
||
| **Description:** When API versions are removed or changed in the TypeSpec, the generator preserves previous version enum members to prevent breaking existing code that references them. | ||
|
|
||
| **Example:** | ||
|
|
||
| Previous version had enum members: | ||
| ```csharp | ||
| public enum ServiceVersion | ||
| { | ||
| V2021_10_01 = 1, | ||
| V2022_01_01 = 2, | ||
| V2022_06_01 = 3, | ||
| } | ||
| ``` | ||
|
|
||
| Current TypeSpec removes `V2021_10_01` and adds `V2023_01_01`: | ||
| ```csharp | ||
| public enum ServiceVersion | ||
| { | ||
| V2022_01_01 = 1, | ||
| V2022_06_01 = 2, | ||
| V2023_01_01 = 3, | ||
| } | ||
| ``` | ||
|
|
||
| **Generated Result:** | ||
| ```csharp | ||
| public enum ServiceVersion | ||
| { | ||
| V2021_10_01 = 1, // Preserved from previous version | ||
| V2022_01_01 = 2, | ||
| V2022_06_01 = 3, | ||
| V2023_01_01 = 4, | ||
| } | ||
| ``` | ||
|
|
||
| **Key Points:** | ||
| - Previous enum members are preserved even if removed from TypeSpec | ||
| - Enum values are re-indexed to maintain sequential ordering | ||
| - Version format and separator are detected from current versions and applied to previous versions | ||
|
|
||
| ## Technical Implementation | ||
jorgerangel-msft marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ### LastContractView Property | ||
|
|
||
| The `LastContractView` property is available on `TypeProvider` and provides access to the type definition from the previous contract: | ||
|
|
||
| ```csharp | ||
| public TypeProvider? LastContractView { get; } | ||
| ``` | ||
|
|
||
| **Key Members Accessed:** | ||
| - `LastContractView.Methods` - Previous method signatures (used in ModelFactoryProvider) | ||
| - `LastContractView.Properties` - Previous property definitions (used in ModelProvider) | ||
| - `LastContractView.Fields` - Previous field definitions (used in ApiVersionEnumProvider) | ||
|
|
||
| ### Implementation Locations | ||
|
|
||
| 1. **ModelFactoryProvider.cs** | ||
| - Method: `BuildMethodsForBackCompatibility()` | ||
| - Compares current and previous factory method signatures | ||
| - Generates compatibility methods for missing overloads | ||
| - Location: `/src/Providers/ModelFactoryProvider.cs` (lines 112-186) | ||
|
|
||
| 2. **ModelProvider.cs** | ||
| - Method: `BuildProperties()` | ||
| - Compares property types using `LastContractPropertiesMap` | ||
| - Preserves previous collection types when changed | ||
| - Location: `/src/Providers/ModelProvider.cs` (lines 446-456) | ||
|
|
||
| 3. **ApiVersionEnumProvider.cs** | ||
| - Method: `BuildApiVersionEnumValuesForBackwardCompatibility()` | ||
| - Preserves enum members from previous versions | ||
| - Re-indexes values to maintain sequential ordering | ||
| - Location: `/src/Providers/ApiVersionEnumProvider.cs` (lines 98-145) | ||
|
|
||
| ### SourceInputModel | ||
|
|
||
| The `SourceInputModel` class provides the mechanism to query types from the last contract: | ||
|
|
||
| ```csharp | ||
| public TypeProvider? FindForTypeInLastContract( | ||
| string ns, | ||
| string name, | ||
| string? declaringTypeName = null) | ||
| ``` | ||
|
|
||
| This method searches the compiled last contract assembly for type symbols matching the specified namespace and name. | ||
|
|
||
| ## Best Practices | ||
jorgerangel-msft marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ### When Using Backward Compatibility | ||
|
|
||
| 1. **Always provide the last contract assembly** when generating code for a new version of your library to ensure backward compatibility is maintained. | ||
|
|
||
| 2. **Review compatibility methods** generated by the system to ensure they behave as expected. Check the generated code for methods marked with `[EditorBrowsable(EditorBrowsableState.Never)]`. | ||
|
|
||
| 3. **Test with existing code** that uses previous API versions to validate that compatibility is maintained. | ||
|
|
||
| 4. **Monitor diagnostics** - The generator logs informational messages when it makes changes for backward compatibility: | ||
| - `"Changed property {Name}.{PropertyName} type to {Type} to match last contract."` | ||
| - `"Unable to create a backward compatible model factory method for {MethodName}."` | ||
|
|
||
| ### Limitations | ||
|
|
||
| 1. **Structural Changes:** Major structural changes (e.g., changing from class to struct) cannot be automatically handled and may require manual intervention. | ||
|
|
||
| 2. **Breaking Changes:** Some changes are inherently breaking and cannot be automatically mitigated: | ||
| - Changing method return types | ||
| - Removing public types | ||
| - Changing property types to incompatible types (non-collection changes) | ||
|
|
||
| 3. **Internal Types:** Backward compatibility only applies to public types and members. Internal types are excluded from compatibility processing. | ||
|
|
||
| 4. **Custom Code:** If you have customized a type or method, the backward compatibility system may not be able to generate appropriate compatibility shims. Manual adjustments may be needed. | ||
|
|
||
| ### Testing | ||
|
|
||
| The generator includes comprehensive tests for backward compatibility scenarios: | ||
|
|
||
| - **Test Location:** `/test/Providers/ModelFactories/ModelFactoryProviderTests.cs` | ||
| - **Test Methods:** | ||
| - `BackCompatibility_NewModelPropertyAdded` - Tests factory methods when properties are added | ||
| - `BackCompatibility_OnlyParamOrderingChanged` - Tests parameter ordering preservation | ||
| - `BackCompatibility_NoCurrentOverloadFound` - Tests renamed method handling | ||
|
|
||
| Run these tests to validate backward compatibility behavior: | ||
| ```bash | ||
| # From the generator root directory | ||
| dotnet test --filter "FullyQualifiedName~ModelFactoryProviderTests.BackCompatibility" | ||
| ``` | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.