From fd196b9add478d0eb8acfd04d48485d3843fe06b Mon Sep 17 00:00:00 2001 From: jolov Date: Fri, 24 Apr 2026 11:02:18 -0700 Subject: [PATCH 1/3] fix(http-client-csharp): don't resurrect stale param names from back-compat baseline when no rename occurred The generic back-compat preservation introduced for non-paging parameters unconditionally rewrote any current parameter to the casing/spelling found in the previous published contract. When the spec parameter name had not been renamed by the generator (Name == OriginalName), this caused parameters whose casing had been intentionally changed in a more recent release to be silently rewritten back to a stale baseline's casing, producing a source-breaking rename for callers using named arguments. Restrict the generic preservation to actual generator-introduced renames (Name != OriginalName). The paging-specific renames (top -> maxCount, maxpagesize -> maxPageSize) continue to be preserved via their own explicit calls. Reproduced against Azure.Developer.LoadTesting where `metricname` was being silently rewritten to `metricName` because the API-compat baseline (an older 1.0.2 release) still had the capitalized form. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/Providers/RestClientProvider.cs | 11 ++- .../ListPageableTests.cs | 69 +++++++++++++++++++ .../StaleBaseline.cs | 21 ++++++ 3 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/CollectionResultDefinitions/TestData/ListPageableTests/NonRenamedParameterNotChangedByStaleBackCompatBaseline/StaleBaseline.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs index 11213a4f00f..55c0a471b25 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs @@ -1109,7 +1109,16 @@ internal static List GetMethodParameters( // generalizes back-compat name preservation beyond the paging-specific renames // above so that any rename emitted by the generator falls back to the prior name // when one was already published. - UpdateParameterNameWithBackCompat(inputParam, inputParam.Name, client.BackCompatProvider, serviceMethod); + // + // Only apply this generic preservation when the parameter has actually been renamed + // away from its spec/wire name (Name != OriginalName). When the current name matches + // the original spec name, there is no rename to revert, and applying back-compat + // would incorrectly resurrect a stale name from an older baseline whose casing has + // since been deliberately changed in a published version. + if (!string.Equals(inputParam.Name, inputParam.OriginalName, StringComparison.Ordinal)) + { + UpdateParameterNameWithBackCompat(inputParam, inputParam.Name, client.BackCompatProvider, serviceMethod); + } ParameterProvider? parameter = ScmCodeModelGenerator.Instance.TypeFactory.CreateParameter(inputParam)?.ToPublicInputParameter(); if (parameter is null) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/CollectionResultDefinitions/ListPageableTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/CollectionResultDefinitions/ListPageableTests.cs index 56b0b87fe10..3edf1edc8fb 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/CollectionResultDefinitions/ListPageableTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/CollectionResultDefinitions/ListPageableTests.cs @@ -237,6 +237,75 @@ public async Task TopParameterPreservedViaBackCompatProvider() "Parameter name should be 'top' (from backcompat provider's LastContractView), not 'maxCount'"); } + [Test] + public async Task NonRenamedParameterNotChangedByStaleBackCompatBaseline() + { + // Regression test for https://github.com/microsoft/typespec/pull/10464 follow-up: + // When the spec parameter name (e.g. "metricname") matches the wire/serialized name and + // has NOT been renamed by the generator, an older back-compat baseline that happens to + // have a differently-cased identifier (e.g. "metricName") must NOT silently rewrite the + // current parameter back to that stale casing. Doing so would re-introduce a + // source-breaking rename for callers relying on the most recently published name. + var metricnameParameter = InputFactory.QueryParameter( + "metricname", + InputPrimitiveType.String, + isRequired: true, + serializedName: "metricname"); + + List parameters = [metricnameParameter]; + List methodParameters = + [ + InputFactory.MethodParameter( + "metricname", + InputPrimitiveType.String, + isRequired: true, + location: InputRequestLocation.Query, + serializedName: "metricname"), + ]; + + var responseModel = InputFactory.Model( + "MetricResponse", + properties: [InputFactory.Property("value", InputPrimitiveType.String)]); + var response = InputFactory.OperationResponse([200], responseModel); + var operation = InputFactory.Operation("getMetrics", responses: [response], parameters: parameters); + var inputServiceMethod = InputFactory.BasicServiceMethod( + "getMetrics", + operation, + parameters: methodParameters); + + var client = InputFactory.Client("metricsClient", methods: [inputServiceMethod]); + + var generator = await MockHelpers.LoadMockGeneratorAsync( + clients: () => [client], + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var clientProvider = generator.Object.OutputLibrary.TypeProviders.OfType().FirstOrDefault(); + Assert.IsNotNull(clientProvider); + + // Simulate a stale baseline (e.g., an older published version) whose method has the + // parameter spelled "metricName" with capital N. + var backCompatProvider = new BackCompatTypeProvider("StaleBaseline", "Sample"); + Assert.IsNotNull(backCompatProvider.LastContractView); + + var methods = clientProvider!.GetMethodCollectionByOperation(operation, backCompatProvider); + // protocol + convenience pairs (sync + async). Inspect all parameter names across all + // generated methods — none of them should have been silently renamed to "metricName". + foreach (var method in methods) + { + var renamed = method.Signature.Parameters.FirstOrDefault(p => + string.Equals(p.Name, "metricName", StringComparison.Ordinal)); + Assert.IsNull(renamed, + $"Parameter must not be renamed to 'metricName' on method '{method.Signature.Name}'. " + + "Stale back-compat baselines must not rewrite parameters that were never renamed."); + } + + // And the original spelling must be preserved on at least one method. + var preserved = methods + .SelectMany(m => m.Signature.Parameters) + .Any(p => string.Equals(p.Name, "metricname", StringComparison.Ordinal)); + Assert.IsTrue(preserved, "Original spec parameter name 'metricname' should be preserved."); + } + /// /// A simple TypeProvider used to simulate a backcompat provider (e.g., MockableResourceProvider) /// whose LastContractView contains previously published parameter names. diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/CollectionResultDefinitions/TestData/ListPageableTests/NonRenamedParameterNotChangedByStaleBackCompatBaseline/StaleBaseline.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/CollectionResultDefinitions/TestData/ListPageableTests/NonRenamedParameterNotChangedByStaleBackCompatBaseline/StaleBaseline.cs new file mode 100644 index 00000000000..95fae7a43b2 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/CollectionResultDefinitions/TestData/ListPageableTests/NonRenamedParameterNotChangedByStaleBackCompatBaseline/StaleBaseline.cs @@ -0,0 +1,21 @@ +#nullable disable + +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Threading; +using System.Threading.Tasks; + +namespace Sample +{ + /// + /// Represents a stale previously-published contract whose parameter is spelled + /// "metricName" (capital N). The current spec uses "metricname" (lowercase) and + /// has not renamed the parameter, so this stale baseline must NOT silently + /// rewrite the current parameter back to "metricName". + /// + public partial class StaleBaseline + { + public virtual Task GetMetricsAsync(string metricName, CancellationToken cancellationToken = default) { return null; } + public virtual ClientResult GetMetrics(string metricName, CancellationToken cancellationToken = default) { return null; } + } +} From 295164386e609e89ffec83e5b023cd75299faf76 Mon Sep 17 00:00:00 2001 From: jolov Date: Fri, 24 Apr 2026 11:38:26 -0700 Subject: [PATCH 2/3] Revert "fix(http-client-csharp): don't resurrect stale param names from back-compat baseline when no rename occurred" This reverts commit fd196b9add478d0eb8acfd04d48485d3843fe06b. --- .../src/Providers/RestClientProvider.cs | 11 +-- .../ListPageableTests.cs | 69 ------------------- .../StaleBaseline.cs | 21 ------ 3 files changed, 1 insertion(+), 100 deletions(-) delete mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/CollectionResultDefinitions/TestData/ListPageableTests/NonRenamedParameterNotChangedByStaleBackCompatBaseline/StaleBaseline.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs index 55c0a471b25..11213a4f00f 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs @@ -1109,16 +1109,7 @@ internal static List GetMethodParameters( // generalizes back-compat name preservation beyond the paging-specific renames // above so that any rename emitted by the generator falls back to the prior name // when one was already published. - // - // Only apply this generic preservation when the parameter has actually been renamed - // away from its spec/wire name (Name != OriginalName). When the current name matches - // the original spec name, there is no rename to revert, and applying back-compat - // would incorrectly resurrect a stale name from an older baseline whose casing has - // since been deliberately changed in a published version. - if (!string.Equals(inputParam.Name, inputParam.OriginalName, StringComparison.Ordinal)) - { - UpdateParameterNameWithBackCompat(inputParam, inputParam.Name, client.BackCompatProvider, serviceMethod); - } + UpdateParameterNameWithBackCompat(inputParam, inputParam.Name, client.BackCompatProvider, serviceMethod); ParameterProvider? parameter = ScmCodeModelGenerator.Instance.TypeFactory.CreateParameter(inputParam)?.ToPublicInputParameter(); if (parameter is null) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/CollectionResultDefinitions/ListPageableTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/CollectionResultDefinitions/ListPageableTests.cs index 3edf1edc8fb..56b0b87fe10 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/CollectionResultDefinitions/ListPageableTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/CollectionResultDefinitions/ListPageableTests.cs @@ -237,75 +237,6 @@ public async Task TopParameterPreservedViaBackCompatProvider() "Parameter name should be 'top' (from backcompat provider's LastContractView), not 'maxCount'"); } - [Test] - public async Task NonRenamedParameterNotChangedByStaleBackCompatBaseline() - { - // Regression test for https://github.com/microsoft/typespec/pull/10464 follow-up: - // When the spec parameter name (e.g. "metricname") matches the wire/serialized name and - // has NOT been renamed by the generator, an older back-compat baseline that happens to - // have a differently-cased identifier (e.g. "metricName") must NOT silently rewrite the - // current parameter back to that stale casing. Doing so would re-introduce a - // source-breaking rename for callers relying on the most recently published name. - var metricnameParameter = InputFactory.QueryParameter( - "metricname", - InputPrimitiveType.String, - isRequired: true, - serializedName: "metricname"); - - List parameters = [metricnameParameter]; - List methodParameters = - [ - InputFactory.MethodParameter( - "metricname", - InputPrimitiveType.String, - isRequired: true, - location: InputRequestLocation.Query, - serializedName: "metricname"), - ]; - - var responseModel = InputFactory.Model( - "MetricResponse", - properties: [InputFactory.Property("value", InputPrimitiveType.String)]); - var response = InputFactory.OperationResponse([200], responseModel); - var operation = InputFactory.Operation("getMetrics", responses: [response], parameters: parameters); - var inputServiceMethod = InputFactory.BasicServiceMethod( - "getMetrics", - operation, - parameters: methodParameters); - - var client = InputFactory.Client("metricsClient", methods: [inputServiceMethod]); - - var generator = await MockHelpers.LoadMockGeneratorAsync( - clients: () => [client], - lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); - - var clientProvider = generator.Object.OutputLibrary.TypeProviders.OfType().FirstOrDefault(); - Assert.IsNotNull(clientProvider); - - // Simulate a stale baseline (e.g., an older published version) whose method has the - // parameter spelled "metricName" with capital N. - var backCompatProvider = new BackCompatTypeProvider("StaleBaseline", "Sample"); - Assert.IsNotNull(backCompatProvider.LastContractView); - - var methods = clientProvider!.GetMethodCollectionByOperation(operation, backCompatProvider); - // protocol + convenience pairs (sync + async). Inspect all parameter names across all - // generated methods — none of them should have been silently renamed to "metricName". - foreach (var method in methods) - { - var renamed = method.Signature.Parameters.FirstOrDefault(p => - string.Equals(p.Name, "metricName", StringComparison.Ordinal)); - Assert.IsNull(renamed, - $"Parameter must not be renamed to 'metricName' on method '{method.Signature.Name}'. " + - "Stale back-compat baselines must not rewrite parameters that were never renamed."); - } - - // And the original spelling must be preserved on at least one method. - var preserved = methods - .SelectMany(m => m.Signature.Parameters) - .Any(p => string.Equals(p.Name, "metricname", StringComparison.Ordinal)); - Assert.IsTrue(preserved, "Original spec parameter name 'metricname' should be preserved."); - } - /// /// A simple TypeProvider used to simulate a backcompat provider (e.g., MockableResourceProvider) /// whose LastContractView contains previously published parameter names. diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/CollectionResultDefinitions/TestData/ListPageableTests/NonRenamedParameterNotChangedByStaleBackCompatBaseline/StaleBaseline.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/CollectionResultDefinitions/TestData/ListPageableTests/NonRenamedParameterNotChangedByStaleBackCompatBaseline/StaleBaseline.cs deleted file mode 100644 index 95fae7a43b2..00000000000 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/CollectionResultDefinitions/TestData/ListPageableTests/NonRenamedParameterNotChangedByStaleBackCompatBaseline/StaleBaseline.cs +++ /dev/null @@ -1,21 +0,0 @@ -#nullable disable - -using System.ClientModel; -using System.ClientModel.Primitives; -using System.Threading; -using System.Threading.Tasks; - -namespace Sample -{ - /// - /// Represents a stale previously-published contract whose parameter is spelled - /// "metricName" (capital N). The current spec uses "metricname" (lowercase) and - /// has not renamed the parameter, so this stale baseline must NOT silently - /// rewrite the current parameter back to "metricName". - /// - public partial class StaleBaseline - { - public virtual Task GetMetricsAsync(string metricName, CancellationToken cancellationToken = default) { return null; } - public virtual ClientResult GetMetrics(string metricName, CancellationToken cancellationToken = default) { return null; } - } -} From cfbbb0a9893a6e541d80f6ce4f1fea498e2dd932 Mon Sep 17 00:00:00 2001 From: jolov Date: Fri, 24 Apr 2026 11:57:40 -0700 Subject: [PATCH 3/3] fix(http-client-csharp): avoid swapping model factory parameter names when previous contract had same-typed params in a different order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PreservePreviousParameterNames in ModelFactoryProvider does positional renames based on the matching previous method's parameters. When the previous and current contracts both have two same-typed parameters but in OPPOSITE order, the positional rename swapped the parameter NAMES while the body still referenced the original ParameterProvider object identities. The result was that GetCtorArgs (which matches by name) wired each parameter to the wrong constructor field — a real correctness regression and source-breaking change for users of model factory methods. Reproduced via Azure.AI.VoiceLive.SessionUpdateConversationItemDeleted, which emitted `new SessionUpdateConversationItemDeleted(.., itemId0, .., eventId0)` where the values intended for `itemId` ended up assigned to `eventId` and vice versa. Fix: skip the rename when applying it would cause `previousName` to collide with another current parameter's current name. This preserves the legitimate casing/spec-rename use case (e.g. metricname -> metricName) while preventing the swap pathology. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/Providers/ModelFactoryProvider.cs | 21 ++++++++ .../ModelFactoryProviderTests.cs | 54 +++++++++++++++++++ .../SampleNamespaceModelFactory.cs | 25 +++++++++ 3 files changed, 100 insertions(+) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_SwapTypeParamsDoesNotCorrupt/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelFactoryProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelFactoryProvider.cs index 79b8e58ac42..2b4a0f08a1f 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelFactoryProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelFactoryProvider.cs @@ -247,6 +247,27 @@ private void PreservePreviousParameterNames(List currentFactoryM continue; } + // Skip the rename when applying it would create a name collision with another + // current parameter (e.g. two same-typed parameters in swapped order between + // the previous and current contracts). A positional rename in that case would + // silently swap which parameter feeds which constructor field via name-based + // lookup in GetCtorArgs, producing semantically wrong (and source-breaking) code. + var previousNameToApply = previousName; + bool wouldCollide = false; + for (int j = 0; j < currentParameters.Count; j++) + { + if (j != i && string.Equals(currentParameters[j].Name, previousNameToApply, StringComparison.Ordinal)) + { + wouldCollide = true; + break; + } + } + + if (wouldCollide) + { + continue; + } + CodeModelGenerator.Instance.Emitter.Debug( $"Preserved parameter name '{previousName}' on '{Name}.{matchingCurrent.Signature.Name}' from last contract (instead of '{currentParam.Name}').", BackCompatibilityChangeCategory.ParameterNamePreserved); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/ModelFactoryProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/ModelFactoryProviderTests.cs index ac5085b8f40..b1146937e31 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/ModelFactoryProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/ModelFactoryProviderTests.cs @@ -705,6 +705,60 @@ public async Task BackCompatibility_NewPropertyAddedWithKeywordPropertyName() result); } + // Regression test for a model factory parameter SWAP bug: when the previous contract + // contained the same-typed parameters in a different order from the current contract, + // a naive positional rename would swap which parameter feeds which constructor field + // via name-based lookup in GetCtorArgs, producing semantically wrong (and + // source-breaking) generated code. Verify no rename occurs in this collision case. + [Test] + public async Task BackCompatibility_SwapTypeParamsDoesNotCorrupt() + { + var swapModelList = new[] + { + InputFactory.Model( + "SwapModel", + properties: + [ + InputFactory.Property("EventId", InputPrimitiveType.String), + InputFactory.Property("ItemId", InputPrimitiveType.String), + ]), + }; + + _instance = (await MockHelpers.LoadMockGeneratorAsync( + inputNamespaceName: "Sample.Namespace", + inputModelTypes: swapModelList, + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync())).Object; + + var modelFactory = _instance!.OutputLibrary.ModelFactory.Value; + modelFactory.ProcessTypeForBackCompatibility(); + + var swapMethods = modelFactory.Methods.Where(m => m.Signature.Name == "SwapModel").ToList(); + Assert.AreEqual(1, swapMethods.Count); + + var method = swapMethods[0]; + var parameters = method.Signature.Parameters; + // The current method should keep its parameters in the order/names derived from the + // current spec: eventId, itemId. A positional rename to the previous contract names + // (which were swapped: itemId, eventId) would corrupt the body, so we explicitly + // skip the rename when it would create a name collision with another parameter. + Assert.AreEqual(2, parameters.Count); + Assert.AreEqual("eventId", parameters[0].Name); + Assert.AreEqual("itemId", parameters[1].Name); + + // No EditorBrowsable hidden overload — there's a single visible method. + Assert.AreEqual(0, method.Signature.Attributes.Count); + + // The body must reference the parameters in their original positions so that + // eventId continues to feed the eventId constructor field and itemId continues + // to feed the itemId constructor field. + var body = method.BodyStatements; + Assert.IsNotNull(body); + var result = body!.ToDisplayString(); + Assert.AreEqual( + "return new global::Sample.Models.SwapModel(eventId, itemId, additionalBinaryDataProperties: null);\n", + result); + } + [Test] public void RequiredConstantPropertiesAreNotExposedAsParameters() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_SwapTypeParamsDoesNotCorrupt/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_SwapTypeParamsDoesNotCorrupt/SampleNamespaceModelFactory.cs new file mode 100644 index 00000000000..233a6562268 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_SwapTypeParamsDoesNotCorrupt/SampleNamespaceModelFactory.cs @@ -0,0 +1,25 @@ +using SampleTypeSpec; +using Sample.Models; + +namespace Sample.Namespace +{ + public static partial class SampleNamespaceModelFactory + { + // Previous contract listed the two same-typed parameters in the OPPOSITE order + // (itemId first, then eventId) compared to the current method (eventId first, + // then itemId). A naive positional rename in PreservePreviousParameterNames would + // swap which parameter feeds which constructor field via name-based lookup in + // GetCtorArgs, producing semantically wrong (and source-breaking) code. Verify + // that no rename is performed in this case. + public static SwapModel SwapModel( + string itemId = default, + string eventId = default) + { } + } +} + +namespace Sample.Models +{ + public partial class SwapModel + { } +}