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 @@ -69,6 +69,7 @@ protected override void Configure()
AddMetadataReference(MetadataReference.CreateFromFile(typeof(ArmClient).Assembly.Location));
// renaming should come first
AddVisitor(new NameVisitor());
AddVisitor(new CollectionResultNameVisitor());
AddVisitor(new SerializationVisitor());
AddVisitor(new RestClientVisitor());
AddVisitor(new ResourceVisitor());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.TypeSpec.Generator.ClientModel;
using Microsoft.TypeSpec.Generator.Providers;
using System;
using System.Security.Cryptography;
using System.Text;

namespace Azure.Generator.Management.Visitors;

internal class CollectionResultNameVisitor : ScmLibraryVisitor
{
// Keep generated CollectionResults file names below the repo path-length budget
// even under long sdk\<service>\<package> prefixes.
internal const int MaxCollectionResultNameLength = 80;
private const int HashLength = 12;

private static readonly string[] _collectionResultSuffixes =
[
"AsyncCollectionResultOfT",
"CollectionResultOfT",
"AsyncCollectionResult",
"CollectionResult"
];

protected override TypeProvider? VisitType(TypeProvider type)
{
if (type.CustomCodeView is null &&
type.Name.Length > MaxCollectionResultNameLength &&
TryGetCollectionResultSuffix(type.Name, out var suffix))
{
type.Update(name: BuildShortenedName(type.Name, suffix));
}

return type;
}

private static bool TryGetCollectionResultSuffix(string name, out string suffix)
{
foreach (var candidate in _collectionResultSuffixes)
{
if (name.EndsWith(candidate, StringComparison.Ordinal))
{
suffix = candidate;
return true;
}
}

suffix = string.Empty;
return false;
}

private static string BuildShortenedName(string originalName, string suffix)
{
var hash = GetStableHash(originalName);
var maxPrefixLength = MaxCollectionResultNameLength - suffix.Length - hash.Length;
var prefixLength = Math.Min(originalName.Length - suffix.Length, maxPrefixLength);
return $"{originalName[..prefixLength]}{hash}{suffix}";
}

private static string GetStableHash(string value)
{
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(value));
return Convert.ToHexString(hash, 0, HashLength / 2);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Reflection;
using Azure.Generator.Management.Tests.TestHelpers;
using Azure.Generator.Management.Visitors;
using Microsoft.TypeSpec.Generator;
using Microsoft.TypeSpec.Generator.ClientModel;
using Microsoft.TypeSpec.Generator.Providers;
using NUnit.Framework;

namespace Azure.Generator.Mgmt.Tests
{
internal class CollectionResultNameVisitorTests
{
[Test]
public void RenamesLongCollectionResultType()
{
const string name = "MicrosoftCertificateRegistrationAppServiceCertificateOrdersRetrieveCertificateEmailHistoryAsyncCollectionResultOfT";
var type = new TestTypeProvider(name);

VisitType(type);

Assert.That(type.Name, Is.Not.EqualTo(name));
Assert.That(type.Name.Length, Is.LessThanOrEqualTo(CollectionResultNameVisitor.MaxCollectionResultNameLength));
Assert.That(type.Name, Does.EndWith("AsyncCollectionResultOfT"));
}

[Test]
public void KeepsShortCollectionResultType()
{
const string name = "FoosGetAllAsyncCollectionResultOfT";
var type = new TestTypeProvider(name);

VisitType(type);

Assert.That(type.Name, Is.EqualTo(name));
}

[Test]
public void KeepsLongNonCollectionResultType()
{
const string name = "MicrosoftCertificateRegistrationAppServiceCertificateOrdersRetrieveCertificateEmailHistoryModel";
var type = new TestTypeProvider(name);

VisitType(type);

Assert.That(type.Name, Is.EqualTo(name));
}

private static void VisitType(TypeProvider type)
{
ManagementMockHelpers.LoadMockPlugin();

var visitTypeCore = typeof(LibraryVisitor).GetMethod(
"VisitTypeCore",
BindingFlags.NonPublic | BindingFlags.Instance);
Assert.That(visitTypeCore, Is.Not.Null, "Could not find LibraryVisitor.VisitTypeCore method");

visitTypeCore!.Invoke(new CollectionResultNameVisitor(), [type]);
}

private class TestTypeProvider(string name) : TypeProvider
{
protected override string BuildName() => name;

protected override string BuildRelativeFilePath() => $"{Name}.cs";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,15 @@ interface Foos {
ArmResponse<FooDependency[]>
>;

@get
@action("retrieveCertificateEmailHistoryPathLengthRepro")
@clientName("GetPathLengthRepro")
microsoftCertificateRegistrationAppServiceCertificateOrdersRetrieveCertificateEmailHistoryPathLengthRepro is ArmResourceActionSync<
Foo,
void,
ArmResponse<FooDependency[]>
>;

/** Get the provisioning state of a Foo resource. */
@get
@action("getProvisioningState")
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading