Skip to content
Merged
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 @@ -42,6 +42,7 @@ private record ApiVersionFields(FieldProvider Field, PropertyProvider? Correspon
private const string ClientSuffix = "Client";
private readonly FormattableString _publicCtorDescription;
private readonly InputClient _inputClient;
internal InputClient InputClient => _inputClient;
private readonly InputAuth? _inputAuth;
private readonly ParameterProvider _endpointParameter;
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,34 @@ private PropertyProvider FindPropertyInModelHierarchy(TypeProvider model, string
protected override string BuildNamespace() => Client.Type.Namespace;

protected override string BuildName()
=> $"{Client.Type.Name}{Operation.Name.ToIdentifierName()}{(IsAsync ? "Async" : "")}CollectionResult{(ItemModelType == null ? "" : "OfT")}";
{
var operationName = Operation.Name.ToIdentifierName();
// Check if there is another paging operation in the same client whose name would produce a collision.
// If so, use the OriginalName to differentiate.
if (HasPagingOperationNameCollision(operationName))
{
operationName = (Operation.OriginalName ?? Operation.Name).ToIdentifierName();
}
return $"{Client.Type.Name}{operationName}{(IsAsync ? "Async" : "")}CollectionResult{(ItemModelType == null ? "" : "OfT")}";
}

private bool HasPagingOperationNameCollision(string operationName)
{
var pagingMethods = Client.InputClient.Methods.OfType<InputPagingServiceMethod>();
int count = 0;
foreach (var method in pagingMethods)
{
if (method.Operation.Name.ToIdentifierName() == operationName)
{
count++;
if (count > 1)
{
return true;
}
}
}
return false;
}

protected override TypeSignatureModifiers BuildDeclarationModifiers()
=> TypeSignatureModifiers.Internal | TypeSignatureModifiers.Partial | TypeSignatureModifiers.Class;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,81 @@ public void TestEmptyStringHandlingForUriNextLink()
"Generated code should check for null URI");
}

[Test]
public void TestCollectionResultNamesDoNotCollideWhenOperationsAreRenamed()
{
// Two paging operations "list" and "listAll" both get renamed to "GetAll" by CleanOperationNames.
// The CollectionResult names should use OriginalName to avoid collision.
var thingModel = InputFactory.Model("thing", properties:
[
InputFactory.Property("name", InputPrimitiveType.String, isRequired: true),
]);
var thingsProperty = InputFactory.Property("things", InputFactory.Array(thingModel));
var nextProperty = InputFactory.Property("next", InputPrimitiveType.Url);
var pageModel = InputFactory.Model("page", properties: [thingsProperty, nextProperty]);
var response = InputFactory.OperationResponse([200], pageModel);

var pagingMetadata = InputFactory.NextLinkPagingMetadata(["things"], ["next"], InputResponseLocation.Body);

// "list" will be renamed to "GetAll", "listAll" will also be renamed to "GetAll"
var listOperation = InputFactory.Operation("list", responses: [response]);
var listAllOperation = InputFactory.Operation("listAll", responses: [response]);

var listServiceMethod = InputFactory.PagingServiceMethod("list", listOperation, pagingMetadata: pagingMetadata);
var listAllServiceMethod = InputFactory.PagingServiceMethod("listAll", listAllOperation, pagingMetadata: pagingMetadata);

var client = InputFactory.Client("FooClient", methods: [listServiceMethod, listAllServiceMethod]);

MockHelpers.LoadMockGenerator(inputModels: () => [thingModel], clients: () => [client]);

var collectionResults = ScmCodeModelGenerator.Instance.OutputLibrary.TypeProviders
.Where(t => t is CollectionResultDefinition)
.ToList();

// Should have 8 CollectionResult types (2 ops × 2 sync/async × 2 typed/untyped) and they should all have unique names
Assert.AreEqual(8, collectionResults.Count,
$"Expected 8 CollectionResult types but found {collectionResults.Count}");
var collectionResultNames = collectionResults.Select(t => t.Name).ToList();
Assert.AreEqual(collectionResultNames.Distinct().Count(), collectionResultNames.Count,
$"CollectionResult names should be unique but found duplicates: {string.Join(", ", collectionResultNames)}");

// Both should use the original names for disambiguation
Assert.IsTrue(collectionResultNames.Any(n => n == "FooClientListCollectionResult"),
$"Expected 'FooClientListCollectionResult' in [{string.Join(", ", collectionResultNames)}]");
Assert.IsTrue(collectionResultNames.Any(n => n == "FooClientListAllCollectionResult"),
$"Expected 'FooClientListAllCollectionResult' in [{string.Join(", ", collectionResultNames)}]");
}

[Test]
public void TestCollectionResultNameUsesCurrentNameWhenNoCollision()
{
// A single paging operation should use the current (cleaned) name, not the original name.
var thingModel = InputFactory.Model("thing", properties:
[
InputFactory.Property("name", InputPrimitiveType.String, isRequired: true),
]);
var thingsProperty = InputFactory.Property("things", InputFactory.Array(thingModel));
var nextProperty = InputFactory.Property("next", InputPrimitiveType.Url);
var pageModel = InputFactory.Model("page", properties: [thingsProperty, nextProperty]);
var response = InputFactory.OperationResponse([200], pageModel);

var pagingMetadata = InputFactory.NextLinkPagingMetadata(["things"], ["next"], InputResponseLocation.Body);

// "listAll" gets renamed to "GetAll" by CleanOperationNames, no collision
var listAllOperation = InputFactory.Operation("listAll", responses: [response]);
var listAllServiceMethod = InputFactory.PagingServiceMethod("listAll", listAllOperation, pagingMetadata: pagingMetadata);

var client = InputFactory.Client("FooClient", methods: [listAllServiceMethod]);

MockHelpers.LoadMockGenerator(inputModels: () => [thingModel], clients: () => [client]);

// When there's no collision, the cleaned name "GetAll" should be used
var collectionResultDefinition = ScmCodeModelGenerator.Instance.OutputLibrary.TypeProviders.FirstOrDefault(
t => t is CollectionResultDefinition && t.Name == "FooClientGetAllCollectionResult") as CollectionResultDefinition;
Assert.IsNotNull(collectionResultDefinition,
"CollectionResult should use cleaned name 'GetAll' when there's no collision");
}

internal static void CreatePagingOperation(InputResponseLocation responseLocation, bool isNested = false)
{
var inputModel = InputFactory.Model("cat", properties:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ public InputOperation() : this(
{ }

public string Name { get; internal set; }

/// <summary>
/// Gets the original name of the operation as defined in the TypeSpec before any mutations.
/// </summary>
public string? OriginalName { get; internal set; }
public string? ResourceName { get; internal set; }
public string? Summary { get; internal set; }
public string? Doc { get; internal set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ public override void Write(Utf8JsonWriter writer, InputOperation value, JsonSeri
}

operation.Name = name ?? throw new JsonException("InputOperation must have name");
operation.OriginalName = name;
operation.ResourceName = resourceName;
operation.Summary = summary;
operation.Doc = doc;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -639,7 +639,7 @@ public static InputOperation Operation(
bool generateConvenienceMethod = true,
string? ns = null)
{
return new InputOperation(
var operation = new InputOperation(
name,
null,
"",
Expand All @@ -658,6 +658,8 @@ public static InputOperation Operation(
generateConvenienceMethod,
name,
ns);
operation.OriginalName = name;
return operation;
}

public static InputPagingServiceMetadata NextLinkPagingMetadata(
Expand Down
Loading