diff --git a/build.fsx b/build.fsx index 0729562..f66eec0 100644 --- a/build.fsx +++ b/build.fsx @@ -3,12 +3,9 @@ #r "nuget: Fake.Core.ReleaseNotes" #r "nuget: Fake.IO.FileSystem" #r "nuget: Fake.DotNet.Cli" -#r "nuget: Fake.DotNet.MSBuild" #r "nuget: Fake.DotNet.AssemblyInfoFile" #r "nuget: Fake.DotNet.Paket" -#r "nuget: Fake.DotNet.FSFormatting" #r "nuget: Fake.Tools.Git" -#r "nuget: Fake.Api.GitHub" // Boilerplate - https://github.com/fsprojects/FAKE/issues/2719#issuecomment-1470687052 System.Environment.GetCommandLineArgs() diff --git a/tests/SwaggerProvider.Tests/SwaggerProvider.Tests.fsproj b/tests/SwaggerProvider.Tests/SwaggerProvider.Tests.fsproj index 36790a6..0b57e74 100644 --- a/tests/SwaggerProvider.Tests/SwaggerProvider.Tests.fsproj +++ b/tests/SwaggerProvider.Tests/SwaggerProvider.Tests.fsproj @@ -16,6 +16,7 @@ + diff --git a/tests/SwaggerProvider.Tests/v3/Schema.TypeMappingTests.fs b/tests/SwaggerProvider.Tests/v3/Schema.TypeMappingTests.fs new file mode 100644 index 0000000..6b77d7a --- /dev/null +++ b/tests/SwaggerProvider.Tests/v3/Schema.TypeMappingTests.fs @@ -0,0 +1,210 @@ +module SwaggerProvider.Tests.v3_Schema_TypeMappingTests + +open System +open Microsoft.OpenApi.Reader +open SwaggerProvider.Internal.v3.Compilers +open Xunit +open FsUnitTyped + +/// Compile a minimal OpenAPI v3 schema containing one "TestType" object with a single +/// "Value" property defined by `propYaml`, and return that property's compiled .NET type. +let private compilePropertyType (propYaml: string) (required: bool) : Type = + let requiredBlock = + if required then + " required:\n - Value\n" + else + "" + + let schemaStr = + sprintf + """openapi: "3.0.0" +info: + title: TypeMappingTest + version: "1.0.0" +paths: {} +components: + schemas: + TestType: + type: object +%s properties: + Value: +%s""" + requiredBlock + propYaml + + let settings = OpenApiReaderSettings() + settings.AddYamlReader() + + let readResult = + Microsoft.OpenApi.OpenApiDocument.Parse(schemaStr, settings = settings) + + // Ensure the schema was parsed successfully before using it + 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 + | _ -> () + + let schema = + match readResult.Document with + | null -> failwith "Failed to parse OpenAPI schema: Document is null." + | doc -> doc + + let defCompiler = DefinitionCompiler(schema, 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.PropertyType + +// ── Required primitive types ───────────────────────────────────────────────── + +[] +let ``required boolean maps to bool``() = + let ty = compilePropertyType " type: boolean\n" true + + ty |> shouldEqual typeof + +[] +let ``required integer (no format) maps to int32``() = + let ty = compilePropertyType " type: integer\n" true + + ty |> shouldEqual typeof + +[] +let ``required integer int64 format maps to int64``() = + let ty = + compilePropertyType " type: integer\n format: int64\n" true + + ty |> shouldEqual typeof + +[] +let ``required number (no format) maps to float32``() = + let ty = compilePropertyType " type: number\n" true + + ty |> shouldEqual typeof + +[] +let ``required number double format maps to double``() = + let ty = + compilePropertyType " type: number\n format: double\n" true + + ty |> shouldEqual typeof + +// ── Required string formats ─────────────────────────────────────────────────── + +[] +let ``required string (no format) maps to string``() = + let ty = compilePropertyType " type: string\n" true + + ty |> shouldEqual typeof + +[] +let ``required string date-time format maps to DateTimeOffset``() = + let ty = + compilePropertyType " type: string\n format: date-time\n" true + + ty |> shouldEqual typeof + +[] +let ``required string date format maps to DateTimeOffset``() = + let ty = compilePropertyType " type: string\n format: date\n" true + + ty |> shouldEqual typeof + +[] +let ``required string uuid format maps to Guid``() = + let ty = compilePropertyType " type: string\n format: uuid\n" true + + ty |> shouldEqual typeof + +[] +let ``required string byte format maps to byte array``() = + let ty = compilePropertyType " type: string\n format: byte\n" true + + // DefinitionCompiler creates a rank-1 explicit array via MakeArrayType(1) + ty |> shouldEqual(typeof.MakeArrayType(1)) + +[] +let ``required string binary format maps to Stream``() = + let ty = + compilePropertyType " type: string\n format: binary\n" true + + ty |> shouldEqual typeof + +// ── Optional (non-required) value types are wrapped in Option ───────────── + +[] +let ``optional boolean maps to Option``() = + let ty = compilePropertyType " type: boolean\n" false + + ty |> shouldEqual(typedefof>.MakeGenericType(typeof)) + +[] +let ``optional integer maps to Option``() = + let ty = compilePropertyType " type: integer\n" false + + ty |> shouldEqual(typedefof>.MakeGenericType(typeof)) + +[] +let ``optional integer int64 maps to Option``() = + let ty = + compilePropertyType " type: integer\n format: int64\n" false + + ty |> shouldEqual(typedefof>.MakeGenericType(typeof)) + +[] +let ``optional number maps to Option``() = + let ty = compilePropertyType " type: number\n" false + + ty + |> shouldEqual(typedefof>.MakeGenericType(typeof)) + +[] +let ``optional number double maps to Option``() = + let ty = + compilePropertyType " type: number\n format: double\n" false + + ty + |> shouldEqual(typedefof>.MakeGenericType(typeof)) + +[] +let ``optional DateTimeOffset maps to Option``() = + let ty = + compilePropertyType " type: string\n format: date-time\n" false + + ty + |> shouldEqual(typedefof>.MakeGenericType(typeof)) + +[] +let ``optional Guid maps to Option``() = + let ty = + compilePropertyType " type: string\n format: uuid\n" false + + ty |> shouldEqual(typedefof>.MakeGenericType(typeof)) + +// ── Optional reference types are NOT wrapped (they are already nullable) ───── + +[] +let ``optional string is not wrapped in Option``() = + let ty = compilePropertyType " type: string\n" false + + // string is a reference type — not wrapped in Option even when non-required + ty |> shouldEqual typeof + +[] +let ``optional byte array is not wrapped in Option``() = + let ty = + compilePropertyType " type: string\n format: byte\n" false + + // byte[*] is a reference type — not wrapped in Option + ty |> shouldEqual(typeof.MakeArrayType(1))