Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serialization and Deserialization of Guid[] fields is incorrectly handled #1478

Closed
TheDusty01 opened this issue Nov 7, 2024 · 5 comments
Closed

Comments

@TheDusty01
Copy link

What are you generating using Kiota, clients or plugins?

API Client

In what context or format are you using Kiota?

Nuget Tool

Describe the bug

When fetching a response with a Guid[] field, Kiota serializes it as a string[] at runtime, even though it's defined as Guid[]. Similarly, when sending a request containing a Guid[] field, the generated JSON is malformed, causing a server-side parse error. See examples below.

Actual behavior

Serialization

The current generated request:

export interface CreatePlanRequest extends Parsable {
    name?: string | null;
    projectId?: Guid | null;
    scheduleIds?: Guid[] | null;
}

The payload sent to the server is malformed (generated by the call to the resource's post method on the Kiota generated api client). The scheduleIds field is incorrectly serialized as an object array instead of a string[]. Meanwhile, the projectId field is correctly serialized as a string:

{
    "name": "Plan Name",
    "projectId": "896efb3d-ac5a-8d1c-b275-d6f393e5c5dc",
    "scheduleIds": [
        {
            "value": "2eeda716-fdfb-ad99-e972-f7826a62a92f"
        },
        ,
        {
            "value": "dadde388-b68c-ac73-1653-0508a722bf02"
        },
    ]
}

Deserialization

This generated response:

export interface PlanResponse extends Parsable {
    id?: Guid | null;
    name?: string | null;
    projectId?: Guid | null;
    scheduleIds?: Guid[] | null;
}

At runtime, the scheduleIds field is incorrectly deserialized as a string[], while Guid fields like id and projectId are correctly deserialized as objects:

{
    "id": {
        "value": "ffec335d-f18e-4702-854b-c331ccfc7b01"
    },
    "name": "Plan 1",
    "projectId": {
        "value": "941a2bb3-af7d-455c-934e-eadeeabba5e6"
    },
    "scheduleIds": [
        "19a8243c-34dd-4707-955b-8394255efaf0",
        "32797c4f-fd8e-4c37-9487-b9e747b47eec",
        "96ec9ac9-7c36-4759-b867-d309287ac270",
        "b1a0dbe7-cb20-433c-aa44-22a070191a72",
        "b652ffa7-9e42-4ded-9338-d95d7de21dea"
    ]
}

TL;DR:
The scheduleIds field is defined as Guid[] in the model, but is treated as string[] at runtime.

Expected behavior

Serialization

The resulting JSON should be well-formed with the scheduleIds field serialized as an array of strings, not as an array of objects.

{
    "name": "Plan Name",
    "projectId": "896efb3d-ac5a-8d1c-b275-d6f393e5c5dc",
    "scheduleIds": [
        "2eeda716-fdfb-ad99-e972-f7826a62a92f",
        "dadde388-b68c-ac73-1653-0508a722bf02"
    ]
}

Deserialization

The scheduleIds field should be deserialized as an array of Guid objects, not as an array of strings.

{
    "id": {
        "value": "ffec335d-f18e-4702-854b-c331ccfc7b01"
    },
    "name": "Plan 1",
    "projectId": {
        "value": "941a2bb3-af7d-455c-934e-eadeeabba5e6"
    },
    "scheduleIds": [
        {
            "value": "19a8243c-34dd-4707-955b-8394255efaf0"
        },
        {
            "value": "32797c4f-fd8e-4c37-9487-b9e747b47eec"
        },
        {
            "value": "96ec9ac9-7c36-4759-b867-d309287ac270"
        },
        {
            "value": "b1a0dbe7-cb20-433c-aa44-22a070191a72"
        },
        {
            "value": "b652ffa7-9e42-4ded-9338-d95d7de21dea"
        }
    ]
}

How to reproduce

  1. Create an API project using ASP.NET Core 8
  2. Add POST and GET endpoints for a resource and add request/response classes
  3. Generate an OpenAPI schema (e.g. using Swagger Nuget packages)
  4. Generate the TypeScript Kiota client
  5. [Serialization] Use the generated client to send a request to the server
    // Import guid-typescript and generated client etc.
    await apiClient.plans.post({
        name: 'Plan Name',
        auditoriumIds: [Guid.create(), Guid.create()],
        projectId: Guid.create(),
    });
  6. [Deserialization] Fetch a response from the server using the generated client:
    // Import guid-typescript and generated client etc.
    const planId = Guid.parse('b652ffa7-9e42-4ded-9338-d95d7de21dea');
    const plan = await apiClient.plans.byPlanId(planId).get();

Open API description file

Click to expand Open API description file
{
  "openapi": "3.0.1",
  "info": {
    "title": "ProjectsApi, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
    "version": "1.0"
  },
  "paths": {
    "/api/v1/Plans/{planId}": {
      "get": {
        "tags": [
          "PlanEndpoints"
        ],
        "operationId": "ApiRoutes.Plans.Get",
        "parameters": [
          {
            "name": "planId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found"
          }
        }
      },
      "put": {
        "tags": [
          "PlanEndpoints"
        ],
        "parameters": [
          {
            "name": "planId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdatePlanRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "No Content"
          },
          "404": {
            "description": "Not Found"
          }
        }
      },
      "delete": {
        "tags": [
          "PlanEndpoints"
        ],
        "parameters": [
          {
            "name": "planId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content"
          },
          "404": {
            "description": "Not Found"
          }
        }
      }
    },
    "/api/v1/Plans": {
      "get": {
        "tags": [
          "PlanEndpoints"
        ],
        "parameters": [
          {
            "name": "Page",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 0
            }
          },
          {
            "name": "PageSize",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 10
            }
          },
          {
            "name": "ProjectId",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetAllPlansResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "PlanEndpoints"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePlanRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "CreatePlanRequest": {
        "required": [
          "name",
          "projectId",
          "scheduleIds"
        ],
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "nullable": true
          },
          "projectId": {
            "type": "string",
            "format": "uuid"
          },
          "scheduleIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "GetAllPlansResponse": {
        "type": "object",
        "properties": {
          "totalItemsCount": {
            "type": "integer",
            "format": "int32"
          },
          "paginatedItems": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PlanResponse"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "PlanResponse": {
        "required": [
          "id",
          "name",
          "projectId",
          "scheduleIds"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "projectId": {
            "type": "string",
            "format": "uuid"
          },
          "scheduleIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "UpdatePlanRequest": {
        "required": [
          "name",
          "projectId",
          "scheduleIds"
        ],
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "nullable": true
          },
          "projectId": {
            "type": "string",
            "format": "uuid"
          },
          "scheduleIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      }
    }
  }
}

Kiota Version

Kiota Version: 1.19.1+d294e04ba7f756896878a7015df1648f9dc0bcde
Kiota TS package version: "@microsoft/kiota-bundle": "^1.0.0-preview.69"

Known Workarounds

Serialization

Use the following workaround to send Guid[] as string[] during runtime:

// "Lie" to TypeScript via the cast and say it's a ``Guid``, even though it's a ``string`` during runtime
const scheduleIdsAsStringArray = [Guid.create(), Guid.create()].map(guid => guid.toString() as unknown as Guid);
apiClient.plans.post({
    name: 'Plan Name',
    scheduleIds: scheduleIdsAsStringArray,
    projectId: Guid.create(),
});

Deserialization

TypeScript treats scheduleIds as a Guid[], while Kiota assigns it as a string[]. Consequently, we must iterate over the array to parse each element into a Guid. However, since TypeScript assumes every element is already a Guid, we first need to cast each element to a string. Since Guid cannot be directly cast to a string, we first cast it to unknown and then to string.

TypeScript expects each element of scheduleIds to already be a Guid, but Kiota assigns it as a string[]. To handle this, we need to convert each element to a Guid. Since TypeScript assumes the elements are already Guids, we first cast them to string by casting to unknown and then to string.

const planId = Guid.parse('b652ffa7-9e42-4ded-9338-d95d7de21dea');
const plan = await apiClient.plans.byPlanId(planId).get();
// Parse each id string to a Guid. TypeScript thinks ``idAsString`` is already a Guid so we must cast it to string before
plan.scheduleIds = plan.scheduleIds?.map(idAsString => Guid.parse(idAsString as unknown as string));

Configuration

  • OS: Windows 11
  • Architecture: x64

Other information

Could be related to #1467 and potentially fixed by #1475

@github-project-automation github-project-automation bot moved this to Needs Triage 🔍 in Kiota Nov 7, 2024
@andrueastman
Copy link
Member

Thanks for raising this @TheDusty01

With the changes in #1475 as well as the latest release of the generator, any chance you can confirm this is still an issue with the latest versions of the packages?

@andrueastman andrueastman moved this from Needs Triage 🔍 to Waits for author 🔁 in Kiota Nov 8, 2024
@andrueastman andrueastman added the status:waiting-for-author-feedback Issue that we've responded but needs author feedback to close label Nov 8, 2024
@TheDusty01
Copy link
Author

Hey, we tried it out with the new Kiota v1.20.0 release and kiota TS preview 74 but when trying to send the Guid array we now get a request generated like this:

Click to expand
���
=y»®`¹�ݯ�'n�¢DñÑ©�LæÀ�1�_2z0Ó׸Ó72üï�wy\Ñ©:�6Er-®Åúß?þ�ú㯿ýý��üÛ?Õ¿ÿüþ?~Ð\7��CU@²�ìÞ=FûÞ~þÖÏo?ê"�¾a%*�l��§ð6�ÊÞl�?¿ýô¾Vñ����ÉaPã
�]éù6>÷ç·�<uÚkNH��!0!�7�µ�u�q<G<��ö�Z§Áù;¦Â÷�Z��ß~öU{~	Þ2�¢Å`G�J=[��·ýüöS�Oz�$±�E?�ÊYnh\rr�Ñ=¶ã5°n�b��{�dfݵ7�òÄ�lEï�J&�[Cp�ôª(ÙëÜU?¿ý¼}4ø9,t�*Áɬá¼*nGuÒ�¸Ó%��4�V��í��ز�o\�Ø,�=�ê:�Y;èó�x�|ñ�%ó�Vb:S¢½�¨ñ�G"X��ã·Hæ6®
ßpp] ð�Á�á�e_·ÅÖ_ÞÖzY��·k�|Û�n�«�¿býùíçÈâ�çAùÝ@��A$Ð�u9å¾55E=��/X��Ñ�¹�Á*¸�r�MzÝm�V�ó~@v.�Å��>Ç.��OlE©�
!:±-�MuØ­���qMl}Î=ö
�¯�U �»À�ö
£Þý~~ûYì¯Þ;ð^�Ð=�!§à�û�=¾å+VE×|�÷�ȼ��~ÀÔ�I�í�,<�®��X¢��7(ó�ÆD�VÉ3O¸i���xÜ�HÏ�è0È\(/#9'!¶�µt�3'�a��MP%ÏSê¸N±�c;¥�z8�ë´åq�Uϯ>g��¦6�|
y���(�ï��â�/óûæ�;½¼(p#�¨�A�%èÆ��íf�8=|v.���@���Åá.¤õ(�
祾V��8·M����ýÇ[�×���;sÉ���gÙ�¢VB�Û��¼ÌKï«Ü��Ô:��uP{�îÃH=����°ÝÜ�A9��	A+�¼BQá½�Ñ�þ6¯Þ¼��½E�÷5¨¬®=�÷$¤;Ö-2м��� R�Ð.%)Ó«¯ßÐ�Ñ�I��e�X¶.%4¯���ô`ÈA�MÆa$�n�ræ
�Í�MHÔ�mh×�$¼Áë,кL�·ò�-«ãa?H¦�ÔÙ`�7��ý�ÈÆ�-äa'9È�Ôß�iÜ�+Ö%õ»¯M±dÕu��Þá^��
Sx�·d¹sâ÷�;EKá^� E�æÜ�cïÎÕ�C�[ì8½
Þ3>t�
Æ�]ï®�GWÅ@¦­ó"ÂE�¶ì�'N¸»�Wµê�Ò¯f{Äg�P�v'��C¿3 zwÛܦ&÷I)ps�yù4AÂæf¾�sÓG\gï�µaF�P��ktèX¼¥õ�OzIßZÒ�zÑ�2,p�JÙt��øû�eR���8U¤|ÁE�8niPnÏ!�ͳï�].�&Çû«-÷y²3ÛìõGÑNäæ�µ�èè�8¸ÁÌ�ÍÐ��2/nÑÅa�3�R�¼	a��&Dz³¾¹°���<:2üöÀ�	Nä©å�kíé�»Êë84R½b°��k#nCjòa¤ô�ZkC�ø/��!�nÛ×+<�>Ô˨��;y�87ø[8�/\WpÇpo�]õªOQ��øÝ�6� Gúý¨&Ut½' ¦�è%C�5(�®¥}0¦ß(Ä.Öè�' �¶Ä:p�-
��úA&pÅ>��wNò�ôÖÍ��î§<y£×Ë��¨ÍhÛ´!6^HÍuF�í_��
ôá*�E���yß×w�Ö§j�m�/�}8Ýë��Çàáò{1´¿YïÎwQ",�ÊvðÓ	[özR·Q¾¼Im³¡ÁG6üÖ ý�N�V½;õ�N�+½Ò`b�Ú�`��}�ä�Ç:��Çû��À8õ�ú!�]�YÏN�z|�%/m#�x�ÀÈ� �
Í�k�dÿ$ÁÃ�7JÀþ�ø¾`Ø�Ì/ýXn^üuï³òj8:	q4�e�}ãÝä3�d�ÑRë�ðn> \6â� ï�Â6§O9dS³"�>5 ¶³µAR)PîéoPÊ��+�ø"�mQ°M�Ö9ó¢üW�öa��¹ç¯�P��^�+«Ö���É­Õðö�O!й3eØ!b_y¢/åþ�Ygu(,��zÄ`N�í÷I.ͯCV^\Ëá� P³�Õ½ð*z;n]ýiËj�í�1\Og!ÄÞ�¼�*�êÞ�$Øb�ôà�6P��#��k!	�îü:d^���3Oå~U�¨³l«xþ�?¥ªu© Öc ^�qº }]Þû]×÷)Uºá�pîû����ÍcÒê�¿¦��h�$c ËÓ½�'�áèûIîG£ðùåYÌ��ß�â@`=¸u�<kÔÇð[¼Tú$�= (�[DP¤ËÃñÚ'	ZúJ�Îv\¿¨F¯&,j,nÚ��rÀxä6ýýÞ°eMM�®±Õ)¯ú°°o�½tàÆ�Ý×�a�ã�®ì¸�iª0��G4`�¿y����¾}��«ÝO÷fq�\8c�èR�o	è{�!VàW,�mº� ?�="N'�[ÂÞó�¦ó�%³ó�!cüÂ��]½�OT�f9ÆGªv�Ià��Ð�T7^°i÷º|:?rð+wc¯��#W¢Ai�´�½ì®×ú½4ݶ�ì=ÅÂ� óù:�ô�F)M[�EG�è@�h9(�Â9­|«0�?é.õ�#Ü��¥#	��®�0Åô_¶B�HV÷$d�Õ
ª¾!�¬�½ý�y£/Ô|�.�&/��PqÓ�ë²�ÞÇ!o����k@{û¨��Ûü�;®øll¬ëKé�k� \�ÙÇ~°^î_�ÐÏòU�­]@gd�t�,y�ûJâ�ëå¹zî��ÅMrð�wÎGéäkÉ�'l´±ê
hm@§FÚ^�éEç$�õËgµÝ\>ânh0c�ír¸§ÝìQî�d�qÝ��W¹�h¤»0�z®ºEañ)®UÒ��}ÓG�;�Ñ�9´Oöã:_#¥��
ð�#°�!Ø�pÅ«�"¿��ën¦�Ào�xm��S�ä�õ�µ¦
Ò«y`~��è�ó��{å�Õ�ü��¨�W��%Ã�X[�Pk½�¶÷Î÷Ñxáq��î�ú~�Nà�jÃòþ�Cù~|pú"�HHÁ�Gð§�j�ÕÌ��tϹ�²���
�£ �Ã�ä�ð¯¦/¨¢álû´å�ðJ8n�Þæì�©ÒmÒ¥Àz�(î�[g�/_��·ä +o�:8b�m¶��Î"°/.×tÂC¿Üî���S£�Ñ�¨¨��:�J|M¿at-1�bt6%�Øô<½°á´°øfÖ¥�º�ÔF�ðm°1�"}»ûâéþ,Ï;ÚR@ñf�ï�Û/¡��uâ¢}ò�6{�Îh[gfÖ��E�þ(2q×çeÞ�3¢QF�Þ»�üæ�ÙH���_[¾~ít
4N�écЧ���¸�äù°°dÏx�à;�ê�Ï2�!·±ì7¥Üß\È&��ýlE�1Åknë�Û5��Ô7-÷ p�Û�Ì\8����]W¡9Vý²oÎö 7Pä�FÂÙ�å�qÆì�§ô,��­�è
E��@o¡Æ{u�¨7Ýe7Ác�¥;À�&rË[�Ëó
dtq,i�ÛS
´
aÞ@|Ê�Ì7�ÕÜ�þÖPÂÙ�ÞÙ��Ïx�Ò�Wësâ��éy�Ü3»�]ày6Ü�1�k½�§4¶Ö%¡wÎ�C��çòºï�É\ßØ-e'¾�û��*&ÐU³�(�>½�=ÁÑ��4ì,�êêqm�ÎSQ»úª~­�Èwð�=rf��øÛ�±O��ïØßJ�x�N�Þkb�³ÍjpÖ�e¶o}M^µ§÷��²�£à��r§ß�éñÍzÏéÏBxãæÉF#E_�¸d�Uö�/!õ6m�Î�¿,¶"�à$µc<Îiºw�2
×ßÑok�À°e�ó1=u?�»8¤gM×-C�Óäûζ#Σ^OÎ'0�[<±�m,O%Ø[
§0ò0ý¿bÝC��
�i�µ.��­ÔiJ¿�K÷Gªçúi�Cß1y ãe�b]^^¿��%bïÑ�î1P¯�|&àx¶½²ÉåÛ`ð{)�Ð'�Yå�³¤¸�4+À�±?å`Éý&�sì[�
�0,{	¿áÞÊ@D�� Ï�iÂ�Éô�ªªc}T�yù�sìQª£{]t�$õ³ËÅ>��NÜÆ¥ ¯È綥	5ÞºÎ�÷ËþË9�} çJ�dðYyHÆÞQ«ò[��Yæ½7¬ï��dèÌ�ÓéÔ=q?U�è«¢�2ö�~Kp'�½}ÉBC�Ï�óÝï¹�õT¡z\ôQ(»%Õ��-ÿù·�þ³þû�ÿÖ?¿ÿ¬ûûµß¯ýçõëÏÏo?�ú÷�ÿö¯þç��¶û_ÿê�þ�¿ýùþø�ÿø»ÿõÇßþ�ÿ�øç_ÿþ_ý/ÿ�¿þýoÿÒù×?ÕÏï?}üî7~v÷�ñ�£��Pð¶7¦Ò�ûï�ùßÿú�@ø÷µ~_ëÿ�ò�þ/õ´Lå}�

Seems to be an error in the kiota json parser.

This is the generated code for parsing the request object to json:

/**
 * Serializes information the current object
 * @param writer Serialization writer to use to serialize this model
 */
// @ts-ignore
export function serializeUpdatePlanRequest(writer: SerializationWriter, updatePlanRequest: Partial<UpdatePlanRequest> | undefined | null = {}) : void {
    if (updatePlanRequest) {
        writer.writeCollectionOfPrimitiveValues<Guid>("scheduleIds", updatePlanRequest.scheduleIds);
        writer.writeTimeOnlyValue("endTime", updatePlanRequest.endTime);
        writer.writeStringValue("name", updatePlanRequest.name);
        writer.writeCollectionOfObjectValues<PlanConfigurationRequest>("planConfigurations", updatePlanRequest.planConfigurations, serializePlanConfigurationRequest);
        writer.writeCollectionOfObjectValues<PlanDataRequest>("planDatas", updatePlanRequest.planDatas, serializePlanDataRequest);
        writer.writeGuidValue("projectId", updatePlanRequest.projectId);
    }
}

Even if I comment out everything apart from the simple string name field, Kiota still sends a data as shown above to the server (but way shorter).

@microsoft-github-policy-service microsoft-github-policy-service bot added Needs: Attention 👋 and removed status:waiting-for-author-feedback Issue that we've responded but needs author feedback to close labels Nov 8, 2024
@baywet
Copy link
Member

baywet commented Nov 11, 2024

Hi @TheDusty01
This is caused by the request compression middleware handler.
You'll get more context in #1439 on how to disable it.
Can you share the updated payload once you've disabled compression please?

@baywet baywet added status:waiting-for-author-feedback Issue that we've responded but needs author feedback to close and removed Needs: Attention 👋 labels Nov 11, 2024
@TheDusty01
Copy link
Author

Thanks for clarifying. This issue got fixed by #1475.

Could you possibly link/share some details on how to setup a default kiota client with the request compression disabled? Creating a new http client by manually adding the default middleware seems weird:

const http = KiotaClientFactory.create(undefined, [
      new RetryHandler(), new RedirectHandler(), new ParametersNameDecodingHandler(), new UserAgentHandler(), new HeadersInspectionHandler()
    ]);
  
    const adapter = new FetchRequestAdapter(undefined, undefined, undefined, http);
    const client = createApiClient(adapter);

@microsoft-github-policy-service microsoft-github-policy-service bot added Needs: Attention 👋 and removed status:waiting-for-author-feedback Issue that we've responded but needs author feedback to close labels Nov 11, 2024
@baywet
Copy link
Member

baywet commented Nov 14, 2024

Thanks for confirming this is solved.
Let's keep the discussion about request compression focused in #1439 .
Closing.

@baywet baywet closed this as completed Nov 14, 2024
@github-project-automation github-project-automation bot moved this from Waits for author 🔁 to Done ✔️ in Kiota Nov 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Done ✔️
Development

No branches or pull requests

3 participants