Repository: microsoft/kiota (csharp)

Issue:
Generated servicenames get too long
When I try to generate a client for an endpoint on our ERP system, I get the following exception:
```
04/12/2023 14:30:54:       error generating the client: The filename, directory name, or volume label syntax is incorrect. : 'C:\Users\schaafa\AppData\Local\Temp\cefe13ac-3a41-4719-be69-731318288748\Item\ErpBOLaborSvc\TimeWeeklyViewsWithCompanyWithEmployeeNumWithWeekBeginDateWithWeekEndDateWithQuickEntryCodeWithProjectIDWithPhaseIDWithTimeTypCdWithJobNumWithAssemblySeqWithOprSeqWithRoleCdWithIndirectCodeWithExpenseCodeWithResourceGrpIDWithResourceIDWithLaborTypePseudoWithShiftWithNewRowTypeWithTimeStatus'
```

This is the command I was using:
```
kiota  generate -l CSharp -d C:\MyService.json -o C:\Users\schaafa\AppData\Local\Temp\cefe13ac-3a41-4719-be69-731318288748 -n Incoe.Epicor.Kiota.OpenAPIs
```
Here is the service file:
[MyService.json](https://github.com/microsoft/kiota/files/13546708/MyService.json)

Pull request description:
Fixes https://github.com/microsoft/kiota/issues/3854

In summary, changes include
- Prevents the namespace folder name from getting too long
- Normalized paths if they are longer than windows MAX_PATH similar to Go

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7b8d981fbb..17625b210b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Fixed a bug where scalar error mappings would be generated even though it's not supported by the http request adapter. [#4018](https://github.com/microsoft/kiota/issues/4018)
 - Switched to proxy generation for TypeScript, leading to about ~44% bundle sizes reduction. [#3642](https://github.com/microsoft/kiota/issues/3642)
 - Fixed a bug where TypeScript models factory methods would be missing return types.
+- Fixed a bug where generated paths would possibly get too long. [#3854](https://github.com/microsoft/kiota/issues/3854)
 
 ## [1.10.1] - 2024-01-12
 
diff --git a/src/Kiota.Builder/PathSegmenters/CSharpPathSegmenter.cs b/src/Kiota.Builder/PathSegmenters/CSharpPathSegmenter.cs
index e9853e6ca6..5dcc4e0d8b 100644
--- a/src/Kiota.Builder/PathSegmenters/CSharpPathSegmenter.cs
+++ b/src/Kiota.Builder/PathSegmenters/CSharpPathSegmenter.cs
@@ -1,4 +1,6 @@
-﻿using Kiota.Builder.CodeDOM;
+﻿using System;
+using System.IO;
+using Kiota.Builder.CodeDOM;
 using Kiota.Builder.Extensions;
 
 namespace Kiota.Builder.PathSegmenters;
@@ -6,6 +8,17 @@ public class CSharpPathSegmenter : CommonPathSegmenter
 {
     public CSharpPathSegmenter(string rootPath, string clientNamespaceName) : base(rootPath, clientNamespaceName) { }
     public override string FileSuffix => ".cs";
-    public override string NormalizeNamespaceSegment(string segmentName) => segmentName.ToFirstCharacterUpperCase();
-    public override string NormalizeFileName(CodeElement currentElement) => GetLastFileNameSegment(currentElement).ToFirstCharacterUpperCase();
+    public override string NormalizeNamespaceSegment(string segmentName) => segmentName.ToFirstCharacterUpperCase().ShortenFileName();
+    public override string NormalizeFileName(CodeElement currentElement) => GetLastFileNameSegment(currentElement).ToFirstCharacterUpperCase().ShortenFileName();
+    public override string NormalizePath(string fullPath)
+    {
+        if (ExceedsMaxPathLength(fullPath) && Path.GetDirectoryName(fullPath) is string directoryName)
+        {
+            var availableLength = MaxFilePathLength - (directoryName.Length + FileSuffix.Length + 2); // one for the folder separator and another to ensure its below limit
+            return Path.Combine(directoryName, Path.GetFileName(fullPath).ShortenFileName(availableLength)[..Math.Min(64, availableLength)]) + FileSuffix;
+        }
+        return fullPath;
+    }
+    internal const int MaxFilePathLength = 32767;
+    private static bool ExceedsMaxPathLength(string fullPath) => !string.IsNullOrEmpty(fullPath) && fullPath.Length > MaxFilePathLength;
 }


Test patch:
diff --git a/tests/Kiota.Builder.Tests/PathSegmenters/CSharpPathSegmenterTests.cs b/tests/Kiota.Builder.Tests/PathSegmenters/CSharpPathSegmenterTests.cs
new file mode 100644
index 0000000000..019c8e2da5
--- /dev/null
+++ b/tests/Kiota.Builder.Tests/PathSegmenters/CSharpPathSegmenterTests.cs
@@ -0,0 +1,51 @@
+﻿using Kiota.Builder.CodeDOM;
+using Kiota.Builder.PathSegmenters;
+using Xunit;
+
+namespace Kiota.Builder.Tests.PathSegmenters
+{
+    public class CSharpPathSegmenterTests
+    {
+        private readonly CSharpPathSegmenter segmenter;
+        public CSharpPathSegmenterTests()
+        {
+            segmenter = new CSharpPathSegmenter("D:\\source\\repos\\kiota-sample", "client");
+        }
+
+        [Fact]
+        public void CSharpPathSegmenterGeneratesCorrectFileName()
+        {
+            var fileName = segmenter.NormalizeFileName(new CodeClass
+            {
+                Name = "testClass"
+            });
+            Assert.Equal("TestClass", fileName);// the file name should be PascalCase
+        }
+
+        [Fact]
+        public void CSharpPathSegmenterDoesNotGenerateNamespaceFoldersThatAreTooLong()
+        {
+            var longNamespace = "ThisIsAVeryLongNamespaceNameThatShouldBeTruncatedAsItIsLongerThanTwoHundredAndFiftySixCharactersAccordingToWindows";
+            longNamespace = longNamespace + longNamespace + longNamespace; //make it more than 256
+            var normalizedNamespace = segmenter.NormalizeNamespaceSegment(longNamespace);
+            Assert.NotEqual(longNamespace, normalizedNamespace);
+            Assert.Equal(64, normalizedNamespace.Length);// shortened to sha256 length
+        }
+
+        [Fact]
+        public void CSharpPathSegmenterDoesNotGeneratePathsThatAreTooLong()
+        {
+            var rootDir = "D:\\source\\repos\\kiota-sample\\Item\\ErpBOLaborSvc\\";
+            var longNamespace = "ThisIsAVeryLongNamespaceNameThatIsNotLongerThanTwoHundredAndFiftySixCharactersAccordingToWindows";
+            while (rootDir.Length < CSharpPathSegmenter.MaxFilePathLength - longNamespace.Length)
+            {
+                rootDir = $"{rootDir}\\{longNamespace}";
+            }
+            var longPathName = $"{rootDir}\\TimeWeeklyViewsWithCompanyWithEmployeeNumWithWeekBeginDateWithWeekEndDateWithQuickEntryCodeWithProjectIDWithPhaseIDWithTimeTypCdWithJobNumWithAssemblySeqWithOprSeqWithRoleCdWithIndirectCodeWithExpenseCodeWithResourceGrpIDWithResourceIDWithLaborTypePseudoWithShiftWithNewRowTypeWithTimeStatusRequestBuilder.cs";
+            var normalizedPath = segmenter.NormalizePath(longPathName);
+            Assert.NotEqual(longPathName, normalizedPath);
+            Assert.True(normalizedPath.Length < longPathName.Length);//new path is shorter than the original
+            Assert.True(normalizedPath.Length < CSharpPathSegmenter.MaxFilePathLength); // new path is shorter than the max path length
+        }
+    }
+}
diff --git a/tests/Kiota.Builder.Tests/PathSegmenters/JavaPathSegmenterTests.cs b/tests/Kiota.Builder.Tests/PathSegmenters/JavaPathSegmenterTests.cs
new file mode 100644
index 0000000000..d965be0a16
--- /dev/null
+++ b/tests/Kiota.Builder.Tests/PathSegmenters/JavaPathSegmenterTests.cs
@@ -0,0 +1,33 @@
+﻿using Kiota.Builder.CodeDOM;
+using Kiota.Builder.PathSegmenters;
+using Xunit;
+
+namespace Kiota.Builder.Tests.PathSegmenters
+{
+    public class JavaPathSegmenterTests
+    {
+        private readonly JavaPathSegmenter segmenter;
+        public JavaPathSegmenterTests()
+        {
+            segmenter = new JavaPathSegmenter("D:\\source\\repos\\kiota-sample", "client");
+        }
+
+        [Fact]
+        public void JavaPathSegmenterGeneratesCorrectFileName()
+        {
+            var fileName = segmenter.NormalizeFileName(new CodeClass
+            {
+                Name = "testClass"
+            });
+            Assert.Equal("TestClass", fileName);// the file name should be PascalCase
+        }
+
+        [Fact]
+        public void JavaPathSegmenterGeneratesNamespaceFolderName()
+        {
+            var namespaceName = "Microsoft.Graph";
+            var normalizedNamespace = segmenter.NormalizeNamespaceSegment(namespaceName);
+            Assert.Equal("microsoft.graph", normalizedNamespace);// the file name should be lower case
+        }
+    }
+}
diff --git a/tests/Kiota.Builder.Tests/PathSegmenters/PythonPathSegmenterTests.cs b/tests/Kiota.Builder.Tests/PathSegmenters/PythonPathSegmenterTests.cs
new file mode 100644
index 0000000000..2944c665d2
--- /dev/null
+++ b/tests/Kiota.Builder.Tests/PathSegmenters/PythonPathSegmenterTests.cs
@@ -0,0 +1,36 @@
+﻿using System.Linq;
+using Kiota.Builder.CodeDOM;
+using Kiota.Builder.PathSegmenters;
+using Xunit;
+
+namespace Kiota.Builder.Tests.PathSegmenters
+{
+    public class PythonPathSegmenterTests
+    {
+        private readonly PythonPathSegmenter segmenter;
+        public PythonPathSegmenterTests()
+        {
+            segmenter = new PythonPathSegmenter("D:\\source\\repos\\kiota-sample", "client");
+        }
+
+        [Fact]
+        public void PythonPathSegmenterGeneratesCorrectFileName()
+        {
+            var rootNamespace = CodeNamespace.InitRootNamespace();
+            var classExample = rootNamespace.AddClass(new CodeClass
+            {
+                Name = "testClass"
+            }).First();
+            var fileName = segmenter.NormalizeFileName(classExample);
+            Assert.Equal("test_class", fileName);// the file name should be snake case
+        }
+
+        [Fact]
+        public void PythonPathSegmenterGeneratesNamespaceFolderName()
+        {
+            var namespaceName = "MicrosoftGraph";
+            var normalizedNamespace = segmenter.NormalizeNamespaceSegment(namespaceName);
+            Assert.Equal("microsoft_graph", normalizedNamespace);// the namespace name should be snake case
+        }
+    }
+}
diff --git a/tests/Kiota.Builder.Tests/PathSegmenters/TypeScriptPathSegmenterTests.cs b/tests/Kiota.Builder.Tests/PathSegmenters/TypeScriptPathSegmenterTests.cs
new file mode 100644
index 0000000000..11054cebf7
--- /dev/null
+++ b/tests/Kiota.Builder.Tests/PathSegmenters/TypeScriptPathSegmenterTests.cs
@@ -0,0 +1,36 @@
+﻿using System.Linq;
+using Kiota.Builder.CodeDOM;
+using Kiota.Builder.PathSegmenters;
+using Xunit;
+
+namespace Kiota.Builder.Tests.PathSegmenters
+{
+    public class TypeScriptPathSegmenterTests
+    {
+        private readonly TypeScriptPathSegmenter segmenter;
+        public TypeScriptPathSegmenterTests()
+        {
+            segmenter = new TypeScriptPathSegmenter("D:\\source\\repos\\kiota-sample", "client");
+        }
+
+        [Fact]
+        public void TypeScriptPathSegmenterGeneratesCorrectFileName()
+        {
+            var rootNamespace = CodeNamespace.InitRootNamespace();
+            var classExample = rootNamespace.AddClass(new CodeClass
+            {
+                Name = "testClass"
+            }).First();
+            var fileName = segmenter.NormalizeFileName(classExample);
+            Assert.Equal("testClass", fileName);// the file name should be camelCase
+        }
+
+        [Fact]
+        public void TypeScriptPathSegmenterGeneratesNamespaceFolderName()
+        {
+            var namespaceName = "MicrosoftGraph";
+            var normalizedNamespace = segmenter.NormalizeNamespaceSegment(namespaceName);
+            Assert.Equal("microsoftGraph", normalizedNamespace);// the namespace name should be camelCase
+        }
+    }
+}