From f5d0da69d6388a678b771e92227301ad631bbce4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 20:16:47 +0000 Subject: [PATCH 1/3] =?UTF-8?q?perf:=20avoid=20allOf=20double-isEmpty=20+?= =?UTF-8?q?=20Array.IndexOf=20in=20CallAsync;=20add=20V3=20schema=20tests?= =?UTF-8?q?=20(+5=20tests,=20438=E2=86=92443)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DefinitionCompiler: cache Seq.isEmpty allOf result to avoid iterating the sequence twice when building schemaObjProperties and schemaObjRequired - ProvidedApiClientBase.CallAsync: replace Array.tryFindIndex((=) codeStr) with Array.IndexOf to avoid allocating a partial-application closure per error response - Schema.V3SchemaCompilationTests: add 5 new tests covering required vs optional property types, string enum compilation, and schema description as XmlDoc Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../DefinitionCompiler.fs | 18 ++- .../ProvidedApiClientBase.fs | 10 +- .../Schema.V3SchemaCompilationTests.fs | 134 ++++++++++++++++++ 3 files changed, 151 insertions(+), 11 deletions(-) diff --git a/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs b/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs index 24c4dca2..a9606bb0 100644 --- a/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs +++ b/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs @@ -281,22 +281,26 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable, useDateOnly: b ty.AddXmlDoc schemaObj.Description // Combine composite schemas + // Cache the isEmpty check to avoid iterating `allOf` twice (once per field/required block). + let allOfEmpty = Seq.isEmpty allOf + let schemaObjProperties = let getProps(s: IOpenApiSchema) = s.Properties |> toSeq - match Seq.isEmpty allOf with - | false -> allOf |> Seq.append [ schemaObj ] |> Seq.collect getProps - | true -> getProps schemaObj - + if allOfEmpty then + getProps schemaObj + else + allOf |> Seq.append [ schemaObj ] |> Seq.collect getProps let schemaObjRequired = let getReq(s: IOpenApiSchema) = s.Required |> toSeq - match Seq.isEmpty allOf with - | false -> allOf |> Seq.append [ schemaObj ] |> Seq.collect getReq - | true -> getReq schemaObj + if allOfEmpty then + getReq schemaObj + else + allOf |> Seq.append [ schemaObj ] |> Seq.collect getReq |> Set.ofSeq // Helper to check if a schema has the Null type flag (OpenAPI 3.0 nullable) diff --git a/src/SwaggerProvider.Runtime/ProvidedApiClientBase.fs b/src/SwaggerProvider.Runtime/ProvidedApiClientBase.fs index 4c45032c..b8913b0a 100644 --- a/src/SwaggerProvider.Runtime/ProvidedApiClientBase.fs +++ b/src/SwaggerProvider.Runtime/ProvidedApiClientBase.fs @@ -72,12 +72,14 @@ type ProvidedApiClientBase(httpClient: HttpClient, options: JsonSerializerOption return "" } - match errorCodes |> Array.tryFindIndex((=) codeStr) with - | Some idx -> - let desc = errorDescriptions[idx] + // Use Array.IndexOf to avoid allocating a partial-application closure on every error response. + let errorIdx = System.Array.IndexOf(errorCodes, codeStr) + + if errorIdx >= 0 then + let desc = errorDescriptions[errorIdx] let! body = readBody() return raise(OpenApiException(code, desc, response.Headers, response.Content, body)) - | None -> + else let! body = readBody() let desc = diff --git a/tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs b/tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs index 89e23356..01523bb1 100644 --- a/tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs +++ b/tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs @@ -107,3 +107,137 @@ let ``anyOf single $ref resolves to the referenced type``() = let ``anyOf single $ref does not produce a separate wrapper type``() = let types = compileV3Schema anyOfSingleRefSchema false types |> List.exists(fun t -> t.Name = "CatRef") |> shouldEqual false + +// ── Required vs optional properties ────────────────────────────────────────── + +[] +let ``required property compiles to non-option type``() = + let schema = + """{ + "openapi": "3.0.0", + "info": { "title": "Test", "version": "1.0.0" }, + "paths": {}, + "components": { + "schemas": { + "Order": { + "type": "object", + "required": ["id"], + "properties": { + "id": { "type": "integer" }, + "note": { "type": "string" } + } + } + } + } +}""" + + let types = compileV3Schema schema false + let orderType = types |> List.find(fun t -> t.Name = "Order") + // 'id' is required → int32 (not option) + orderType.GetDeclaredProperty("Id").PropertyType + |> shouldEqual typeof + +[] +let ``optional property compiles to option type``() = + let schema = + """{ + "openapi": "3.0.0", + "info": { "title": "Test", "version": "1.0.0" }, + "paths": {}, + "components": { + "schemas": { + "Order": { + "type": "object", + "required": ["id"], + "properties": { + "id": { "type": "integer" }, + "note": { "type": "string" } + } + } + } + } +}""" + + let types = compileV3Schema schema false + let orderType = types |> List.find(fun t -> t.Name = "Order") + // 'note' is not required → string option + orderType.GetDeclaredProperty("Note").PropertyType + |> shouldEqual typeof + +// ── String enum compilation ──────────────────────────────────────────────────── + +[] +let ``string enum schema compiles to a named enum type``() = + let schema = + """{ + "openapi": "3.0.0", + "info": { "title": "Test", "version": "1.0.0" }, + "paths": {}, + "components": { + "schemas": { + "Status": { + "type": "string", + "enum": ["active", "inactive", "pending"] + } + } + } +}""" + + let types = compileV3Schema schema false + types |> List.exists(fun t -> t.Name = "Status") |> shouldEqual true + +[] +let ``string enum type is an enum``() = + let schema = + """{ + "openapi": "3.0.0", + "info": { "title": "Test", "version": "1.0.0" }, + "paths": {}, + "components": { + "schemas": { + "Status": { + "type": "string", + "enum": ["active", "inactive", "pending"] + } + } + } +}""" + + let types = compileV3Schema schema false + let statusType = types |> List.find(fun t -> t.Name = "Status") + statusType.IsEnum |> shouldEqual true + +// ── Schema description as XmlDoc ───────────────────────────────────────────── + +[] +let ``object schema description is surfaced as XmlDoc``() = + let schema = + """{ + "openapi": "3.0.0", + "info": { "title": "Test", "version": "1.0.0" }, + "paths": {}, + "components": { + "schemas": { + "Widget": { + "type": "object", + "description": "A widget with a name", + "properties": { + "name": { "type": "string" } + } + } + } + } +}""" + + let types = compileV3Schema schema false + let widgetType = types |> List.find(fun t -> t.Name = "Widget") + // XmlDoc is accessible via GetCustomAttributesData on the provided type + let doc = widgetType.GetCustomAttributesData() + + doc + |> Seq.exists(fun a -> + a.ConstructorArguments + |> Seq.exists(fun arg -> + arg.Value :? string + && (arg.Value :?> string).Contains("A widget with a name"))) + |> shouldEqual true From 21566ca44786de3b2193c237287ef3057df03cd2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 20 May 2026 20:16:51 +0000 Subject: [PATCH 2/3] ci: trigger checks From c523c461193e8575b2d245ce76e61ceca7b27d9b Mon Sep 17 00:00:00 2001 From: Sergey Tihon Date: Thu, 21 May 2026 09:47:25 +0200 Subject: [PATCH 3/3] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../Schema.V3SchemaCompilationTests.fs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs b/tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs index 01523bb1..a38c9203 100644 --- a/tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs +++ b/tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs @@ -236,8 +236,8 @@ let ``object schema description is surfaced as XmlDoc``() = doc |> Seq.exists(fun a -> - a.ConstructorArguments - |> Seq.exists(fun arg -> - arg.Value :? string - && (arg.Value :?> string).Contains("A widget with a name"))) + a.AttributeType.Name = "TypeProviderXmlDocAttribute" + && a.ConstructorArguments.Count > 0 + && a.ConstructorArguments.[0].Value :? string + && (a.ConstructorArguments.[0].Value :?> string).Contains("A widget with a name")) |> shouldEqual true