diff --git a/.github/workflows/surface-area-tests.yml b/.github/workflows/surface-area-tests.yml
index e2185e3250..86a836464e 100644
--- a/.github/workflows/surface-area-tests.yml
+++ b/.github/workflows/surface-area-tests.yml
@@ -68,14 +68,18 @@ jobs:
shell: pwsh
timeout-minutes: 30
run: |
- $patchPath = Join-Path -Path (Get-Location) -ChildPath "${{ matrix.language }}-dom-export.patch"
+ $patchFileName = "${{ matrix.language }}-dom-export.patch"
+ $patchPath = Join-Path -Path (Get-Location) -ChildPath $patchFileName
./it/compare-dom-export.ps1 `
-descriptionUrl "${{ matrix.description }}" `
-language "${{ matrix.language }}" `
-patchPath $patchPath `
-baselineVersion "${{ github.event.inputs.baselineVersion }}"
$hasPatch = (Test-Path $patchPath) -and ((Get-Item $patchPath).Length -gt 0)
- Write-Output "patchFilePath=$patchPath" >> $Env:GITHUB_OUTPUT
+ # The diff tool runs as a Docker container action with the repository mounted at
+ # /github/workspace, so it must receive a workspace-relative path. Passing the
+ # absolute host path fails inside the container with a DirectoryNotFoundException.
+ Write-Output "patchFilePath=$patchFileName" >> $Env:GITHUB_OUTPUT
Write-Output "hasPatch=$($hasPatch.ToString().ToLowerInvariant())" >> $Env:GITHUB_OUTPUT
- name: Analyze DOM export diff for breaking changes
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fa0370342d..128bb9ecc3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
+- Reduced the aggressiveness of namespace segment and type name shortening by raising the length threshold to 200 characters, so only names longer than 200 characters are truncated (to a 191-character prefix plus an underscore and 8-character hash). The threshold stays well below the 255 file-system per-component limit to leave room for extensions and language-specific suffixes appended by path segmenters and compilers (e.g. Java nested-class `.class` files and the extra underscores Python adds when converting names to snake_case).
+
## [1.34.0] - 2026-07-08
### Added
diff --git a/src/Kiota.Builder/Extensions/StringExtensions.cs b/src/Kiota.Builder/Extensions/StringExtensions.cs
index bec39ad53e..3f6fc87d05 100644
--- a/src/Kiota.Builder/Extensions/StringExtensions.cs
+++ b/src/Kiota.Builder/Extensions/StringExtensions.cs
@@ -75,14 +75,18 @@ public static string ShortenFileName(this string name, int length = 251) =>
(!string.IsNullOrEmpty(name) && name.Length > length) ? HashString(name).ToLowerInvariant() : name;
#pragma warning restore CA1308
- private const int DefaultMaxNameSegmentLength = 64;
+ // 200 keeps the shortened name well below the 255 file-system per-component limit (NAME_MAX),
+ // leaving room for extensions and language-specific suffixes that path segmenters and compilers
+ // append (e.g. ".java" plus nested-class suffixes like "$GetQueryParameters.class", or the extra
+ // underscores Python introduces when converting names to snake_case file names).
+ private const int DefaultMaxNameSegmentLength = 200;
private const int HashSuffixLength = 8;
///
/// Shortens a name segment to the specified maximum length by truncating and appending a short hash suffix.
/// Preserves human readability by keeping the first part of the original name.
///
/// The name to potentially shorten
- /// The maximum allowed length. Default is 64.
+ /// The maximum allowed length. Default is 200.
/// The original name if within limits, or a truncated name with hash suffix for uniqueness
public static string ShortenNameSegment(this string name, int maxLength = DefaultMaxNameSegmentLength)
{
diff --git a/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs b/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs
index f8e8015320..6d5e270c4d 100644
--- a/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs
+++ b/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs
@@ -1635,7 +1635,7 @@ protected static void DeduplicateErrorMappings(CodeElement codeElement)
/// that map only namespaces to directories and group types into barrel files, where renaming types would
/// break downstream name-based generation without any file-system benefit.
///
- protected static void ShortenOversizedNamespaceSegments(CodeElement currentElement, int maxSegmentLength = 64, bool shortenTypeNames = true)
+ protected static void ShortenOversizedNamespaceSegments(CodeElement currentElement, int maxSegmentLength = 200, bool shortenTypeNames = true)
{
if (currentElement is CodeNamespace codeNamespace && !string.IsNullOrEmpty(codeNamespace.Name))
{
diff --git a/tests/Kiota.Builder.Tests/Extensions/StringExtensionsTests.cs b/tests/Kiota.Builder.Tests/Extensions/StringExtensionsTests.cs
index 7c3dab379e..62921af62e 100644
--- a/tests/Kiota.Builder.Tests/Extensions/StringExtensionsTests.cs
+++ b/tests/Kiota.Builder.Tests/Extensions/StringExtensionsTests.cs
@@ -1,4 +1,6 @@
-using Kiota.Builder.Extensions;
+using System.Linq;
+
+using Kiota.Builder.Extensions;
using Xunit;
@@ -140,23 +142,23 @@ public void ShortenNameSegmentReturnsOriginalWhenUnderLimit()
[Fact]
public void ShortenNameSegmentReturnsOriginalAtExactLimit()
{
- var name = new string('a', 64);
+ var name = new string('a', 200);
Assert.Equal(name, name.ShortenNameSegment());
}
[Fact]
public void ShortenNameSegmentTruncatesAndAppendsHashWhenOverLimit()
{
- var longName = "MicrosoftGraphNetworkaccessDeviceReportWithStartDateTimeWithEndDateTimeWithDiscoveredApplicationSegmentId";
+ var longName = string.Concat(Enumerable.Repeat("MicrosoftGraphNetworkaccessDeviceReportSegment", 8)); // 368 chars > 200
var result = longName.ShortenNameSegment();
- Assert.Equal(64, result.Length);
- // First 55 chars are preserved (64 - 8 hash - 1 underscore)
- Assert.StartsWith("MicrosoftGraphNetworkaccessDeviceReportWithStartDateTim", result);
- Assert.Contains("_", result);
+ Assert.Equal(200, result.Length);
+ // First 191 chars are preserved (200 - 8 hash - 1 underscore)
+ Assert.StartsWith(longName[..191], result);
+ Assert.Equal('_', result[191]);
}
[Fact]
public void ShortenNameSegmentIsDeterministic()
{
- var longName = "MicrosoftGraphNetworkaccessDeviceReportWithStartDateTimeWithEndDateTimeWithDiscoveredApplicationSegmentId";
+ var longName = string.Concat(Enumerable.Repeat("MicrosoftGraphNetworkaccessDeviceReportSegment", 8)); // 368 chars > 255
var result1 = longName.ShortenNameSegment();
var result2 = longName.ShortenNameSegment();
Assert.Equal(result1, result2);
@@ -164,8 +166,8 @@ public void ShortenNameSegmentIsDeterministic()
[Fact]
public void ShortenNameSegmentProducesDifferentResultsForDifferentInputs()
{
- var name1 = "MicrosoftGraphNetworkaccessDeviceReportWithStartDateTimeWithEndDateTimeWithDiscoveredApplicationSegmentId";
- var name2 = "MicrosoftGraphNetworkaccessDeviceReportWithStartDateTimeWithEndDateTimeWithApplicationId";
+ var name1 = string.Concat(Enumerable.Repeat("MicrosoftGraphNetworkaccessDeviceReportSegment", 8)) + "One"; // > 255
+ var name2 = string.Concat(Enumerable.Repeat("MicrosoftGraphNetworkaccessDeviceReportSegment", 8)) + "Two"; // > 255
var result1 = name1.ShortenNameSegment();
var result2 = name2.ShortenNameSegment();
Assert.NotEqual(result1, result2);
diff --git a/tests/Kiota.Builder.Tests/Refiners/JavaLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/JavaLanguageRefinerTests.cs
index 4edf0c13c2..c433c1ba83 100644
--- a/tests/Kiota.Builder.Tests/Refiners/JavaLanguageRefinerTests.cs
+++ b/tests/Kiota.Builder.Tests/Refiners/JavaLanguageRefinerTests.cs
@@ -774,7 +774,7 @@ public async Task AddsUsingForUntypedNodeInMethodParameterAsync()
public async Task ShortensOversizedNamespaceSegmentsAsync()
{
// Simulate the problematic long namespace segment from OData functions with many parameters
- var longSegment = "microsoftgraphnetworkaccessdevicereportwithstartdatetimewithenddatetimediscoveredapplicationsegmentiddiscoveredapplicationsegmentidapplicationidapplicationidaiagentidaiagentidaiagentnameaiagentnamecloudapplicationnamecloudapplicationname";
+ var longSegment = string.Concat(Enumerable.Repeat("microsoftgraphnetworkaccessdevicereportsegment", 6)); // 276 chars > 255
var childNs = root.AddNamespace($"com.microsoft.graph.beta.networkaccess.reports.{longSegment}");
var requestBuilderClass = childNs.AddClass(new CodeClass
{
@@ -791,11 +791,11 @@ public async Task ShortensOversizedNamespaceSegmentsAsync()
var nameSegments = childNs.Name.Split('.');
foreach (var segment in nameSegments)
{
- Assert.True(segment.Length <= 64, $"Segment '{segment}' exceeds 64 chars (length: {segment.Length})");
+ Assert.True(segment.Length <= 200, $"Segment '{segment}' exceeds 200 chars (length: {segment.Length})");
}
// Class name should be shortened
- Assert.True(requestBuilderClass.Name.Length <= 64, $"Class name '{requestBuilderClass.Name}' exceeds 64 chars (length: {requestBuilderClass.Name.Length})");
+ Assert.True(requestBuilderClass.Name.Length <= 200, $"Class name '{requestBuilderClass.Name}' exceeds 200 chars (length: {requestBuilderClass.Name.Length})");
// Doc comment should contain original name for disambiguation
Assert.Contains("Original name:", requestBuilderClass.Documentation.DescriptionTemplate);
@@ -816,7 +816,7 @@ public async Task DoesNotShortenNormalLengthNamespacesAsync()
[Fact]
public async Task ShorteningIsDeterministicAsync()
{
- var longSegment = "microsoftgraphnetworkaccessdevicereportwithstartdatetimewithenddatetimediscoveredapplicationsegmentiddiscoveredapplicationsegmentid";
+ var longSegment = string.Concat(Enumerable.Repeat("microsoftgraphnetworkaccessdevicereportsegment", 6)); // 276 chars > 255
var root2 = CodeNamespace.InitRootNamespace();
var childNs1 = root.AddNamespace($"com.microsoft.graph.beta.{longSegment}");
var childNs2 = root2.AddNamespace($"com.microsoft.graph.beta.{longSegment}");
@@ -830,7 +830,7 @@ public async Task ShorteningIsDeterministicAsync()
public async Task ShortenedClassNameIsReflectedInMethodReturnTypeAsync()
{
// Simulate: parent request builder has a method returning a child request builder with a long name
- var longClassName = "MicrosoftGraphNetworkaccessDeviceReportWithStartDateTimeWithEndDateTimeRequestBuilder";
+ var longClassName = string.Concat(Enumerable.Repeat("MicrosoftGraphNetworkAccessDeviceReportSegment", 6)) + "RequestBuilder"; // > 255
var parentNs = root.AddNamespace("com.microsoft.graph.beta.networkaccess.reports");
var longSegment = "microsoftgraphnetworkaccessdevicereportwithstartdatetimewithenddatetime";
var childNs = root.AddNamespace($"com.microsoft.graph.beta.networkaccess.reports.{longSegment}");
@@ -861,16 +861,16 @@ public async Task ShortenedClassNameIsReflectedInMethodReturnTypeAsync()
await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.Java }, root, cancellationToken: TestContext.Current.CancellationToken);
// The class should be shortened
- Assert.True(targetClass.Name.Length <= 64, $"Class name '{targetClass.Name}' exceeds 64 chars");
+ Assert.True(targetClass.Name.Length <= 200, $"Class name '{targetClass.Name}' exceeds 200 chars");
// The method's return type should automatically reflect the shortened name via TypeDefinition
Assert.Equal(targetClass.Name, returnType.Name);
- Assert.True(returnType.Name.Length <= 64, $"Return type name '{returnType.Name}' exceeds 64 chars");
+ Assert.True(returnType.Name.Length <= 200, $"Return type name '{returnType.Name}' exceeds 200 chars");
}
[Fact]
public async Task ShorteningWorksWhenDocumentationIsNullAsync()
{
- var longSegment = "microsoftgraphnetworkaccessdevicereportwithstartdatetimewithenddatetimediscoveredapplicationsegmentiddiscoveredapplicationsegmentid";
+ var longSegment = string.Concat(Enumerable.Repeat("microsoftgraphnetworkaccessdevicereportsegment", 6)); // 276 chars > 255
var childNs = root.AddNamespace($"com.microsoft.graph.beta.{longSegment}");
var requestBuilderClass = childNs.AddClass(new CodeClass
{
@@ -883,7 +883,7 @@ public async Task ShorteningWorksWhenDocumentationIsNullAsync()
await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.Java }, root, cancellationToken: TestContext.Current.CancellationToken);
// Should shorten without throwing
- Assert.True(requestBuilderClass.Name.Length <= 64);
+ Assert.True(requestBuilderClass.Name.Length <= 200);
}
#endregion
}
diff --git a/tests/Kiota.Builder.Tests/Refiners/PhpLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/PhpLanguageRefinerTests.cs
index dcf5d44160..66067b1f7a 100644
--- a/tests/Kiota.Builder.Tests/Refiners/PhpLanguageRefinerTests.cs
+++ b/tests/Kiota.Builder.Tests/Refiners/PhpLanguageRefinerTests.cs
@@ -303,7 +303,7 @@ public async Task DoesNotCreateDuplicateComposedTypeWrapperIfOneAlreadyExistsAsy
[Fact]
public async Task ShortensOversizedNamespaceSegmentsAsync()
{
- var longSegment = "MicrosoftGraphNetworkaccessDeviceReportWithStartDateTimeWithEndDateTimeDiscoveredApplicationSegmentIdDiscoveredApplicationSegmentIdApplicationIdApplicationId";
+ var longSegment = string.Concat(Enumerable.Repeat("MicrosoftGraphNetworkAccessDeviceReportSegment", 6)); // 276 chars > 255
var childNs = root.AddNamespace($"ApiSdk.NetworkAccess.Reports.{longSegment}");
var requestBuilderClass = childNs.AddClass(new CodeClass
{
@@ -319,18 +319,18 @@ public async Task ShortensOversizedNamespaceSegmentsAsync()
// Namespace segment should be shortened
foreach (var segment in childNs.Name.Split('.'))
{
- Assert.True(segment.Length <= 64, $"Segment '{segment}' exceeds 64 chars (length: {segment.Length})");
+ Assert.True(segment.Length <= 200, $"Segment '{segment}' exceeds 200 chars (length: {segment.Length})");
}
// Class name should be shortened
- Assert.True(requestBuilderClass.Name.Length <= 64, $"Class name '{requestBuilderClass.Name}' exceeds 64 chars");
+ Assert.True(requestBuilderClass.Name.Length <= 200, $"Class name '{requestBuilderClass.Name}' exceeds 200 chars");
}
[Fact]
public async Task ShortenedClassImportsAreConsistentAsync()
{
// Parent request builder references a child with a long name via method return type
var parentNs = root.AddNamespace("ApiSdk.NetworkAccess.Reports");
- var longSegment = "MicrosoftGraphNetworkaccessDeviceReportWithStartDateTimeWithEndDateTimeDiscoveredApplicationSegmentId";
+ var longSegment = string.Concat(Enumerable.Repeat("MicrosoftGraphNetworkAccessDeviceReportSegment", 6)); // 276 chars > 255
var childNs = root.AddNamespace($"ApiSdk.NetworkAccess.Reports.{longSegment}");
var targetClass = childNs.AddClass(new CodeClass
@@ -363,7 +363,7 @@ public async Task ShortenedClassImportsAreConsistentAsync()
var nsSegments = u.Name.Split('.');
foreach (var seg in nsSegments)
{
- Assert.True(seg.Length <= 64, $"Using namespace segment '{seg}' exceeds 64 chars — stale pre-shortened name");
+ Assert.True(seg.Length <= 200, $"Using namespace segment '{seg}' exceeds 200 chars — stale pre-shortened name");
}
}
}
diff --git a/tests/Kiota.Builder.Tests/Refiners/PythonLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/PythonLanguageRefinerTests.cs
index 8a24fc95ab..4f4e158147 100644
--- a/tests/Kiota.Builder.Tests/Refiners/PythonLanguageRefinerTests.cs
+++ b/tests/Kiota.Builder.Tests/Refiners/PythonLanguageRefinerTests.cs
@@ -652,4 +652,85 @@ public async Task ReplacesUntypedNodeInMethodParameterAndReturnTypeAsync()
Assert.Equal("bytes", method.ReturnType.Name);// return type is renamed to use the stream type
}
#endregion
+ #region NameShortening
+ [Fact]
+ public async Task ShortensOversizedNamespaceSegmentsAsync()
+ {
+ // Simulate the problematic long namespace segment from OData functions with many parameters
+ var longSegment = string.Concat(Enumerable.Repeat("microsoftgraphnetworkaccessdevicereportsegment", 6)); // 276 chars > 255
+ var childNs = root.AddNamespace($"graph.networkaccess.reports.{longSegment}");
+ var requestBuilderClass = childNs.AddClass(new CodeClass
+ {
+ Name = $"{longSegment}RequestBuilder",
+ Kind = CodeClassKind.RequestBuilder,
+ Documentation = new()
+ {
+ DescriptionTemplate = "Builds and executes requests for operations under /networkAccess/reports/...",
+ },
+ }).First();
+ await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.Python }, root, cancellationToken: TestContext.Current.CancellationToken);
+
+ // Namespace segment should be shortened
+ foreach (var segment in childNs.Name.Split('.'))
+ {
+ Assert.True(segment.Length <= 200, $"Segment '{segment}' exceeds 200 chars (length: {segment.Length})");
+ }
+
+ // Class name should be shortened
+ Assert.True(requestBuilderClass.Name.Length <= 200, $"Class name '{requestBuilderClass.Name}' exceeds 200 chars (length: {requestBuilderClass.Name.Length})");
+
+ // Doc comment should contain original name for disambiguation
+ Assert.Contains("Original name:", requestBuilderClass.Documentation.DescriptionTemplate);
+ }
+ [Fact]
+ public async Task DoesNotShortenNormalLengthNamespacesAsync()
+ {
+ var childNs = root.AddNamespace("graph.networkaccess.users");
+ childNs.AddClass(new CodeClass
+ {
+ Name = "UsersRequestBuilder",
+ Kind = CodeClassKind.RequestBuilder,
+ });
+ await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.Python }, root, cancellationToken: TestContext.Current.CancellationToken);
+
+ Assert.Equal("graph.networkaccess.users", childNs.Name);
+ }
+ [Fact]
+ public async Task DoesNotShortenSegmentsWithinThresholdAsync()
+ {
+ // 150 characters: above the previous 64/128 thresholds but within the current 200 threshold,
+ // so it must be preserved verbatim (both namespace segment and type name).
+ var mediumSegment = new string('a', 150);
+ var childNs = root.AddNamespace($"graph.networkaccess.{mediumSegment}");
+ var requestBuilderClass = childNs.AddClass(new CodeClass
+ {
+ Name = mediumSegment,
+ Kind = CodeClassKind.RequestBuilder,
+ }).First();
+ await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.Python }, root, cancellationToken: TestContext.Current.CancellationToken);
+
+ Assert.EndsWith($".{mediumSegment}", childNs.Name);
+ // The type name is left intact (case normalization aside) — not hash-shortened.
+ Assert.Equal(mediumSegment.Length, requestBuilderClass.Name.Length);
+ Assert.Equal(mediumSegment, requestBuilderClass.Name, ignoreCase: true);
+ }
+ [Fact]
+ public async Task ShorteningWorksWhenDocumentationIsNullAsync()
+ {
+ var longSegment = string.Concat(Enumerable.Repeat("microsoftgraphnetworkaccessdevicereportsegment", 6)); // 276 chars > 255
+ var childNs = root.AddNamespace($"graph.networkaccess.{longSegment}");
+ var requestBuilderClass = childNs.AddClass(new CodeClass
+ {
+ Name = $"{longSegment}RequestBuilder",
+ Kind = CodeClassKind.RequestBuilder,
+ }).First();
+ // Explicitly set Documentation to null to test the defensive null guard
+ requestBuilderClass.Documentation = null!;
+
+ await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.Python }, root, cancellationToken: TestContext.Current.CancellationToken);
+
+ // Should shorten without throwing
+ Assert.True(requestBuilderClass.Name.Length <= 200);
+ }
+ #endregion
}
diff --git a/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs
index ea49d3957a..301f73ddd8 100644
--- a/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs
+++ b/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs
@@ -93,7 +93,7 @@ public async Task AddStaticMethodsUsingsForDeserializerAsync()
[Fact]
public async Task ShortensOversizedNamespaceSegmentsAsync()
{
- var longSegment = "MicrosoftGraphNetworkaccessDeviceReportWithStartDateTimeWithEndDateTimeDiscoveredApplicationSegmentIdDiscoveredApplicationSegmentIdApplicationIdApplicationId";
+ var longSegment = string.Concat(Enumerable.Repeat("MicrosoftGraphNetworkAccessDeviceReportSegment", 6)); // 276 chars > 255
var childNs = root.AddNamespace($"ApiSdk.NetworkAccess.Reports.{longSegment}");
var requestBuilderClass = childNs.AddClass(new CodeClass
{
@@ -120,7 +120,7 @@ public async Task ShortensOversizedNamespaceSegmentsAsync()
// TypeScript maps namespaces (not type names) to directories, so only segments are shortened.
foreach (var segment in childNs.Name.Split('.'))
{
- Assert.True(segment.Length <= 64, $"Segment '{segment}' exceeds 64 chars (length: {segment.Length})");
+ Assert.True(segment.Length <= 200, $"Segment '{segment}' exceeds 200 chars (length: {segment.Length})");
}
// Type names must be left intact: TypeScript groups types into index.ts barrels and relies on