Skip to content

Commit 11909fc

Browse files
Adjust serviceversion backcompat enum member building (#8859)
This PR fixes the algorithm for handling back compat service version enum members. Rather than relying on sorting by version string, we know attempt to honor the back-compat order, followed by the order of the versions returned by TCGC. fixes: #8860
1 parent 97ba887 commit 11909fc

File tree

2 files changed

+40
-69
lines changed

2 files changed

+40
-69
lines changed

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ApiVersionEnumProvider.cs

Lines changed: 36 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -106,14 +106,18 @@ private List<EnumTypeMember> BuildApiVersionEnumValuesForBackwardCompatibility(L
106106
return currentApiVersions;
107107
}
108108

109-
var currentVersionNames = new HashSet<string>(currentApiVersions.Select(v => v.Name), StringComparer.OrdinalIgnoreCase);
109+
var currentVersionsLookup = currentApiVersions.ToDictionary(v => v.Name, StringComparer.OrdinalIgnoreCase);
110110
var allMembers = new List<EnumTypeMember>(currentApiVersions.Count + lastContractFields.Count);
111-
allMembers.AddRange(currentApiVersions);
112-
113111
bool addedPreviousApiVersion = false;
112+
113+
// First, add all missing backward compatibility versions in their original order
114114
foreach (var field in lastContractFields)
115115
{
116-
if (!currentVersionNames.Contains(field.Name))
116+
if (currentVersionsLookup.TryGetValue(field.Name, out var existingMember))
117+
{
118+
allMembers.Add(existingMember);
119+
}
120+
else
117121
{
118122
var (versionPrefix, versionSeparator) = ExtractVersionFormatInfo(field.Name, currentApiVersions);
119123
string enumValue = field.Name.ToApiVersionValue(versionPrefix, versionSeparator);
@@ -127,7 +131,15 @@ private List<EnumTypeMember> BuildApiVersionEnumValuesForBackwardCompatibility(L
127131
return currentApiVersions;
128132
}
129133

130-
SortApiVersions(allMembers);
134+
var processedNames = new HashSet<string>(lastContractFields.Select(f => f.Name), StringComparer.OrdinalIgnoreCase);
135+
// Then, add new versions in the wire order
136+
foreach (var currentVersion in currentApiVersions)
137+
{
138+
if (!processedNames.Contains(currentVersion.Name))
139+
{
140+
allMembers.Add(currentVersion);
141+
}
142+
}
131143

132144
for (int i = 0; i < allMembers.Count; i++)
133145
{
@@ -145,59 +157,6 @@ private List<EnumTypeMember> BuildApiVersionEnumValuesForBackwardCompatibility(L
145157
return allMembers;
146158
}
147159

148-
private static void SortApiVersions(List<EnumTypeMember> allMembers)
149-
{
150-
allMembers.Sort((x, y) =>
151-
{
152-
// Extract base names and version types
153-
var (xBase, xType, xPrereleaseNumber) = ParseVersionInfo(x.Name);
154-
var (yBase, yType, yPreReleaseNumber) = ParseVersionInfo(y.Name);
155-
156-
// First compare base names
157-
int baseComparison = string.Compare(xBase, yBase, StringComparison.OrdinalIgnoreCase);
158-
if (baseComparison != 0)
159-
{
160-
return baseComparison;
161-
}
162-
163-
// If base names are equal, and version types are equal, compare prerelease numbers
164-
if (xType == yType)
165-
{
166-
return xPrereleaseNumber.CompareTo(yPreReleaseNumber);
167-
}
168-
169-
return xType.CompareTo(yType);
170-
});
171-
172-
static (string BaseName, VersionType VersionType, int PrereleaseNumber) ParseVersionInfo(string name)
173-
{
174-
// Common patterns for Beta/Preview versions
175-
string[] versionIndicators = ["_Beta", "_Preview"];
176-
177-
foreach (var indicator in versionIndicators)
178-
{
179-
int index = name.IndexOf(indicator, StringComparison.OrdinalIgnoreCase);
180-
if (index >= 0)
181-
{
182-
string baseName = name.Substring(0, index).Trim('_');
183-
return (baseName, Enum.Parse<VersionType>(indicator.TrimStart('_')),
184-
int.TryParse(name.Substring(index + indicator.Length).Trim('_'), out int prereleaseNumber) ? prereleaseNumber : 0);
185-
}
186-
}
187-
188-
// No version indicator found, it's a GA version
189-
return (name, VersionType.GA, 0);
190-
}
191-
}
192-
193-
private enum VersionType
194-
{
195-
Beta,
196-
// Beta and Preview should never occur in the same enum, but handle it gracefully
197-
Preview,
198-
GA
199-
}
200-
201160
private static (string? Prefix, char? Separator) ExtractVersionFormatInfo(string previousVersion, List<EnumTypeMember> currentApiVersions)
202161
{
203162
if (currentApiVersions.Count == 0)
@@ -206,18 +165,19 @@ private static (string? Prefix, char? Separator) ExtractVersionFormatInfo(string
206165
}
207166

208167
bool previousVersionIsDateFormat = IsDateFormat(previousVersion);
168+
string? versionPrefix = null;
169+
char? separator = null;
209170

210171
// validate if any current version is also a date format, if so follow the same format
211172
if (previousVersionIsDateFormat)
212173
{
213174
EnumTypeMember? dateFormatVersion = currentApiVersions.FirstOrDefault(v => v.Value is string apiValue && IsDateFormat(apiValue));
214175
if (dateFormatVersion?.Value is string apiValue)
215176
{
216-
string? versionPrefix = apiValue.StartsWith("v", StringComparison.InvariantCultureIgnoreCase)
177+
versionPrefix = apiValue.StartsWith("v", StringComparison.InvariantCultureIgnoreCase)
217178
? apiValue[0].ToString()
218179
: null;
219-
char? separator = ExtractApiVersionSeparator(apiValue);
220-
return (versionPrefix, separator);
180+
separator = ExtractApiVersionSeparator(apiValue);
221181
}
222182
}
223183
else
@@ -226,15 +186,26 @@ private static (string? Prefix, char? Separator) ExtractVersionFormatInfo(string
226186
EnumTypeMember? nonDateVersion = currentApiVersions.FirstOrDefault(v => v.Value is string apiValue && !IsDateFormat(apiValue));
227187
if (nonDateVersion?.Value is string currentVersionValue)
228188
{
229-
string? versionPrefix = currentVersionValue.StartsWith("v", StringComparison.InvariantCultureIgnoreCase)
189+
versionPrefix = currentVersionValue.StartsWith("v", StringComparison.InvariantCultureIgnoreCase)
230190
? currentVersionValue[0].ToString()
231191
: null;
232-
char? separator = ExtractApiVersionSeparator(currentVersionValue);
233-
return (versionPrefix, separator);
192+
separator = ExtractApiVersionSeparator(currentVersionValue);
234193
}
235194
}
236195

237-
return (null, null);
196+
if (!previousVersionIsDateFormat && versionPrefix == null && IsSingleDigitVersion(previousVersion))
197+
{
198+
versionPrefix = "v";
199+
}
200+
201+
return (versionPrefix, separator);
202+
}
203+
204+
private static bool IsSingleDigitVersion(string version)
205+
{
206+
return version.Length == 2
207+
&& version.StartsWith("v", StringComparison.InvariantCultureIgnoreCase)
208+
&& char.IsDigit(version[1]);
238209
}
239210

240211
private static bool IsDateFormat(string version)

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/ApiVersionEnumProviderTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public async Task BackCompat_PrereleaseApiVersionsAdded()
3636
Assert.IsNotNull(provider);
3737
Assert.That(provider.Name, Is.EqualTo("ServiceVersion"));
3838
Assert.That(provider.EnumValues.Count, Is.EqualTo(3));
39-
Assert.IsTrue(provider.EnumValues.Select(v => v.Name).SequenceEqual(["V1_0_0", "V2023_10_01_Beta", "V2023_11_01_Beta"]));
39+
Assert.IsTrue(provider.EnumValues.Select(v => v.Name).SequenceEqual(["V2023_10_01_Beta", "V2023_11_01_Beta", "V1_0_0"]));
4040
}
4141

4242
[Test]
@@ -60,13 +60,13 @@ public async Task BackCompat_PrereleaseAndGAApiVersionsAdded()
6060
Assert.IsNotNull(provider);
6161
Assert.That(provider.Name, Is.EqualTo("ServiceVersion"));
6262
Assert.That(provider.EnumValues.Count, Is.EqualTo(6));
63-
Assert.IsTrue(provider.EnumValues.Select(v => v.Name).SequenceEqual(["V1_0_0", "V2023_10_01_Beta_1", "V2023_10_01_Beta_2", "V2023_11_01", "V2024_01_01_Beta", "V2024_01_01"]));
63+
Assert.IsTrue(provider.EnumValues.Select(v => v.Name).SequenceEqual(["V2023_10_01_Beta_1", "V2023_10_01_Beta_2", "V2023_11_01", "V2024_01_01_Beta", "V2024_01_01", "V1_0_0"]));
6464
}
6565

6666
[Test]
6767
public async Task BackCompat_PreviewAndGAApiVersionsAdded()
6868
{
69-
string[] apiVersions = ["1.0.0", "V2023_10_01_Preview_2"];
69+
string[] apiVersions = ["1.0.0", "V2025_01_01_Preview"];
7070
var input = InputFactory.Int32Enum(
7171
"mockInputEnum",
7272
apiVersions.Select((a, index) => (a, index)),
@@ -84,7 +84,7 @@ public async Task BackCompat_PreviewAndGAApiVersionsAdded()
8484
Assert.IsNotNull(provider);
8585
Assert.That(provider.Name, Is.EqualTo("ServiceVersion"));
8686
Assert.That(provider.EnumValues.Count, Is.EqualTo(6));
87-
Assert.IsTrue(provider.EnumValues.Select(v => v.Name).SequenceEqual(["V1_0_0", "V2023_10_01_Preview_1", "V2023_10_01_Preview_2", "V2023_11_01", "V2024_01_01_Preview", "V2024_01_01"]));
87+
Assert.IsTrue(provider.EnumValues.Select(v => v.Name).SequenceEqual(["V2023_10_01_Preview_1", "V2023_11_01", "V2024_01_01_Preview", "V2024_01_01", "V1_0_0", "V2025_01_01_Preview"]));
8888
}
8989

9090
[Test]

0 commit comments

Comments
 (0)