From b63814c4202e3dcaff1a2fde58922b582f2bb573 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Thu, 23 Apr 2026 22:06:31 +0000
Subject: [PATCH 1/3] =?UTF-8?q?improve:=20surface=20enum=20allowed-values?=
=?UTF-8?q?=20in=20operation=20parameter=20XmlDocs=20(+3=20tests,=20324?=
=?UTF-8?q?=E2=86=92327)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Extract the enum-value formatter from DefinitionCompiler into a shared
XmlDoc.buildEnumDoc helper in Utils.fs, then use it in both places:
- DefinitionCompiler already added 'Allowed values: ...' to object
property XmlDocs; this PR refactors the local formatEnumValue function
out to the shared module (no behaviour change for properties).
- OperationCompiler now also adds enum value hints to the tags
in generated method XmlDocs, so IntelliSense shows valid values for
query/path/header/cookie parameters that have an enum schema.
Before:
/// List items
/// Filter by status
After:
/// List items
/// Filter by status
/// Allowed values: active, inactive, pending
Also refactors Schema.XmlDocTests.fs: extract parseSchema / getXmlDocAttr
helpers to eliminate duplication and add a getMethodXmlDoc helper used by
3 new tests covering the operation-parameter enum doc feature.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../DefinitionCompiler.fs | 22 +--
.../OperationCompiler.fs | 20 ++-
src/SwaggerProvider.DesignTime/Utils.fs | 23 +++
.../Schema.XmlDocTests.fs | 149 +++++++++++++++---
4 files changed, 167 insertions(+), 47 deletions(-)
diff --git a/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs b/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs
index 394c018a..31ac440a 100644
--- a/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs
+++ b/src/SwaggerProvider.DesignTime/DefinitionCompiler.fs
@@ -305,27 +305,7 @@ type DefinitionCompiler(schema: OpenApiDocument, provideNullable, useDateOnly: b
let pField, pProp = generateProperty propName pTy
- let formatEnumValue(v: System.Text.Json.Nodes.JsonNode) =
- if isNull v then
- "null"
- else
- // Format known JsonNode scalar types directly so documentation does not depend
- // on JSON serialization/escaping or specific ToString() implementations.
- match v with
- | :? System.Text.Json.Nodes.JsonValue as jv ->
- match jv.GetValueKind() with
- | System.Text.Json.JsonValueKind.String -> jv.GetValue()
- | System.Text.Json.JsonValueKind.Null -> "null"
- | _ -> jv.ToString()
- | _ -> v.ToString()
-
- let enumValuesDoc =
- if not(isNull propSchema.Enum) && propSchema.Enum.Count > 0 then
- let values = propSchema.Enum |> Seq.map formatEnumValue |> String.concat ", "
-
- Some $"Allowed values: {values}"
- else
- None
+ let enumValuesDoc = XmlDoc.buildEnumDoc propSchema.Enum
let propDoc =
match
diff --git a/src/SwaggerProvider.DesignTime/OperationCompiler.fs b/src/SwaggerProvider.DesignTime/OperationCompiler.fs
index 938dbf7e..d82de838 100644
--- a/src/SwaggerProvider.DesignTime/OperationCompiler.fs
+++ b/src/SwaggerProvider.DesignTime/OperationCompiler.fs
@@ -495,8 +495,26 @@ type OperationCompiler(schema: OpenApiDocument, defCompiler: DefinitionCompiler,
)
let xmlDoc =
+ let buildParamDesc(p: IOpenApiParameter) =
+ let enumDoc =
+ if not(isNull p.Schema) then
+ XmlDoc.buildEnumDoc p.Schema.Enum
+ else
+ None
+
+ match
+ p.Description
+ |> Option.ofObj
+ |> Option.filter(String.IsNullOrWhiteSpace >> not),
+ enumDoc
+ with
+ | None, None -> null
+ | Some d, None -> d
+ | None, Some ev -> ev
+ | Some d, Some ev -> $"{d}\n{ev}"
+
let paramDescriptions =
- [ for p in openApiParameters -> niceCamelName p.Name, p.Description
+ [ for p in openApiParameters -> niceCamelName p.Name, buildParamDesc p
if not(isNull operation.RequestBody) then
yield niceCamelName(payloadTy.ToString()), operation.RequestBody.Description ]
diff --git a/src/SwaggerProvider.DesignTime/Utils.fs b/src/SwaggerProvider.DesignTime/Utils.fs
index ff3890c6..6922593e 100644
--- a/src/SwaggerProvider.DesignTime/Utils.fs
+++ b/src/SwaggerProvider.DesignTime/Utils.fs
@@ -334,10 +334,33 @@ module SchemaReader =
module XmlDoc =
open System
+ open System.Collections.Generic
+ open System.Text.Json
+ open System.Text.Json.Nodes
let private escapeXml(s: string) =
s.Replace("&", "&").Replace("<", "<").Replace(">", ">")
+ let private formatEnumValue(v: JsonNode) =
+ if isNull v then
+ "null"
+ else
+ match v with
+ | :? JsonValue as jv ->
+ match jv.GetValueKind() with
+ | JsonValueKind.String -> jv.GetValue()
+ | JsonValueKind.Null -> "null"
+ | _ -> jv.ToString()
+ | _ -> v.ToString()
+
+ /// Returns "Allowed values: x, y, z" if the schema has enum values, otherwise None.
+ let buildEnumDoc(enumValues: IList) =
+ if isNull enumValues || enumValues.Count = 0 then
+ None
+ else
+ let values = enumValues |> Seq.map formatEnumValue |> String.concat ", "
+ Some $"Allowed values: {values}"
+
/// Builds a structured XML doc string from summary, description, and parameter descriptions.
/// paramDescriptions is a sequence of (camelCaseName, description) pairs.
let buildXmlDoc (summary: string) (description: string) (paramDescriptions: (string * string) seq) =
diff --git a/tests/SwaggerProvider.Tests/Schema.XmlDocTests.fs b/tests/SwaggerProvider.Tests/Schema.XmlDocTests.fs
index 13d71e98..75246086 100644
--- a/tests/SwaggerProvider.Tests/Schema.XmlDocTests.fs
+++ b/tests/SwaggerProvider.Tests/Schema.XmlDocTests.fs
@@ -6,6 +6,33 @@ open SwaggerProvider.Internal.Compilers
open Xunit
open FsUnitTyped
+let private parseSchema(schemaStr: string) =
+ let settings = OpenApiReaderSettings()
+ settings.AddYamlReader()
+
+ let readResult =
+ Microsoft.OpenApi.OpenApiDocument.Parse(schemaStr, settings = settings)
+
+ match readResult.Diagnostic with
+ | null -> ()
+ | diagnostic when diagnostic.Errors |> Seq.isEmpty |> not ->
+ let errorText =
+ diagnostic.Errors
+ |> Seq.map string
+ |> String.concat Environment.NewLine
+
+ failwithf "Failed to parse OpenAPI schema:%s%s" Environment.NewLine errorText
+ | _ -> ()
+
+ match readResult.Document with
+ | null -> failwith "Failed to parse OpenAPI schema: Document is null."
+ | doc -> doc
+
+let private getXmlDocAttr(m: System.Reflection.MemberInfo) =
+ m.GetCustomAttributesData()
+ |> Seq.tryFind(fun a -> a.AttributeType.Name = "TypeProviderXmlDocAttribute")
+ |> Option.map(fun a -> a.ConstructorArguments.[0].Value :?> string)
+
/// Compile a minimal OpenAPI v3 schema and return the XmlDoc string for the "Value" property
/// of "TestType", or None if no XmlDoc was added.
let private getPropertyXmlDoc(propYaml: string) : string option =
@@ -25,41 +52,47 @@ components:
%s"""
propYaml
- let settings = OpenApiReaderSettings()
- settings.AddYamlReader()
+ let schema = parseSchema schemaStr
- let readResult =
- Microsoft.OpenApi.OpenApiDocument.Parse(schemaStr, settings = settings)
+ let defCompiler = DefinitionCompiler(schema, false, false)
+ let opCompiler = OperationCompiler(schema, defCompiler, true, false, true)
+ opCompiler.CompileProvidedClients(defCompiler.Namespace)
- match readResult.Diagnostic with
- | null -> ()
- | diagnostic when diagnostic.Errors |> Seq.isEmpty |> not ->
- let errorText =
- diagnostic.Errors
- |> Seq.map string
- |> String.concat Environment.NewLine
+ let types = defCompiler.Namespace.GetProvidedTypes()
+ let testType = types |> List.find(fun t -> t.Name = "TestType")
- failwithf "Failed to parse OpenAPI schema:%s%s" Environment.NewLine errorText
- | _ -> ()
+ match testType.GetDeclaredProperty("Value") with
+ | null -> failwith "Property 'Value' not found on TestType"
+ | prop -> getXmlDocAttr prop
+
+/// Compile a minimal OpenAPI v3 schema and return the XmlDoc string for the generated
+/// operation method, or None if no XmlDoc was added.
+let private getMethodXmlDoc (pathsYaml: string) (operationId: string) : string option =
+ let schemaStr =
+ sprintf
+ """openapi: "3.0.0"
+info:
+ title: XmlDocMethodTest
+ version: "1.0.0"
+paths:
+%s
+components:
+ schemas: {}
+"""
+ pathsYaml
- let schema =
- match readResult.Document with
- | null -> failwith "Failed to parse OpenAPI schema: Document is null."
- | doc -> doc
+ let schema = parseSchema schemaStr
let defCompiler = DefinitionCompiler(schema, false, false)
let opCompiler = OperationCompiler(schema, defCompiler, true, false, true)
opCompiler.CompileProvidedClients(defCompiler.Namespace)
let types = defCompiler.Namespace.GetProvidedTypes()
- let testType = types |> List.find(fun t -> t.Name = "TestType")
- match testType.GetDeclaredProperty("Value") with
- | null -> failwith "Property 'Value' not found on TestType"
- | prop ->
- prop.GetCustomAttributesData()
- |> Seq.tryFind(fun a -> a.AttributeType.Name = "TypeProviderXmlDocAttribute")
- |> Option.map(fun a -> a.ConstructorArguments.[0].Value :?> string)
+ types
+ |> List.collect(fun t -> t.GetMethods() |> Array.toList)
+ |> List.tryFind(fun m -> m.Name.Equals(operationId, StringComparison.OrdinalIgnoreCase))
+ |> Option.bind getXmlDocAttr
// ── Property description ─────────────────────────────────────────────────────
@@ -75,7 +108,7 @@ let ``no XmlDoc added when no description and no enum``() =
let doc = getPropertyXmlDoc " type: string\n"
doc |> shouldEqual None
-// ── Enum values in XmlDoc ────────────────────────────────────────────────────
+// ── Enum values in property XmlDoc ────────────────────────────────────────────
[]
let ``string enum values appear in property XmlDoc``() =
@@ -110,3 +143,69 @@ let ``description is preserved alongside enum values``() =
doc.Value |> shouldContainText "Allowed values:"
doc.Value |> shouldContainText "active"
doc.Value |> shouldContainText "inactive"
+
+// ── Enum values in operation parameter XmlDoc ─────────────────────────────────
+
+let private statusEnumParamSchema =
+ """ /items:
+ get:
+ operationId: listItems
+ summary: List items
+ parameters:
+ - name: status
+ in: query
+ description: "Filter by status"
+ schema:
+ type: string
+ enum:
+ - active
+ - inactive
+ - pending
+ responses:
+ "200":
+ description: OK
+ content:
+ application/json:
+ schema:
+ type: string
+"""
+
+[]
+let ``enum query parameter values appear in method XmlDoc param tag``() =
+ let doc = getMethodXmlDoc statusEnumParamSchema "ListItems"
+ doc.IsSome |> shouldEqual true
+ doc.Value |> shouldContainText "Allowed values:"
+ doc.Value |> shouldContainText "active"
+ doc.Value |> shouldContainText "inactive"
+ doc.Value |> shouldContainText "pending"
+
+[]
+let ``enum parameter description and allowed values are both preserved in method XmlDoc``() =
+ let doc = getMethodXmlDoc statusEnumParamSchema "ListItems"
+ doc.IsSome |> shouldEqual true
+ doc.Value |> shouldContainText "Filter by status"
+ doc.Value |> shouldContainText "Allowed values:"
+
+let private noEnumParamSchema =
+ """ /health:
+ get:
+ operationId: getHealth
+ summary: Health check
+ parameters:
+ - name: verbose
+ in: query
+ description: "Verbose output"
+ schema:
+ type: boolean
+ responses:
+ "200":
+ description: OK
+"""
+
+[]
+let ``non-enum query parameter does not add Allowed values to XmlDoc``() =
+ let doc = getMethodXmlDoc noEnumParamSchema "GetHealth"
+ // There is a summary, so XmlDoc should be set, but must NOT contain "Allowed values"
+ match doc with
+ | None -> () // no XmlDoc at all — also fine
+ | Some d -> d |> shouldNotContainText "Allowed values:"
From ff4b33021655be184ac29fb79f49c367286886fe Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
Date: Thu, 23 Apr 2026 22:06:34 +0000
Subject: [PATCH 2/3] ci: trigger checks
From 9be7bbcc684db3df260b859eca11b63fcb84fa95 Mon Sep 17 00:00:00 2001
From: Sergey Tihon
Date: Fri, 24 Apr 2026 17:00:14 +0200
Subject: [PATCH 3/3] Update tests/SwaggerProvider.Tests/Schema.XmlDocTests.fs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
tests/SwaggerProvider.Tests/Schema.XmlDocTests.fs | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/tests/SwaggerProvider.Tests/Schema.XmlDocTests.fs b/tests/SwaggerProvider.Tests/Schema.XmlDocTests.fs
index 75246086..910abce1 100644
--- a/tests/SwaggerProvider.Tests/Schema.XmlDocTests.fs
+++ b/tests/SwaggerProvider.Tests/Schema.XmlDocTests.fs
@@ -205,7 +205,6 @@ let private noEnumParamSchema =
[]
let ``non-enum query parameter does not add Allowed values to XmlDoc``() =
let doc = getMethodXmlDoc noEnumParamSchema "GetHealth"
- // There is a summary, so XmlDoc should be set, but must NOT contain "Allowed values"
- match doc with
- | None -> () // no XmlDoc at all — also fine
- | Some d -> d |> shouldNotContainText "Allowed values:"
+ doc.IsSome |> shouldEqual true
+ doc.Value |> shouldContainText "Health check"
+ doc.Value |> shouldNotContainText "Allowed values:"