|
14 | 14 | - [Parameter Naming](#parameter-naming) |
15 | 15 | - [Page Size Parameter Casing Correction](#scenario-page-size-parameter-casing-correction) |
16 | 16 | - [Top Parameter Conversion to MaxCount](#scenario-top-parameter-conversion-to-maxcount) |
| 17 | + - [Content-Type Parameter Ordering](#content-type-parameter-ordering) |
| 18 | + - [Content-Type Before Body Preserved from Last Contract](#scenario-content-type-before-body-preserved-from-last-contract) |
17 | 19 |
|
18 | 20 | ## Overview |
19 | 21 |
|
@@ -110,15 +112,7 @@ public static PublicModel1 PublicModel1( |
110 | 112 |
|
111 | 113 | ### Model Properties |
112 | 114 |
|
113 | | -The generator attempts to maintain backward compatibility for all public model property types by preserving the previous property type when it differs from the type that would be generated from the current spec but is still logically compatible. |
114 | | - |
115 | | -The following scenarios are supported for any public model property: |
116 | | - |
117 | | -- **Collection wrapper change:** A property previously generated as a read-only collection (e.g. `IReadOnlyList<T>` / `IReadOnlyDictionary<TKey, TValue>`) that is now produced as a read-write collection (e.g. `IList<T>` / `IDictionary<TKey, TValue>`), or vice versa. The collection element/key/value type names must still match – the wrapper is preserved but genuine element-type changes are honoured. |
118 | | -- **Nullability change:** A scalar, enum, or model property that was previously generated as nullable (e.g. `int?`, `StatusEnum?`) and is now produced as non-nullable (or vice versa). The last contract's nullability is preserved when the top-level type name (and any generic argument names) still matches. |
119 | | -- **Same-name types from different sources:** Properties whose generated type is logically the same as the last contract's type, but sourced from different assemblies (e.g. a `TypeProvider`-produced type vs. a compiled-assembly type). Equality is evaluated by name rather than identity so these are treated as the same type. |
120 | | - |
121 | | -The override is **not** applied when the top-level property type names differ (for example a property that changed from `string` to `int`) – such changes are passed through so that the new spec is honoured. |
| 115 | +The generator attempts to maintain backward compatibility for model property types by preserving the previous property type whenever it differs from the type produced by the current spec but is still logically compatible. This applies to all public model properties (scalars, enums, models, and collections). |
122 | 116 |
|
123 | 117 | #### Scenario: Collection Property Type Changed |
124 | 118 |
|
@@ -171,8 +165,8 @@ public int? Count { get; set; } |
171 | 165 | **Implementation Details:** |
172 | 166 |
|
173 | 167 | - The generator compares property types against the `LastContractView`. |
174 | | -- For read-write lists and dictionaries, if the previous type was different but the element/key/value type names match, the previous type is retained. |
175 | | -- For all other properties, if the previous type's top-level name (including any generic argument names) matches the new type's top-level name, the previous type is retained – this covers nullability changes and same-name types sourced from different assemblies. |
| 168 | +- If the previous type is logically compatible with the new type (same top-level name, with matching generic argument names), the previous type is retained. This covers read-write collection wrapper changes (e.g. `IList<T>` ↔ `IReadOnlyList<T>`) as well as nullability changes on scalars, enums, and models. |
| 169 | +- If the top-level property type names differ entirely (for example `string` → `int`), the new spec is honoured and no override is applied. |
176 | 170 | - A diagnostic message is logged: `"Changed property {ModelName}.{PropertyName} type to {LastContractType} to match last contract."` |
177 | 171 |
|
178 | 172 | ### AdditionalProperties Type Preservation |
@@ -623,3 +617,47 @@ public virtual AsyncPageable<Item> GetItemsAsync(int? maxCount = null, Cancellat |
623 | 617 | - This conversion is specific to paging operations only |
624 | 618 | - Existing client code with `top` continues to compile without changes |
625 | 619 | - New code benefits from the standardized `maxCount` naming convention |
| 620 | + |
| 621 | +### Content-Type Parameter Ordering |
| 622 | + |
| 623 | +The generator places the `contentType` parameter after the body (`content`) parameter in method signatures. However, backward compatibility is maintained when the last contract had a different ordering. |
| 624 | + |
| 625 | +#### Scenario: Content-Type Before Body Preserved from Last Contract |
| 626 | + |
| 627 | +**Description:** The generator places `contentType` after the `content` (body) parameter. However, if the last contract had `contentType` before `content`, the generator preserves that ordering to avoid breaking existing code. |
| 628 | + |
| 629 | +This commonly occurs when a library was previously generated with contentType before body and has already been released (GA'd). |
| 630 | + |
| 631 | +**Example:** |
| 632 | + |
| 633 | +**contentType before body exists in LastContractView - preserved for backward compatibility** |
| 634 | + |
| 635 | +Previous version had `contentType` before `content`: |
| 636 | + |
| 637 | +```csharp |
| 638 | +public virtual ClientResult UpdateSkillDefaultVersion(string skillId, string contentType, BinaryContent content, RequestOptions options = null) |
| 639 | +{ |
| 640 | + // ... |
| 641 | +} |
| 642 | +``` |
| 643 | + |
| 644 | +Current TypeSpec defines a content type: |
| 645 | + |
| 646 | +```typespec |
| 647 | +op UpdateSkillDefaultVersion( |
| 648 | + @path skill_id: string, |
| 649 | + @header contentType: string, |
| 650 | + @body body: SetDefaultSkillVersionBody, |
| 651 | +): SkillResource; |
| 652 | +``` |
| 653 | + |
| 654 | +**Generated Compatibility Result:** |
| 655 | + |
| 656 | +The generator detects that the previous contract had `contentType` before `content` and preserves that ordering: |
| 657 | + |
| 658 | +```csharp |
| 659 | +public virtual ClientResult UpdateSkillDefaultVersion(string skillId, string contentType, BinaryContent content, RequestOptions options = null) |
| 660 | +{ |
| 661 | + // contentType stays before content for backward compatibility |
| 662 | +} |
| 663 | +``` |
0 commit comments