From cc9482296dcf3d096c1b0edaa80579dc6f38f53e Mon Sep 17 00:00:00 2001 From: Heath Stewart Date: Mon, 27 Mar 2023 06:49:37 -0700 Subject: [PATCH] Render response models breadth-first (#5802) Render response models breadth-first --- .../SwaggerSpec/SwaggerTypes.cs | 17 +- .../SwaggerAPIViewGeneratorTest.cs | 25 +++ .../fixtures/communicationserviceschat.json | 200 ++++++++++++++++++ 3 files changed, 238 insertions(+), 4 deletions(-) create mode 100644 tools/apiview/parsers/swagger-api-parser/SwaggerApiParserTest/fixtures/communicationserviceschat.json diff --git a/tools/apiview/parsers/swagger-api-parser/SwaggerApiParser/SwaggerSpec/SwaggerTypes.cs b/tools/apiview/parsers/swagger-api-parser/SwaggerApiParser/SwaggerSpec/SwaggerTypes.cs index 9b56420241e..e950973d884 100644 --- a/tools/apiview/parsers/swagger-api-parser/SwaggerApiParser/SwaggerSpec/SwaggerTypes.cs +++ b/tools/apiview/parsers/swagger-api-parser/SwaggerApiParser/SwaggerSpec/SwaggerTypes.cs @@ -85,6 +85,7 @@ public bool IsPropertyRequired(string propertyName) [JsonPropertyName("$ref")] public string Ref { get; set; } private List tableItems; + private Queue<(BaseSchema, SerializeContext)> propertyQueue = new(); public bool IsRefObj() @@ -158,6 +159,7 @@ private CodeFileToken[] TokenSerializeInternal(SerializeContext context, BaseSch if (schema.properties?.Count != 0) { + // BUGBUG: Herein lies the problem. We're recursing down into child objects when we should be queuing them instead. TokenSerializeProperties(context, schema, schema.properties, ret, ref flattenedTableItems, serializeRef); } @@ -203,6 +205,13 @@ private CodeFileToken[] TokenSerializeInternal(SerializeContext context, BaseSch } } + // Now recurse into nested model definitions so all properties are grouped with their models. + while (this.propertyQueue.TryDequeue(out var property)) + { + var (item, childContext) = property; + ret.AddRange(item.TokenSerializeInternal(childContext, item, ref flattenedTableItems, serializeRef)); + } + return ret.ToArray(); } @@ -222,7 +231,7 @@ private static List GetPropertyKeywordsFromBaseSchema(BaseSchema baseSch return keywords.ToList(); } - private static void TokenSerializeProperties(SerializeContext context, BaseSchema schema, Dictionary properties, List ret, ref List flattenedTableItems, + private void TokenSerializeProperties(SerializeContext context, BaseSchema schema, Dictionary properties, List ret, ref List flattenedTableItems, Boolean serializeRef = true) { if (properties == null) @@ -248,7 +257,7 @@ private static void TokenSerializeProperties(SerializeContext context, BaseSchem ret.Add(TokenSerializer.NewLine()); if (serializeRef) { - ret.AddRange(schema.TokenSerializeInternal(new SerializeContext(context.intent + 1, context.IteratorPath), kv.Value, ref flattenedTableItems, serializeRef)); + this.propertyQueue.Enqueue((kv.Value, new SerializeContext(context.intent + 1, context.IteratorPath))); } } // Circular reference case: the ref won't be expanded. @@ -291,7 +300,7 @@ private static void TokenSerializeProperties(SerializeContext context, BaseSchem } } - private static void TokenSerializeArray(SerializeContext context, List ret, BaseSchema arraySchema, ref List flattenedTableItems, Boolean serializeRef) + private void TokenSerializeArray(SerializeContext context, List ret, BaseSchema arraySchema, ref List flattenedTableItems, Boolean serializeRef) { ret.Add(new CodeFileToken("array", CodeFileTokenKind.Keyword)); if (arraySchema.items == null) @@ -322,7 +331,7 @@ private static void TokenSerializeArray(SerializeContext context, List (item, index)) + .SkipWhile(elem => elem.item.Value != "200") + .Where(elem => elem.item.Value == "nextLink" || elem.item.Value == "retentionPolicy") + .Take(2); + Assert.Collection(elems, elem => Assert.Equal("nextLink", elem.item.Value), elem => Assert.Equal("retentionPolicy", elem.item.Value)); + } } diff --git a/tools/apiview/parsers/swagger-api-parser/SwaggerApiParserTest/fixtures/communicationserviceschat.json b/tools/apiview/parsers/swagger-api-parser/SwaggerApiParserTest/fixtures/communicationserviceschat.json new file mode 100644 index 00000000000..c8c596604cd --- /dev/null +++ b/tools/apiview/parsers/swagger-api-parser/SwaggerApiParserTest/fixtures/communicationserviceschat.json @@ -0,0 +1,200 @@ +{ + "swagger": "2.0", + "info": { + "title": "Azure Communication Chat Service", + "description": "Azure Communication Chat Service", + "version": "2023-07-01-preview" + }, + "paths": { + "/chat/threads": { + "get": { + "tags": [ + "Threads" + ], + "summary": "Gets the list of chat threads of a user.", + "operationId": "Chat_ListChatThreads", + "produces": [ + "application/json" + ], + "parameters": [ + { + "in": "query", + "name": "maxPageSize", + "description": "The maximum number of chat threads returned per page.", + "type": "integer", + "format": "int32" + }, + { + "in": "query", + "name": "startTime", + "description": "The earliest point in time to get chat threads up to. The timestamp should be in RFC3339 format: `yyyy-MM-ddTHH:mm:ssZ`.", + "type": "string", + "format": "date-time" + }, + { + "$ref": "#/parameters/ApiVersionParameter" + } + ], + "responses": { + "200": { + "description": "Request successful. The action returns a `GetThreadsResponse` resource.", + "schema": { + "$ref": "#/definitions/ChatThreadsItemCollection" + } + } + }, + "x-ms-pageable": { + "nextLinkName": "nextLink", + "itemName": "value" + } + } + } + }, + "definitions": { + "ChatThreadItem": { + "description": "Summary information of a chat thread.", + "required": [ + "id", + "topic" + ], + "type": "object", + "properties": { + "id": { + "description": "Chat thread id.", + "type": "string", + "example": "19:uni01_uy5ucb66ugp3lrhe7pxso6xx4hsmm3dl6eyjfefv2n6x3rrurpea@thread.v2" + }, + "topic": { + "description": "Chat thread topic.", + "type": "string", + "example": "Lunch Chat thread" + }, + "deletedOn": { + "format": "date-time", + "description": "The timestamp when the chat thread was deleted. The timestamp is in RFC3339 format: `yyyy-MM-ddTHH:mm:ssZ`.", + "type": "string", + "example": "2020-10-30T10:50:50Z" + }, + "lastMessageReceivedOn": { + "format": "date-time", + "description": "The timestamp when the last message arrived at the server. The timestamp is in RFC3339 format: `yyyy-MM-ddTHH:mm:ssZ`.", + "type": "string", + "readOnly": true, + "example": "2020-10-30T10:50:50Z" + }, + "retentionPolicy": { + "$ref": "#/definitions/RetentionPolicy" + } + } + }, + "ChatThreadsItemCollection": { + "description": "Collection of chat threads.", + "required": [ + "value" + ], + "type": "object", + "properties": { + "value": { + "description": "Collection of chat threads.", + "type": "array", + "items": { + "$ref": "#/definitions/ChatThreadItem" + } + }, + "nextLink": { + "description": "If there are more chat threads that can be retrieved, the next link will be populated.", + "type": "string", + "readOnly": true + } + } + }, + "RetentionPolicy": { + "description": "Data retention policy for auto deletion.", + "type": "object", + "discriminator": "policyType", + "properties": { + "policyType": { + "description": "Retention Policy Type", + "enum": [ + "basedOnThreadCreationDate" + ], + "type": "string", + "x-ms-enum": { + "name": "policyType", + "modelAsString": true, + "values": [ + { + "value": "basedOnThreadCreationDate", + "description": "Thread retention policy based on thread creation date." + } + ] + } + } + }, + "required": [ + "policyType" + ] + }, + "BasedOnThreadCreationDateRetentionPolicy": { + "description": "Thread retention policy based on thread creation date.", + "type": "object", + "x-ms-discriminator-value": "basedOnThreadCreationDate", + "allOf": [ + { + "$ref": "#/definitions/RetentionPolicy" + } + ], + "properties": { + "daysAfterCreation": { + "type": "integer", + "format": "int32", + "description": "Indicates how many days after the thread creation the thread will be deleted. Only 90 is accepted for now." + } + }, + "required": [ + "daysAfterCreation" + ] + } + }, + "parameters": { + "ApiVersionParameter": { + "in": "query", + "name": "api-version", + "description": "Version of API to invoke.", + "required": true, + "type": "string", + "x-ms-parameter-location": "method" + }, + "Endpoint": { + "in": "path", + "name": "endpoint", + "description": "The endpoint of the Azure Communication resource.", + "required": true, + "type": "string", + "x-ms-skip-url-encoding": true, + "x-ms-parameter-location": "client" + } + }, + "securityDefinitions": { + "Authorization": { + "type": "apiKey", + "name": "Authorization", + "in": "header", + "description": "An ACS (Azure Communication Services) user access token." + } + }, + "security": [ + { + "Authorization": [] + } + ], + "x-ms-parameterized-host": { + "hostTemplate": "{endpoint}", + "useSchemePrefix": false, + "parameters": [ + { + "$ref": "#/parameters/Endpoint" + } + ] + } +}