Repository: microsoft/kiota (csharp)

Issue:
Stack overflow exception generating a client
### What are you generating using Kiota, clients or plugins?

API Client/SDK

### In what context or format are you using Kiota?

Windows executable

### Client library/SDK language

Csharp

### Describe the bug

I'm using Kiota (version 1.19.1+d294e04ba7f756896878a7015df1648f9dc0bcde) to generate HubSpot clients from their [OpenAPI catalog](https://api.hubspot.com/public/api/spec/v1/specs) which contains ~85 separate specs.  4 of which failed with a stack overflow exception:

```
 at Kiota.Builder.Extensions.OpenApiSchemaExtensions.GetDiscriminatorPropertyName(Microsoft.OpenApi.Models.OpenApiSchema)
   at System.Linq.Enumerable+SelectListIterator`2[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext()
   at System.Linq.Enumerable.TryGetFirst[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](System.Collections.Generic.IEnumerable`1<System.__Canon>, System.Func`2<System.__Canon,Boolean>, Boolean ByRef)
   at System.Linq.Enumerable.FirstOrDefault[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](System.Collections.Generic.IEnumerable`1<System.__Canon>, System.Func`2<System.__Canon,Boolean>)
   at Kiota.Builder.Extensions.OpenApiSchemaExtensions.GetDiscriminatorPropertyName(Microsoft.OpenApi.Models.OpenApiSchema)
   at System.Linq.Enumerable+SelectListIterator`2[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext()
   at System.Linq.Enumerable.TryGetFirst[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](System.Collections.Generic.IEnumerable`1<System.__Canon>, System.Func`2<System.__Canon,Boolean>, Boolean ByRef)
   at System.Linq.Enumerable.FirstOrDefault[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](System.Collections.Generic.IEnumerable`1<System.__Canon>, System.Func`2<System.__Canon,Boolean>)
   ... repeats
```



### Expected behavior

Should generate a c# client.

### How to reproduce

```
kiota generate --openapi https://api.hubspot.com/public/api/spec/v1/specs/Automation/Automation%20V4/versions/144908 --language csharp
kiota generate --openapi https://api.hubspot.com/public/api/spec/v1/specs/CRM/Exports/versions/95922 --language csharp
kiota generate --openapi https://api.hubspot.com/public/api/spec/v1/specs/Conversations/Conversations%20Inbox%20&%20Messages/versions/54902 --language csharp
```

### Open API description file

https://api.hubspot.com/public/api/spec/v1/specs/Automation/Automation%20V4/versions/144908 
https://api.hubspot.com/public/api/spec/v1/specs/CRM/Exports/versions/95922
https://api.hubspot.com/public/api/spec/v1/specs/Conversations/Conversations%20Inbox%20&%20Messages/versions/54902

### Kiota Version

1.19.1+d294e04ba7f756896878a7015df1648f9dc0bcde

### Latest Kiota version known to work for scenario above?(Not required)

_No response_

### Known Workarounds

None

### Configuration

- OS: Windows 11 x64.

### Debug output

`--debug` flag didn't work.

![Image](https://github.com/user-attachments/assets/19b58853-cff5-45cd-b68d-a58bf119540a)


### Other information

_No response_

Pull request description:
fixes #5771

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 17076aa315..d85163d3e4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Fixed Python error when a class inherits from a base class and implements an interface. [5637](https://github.com/microsoft/kiota/issues/5637)
 - Fix anyOf/oneOf generation in TypeScript. [5353](https://github.com/microsoft/kiota/issues/5353)
 - Fixed invalid code in Php caused by "*/*/" in property description. [5635](https://github.com/microsoft/kiota/issues/5635)
+- Fixed a bug where discriminator property name lookup could end up in an infinite loop. [#5771](https://github.com/microsoft/kiota/issues/5771)
 - Fixed TypeScript generation error when generating usings from shaken serializers. [#5634](https://github.com/microsoft/kiota/issues/5634)
 
 ## [1.20.0] - 2024-11-07
diff --git a/src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs b/src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs
index c5ed4b24e9..045218eced 100644
--- a/src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs
+++ b/src/Kiota.Builder/Extensions/OpenApiSchemaExtensions.cs
@@ -246,19 +246,25 @@ private static IEnumerable<OpenApiSchema> FlattenEmptyEntries(this IEnumerable<O
         }
         return result;
     }
-    internal static string GetDiscriminatorPropertyName(this OpenApiSchema schema)
+    internal static string GetDiscriminatorPropertyName(this OpenApiSchema schema, HashSet<OpenApiSchema>? visitedSchemas = default)
     {
+
         if (schema == null)
             return string.Empty;
 
+        visitedSchemas ??= [];
+        if (visitedSchemas.Contains(schema))
+            return string.Empty;
+        visitedSchemas.Add(schema);
+
         if (!string.IsNullOrEmpty(schema.Discriminator?.PropertyName))
             return schema.Discriminator.PropertyName;
 
-        if (schema.OneOf.Select(GetDiscriminatorPropertyName).FirstOrDefault(static x => !string.IsNullOrEmpty(x)) is string oneOfDiscriminatorPropertyName)
+        if (schema.OneOf.Select(x => x.GetDiscriminatorPropertyName(visitedSchemas)).FirstOrDefault(static x => !string.IsNullOrEmpty(x)) is string oneOfDiscriminatorPropertyName)
             return oneOfDiscriminatorPropertyName;
-        if (schema.AnyOf.Select(GetDiscriminatorPropertyName).FirstOrDefault(static x => !string.IsNullOrEmpty(x)) is string anyOfDiscriminatorPropertyName)
+        if (schema.AnyOf.Select(x => x.GetDiscriminatorPropertyName(visitedSchemas)).FirstOrDefault(static x => !string.IsNullOrEmpty(x)) is string anyOfDiscriminatorPropertyName)
             return anyOfDiscriminatorPropertyName;
-        if (schema.AllOf.Select(GetDiscriminatorPropertyName).FirstOrDefault(static x => !string.IsNullOrEmpty(x)) is string allOfDiscriminatorPropertyName)
+        if (schema.AllOf.Select(x => x.GetDiscriminatorPropertyName(visitedSchemas)).FirstOrDefault(static x => !string.IsNullOrEmpty(x)) is string allOfDiscriminatorPropertyName)
             return allOfDiscriminatorPropertyName;
 
         return string.Empty;


Test patch:
diff --git a/tests/Kiota.Builder.Tests/Extensions/OpenApiSchemaExtensionsTests.cs b/tests/Kiota.Builder.Tests/Extensions/OpenApiSchemaExtensionsTests.cs
index d0c7a81f9f..800a449504 100644
--- a/tests/Kiota.Builder.Tests/Extensions/OpenApiSchemaExtensionsTests.cs
+++ b/tests/Kiota.Builder.Tests/Extensions/OpenApiSchemaExtensionsTests.cs
@@ -847,4 +847,63 @@ public void IsOdataPrimitiveBackwardCompatible()
         };
         Assert.True(schema.IsODataPrimitiveType());
     }
+    [Fact]
+    public void ReturnsEmptyPropertyNameOnCircularReferences()
+    {
+        var entitySchema = new OpenApiSchema
+        {
+            Reference = new OpenApiReference
+            {
+                Id = "microsoft.graph.entity"
+            },
+            Properties = new Dictionary<string, OpenApiSchema>
+            {
+                ["id"] = new OpenApiSchema
+                {
+                    Reference = new OpenApiReference
+                    {
+                        Id = "microsoft.graph.entity"
+                    }
+                }
+            }
+        };
+        var userSchema = new OpenApiSchema
+        {
+            Reference = new OpenApiReference
+            {
+                Id = "microsoft.graph.user"
+            },
+            OneOf =
+            [
+                entitySchema,
+                new OpenApiSchema
+                {
+                    Type = "object",
+                    Properties = new Dictionary<string, OpenApiSchema>
+                    {
+                        ["firstName"] = new OpenApiSchema
+                        {
+                            Reference = new OpenApiReference
+                            {
+                                Id = "microsoft.graph.entity"
+                            }
+                        }
+                    }
+                }
+            ],
+            Discriminator = new OpenApiDiscriminator
+            {
+                Mapping = new Dictionary<string, string>
+                {
+                    ["microsoft.graph.entity"] = "entity",
+                    ["microsoft.graph.user"] = "user"
+                }
+            }
+        };
+        entitySchema.AllOf =
+        [
+            userSchema
+        ];
+        Assert.Empty(userSchema.GetDiscriminatorPropertyName());
+    }
 }