Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1116,6 +1116,11 @@ private static bool TryGetSpecialHeaderParam(InputParameter inputParameter, [Not

private static void UpdateParameterNameWithBackCompat(InputParameter inputParameter, string proposedName, TypeProvider backCompatProvider, InputServiceMethod? serviceMethod = null)
{
if (inputParameter.IsExactName)
{
return;
}

// Look up the parameter's original (spec) name in the previous contract.
// When a service method is supplied, scope the search to methods whose name matches
// the current service method (allowing for sync/async pairing) so that a common
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,32 @@ public async Task ParameterNamePreservedFromLastContractView()
"When 'oldParam' is preserved, the renamed 'newParam' must not appear.");
}

[Test]
public async Task ExactParameterNameTakesPrecedenceOverLastContractView()
{
var queryParam = InputFactory.QueryParameter(
"oldParam",
InputPrimitiveType.String,
isRequired: true,
isExactName: true);
queryParam.Update(name: "exact_param");

var operation = InputFactory.Operation("GetSomething", parameters: [queryParam]);
var serviceMethod = InputFactory.BasicServiceMethod("GetSomething", operation);
var client = InputFactory.Client("TestClient", methods: [serviceMethod]);

var generator = await MockHelpers.LoadMockGeneratorAsync(
clients: () => [client],
lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(
method: nameof(ParameterNamePreservedFromLastContractView)));

var clientProvider = generator.Object.OutputLibrary.TypeProviders.OfType<ClientProvider>().First();
var protocolParams = RestClientProvider.GetMethodParameters(serviceMethod, ScmMethodKind.Protocol, clientProvider);

Assert.IsNotNull(protocolParams.SingleOrDefault(p => p.Name == "exact_param"));
Assert.IsNull(protocolParams.SingleOrDefault(p => p.Name == "oldParam"));
}

[Test]
public async Task ParameterNamePreservedFromInternalLastContractMethod()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,8 @@ protected internal sealed override IReadOnlyList<MethodProvider> BuildMethodsFor
{
if (currentMethodSignature.Name.Equals(previousMethod.Signature.Name))
{
if (MethodSignatureHelper.HaveSameParametersInSameOrder(currentMethodSignature, previousMethod.Signature))
if (MethodSignatureHelper.HaveSameParametersInSameOrder(currentMethodSignature, previousMethod.Signature) ||
HasMatchingExactParameterNames(currentMethodSignature, previousMethod.Signature))
{
foundCompatibleOverload = true;
break;
Expand Down Expand Up @@ -219,6 +220,25 @@ protected internal sealed override IReadOnlyList<MethodProvider> BuildMethodsFor
return [.. factoryMethods];
}

private static bool HasMatchingExactParameterNames(MethodSignature current, MethodSignature previous)
{
if (!MethodSignature.MethodSignatureComparer.Equals(current, previous))
{
return false;
}

for (int i = 0; i < current.Parameters.Count; i++)
{
if (current.Parameters[i].Name != previous.Parameters[i].Name &&
!current.Parameters[i].IsExactName)
{
return false;
}
}

return true;
}

internal static IReadOnlyList<string> GetUnavailableSignatureTypes(MethodSignature signature)
{
var unavailableTypes = new HashSet<string>(StringComparer.Ordinal);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ public sealed class ParameterProvider : IEquatable<ParameterProvider>
/// </summary>
public InputParameter? InputParameter { get; private set; }

internal bool IsExactName =>
InputParameter?.IsExactName == true ||
Property?.InputProperty?.IsExactName == true;

/// <summary>
/// Creates a <see cref="ParameterProvider"/> from an <see cref="InputParameter"/>.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,11 @@ public static void RestorePreviousParameterNames(
for (int i = 0; i < currentParameters.Count; i++)
{
var parameter = currentParameters[i];
if (parameter.IsExactName)
{
continue;
}

string? preservedName = null;

var inputParameter = parameter.InputParameter;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,26 @@ public async Task BackCompatibility_OnlyParamNameChanged()
Assert.AreEqual("oldModelProp", docParams[1].Parameter.Name);
}

[Test]
public async Task BackCompatibility_ExactPropertyNameTakesPrecedence()
{
var inputModels = GetTestModels(isStringPropExact: true);
_instance = (await MockHelpers.LoadMockGeneratorAsync(
inputNamespaceName: "Sample.Namespace",
inputModelTypes: inputModels,
lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(
method: nameof(BackCompatibility_OnlyParamNameChanged)))).Object;

var modelFactory = _instance!.OutputLibrary.ModelFactory.Value;
modelFactory.ProcessTypeForBackCompatibility();

var parameters = modelFactory.Methods
.Single(m => m.Signature.Name == "PublicModel1")
.Signature.Parameters;
Assert.AreEqual("stringProp", parameters[0].Name);
Assert.AreEqual("oldModelProp", parameters[1].Name);
}

// Validates that when ALL parameters in a factory method are renamed in the previous
// contract, every preserved name is propagated to the current method. This complements
// BackCompatibility_OnlyParamNameChanged which exercises a partial rename.
Expand Down Expand Up @@ -1250,12 +1270,12 @@ public async Task BackCompatibility_BackCompatMethodCanBeMutatedByVisitor()
Assert.IsNotNull(renamed, "The visitor's rename of the back-compat method was not applied.");
}

private static InputModelType[] GetTestModels()
private static InputModelType[] GetTestModels(bool isStringPropExact = false)
{
InputType additionalPropertiesUnknown = InputPrimitiveType.Any;
InputModelProperty[] properties =
[
InputFactory.Property("StringProp", InputPrimitiveType.String),
InputFactory.Property("StringProp", InputPrimitiveType.String, isExactName: isStringPropExact),
InputFactory.Property("ModelProp", InputFactory.Model("Thing")),
InputFactory.Property("ListProp", InputFactory.Array(InputPrimitiveType.String)),
InputFactory.Property("DictProp", InputFactory.Dictionary(InputPrimitiveType.String, InputPrimitiveType.String)),
Expand Down