Closed kfcampbell closed 2 weeks ago
When originally creating this issue, I somehow neglected to note it was originally spotted by the eagle eyes of @martincostello. Thanks Martin!
Hi @kfcampbell Thanks for reaching out on this topic! Aren't our MVPs precious? ;-)
This behaviour is expected, this is a union type, but it doesn't have discriminator information. Adding it to your description should fix the deserialization issue. (assuming you have a property that gives you the user kind). Additionally you might add mappings if the user kinds are not strictly matching the component name.
"responses": {
"200": {
"description": "Response",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/private-user"
},
{
"$ref": "#/components/schemas/public-user"
}
]
},
+ "discriminator": {
+ "propertyName": "foo"
+ },
"examples": {
"default-response": {
"$ref": "#/components/examples/public-user-default-response"
},
"response-with-git-hub-plan-information": {
"$ref": "#/components/examples/public-user-response-with-git-hub-plan-information"
}
}
}
}
},
Thanks for the information! I know historically we've had teams resistant to adding discriminators to our specs because they would break other internal tooling for some reason. I've asked to see if that is still the case.
Additionally you might add mappings if the user kinds are not strictly matching the component name.
Do you mind telling me a little bit more about this? I'm not quite sure I understand what you're getting at here, I'm sorry.
Do you mind telling me a little bit more about this?
I meant a mappings element under the discriminator one. But if you're already hesitant to add a discriminator to the description, this is not going to help (not an alternative)
I'd like to come back to this issue to report some weird findings. In the hopes of fixing this behavior, I've added a discriminator locally to my API, and I've confirmed payloads are coming back correctly:
abridged public user payload:
{
"login": "monalisa",
"id": 2,
"followers": 0,
"created_at": "2024-08-23T15:33:30Z",
"updated_at": "2024-08-23T17:21:54Z",
"user_type": "public"
}
abridged private user payload:
{
"login": "monalisa",
"id": 2,
"following": 0,
"created_at": "2024-08-23T15:33:30Z",
"updated_at": "2024-08-23T17:21:54Z",
"user_type": "private",
"private_gists": 0,
"two_factor_authentication": true,
}
The users endpoint OpenAPI spec I used to generate the SDK looks like this:
"/user": {
"get": {
"summary": "Get the authenticated user",
"description": "OAuth app tokens and personal access tokens (classic) need the `user` scope in order for the response to include private profile information.",
"tags": [
"users"
],
"operationId": "users/get-authenticated",
"externalDocs": {
"description": "API method documentation",
"url": "https://docs.github.com/rest/users/users#get-the-authenticated-user"
},
"parameters": [
],
"responses": {
"200": {
"description": "Response",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/private-user"
},
{
"$ref": "#/components/schemas/public-user"
}
],
"discriminator": {
"propertyName": "user_type",
"mapping": {
"public": "../../components/schemas/public-user.yaml",
"private": "../../components/schemas/private-user.yaml"
}
}
},
"examples": {
"response-with-public-and-private-profile-information": {
"$ref": "#/components/examples/private-user-response-with-public-and-private-profile-information"
},
"response-with-public-profile-information": {
"$ref": "#/components/examples/private-user-response-with-public-profile-information"
}
}
}
}
},
"304": {
"$ref": "#/components/responses/not_modified"
},
"403": {
"$ref": "#/components/responses/forbidden"
},
"401": {
"$ref": "#/components/responses/requires_authentication"
}
}
},
The full spec is available upon request.
What's interesting here is even with the discriminator, the code still does not serialize the HTTP response body into the type correctly. The following Go Kiota code prints an empty object (where gh
is a variable holding an initialized Kiota client):
user, err := gh.User().Get(ctx, nil)
if err != nil {
log.Fatalf("error: ", err)
}
fmt.Println("user: ", user) // prints user: &{<nil> <nil>}
The Kiota serialization code:
// CreateUserGetResponseFromDiscriminatorValue creates a new instance of the appropriate class based on discriminator value
// returns a Parsable when successful
func CreateUserGetResponseFromDiscriminatorValue(parseNode i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) (i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable, error) {
result := NewUserGetResponse()
if parseNode != nil {
mappingValueNode, err := parseNode.GetChildNode("user_type")
if err != nil {
return nil, err
}
if mappingValueNode != nil {
mappingValue, err := mappingValueNode.GetStringValue()
if err != nil {
return nil, err
}
if mappingValue != nil {
}
}
}
return result, nil
}
is creating a type, UserGetResponse
, that contains both a publicUser
and a privateUser
internal field in it. However, neither of these are initialized. What I would expect is that based on the mappingValue
, there would be either a PublicUserResponse
or a PrivateUserResponse
type generated, initialized, and populated.
Does that make sense? Is there an error in my API code/responses or OpenAPI spec somewhere?
Hey @kfcampbell Thank you for the additional information. Re-opening so it's easier to track.
have you tried the following instead
"discriminator": {
"propertyName": "user_type",
"mapping": {
- "public": "../../components/schemas/public-user.yaml",
- "private": "../../components/schemas/private-user.yaml"
+ "public": "#/components/schemas/public-user",
+ "private": "#/components/schemas/private-user"
}
}
Interestingly, that appears to be an artifact of GitHub's internal schema construction: we have separate files for each operation and schema, and they're all munged together. Something in that engine doesn't appear to be following up the reference correctly to replace the prepended relative path with the #/components/schemas
directory.
That's gotten us a little further! Given a finalized schema of:
"/user": {
"get": {
"summary": "Get the authenticated user",
"description": "OAuth app tokens and personal access tokens (classic) need the `user` scope in order for the response to include private profile information.",
"tags": [
"users"
],
"operationId": "users/get-authenticated",
"parameters": [
],
"responses": {
"200": {
"description": "Response",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/private-user"
},
{
"$ref": "#/components/schemas/public-user"
}
],
"discriminator": {
"propertyName": "user_type",
"mapping": {
"public": "#/components/schemas/public-user",
"private": "#/components/schemas/private-user"
}
}
},
"examples": {
"response-with-public-and-private-profile-information": {
"$ref": "#/components/examples/private-user-response-with-public-and-private-profile-information"
},
"response-with-public-profile-information": {
"$ref": "#/components/examples/private-user-response-with-public-profile-information"
}
}
}
}
},
"304": {
"$ref": "#/components/responses/not_modified"
},
"403": {
"$ref": "#/components/responses/forbidden"
},
"401": {
"$ref": "#/components/responses/requires_authentication"
}
},
},
new Kiota code is produced:
func CreateUserGetResponseFromDiscriminatorValue(parseNode i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode)(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable, error) {
result := NewUserGetResponse()
if parseNode != nil {
mappingValueNode, err := parseNode.GetChildNode("user_type")
if err != nil {
return nil, err
}
if mappingValueNode != nil {
mappingValue, err := mappingValueNode.GetStringValue()
if err != nil {
return nil, err
}
if mappingValue != nil {
if ie967d16dae74a49b5e0e051225c5dac0d76e5e38f13dd1628028cbce108c25b6.EqualFold(*mappingValue, "private") {
result.SetPrivateUser(i59ea7d99994c6a4bb9ef742ed717844297d055c7fd3742131406eea67a6404b6.NewPrivateUser())
} else if ie967d16dae74a49b5e0e051225c5dac0d76e5e38f13dd1628028cbce108c25b6.EqualFold(*mappingValue, "public") {
result.SetPublicUser(i59ea7d99994c6a4bb9ef742ed717844297d055c7fd3742131406eea67a6404b6.NewPublicUser())
}
}
}
}
return result, nil
}
Note the extra code visible after if mappingValue != nil
. When running against a local API that returns the user_type
discriminator, the privateUser
sub-object is correctly initialized.
Later on, in github.com/microsoft/kiota-serialization-json-go's json_parse_node.go
's GetObjectValue method, it looks like all the properties are correctly identified:
but the fields are not:
which means the result stays empty as initialized:
Do you have any idea what might be going on there?
Sidenote: it's weird to me that given a oneOf
scenario, Kiota generates a top-level object that contains both possibilities, and only populates one of them. Why is that? Is that due to something I'm doing wrong on the API side?
On the side note: that's because at the time we implemented composed types support for go, go did not support union types. I think it's changed in recent versions but changing that now would be a source breaking change. The model looks correct, at least for its factory
On the main problem: can you share the generated get field deserializer body for the user get response type please? (The Union model)
There's this function:
// GetFieldDeserializers the deserialization information for the current model
// returns a map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode)(error) when successful
func (m *UserGetResponse) GetFieldDeserializers()(map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode)(error)) {
return make(map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode)(error))
}
inside of user_request_builder.go
that's suspiciously empty. Is that what you're referring to?
Thank you for the additional information. Yes that's what I was referring to. And yes that's emptier than I expected. I expected to see a "triage" implementation "if memberType1 is not null, then return the field deserializers from that type", etc...
This is the method which is supposed to write that. It'd be interesting to trace the generation to understand whether it gets there at all. https://github.com/microsoft/kiota/blob/61d036ed06c2c9942a6ba3e9e8f4abe8b4abd1e0/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs#L629 Let us know if you have any additional comments or questions.
What's the best way to do that? I can provide debug logs and a full OpenAPI specification, of course. I could also maybe manually run Kiota from source in debug mode and step through to get to that method?
It's worth noting this occurs both on Kiota v1.14.0 (which is what we were running prior to this week) and Kiota v1.17.0 (which is what we upgraded to this week).
clone the repo, change the target description for the go launch settings, set a breakpoint on that method.
I'm still working through this and haven't had as much time as I'd like yet to debug with Kiota. We've had issues with our OpenAPI tooling and we're in the process of getting discriminators to a point where they'll even be included in our specification. Stay tuned.
Okay, I've done some more investigation here. It turns out this is (at least) a Go thing: the serialization works in .NET. When given a correct spec (one that includes the discriminator as described here, Kiota generates correct .NET code to deserialize the object.
Kiota v1.17.0 Microsoft.Kiota.Abstractions 1.11.0 Microsoft.Kiota.Http.HttpClientLibrary 1.11.0 Microsoft.Kiota.Serialization.Form 1.11.0 Microsoft.Kiota.Serialization.Json 1.11.0 Microsoft.Kiota.Serialization.Multipart 1.11.0 Microsoft.Kiota.Serialization.Text 1.11.0 Microsoft.Kiota.Authentication.Azure 1.11.0
GetFieldDeserializers
is properly generating in .NET:
/// <summary>
/// The deserialization information for the current model
/// </summary>
/// <returns>A IDictionary<string, Action<IParseNode>></returns>
public virtual IDictionary<string, Action<IParseNode>> GetFieldDeserializers()
{
return new Dictionary<string, Action<IParseNode>>
{
{ "avatar_url", n => { AvatarUrl = n.GetStringValue(); } },
{ "bio", n => { Bio = n.GetStringValue(); } },
{ "blog", n => { Blog = n.GetStringValue(); } },
{ "business_plus", n => { BusinessPlus = n.GetBoolValue(); } },
{ "collaborators", n => { Collaborators = n.GetIntValue(); } },
{ "company", n => { Company = n.GetStringValue(); } },
{ "created_at", n => { CreatedAt = n.GetDateTimeOffsetValue(); } },
{ "disk_usage", n => { DiskUsage = n.GetIntValue(); } },
{ "email", n => { Email = n.GetStringValue(); } },
{ "events_url", n => { EventsUrl = n.GetStringValue(); } },
{ "followers", n => { Followers = n.GetIntValue(); } },
{ "followers_url", n => { FollowersUrl = n.GetStringValue(); } },
{ "following", n => { Following = n.GetIntValue(); } },
{ "following_url", n => { FollowingUrl = n.GetStringValue(); } },
{ "gists_url", n => { GistsUrl = n.GetStringValue(); } },
{ "gravatar_id", n => { GravatarId = n.GetStringValue(); } },
{ "hireable", n => { Hireable = n.GetBoolValue(); } },
{ "html_url", n => { HtmlUrl = n.GetStringValue(); } },
{ "id", n => { Id = n.GetLongValue(); } },
{ "ldap_dn", n => { LdapDn = n.GetStringValue(); } },
{ "location", n => { Location = n.GetStringValue(); } },
{ "login", n => { Login = n.GetStringValue(); } },
{ "name", n => { Name = n.GetStringValue(); } },
{ "node_id", n => { NodeId = n.GetStringValue(); } },
{ "notification_email", n => { NotificationEmail = n.GetStringValue(); } },
{ "organizations_url", n => { OrganizationsUrl = n.GetStringValue(); } },
{ "owned_private_repos", n => { OwnedPrivateRepos = n.GetIntValue(); } },
{ "plan", n => { Plan = n.GetObjectValue<global::GitHub.Models.PrivateUser_plan>(global::GitHub.Models.PrivateUser_plan.CreateFromDiscriminatorValue); } },
{ "private_gists", n => { PrivateGists = n.GetIntValue(); } },
{ "public_gists", n => { PublicGists = n.GetIntValue(); } },
{ "public_repos", n => { PublicRepos = n.GetIntValue(); } },
{ "received_events_url", n => { ReceivedEventsUrl = n.GetStringValue(); } },
{ "repos_url", n => { ReposUrl = n.GetStringValue(); } },
{ "site_admin", n => { SiteAdmin = n.GetBoolValue(); } },
{ "starred_url", n => { StarredUrl = n.GetStringValue(); } },
{ "subscriptions_url", n => { SubscriptionsUrl = n.GetStringValue(); } },
{ "suspended_at", n => { SuspendedAt = n.GetDateTimeOffsetValue(); } },
{ "total_private_repos", n => { TotalPrivateRepos = n.GetIntValue(); } },
{ "twitter_username", n => { TwitterUsername = n.GetStringValue(); } },
{ "two_factor_authentication", n => { TwoFactorAuthentication = n.GetBoolValue(); } },
{ "type", n => { Type = n.GetStringValue(); } },
{ "updated_at", n => { UpdatedAt = n.GetDateTimeOffsetValue(); } },
{ "url", n => { Url = n.GetStringValue(); } },
{ "user_type", n => { UserType = n.GetStringValue(); } },
};
}
but not for Go:
// GetFieldDeserializers the deserialization information for the current model
// returns a map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode)(error) when successful
func (m *UserGetResponse) GetFieldDeserializers()(map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode)(error)) {
return make(map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode)(error))
}
leading to the empty response body in Go only.
Go versions: Kiota v1.17.0 github.com/microsoft/kiota-abstractions-go v1.6.1 github.com/microsoft/kiota-http-go v1.4.3 github.com/microsoft/kiota-serialization-form-go v1.0.0 github.com/microsoft/kiota-serialization-json-go v1.0.7 github.com/microsoft/kiota-serialization-multipart-go v1.0.0 github.com/microsoft/kiota-serialization-text-go v1.0.0
@baywet should this be transfered perhaps to github.com/microsoft/kiota-abstractions-go or github.com/microsoft/kiota-http-go?
Thank you for the additional information. If we're comparing the serialization methods for the same types across different languages, this is definitively a generation issue and should remain here. Assuming those methods are both for the UserGetResponse type (obvious in go, but not in dotnet from what you've shared), I think the next step would be to approach that in a dichotomic way: in debug, are the writers receiving similar information? are they implemented in a similar way? (they should beyond the fact that go has intermediate interfaces which is what might be messing up the logic here). Also doing a sanity check with Java would be a good thing, since Java is close to both languages. Let us know if you have any additional comments or questions.
@baywet apologies for the delay! Things have been a little crazy. @nickfloyd and I did some pairing on this earlier, and poked it a little farther. So the problem actually isn't with the discriminated types' deserializers: those are being generated correctly. In both C# and Go, public and private user models exist that have GetFieldDeserializers
defined correctly.
Example OpenAPI JSON definition:
"/users/{username}": {
"get": {
"summary": "Get a user",
"description": "Provides publicly available information about someone with a GitHub account.\n\nThe `email` key in the following response is the publicly visible email address from your GitHub [profile page](https://github.com/settings/profile). When setting up your profile, you can select a primary email address to be “public” which provides an email entry for this endpoint. If you do not set a public email address for `email`, then it will have a value of `null`. You only see publicly visible email addresses when authenticated with GitHub. For more information, see [Authentication](https://docs.github.com/rest/guides/getting-started-with-the-rest-api#authentication).\n\nThe Emails API enables you to list all of your email addresses, and toggle a primary email to be visible publicly. For more information, see \"[Emails API](https://docs.github.com/rest/users/emails)\".",
"operationId": "users/get-by-username",
"parameters": [
{
"$ref": "#/components/parameters/username"
}
],
"responses": {
"200": {
"description": "Response",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/private-user"
},
{
"$ref": "#/components/schemas/public-user"
}
],
"discriminator": {
"propertyName": "user_view_type",
"mapping": {
"public": "#/components/schemas/public-user",
"private": "#/components/schemas/private-user"
}
}
},
"examples": {
"default-response": {
"$ref": "#/components/examples/public-user-default-response"
},
"response-with-git-hub-plan-information": {
"$ref": "#/components/examples/public-user-response-with-git-hub-plan-information"
}
}
}
}
},
"404": {
"$ref": "#/components/responses/not_found"
}
},
}
},
Go's public_user.go
(snippet):
func (m *PublicUser) GetFieldDeserializers()(map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode)(error)) {
res := make(map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode)(error))
res["avatar_url"] = func (n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error {
val, err := n.GetStringValue()
if err != nil {
return err
}
if val != nil {
m.SetAvatarUrl(val)
}
return nil
}
res["bio"] = func (n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error {
val, err := n.GetStringValue()
if err != nil {
return err
}
if val != nil {
m.SetBio(val)
}
return nil
}
C#'s PrivateUser.cs (snippet):
public virtual IDictionary<string, Action<IParseNode>> GetFieldDeserializers()
{
return new Dictionary<string, Action<IParseNode>>
{
{ "avatar_url", n => { AvatarUrl = n.GetStringValue(); } },
{ "bio", n => { Bio = n.GetStringValue(); } },
{ "blog", n => { Blog = n.GetStringValue(); } },
{ "business_plus", n => { BusinessPlus = n.GetBoolValue(); } },
The difference occurs in the user request builder, when that calls GetFieldDeserializers
.
In C#, it's populated with logic that checks whether the public or private user is null (based upon what is previously set in CreateUserGetResponseFromDiscriminatorValue
):
public virtual IDictionary<string, Action<IParseNode>> GetFieldDeserializers()
{
if(PrivateUser != null)
{
return PrivateUser.GetFieldDeserializers();
}
else if(PublicUser != null)
{
return PublicUser.GetFieldDeserializers();
}
return new Dictionary<string, Action<IParseNode>>();
}
In Go, however, it simply returns a blank map:
func (m *UserGetResponse) GetFieldDeserializers() map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error {
return make(map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error)
}
Nick and I have confirmed that manually adding the missing lines into the Go build will cause the request to correctly deserialize:
func (m *UserGetResponse) GetFieldDeserializers() map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error {
if m.privateUser != nil {
return m.privateUser.GetFieldDeserializers()
} else if m.publicUser != nil {
return m.publicUser.GetFieldDeserializers()
}
return make(map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error)
}
However, we're at a bit of a loss on where to troubleshoot the logic for how GetFieldDeserializers
gets populated. Does that happen in the Go language refiner? In the Kiota builder? Would you mind pointing us in the right direction?
Thank you for the additional information. I think this is what you're looking for.
Hmm...as best we can tell, it looks like on the Go side, the original ".NET" implementation seems correct. None of the information in the WriteSerializerBodyForUnionModel
is obviously missing, and the code steps through there as we'd expect.
Is there a way to dump the contents of the LanguageWriter
's writer
byte array in that method? It might be that the error is happening later in the refiner or the "translation" to Go.
The LanguageWriter writes directly to the the file for performance reasons (memory pressure and whatnot). So no, unless me makes changes.
However, you could tweak/duplicate the unit test, and that'll give you access to the resulting string. Plus it's going to be much faster to run than the full engine. https://github.com/microsoft/kiota/blob/5ec4690a734c6db48e0b28740e6cacd165665fb8/tests/Kiota.Builder.Tests/Writers/Go/CodeMethodWriterTests.cs#L1495
After a bit of foot work (thx for all of the tips @baywet ❤ ). I tracked it down to this:
The issue is in WriteDeserializerBodyForUnionModel or at least why the method body is not getting generated. So the LINQ statement for Go is:
var otherPropGetters = parentClass
.GetPropertiesOfKind(CodePropertyKind.Custom)
.Where(static x => !x.ExistsInBaseType && x.Getter != null)
.Where(static x => x.Type is CodeType propertyType && !propertyType.IsCollection && propertyType.TypeDefinition is CodeClass)
.OrderBy(static x => x, CodePropertyTypeForwardComparer)
.ThenBy(static x => x.Name)
.Select(static x => x.Getter!.Name.ToFirstCharacterUpperCase())
.ToArray();
The problem is that for these discriminators the TypeDefinition is not of type CodeClass but of type CodeInterface
When the LINQ is rewritten like this, it works:
var otherPropGetters = parentClass
.GetPropertiesOfKind(CodePropertyKind.Custom)
.Where(static x => !x.ExistsInBaseType && x.Getter != null)
.Where(static x => x.Type is CodeType propertyType && !propertyType.IsCollection && propertyType.TypeDefinition is CodeInterface)
.OrderBy(static x => x, CodePropertyTypeForwardComparer)
.ThenBy(static x => x.Name)
.Select(static x => x.Getter!.Name.ToFirstCharacterUpperCase())
.ToArray();
Given my limited understanding of the architecture here, when the models are generated in go they are done so as interfaces. i.e.
type ContentSymlinkable interface
whereas .NET generates them as classes.
If this seems like a plausible fix, we can create a quick PR with some tests and get this resolved; just double checking here given my limited knowledge of the kiota architecture.
Great work getting to the root cause here! Yeah this has probably been caused by some bad copy pasta. Please go ahead and submit a pull request for that. If you could please double check other instances of the same pattern in the same source file as well, that'd help too!
What are you generating using Kiota, clients or plugins?
API Client/SDK
In what context or format are you using Kiota?
Nuget tool
Client library/SDK language
Csharp
Describe the bug
I am trying to get users from the GitHub API, like
var users = await gitHubClient.Users["kfcampbell"].GetAsync();
I'm using the dotnet-sdk generated from Kiota to do this, but it will reproduce with a fresh SDK generated from the Kiota CLI. The offending code is this generated code:
When generating, the above code lives in a file called
Users/Item/WithUsernameItemRequestBuilder.cs
. What happens is thatmappingValue
is always null, which means theif
statements never trigger, which means the returned users are always null in the result.Expected behavior
I expect the returned users to be populated correctly.
How to reproduce
You may use the dotnet-sdk and modify the CLI to include the following C# code:
Then run
dotnet build
, export yourGITHUB_TOKEN
, and rundotnet run
. Alternately, you may generate a fresh C# SDK from the Kiota CLI, create your own CLI, input that code, and follow the same steps.Open API description file
https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.json
Kiota Version
v1.14.0
Latest Kiota version known to work for scenario above?(Not required)
No response
Known Workarounds
Manually removing the
if
statements so the generated code reads like:will fix the issue.
Configuration
This occurs both on my machine (an Ubuntu 22.04 derivative, 64-bit Linux box) and GitHub Actions (Ubuntu 22.04, 64-bit Linux). I do not believe it is specific to this configuration.
Debug output
N/A
Other information
No response