From 7dfc0a2502c52fe5c0d8a9258fddabcf76a4748f Mon Sep 17 00:00:00 2001 From: Moises Nessim Date: Tue, 30 May 2023 20:36:22 -0500 Subject: [PATCH 01/26] Update version of FSharp.SystemTextJson to 1.1.23 Use net6.0 since net5.0 is not supported Use fluid version of JsonConverterOptions Allow SkippableOptionFields, since the lack of it was causing tests to fail --- .github/workflows/ci.yml | 4 ++-- build.sh | 2 +- src/Directory.Build.props | 2 +- .../FSharp.Data.JsonSchema.fsproj | 6 +++--- src/FSharp.Data.JsonSchema/Serializer.fs | 12 +++++++----- .../FSharp.Data.JsonSchema.Tests.fsproj | 2 +- 6 files changed, 15 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f518144..ec6e4f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,13 +19,13 @@ jobs: - name: Use dotnet CLI uses: actions/setup-dotnet@v1 with: - dotnet-version: "5.0.x" # SDK Version to use. + dotnet-version: "6.0.x" # SDK Version to use. - name: Build run: | dotnet restore dotnet build -c Release --no-restore - name: Test - run: dotnet test -c Release --no-restore --no-build --framework net5.0 --logger "GitHubActions;report-warnings=false" + run: dotnet test -c Release --no-restore --no-build --framework net6.0 --logger "GitHubActions;report-warnings=false" - name: Pack run: dotnet pack -c Release --no-restore --no-build --include-symbols -o out - name: Upload artifact diff --git a/build.sh b/build.sh index b816886..bd41f2d 100755 --- a/build.sh +++ b/build.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash dotnet restore dotnet build -c Release --no-restore -dotnet test -c Release --no-restore --no-build --framework net5.0 +dotnet test -c Release --no-restore --no-build --framework net6.0 dotnet pack -c Release -o bin --no-restore --no-build diff --git a/src/Directory.Build.props b/src/Directory.Build.props index bd8786a..0de1774 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,6 +1,6 @@ - 2.0.2 + 2.1.2 Ryan Riley Ryan Riley https://github.com/panesofglass/FSharp.Data.JsonSchema diff --git a/src/FSharp.Data.JsonSchema/FSharp.Data.JsonSchema.fsproj b/src/FSharp.Data.JsonSchema/FSharp.Data.JsonSchema.fsproj index f427b31..d2087bb 100644 --- a/src/FSharp.Data.JsonSchema/FSharp.Data.JsonSchema.fsproj +++ b/src/FSharp.Data.JsonSchema/FSharp.Data.JsonSchema.fsproj @@ -1,14 +1,14 @@  - netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0 + netstandard2.0;netstandard2.1;netcoreapp3.1;net6.0 - + - \ No newline at end of file + diff --git a/src/FSharp.Data.JsonSchema/Serializer.fs b/src/FSharp.Data.JsonSchema/Serializer.fs index dae0cc6..bffdcb0 100644 --- a/src/FSharp.Data.JsonSchema/Serializer.fs +++ b/src/FSharp.Data.JsonSchema/Serializer.fs @@ -19,11 +19,13 @@ type Json private () = options.Converters.Add( JsonFSharpConverter( - JsonUnionEncoding.InternalTag - ||| JsonUnionEncoding.NamedFields - ||| JsonUnionEncoding.UnwrapFieldlessTags - ||| JsonUnionEncoding.UnwrapOption, - unionTagName = Json.DefaultCasePropertyName + JsonFSharpOptions + .Default() + .WithUnionInternalTag() + .WithUnionNamedFields() + .WithUnwrapOption() + .WithSkippableOptionFields() + .WithUnionTagName(Json.DefaultCasePropertyName) ) ) diff --git a/test/FSharp.Data.JsonSchema.Tests/FSharp.Data.JsonSchema.Tests.fsproj b/test/FSharp.Data.JsonSchema.Tests/FSharp.Data.JsonSchema.Tests.fsproj index 9bdd0e4..5069248 100644 --- a/test/FSharp.Data.JsonSchema.Tests/FSharp.Data.JsonSchema.Tests.fsproj +++ b/test/FSharp.Data.JsonSchema.Tests/FSharp.Data.JsonSchema.Tests.fsproj @@ -1,7 +1,7 @@ Exe - netcoreapp3.1;net5.0 + netcoreapp3.1;net6.0 false false From 56cdb7c8a2d01ebfafd4b442da2da5de8e723cf1 Mon Sep 17 00:00:00 2001 From: Moises Nessim Date: Tue, 6 Jun 2023 16:40:22 -0500 Subject: [PATCH 02/26] Serializer: UnwrapFieldlessTags --- src/FSharp.Data.JsonSchema/Serializer.fs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/FSharp.Data.JsonSchema/Serializer.fs b/src/FSharp.Data.JsonSchema/Serializer.fs index bffdcb0..804efa5 100644 --- a/src/FSharp.Data.JsonSchema/Serializer.fs +++ b/src/FSharp.Data.JsonSchema/Serializer.fs @@ -26,6 +26,7 @@ type Json private () = .WithUnwrapOption() .WithSkippableOptionFields() .WithUnionTagName(Json.DefaultCasePropertyName) + .WithUnionUnwrapFieldlessTags() ) ) From c06727d5e926ee225fc9de650af54a62c597c586 Mon Sep 17 00:00:00 2001 From: Moises Nessim Date: Tue, 6 Jun 2023 16:41:50 -0500 Subject: [PATCH 03/26] Windows: run tests in net6.0 --- build.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.ps1 b/build.ps1 index 0d38c28..acd306b 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,4 +1,4 @@ dotnet restore dotnet build -c Release --no-restore -dotnet test -c Release --no-restore --no-build --framework net5.0 +dotnet test -c Release --no-restore --no-build --framework net6.0 dotnet pack -c Release -o bin --no-restore --no-build From 5398d0f77ea4515af9779978d0d17c8c2b5bc0a3 Mon Sep 17 00:00:00 2001 From: Moises Nessim Date: Fri, 9 Jun 2023 17:47:43 -0500 Subject: [PATCH 04/26] JsonSchema: Use VerifyTests [B1PC-4] --- .github/workflows/ci.yml | 7 + .gitignore | 3 + src/FSharp.Data.JsonSchema/JsonSchema.fs | 22 ++ .../.editorconfig | 9 + .../.gitattributes | 3 + .../FSharp.Data.JsonSchema.Tests.fsproj | 3 + .../GeneratorTests.fs | 118 +++---- .../FSharp.Data.JsonSchema.Tests/TestTypes.fs | 4 + .../ValidationTests.fs | 37 +++ ...Class generates proper schema.verified.txt | 14 + ....Enum generates proper schema.verified.txt | 16 + ...imal generates correct schema.verified.txt | 64 ++++ ... list generates proper schema.verified.txt | 35 ++ ...se DU generates proper schema.verified.txt | 80 +++++ ...ested generates proper schema.verified.txt | 298 ++++++++++++++++++ ...t__T_ generates proper schema.verified.txt | 24 ++ ...ption generates proper schema.verified.txt | 17 + ...ecord generates proper schema.verified.txt | 14 + ...gleDU generates proper schema.verified.txt | 16 + ...n__a_ generates proper schema.verified.txt | 14 + ..._int_ generates proper schema.verified.txt | 9 + 21 files changed, 739 insertions(+), 68 deletions(-) create mode 100644 test/FSharp.Data.JsonSchema.Tests/.editorconfig create mode 100644 test/FSharp.Data.JsonSchema.Tests/.gitattributes create mode 100644 test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Class generates proper schema.verified.txt create mode 100644 test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Enum generates proper schema.verified.txt create mode 100644 test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.FSharp decimal generates correct schema.verified.txt create mode 100644 test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.FSharp list generates proper schema.verified.txt create mode 100644 test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Multi-case DU generates proper schema.verified.txt create mode 100644 test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Nested generates proper schema.verified.txt create mode 100644 test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.PaginatedResult__T_ generates proper schema.verified.txt create mode 100644 test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.RecWithOption generates proper schema.verified.txt create mode 100644 test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Record generates proper schema.verified.txt create mode 100644 test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.TestSingleDU generates proper schema.verified.txt create mode 100644 test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.option__a_ generates proper schema.verified.txt create mode 100644 test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.option_int_ generates proper schema.verified.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ec6e4f3..cccb927 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,13 @@ jobs: run: dotnet test -c Release --no-restore --no-build --framework net6.0 --logger "GitHubActions;report-warnings=false" - name: Pack run: dotnet pack -c Release --no-restore --no-build --include-symbols -o out + - name: Upload Test Results + if: failure() + uses: actions/upload-artifact@v2 + with: + name: verify-test-results + path: | + **/*.received.* - name: Upload artifact uses: actions/upload-artifact@v1 with: diff --git a/.gitignore b/.gitignore index b04b17e..ee6680e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. +# Verify Tests received files +*.received.* + # User-specific files *.suo *.user diff --git a/src/FSharp.Data.JsonSchema/JsonSchema.fs b/src/FSharp.Data.JsonSchema/JsonSchema.fs index e38ab13..6694bd8 100644 --- a/src/FSharp.Data.JsonSchema/JsonSchema.fs +++ b/src/FSharp.Data.JsonSchema/JsonSchema.fs @@ -195,6 +195,26 @@ type MultiCaseDuSchemaProcessor(?casePropertyName) = interface ISchemaProcessor with member this.Process(context) = this.Process(context) + +type RecordSchemaProcessor() = + member this.Process(context: SchemaProcessorContext) = + if + FSharpType.IsRecord(context.Type) + then + printfn "Type: %s" context.Type.Name + let schema = context.Schema + + for KeyValue(propertyName, property) in schema.Properties do + printfn "Property: %s, Required: %b, Type: %A" propertyName property.IsRequired property.Type + if property.Type.HasFlag JsonObjectType.Null |> not then + property.IsRequired <- true + + interface ISchemaProcessor with + member this.Process(context) = this.Process(context) + + + + [] type internal SchemaNameGenerator() = inherit DefaultSchemaNameGenerator() @@ -231,6 +251,7 @@ type internal ReflectionService() = else base.GetDescription(contextualType, defaultReferenceTypeNullHandling, settings) + [] type Generator private () = static let cache = @@ -248,6 +269,7 @@ type Generator private () = settings.SchemaProcessors.Add(OptionSchemaProcessor()) settings.SchemaProcessors.Add(SingleCaseDuSchemaProcessor()) settings.SchemaProcessors.Add(MultiCaseDuSchemaProcessor(?casePropertyName = casePropertyName)) + // settings.SchemaProcessors.Add(RecordSchemaProcessor()) fun ty -> JsonSchema.FromType(ty, settings) /// Creates a generator using the specified casePropertyName and generationProviders. diff --git a/test/FSharp.Data.JsonSchema.Tests/.editorconfig b/test/FSharp.Data.JsonSchema.Tests/.editorconfig new file mode 100644 index 0000000..4171d33 --- /dev/null +++ b/test/FSharp.Data.JsonSchema.Tests/.editorconfig @@ -0,0 +1,9 @@ +# Verify settings +[*.{received,verified}.{txt,xml,json}] +charset = "utf-8-bom" +end_of_line = lf +indent_size = unset +indent_style = unset +insert_final_newline = false +tab_width = unset +trim_trailing_whitespace = false \ No newline at end of file diff --git a/test/FSharp.Data.JsonSchema.Tests/.gitattributes b/test/FSharp.Data.JsonSchema.Tests/.gitattributes new file mode 100644 index 0000000..ce824b8 --- /dev/null +++ b/test/FSharp.Data.JsonSchema.Tests/.gitattributes @@ -0,0 +1,3 @@ +*.verified.txt text eol=lf working-tree-encoding=UTF-8 +*.verified.xml text eol=lf working-tree-encoding=UTF-8 +*.verified.json text eol=lf working-tree-encoding=UTF-8 diff --git a/test/FSharp.Data.JsonSchema.Tests/FSharp.Data.JsonSchema.Tests.fsproj b/test/FSharp.Data.JsonSchema.Tests/FSharp.Data.JsonSchema.Tests.fsproj index 5069248..764c61f 100644 --- a/test/FSharp.Data.JsonSchema.Tests/FSharp.Data.JsonSchema.Tests.fsproj +++ b/test/FSharp.Data.JsonSchema.Tests/FSharp.Data.JsonSchema.Tests.fsproj @@ -20,6 +20,9 @@ + + + \ No newline at end of file diff --git a/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs b/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs index 107f80a..15594ba 100644 --- a/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs +++ b/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs @@ -2,6 +2,32 @@ module FSharp.Data.JsonSchema.Tests.GeneratorTests open FSharp.Data.JsonSchema open Expecto +open VerifyTests +open VerifyExpecto + +// do VerifyDiffPlex.Initialize() +do ClipboardAccept.Enable() + +let verifySettings = + let s = VerifySettings() + s.UseDirectory("generator-verified") + s + +type VerifyBuilder(name,focusState) = + inherit TestCaseBuilder(name,focusState) + let makeValidFilePath (input: string) : string = + let invalidChars = System.IO.Path.GetInvalidFileNameChars() |> Array.append [| '\''; '"'; '<'; '>'; '|'; '?'; '*'; ':'; '\\'|] + + let replaceChar = '_' + input.Trim() + |> Seq.map(fun c -> if Array.contains c invalidChars then replaceChar else c ) + |> System.String.Concat + + member __.Return<'T>(v:'T) = Verifier.Verify(makeValidFilePath name, v,settings= verifySettings).Wait() + +let verify name = VerifyBuilder(name,FocusState.Normal) + +let json ( schema: NJsonSchema.JsonSchema ) = schema.ToJson() [] let tests = @@ -13,72 +39,19 @@ let tests = testList "schema generation" - [ test "Enum generates proper schema" { - let expected = - """{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "TestEnum", - "type": "string", - "description": "", - "x-enumNames": [ - "First", - "Second", - "Third" - ], - "enum": [ - "First", - "Second", - "Third" - ] -}""" - - let actual = generator (typeof) - "╰〳 ಠ 益 ಠೃ 〵╯" |> equal actual expected + [ verify "Enum generates proper schema" { + return generator (typeof) |> json } - test "Class generates proper schema" { - let expected = - """{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "TestClass", - "type": "object", - "additionalProperties": false, - "properties": { - "firstName": { - "type": "string" - }, - "lastName": { - "type": "string" - } - } -}""" - - let actual = generator (typeof) - "╰〳 ಠ 益 ಠೃ 〵╯" |> equal actual expected + verify "Class generates proper schema" { + return generator (typeof) |> json } - test "Record generates proper schema" { - let expected = - """{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "TestRecord", - "type": "object", - "additionalProperties": false, - "properties": { - "firstName": { - "type": "string" - }, - "lastName": { - "type": "string" - } - } -}""" - - let actual = generator (typeof) - "╰〳 ಠ 益 ಠೃ 〵╯" |> equal actual expected + verify "Record generates proper schema" { + return generator (typeof) |> json } - test "option<'a> generates proper schema" { + verify "option<'a> generates proper schema" { let expected = """{ "$schema": "http://json-schema.org/draft-04/schema#", @@ -98,9 +71,10 @@ let tests = let ty = typeof> let actual = generator (ty) "╰〳 ಠ 益 ಠೃ 〵╯" |> equal actual expected + return json actual } - test "option generates proper schema" { + verify "option generates proper schema" { let expected = """{ "$schema": "http://json-schema.org/draft-04/schema#", @@ -115,9 +89,10 @@ let tests = let ty = typeof> let actual = generator (ty) "╰〳 ಠ 益 ಠೃ 〵╯" |> equal actual expected + return json actual } - test "TestSingleDU generates proper schema" { + verify "TestSingleDU generates proper schema" { let expected = """{ "$schema": "http://json-schema.org/draft-04/schema#", @@ -139,9 +114,10 @@ let tests = let ty = typeof let actual = generator (ty) "╰〳 ಠ 益 ಠೃ 〵╯" |> equal actual expected + return json actual } - test "Multi-case DU generates proper schema" { + verify "Multi-case DU generates proper schema" { let expected = """{ "$schema": "http://json-schema.org/draft-04/schema#", @@ -215,9 +191,10 @@ let tests = let ty = typeof let actual = generator (ty) "╰〳 ಠ 益 ಠೃ 〵╯" |> equal actual expected + return json actual } - test "Nested generates proper schema" { + verify "Nested generates proper schema" { let expected = """{ "$schema": "http://json-schema.org/draft-04/schema#", @@ -520,9 +497,10 @@ let tests = let actual = generator (typeof) "╰〳 ಠ 益 ಠೃ 〵╯" |> equal actual expected + return json actual } - test "RecWithOption generates proper schema" { + verify "RecWithOption generates proper schema" { let expected = """{ "$schema": "http://json-schema.org/draft-04/schema#", @@ -544,9 +522,10 @@ let tests = let actual = generator (typeof) "╰〳 ಠ 益 ಠೃ 〵╯" |> equal actual expected + return json actual } - test "PaginatedResult<'T> generates proper schema" { + verify "PaginatedResult<'T> generates proper schema" { let expected = """{ "$schema": "http://json-schema.org/draft-04/schema#", @@ -575,9 +554,10 @@ let tests = let actual = generator (typeof>) "╰〳 ಠ 益 ಠೃ 〵╯" |> equal actual expected + return json actual } - test "FSharp list generates proper schema" { + verify "FSharp list generates proper schema" { let expected = """ { "$schema": "http://json-schema.org/draft-04/schema#", @@ -617,8 +597,9 @@ let tests = """ let actual = generator typeof "╰〳 ಠ 益 ಠೃ 〵╯" |> equal actual expected + return json actual } - test "FSharp decimal generates correct schema" { + verify "FSharp decimal generates correct schema" { let expected = """ { "$schema": "http://json-schema.org/draft-04/schema#", @@ -687,4 +668,5 @@ let tests = """ let actual = generator typeof "╰〳 ಠ 益 ಠೃ 〵╯" |> equal actual expected + return json actual } ] diff --git a/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs b/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs index 2afdd20..a83ed34 100644 --- a/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs +++ b/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs @@ -47,6 +47,10 @@ type RecWithOption = { Name: string Description: string option } +type RecWithNullable = + { Need: int + NoNeed: System.Nullable } + module Util = let stripWhitespace text = System.Text.RegularExpressions.Regex.Replace(text, @"\s+", "") diff --git a/test/FSharp.Data.JsonSchema.Tests/ValidationTests.fs b/test/FSharp.Data.JsonSchema.Tests/ValidationTests.fs index 6b3e1e2..eb29b41 100644 --- a/test/FSharp.Data.JsonSchema.Tests/ValidationTests.fs +++ b/test/FSharp.Data.JsonSchema.Tests/ValidationTests.fs @@ -30,6 +30,43 @@ let tests = "╰〳 ಠ 益 ಠೃ 〵╯" |> Expect.equal actual (Ok()) } + test "Record missing field does not validate against schema" { + let schema = generator (typeof) + + let json = """{"firstName":"Ryan"}""" + + let actual = Validation.validate schema json + "╰〳 ಠ 益 ಠೃ 〵╯" |> Expect.isError actual + } + + test "Record missing optional field validates against schema" { + let schema = generator (typeof) + + let json = """{"name":"Ryan"}""" + + let actual = Validation.validate schema json + "╰〳 ಠ 益 ಠೃ 〵╯" |> Expect.isOk actual + } + + test "Record missing nullable field validates against schema" { + let schema = generator (typeof) + + let json = """{"need":1}""" + + let actual = Validation.validate schema json + "╰〳 ಠ 益 ಠೃ 〵╯" |> Expect.isOk actual + } + + test "Record missing array field validates against schema" { + let schema = generator (typeof) + + let json = """{"id":1,"name":"Ryan"}""" + + let actual = Validation.validate schema json + "╰〳 ಠ 益 ಠೃ 〵╯" |> Expect.isOk actual + } + + test "None validates against schema for option<_>" { let schema = generator(typeof>) let json = Json.Serialize(None, "tag") diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Class generates proper schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Class generates proper schema.verified.txt new file mode 100644 index 0000000..90a8949 --- /dev/null +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Class generates proper schema.verified.txt @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "TestClass", + "type": "object", + "additionalProperties": false, + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + } + } +} \ No newline at end of file diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Enum generates proper schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Enum generates proper schema.verified.txt new file mode 100644 index 0000000..70620cf --- /dev/null +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Enum generates proper schema.verified.txt @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "TestEnum", + "type": "string", + "description": "", + "x-enumNames": [ + "First", + "Second", + "Third" + ], + "enum": [ + "First", + "Second", + "Third" + ] +} \ No newline at end of file diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.FSharp decimal generates correct schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.FSharp decimal generates correct schema.verified.txt new file mode 100644 index 0000000..f97a0a0 --- /dev/null +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.FSharp decimal generates correct schema.verified.txt @@ -0,0 +1,64 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "TestDecimal", + "type": "object", + "additionalProperties": false, + "properties": { + "test": { + "$ref": "#/definitions/DuWithDecimal" + }, + "total": { + "type": "number", + "format": "decimal" + } + }, + "definitions": { + "DuWithDecimal": { + "definitions": { + "Nothing": { + "type": "string", + "default": "Nothing", + "additionalProperties": false, + "x-enumNames": [ + "Nothing" + ], + "enum": [ + "Nothing" + ] + }, + "Amount": { + "type": "object", + "additionalProperties": false, + "required": [ + "tag", + "item" + ], + "properties": { + "tag": { + "type": "string", + "default": "Amount", + "x-enumNames": [ + "Amount" + ], + "enum": [ + "Amount" + ] + }, + "item": { + "type": "number", + "format": "decimal" + } + } + } + }, + "anyOf": [ + { + "$ref": "#/definitions/DuWithDecimal/definitions/Nothing" + }, + { + "$ref": "#/definitions/DuWithDecimal/definitions/Amount" + } + ] + } + } +} \ No newline at end of file diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.FSharp list generates proper schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.FSharp list generates proper schema.verified.txt new file mode 100644 index 0000000..b67b8cc --- /dev/null +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.FSharp list generates proper schema.verified.txt @@ -0,0 +1,35 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "TestList", + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "name": { + "type": "string" + }, + "records": { + "type": "array", + "items": { + "$ref": "#/definitions/TestRecord" + } + } + }, + "definitions": { + "TestRecord": { + "type": "object", + "additionalProperties": false, + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Multi-case DU generates proper schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Multi-case DU generates proper schema.verified.txt new file mode 100644 index 0000000..8295a2a --- /dev/null +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Multi-case DU generates proper schema.verified.txt @@ -0,0 +1,80 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "TestDU", + "definitions": { + "Case": { + "type": "string", + "default": "Case", + "additionalProperties": false, + "x-enumNames": [ + "Case" + ], + "enum": [ + "Case" + ] + }, + "WithOneField": { + "type": "object", + "additionalProperties": false, + "required": [ + "tag", + "item" + ], + "properties": { + "tag": { + "type": "string", + "default": "WithOneField", + "x-enumNames": [ + "WithOneField" + ], + "enum": [ + "WithOneField" + ] + }, + "item": { + "type": "integer", + "format": "int32" + } + } + }, + "WithNamedFields": { + "type": "object", + "additionalProperties": false, + "required": [ + "tag", + "name", + "value" + ], + "properties": { + "tag": { + "type": "string", + "default": "WithNamedFields", + "x-enumNames": [ + "WithNamedFields" + ], + "enum": [ + "WithNamedFields" + ] + }, + "name": { + "type": "string" + }, + "value": { + "type": "number", + "format": "double" + } + } + } + }, + "anyOf": [ + { + "$ref": "#/definitions/Case" + }, + { + "$ref": "#/definitions/WithOneField" + }, + { + "$ref": "#/definitions/WithNamedFields" + } + ] +} \ No newline at end of file diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Nested generates proper schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Nested generates proper schema.verified.txt new file mode 100644 index 0000000..89f185a --- /dev/null +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Nested generates proper schema.verified.txt @@ -0,0 +1,298 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Nested", + "definitions": { + "TestRecord": { + "title": "TestRecord", + "type": "object", + "additionalProperties": false, + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + } + } + }, + "Rec": { + "type": "object", + "additionalProperties": false, + "required": [ + "tag", + "item" + ], + "properties": { + "tag": { + "type": "string", + "default": "Rec", + "x-enumNames": [ + "Rec" + ], + "enum": [ + "Rec" + ] + }, + "item": { + "$ref": "#/definitions/TestRecord" + } + } + }, + "TestDU": { + "title": "TestDU", + "definitions": { + "Case": { + "type": "string", + "default": "Case", + "additionalProperties": false, + "x-enumNames": [ + "Case" + ], + "enum": [ + "Case" + ] + }, + "WithOneField": { + "type": "object", + "additionalProperties": false, + "required": [ + "tag", + "item" + ], + "properties": { + "tag": { + "type": "string", + "default": "WithOneField", + "x-enumNames": [ + "WithOneField" + ], + "enum": [ + "WithOneField" + ] + }, + "item": { + "type": "integer", + "format": "int32" + } + } + }, + "WithNamedFields": { + "type": "object", + "additionalProperties": false, + "required": [ + "tag", + "name", + "value" + ], + "properties": { + "tag": { + "type": "string", + "default": "WithNamedFields", + "x-enumNames": [ + "WithNamedFields" + ], + "enum": [ + "WithNamedFields" + ] + }, + "name": { + "type": "string" + }, + "value": { + "type": "number", + "format": "double" + } + } + } + }, + "anyOf": [ + { + "$ref": "#/definitions/TestDU/definitions/Case" + }, + { + "$ref": "#/definitions/TestDU/definitions/WithOneField" + }, + { + "$ref": "#/definitions/TestDU/definitions/WithNamedFields" + } + ] + }, + "Du": { + "type": "object", + "additionalProperties": false, + "required": [ + "tag", + "item" + ], + "properties": { + "tag": { + "type": "string", + "default": "Du", + "x-enumNames": [ + "Du" + ], + "enum": [ + "Du" + ] + }, + "item": { + "$ref": "#/definitions/TestDU" + } + } + }, + "TestSingleDU": { + "title": "TestSingleDU", + "type": "string", + "additionalProperties": false, + "x-enumNames": [ + "Single", + "Double", + "Triple" + ], + "enum": [ + "Single", + "Double", + "Triple" + ] + }, + "SingleDu": { + "type": "object", + "additionalProperties": false, + "required": [ + "tag", + "item" + ], + "properties": { + "tag": { + "type": "string", + "default": "SingleDu", + "x-enumNames": [ + "SingleDu" + ], + "enum": [ + "SingleDu" + ] + }, + "item": { + "$ref": "#/definitions/TestSingleDU" + } + } + }, + "TestEnum": { + "title": "TestEnum", + "type": "string", + "description": "", + "x-enumNames": [ + "First", + "Second", + "Third" + ], + "enum": [ + "First", + "Second", + "Third" + ] + }, + "Enum": { + "type": "object", + "additionalProperties": false, + "required": [ + "tag", + "item" + ], + "properties": { + "tag": { + "type": "string", + "default": "Enum", + "x-enumNames": [ + "Enum" + ], + "enum": [ + "Enum" + ] + }, + "item": { + "$ref": "#/definitions/TestEnum" + } + } + }, + "TestClass": { + "title": "TestClass", + "type": "object", + "additionalProperties": false, + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + } + } + }, + "Class": { + "type": "object", + "additionalProperties": false, + "required": [ + "tag", + "item" + ], + "properties": { + "tag": { + "type": "string", + "default": "Class", + "x-enumNames": [ + "Class" + ], + "enum": [ + "Class" + ] + }, + "item": { + "$ref": "#/definitions/TestClass" + } + } + }, + "Opt": { + "type": "object", + "additionalProperties": false, + "required": [ + "tag" + ], + "properties": { + "tag": { + "type": "string", + "default": "Opt", + "x-enumNames": [ + "Opt" + ], + "enum": [ + "Opt" + ] + }, + "item": { + "$ref": "#/definitions/TestRecord" + } + } + } + }, + "anyOf": [ + { + "$ref": "#/definitions/Rec" + }, + { + "$ref": "#/definitions/Du" + }, + { + "$ref": "#/definitions/SingleDu" + }, + { + "$ref": "#/definitions/Enum" + }, + { + "$ref": "#/definitions/Class" + }, + { + "$ref": "#/definitions/Opt" + } + ] +} \ No newline at end of file diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.PaginatedResult__T_ generates proper schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.PaginatedResult__T_ generates proper schema.verified.txt new file mode 100644 index 0000000..43df531 --- /dev/null +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.PaginatedResult__T_ generates proper schema.verified.txt @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "PaginatedResultOfObject", + "type": "object", + "additionalProperties": false, + "properties": { + "page": { + "type": "integer", + "format": "int32" + }, + "perPage": { + "type": "integer", + "format": "int32" + }, + "total": { + "type": "integer", + "format": "int32" + }, + "results": { + "type": "array", + "items": {} + } + } +} \ No newline at end of file diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.RecWithOption generates proper schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.RecWithOption generates proper schema.verified.txt new file mode 100644 index 0000000..bd75363 --- /dev/null +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.RecWithOption generates proper schema.verified.txt @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "RecWithOption", + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": [ + "null", + "string" + ] + } + } +} \ No newline at end of file diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Record generates proper schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Record generates proper schema.verified.txt new file mode 100644 index 0000000..7eb0bf2 --- /dev/null +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Record generates proper schema.verified.txt @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "TestRecord", + "type": "object", + "additionalProperties": false, + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + } + } +} \ No newline at end of file diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.TestSingleDU generates proper schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.TestSingleDU generates proper schema.verified.txt new file mode 100644 index 0000000..9ed89e2 --- /dev/null +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.TestSingleDU generates proper schema.verified.txt @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "TestSingleDU", + "type": "string", + "additionalProperties": false, + "x-enumNames": [ + "Single", + "Double", + "Triple" + ], + "enum": [ + "Single", + "Double", + "Triple" + ] +} \ No newline at end of file diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.option__a_ generates proper schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.option__a_ generates proper schema.verified.txt new file mode 100644 index 0000000..2d81f00 --- /dev/null +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.option__a_ generates proper schema.verified.txt @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Any", + "type": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ], + "additionalProperties": false +} \ No newline at end of file diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.option_int_ generates proper schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.option_int_ generates proper schema.verified.txt new file mode 100644 index 0000000..f542b0c --- /dev/null +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.option_int_ generates proper schema.verified.txt @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Integer", + "type": [ + "integer", + "null" + ], + "format": "int32" +} \ No newline at end of file From 7492fe50a252677738f37ad9265d2168bd89e567 Mon Sep 17 00:00:00 2001 From: Moises Nessim Date: Tue, 13 Jun 2023 10:09:23 -0500 Subject: [PATCH 05/26] Make all non-optional fields required in the schema Refactor the serializer options to be more DRY Modify and fix tests [B1PC-4] --- src/FSharp.Data.JsonSchema/JsonSchema.fs | 4 +- src/FSharp.Data.JsonSchema/Serializer.fs | 130 +--- test/FSharp.Data.JsonSchema.Tests/Bug10.fs | 9 + .../GeneratorTests.fs | 613 +----------------- .../JsonSerializationTests.fs | 20 + .../ValidationTests.fs | 4 +- ...imal generates correct schema.verified.txt | 4 + ... list generates proper schema.verified.txt | 9 + ...ested generates proper schema.verified.txt | 4 + ...t__T_ generates proper schema.verified.txt | 6 + ...lable generates proper schema.verified.txt | 22 + ...ption generates proper schema.verified.txt | 3 + ...ecord generates proper schema.verified.txt | 4 + 13 files changed, 129 insertions(+), 703 deletions(-) create mode 100644 test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.RecWithNullable generates proper schema.verified.txt diff --git a/src/FSharp.Data.JsonSchema/JsonSchema.fs b/src/FSharp.Data.JsonSchema/JsonSchema.fs index 6694bd8..5ce99a6 100644 --- a/src/FSharp.Data.JsonSchema/JsonSchema.fs +++ b/src/FSharp.Data.JsonSchema/JsonSchema.fs @@ -201,11 +201,9 @@ type RecordSchemaProcessor() = if FSharpType.IsRecord(context.Type) then - printfn "Type: %s" context.Type.Name let schema = context.Schema for KeyValue(propertyName, property) in schema.Properties do - printfn "Property: %s, Required: %b, Type: %A" propertyName property.IsRequired property.Type if property.Type.HasFlag JsonObjectType.Null |> not then property.IsRequired <- true @@ -269,7 +267,7 @@ type Generator private () = settings.SchemaProcessors.Add(OptionSchemaProcessor()) settings.SchemaProcessors.Add(SingleCaseDuSchemaProcessor()) settings.SchemaProcessors.Add(MultiCaseDuSchemaProcessor(?casePropertyName = casePropertyName)) - // settings.SchemaProcessors.Add(RecordSchemaProcessor()) + settings.SchemaProcessors.Add(RecordSchemaProcessor()) fun ty -> JsonSchema.FromType(ty, settings) /// Creates a generator using the specified casePropertyName and generationProviders. diff --git a/src/FSharp.Data.JsonSchema/Serializer.fs b/src/FSharp.Data.JsonSchema/Serializer.fs index 804efa5..8b97a67 100644 --- a/src/FSharp.Data.JsonSchema/Serializer.fs +++ b/src/FSharp.Data.JsonSchema/Serializer.fs @@ -3,43 +3,43 @@ namespace FSharp.Data open System.Text.Json open System.Text.Json.Serialization -[] -type Json private () = - static let optionsCache = - System.Collections.Concurrent.ConcurrentDictionary(dict [| Json.DefaultCasePropertyName, Json.DefaultOptions |]) +[] +module private Defaults = + let private jsonFSharpConverterOptions = + JsonFSharpOptions + .Default() + .WithUnionInternalTag() + .WithUnionNamedFields() + .WithUnwrapOption() + .WithSkippableOptionFields() + .WithUnionUnwrapFieldlessTags() - static member internal DefaultCasePropertyName = "kind" + let mkOptions unionTagName = - static member DefaultOptions = - let options = - JsonSerializerOptions(PropertyNamingPolicy = JsonNamingPolicy.CamelCase) + let options = JsonSerializerOptions(PropertyNamingPolicy = JsonNamingPolicy.CamelCase) options.Converters.Add(JsonStringEnumConverter()) options.Converters.Add( JsonFSharpConverter( - JsonFSharpOptions - .Default() - .WithUnionInternalTag() - .WithUnionNamedFields() - .WithUnwrapOption() - .WithSkippableOptionFields() - .WithUnionTagName(Json.DefaultCasePropertyName) - .WithUnionUnwrapFieldlessTags() + jsonFSharpConverterOptions + .WithUnionTagName(unionTagName) ) ) options - (* - JsonSerializerOptions( - Converters=[|Converters.StringEnumConverter() - OptionConverter() - SingleCaseDuConverter() - MultiCaseDuConverter()|], - ContractResolver=Serialization.CamelCasePropertyNamesContractResolver(), - NullValueHandling=NullValueHandling.Ignore) -*) + + +[] +type Json private () = + + static let optionsCache = + System.Collections.Concurrent.ConcurrentDictionary(dict [| Json.DefaultCasePropertyName, Json.DefaultOptions |]) + + static member internal DefaultCasePropertyName = "kind" + + static member DefaultOptions = mkOptions Json.DefaultCasePropertyName static member Serialize(value) = JsonSerializer.Serialize(value, Json.DefaultOptions) @@ -48,22 +48,7 @@ type Json private () = let options = optionsCache.GetOrAdd( casePropertyName, - let options = - JsonSerializerOptions(IgnoreNullValues = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase) - - options.Converters.Add(JsonStringEnumConverter()) - - options.Converters.Add( - JsonFSharpConverter( - JsonUnionEncoding.InternalTag - ||| JsonUnionEncoding.NamedFields - ||| JsonUnionEncoding.UnwrapFieldlessTags - ||| JsonUnionEncoding.UnwrapOption, - unionTagName = casePropertyName - ) - ) - - options + fun key -> mkOptions casePropertyName ) JsonSerializer.Serialize(value, options) @@ -81,26 +66,7 @@ type Json private () = let options = optionsCache.GetOrAdd( casePropertyName, - fun key -> - let options = - JsonSerializerOptions( - IgnoreNullValues = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - ) - - options.Converters.Add(JsonStringEnumConverter()) - - options.Converters.Add( - JsonFSharpConverter( - JsonUnionEncoding.InternalTag - ||| JsonUnionEncoding.NamedFields - ||| JsonUnionEncoding.UnwrapFieldlessTags - ||| JsonUnionEncoding.UnwrapOption, - unionTagName = casePropertyName - ) - ) - - options + fun key -> mkOptions casePropertyName ) JsonSerializer.Deserialize<'T>(json, options = options) @@ -109,26 +75,7 @@ type Json private () = let options = optionsCache.GetOrAdd( casePropertyName, - fun key -> - let options = - JsonSerializerOptions( - IgnoreNullValues = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - ) - - options.Converters.Add(JsonStringEnumConverter()) - - options.Converters.Add( - JsonFSharpConverter( - JsonUnionEncoding.InternalTag - ||| JsonUnionEncoding.NamedFields - ||| JsonUnionEncoding.UnwrapFieldlessTags - ||| JsonUnionEncoding.UnwrapOption, - unionTagName = casePropertyName - ) - ) - - options + fun key -> mkOptions casePropertyName ) JsonSerializer.Deserialize<'T>(json, options = options) @@ -137,26 +84,7 @@ type Json private () = let options = optionsCache.GetOrAdd( casePropertyName, - fun key -> - let options = - JsonSerializerOptions( - IgnoreNullValues = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - ) - - options.Converters.Add(JsonStringEnumConverter()) - - options.Converters.Add( - JsonFSharpConverter( - JsonUnionEncoding.InternalTag - ||| JsonUnionEncoding.NamedFields - ||| JsonUnionEncoding.UnwrapFieldlessTags - ||| JsonUnionEncoding.UnwrapOption, - unionTagName = casePropertyName - ) - ) - - options + fun key -> mkOptions casePropertyName ) JsonSerializer.Deserialize<'T>(&json, options = options) diff --git a/test/FSharp.Data.JsonSchema.Tests/Bug10.fs b/test/FSharp.Data.JsonSchema.Tests/Bug10.fs index 9a9cc4e..e9a096f 100644 --- a/test/FSharp.Data.JsonSchema.Tests/Bug10.fs +++ b/test/FSharp.Data.JsonSchema.Tests/Bug10.fs @@ -35,6 +35,7 @@ let tests = "title": "outerReq", "type": "object", "additionalProperties": false, + "required":["outer1"], "properties": { "outer1": { "$ref": "#/definitions/Inner" @@ -44,6 +45,7 @@ let tests = "Inner": { "type": "object", "additionalProperties": false, + "required":["inner1","inner2"], "properties": { "inner1": { "type": "integer", @@ -57,6 +59,11 @@ let tests = } }""" + """ + +{"$schema":"http://json-schema.org/draft-04/schema#","title":"outer","type":"object","additionalProperties":false,"required":["outer1"],"properties":{"outer1":{"oneOf":[{"type":"null"},{"$ref":"#/definitions/Inner"}]}},"definitions":{"Inner":{"type":"object","additionalProperties":false,"required":["inner1","inner2"],"properties":{"inner1":{"type":"integer","format":"int32"},"inner2":{"type":"string"}}}}} + """ + let gen = Generator.CreateMemoized("out") let actual = gen (typeof) equal actual expected "Expected detailed type definition in definitions." @@ -69,6 +76,7 @@ let tests = "title": "outer", "type": "object", "additionalProperties": false, + "required":["outer1"], "properties": { "outer1": { "oneOf": [ @@ -81,6 +89,7 @@ let tests = "Inner": { "type": "object", "additionalProperties": false, + "required":["inner1","inner2"], "properties": { "inner1": { "type": "integer", diff --git a/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs b/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs index 15594ba..8e72463 100644 --- a/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs +++ b/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs @@ -40,633 +40,52 @@ let tests = testList "schema generation" [ verify "Enum generates proper schema" { - return generator (typeof) |> json + return generator typeof |> json } verify "Class generates proper schema" { - return generator (typeof) |> json + return generator typeof |> json } verify "Record generates proper schema" { - return generator (typeof) |> json + return generator typeof |> json } verify "option<'a> generates proper schema" { - let expected = - """{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Any", - "type": [ - "array", - "boolean", - "integer", - "null", - "number", - "object", - "string" - ], - "additionalProperties": false -}""" - - let ty = typeof> - let actual = generator (ty) - "╰〳 ಠ 益 ಠೃ 〵╯" |> equal actual expected - return json actual + return generator typeof> |> json } verify "option generates proper schema" { - let expected = - """{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Integer", - "type": [ - "integer", - "null" - ], - "format": "int32" -}""" - - let ty = typeof> - let actual = generator (ty) - "╰〳 ಠ 益 ಠೃ 〵╯" |> equal actual expected - return json actual + return generator typeof> |> json } verify "TestSingleDU generates proper schema" { - let expected = - """{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "TestSingleDU", - "type": "string", - "additionalProperties": false, - "x-enumNames": [ - "Single", - "Double", - "Triple" - ], - "enum": [ - "Single", - "Double", - "Triple" - ] -}""" - - let ty = typeof - let actual = generator (ty) - "╰〳 ಠ 益 ಠೃ 〵╯" |> equal actual expected - return json actual + return generator typeof |> json } verify "Multi-case DU generates proper schema" { - let expected = - """{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "TestDU", - "definitions": { - "Case": { - "type": "string", - "default": "Case", - "additionalProperties": false, - "x-enumNames": ["Case"], - "enum": ["Case"] - }, - "WithOneField": { - "type": "object", - "additionalProperties": false, - "required": [ - "tag", - "item" - ], - "properties": { - "tag": { - "type": "string", - "default": "WithOneField", - "x-enumNames": ["WithOneField"], - "enum": ["WithOneField"] - }, - "item": { - "type": "integer", - "format": "int32" - } - } - }, - "WithNamedFields": { - "type": "object", - "additionalProperties": false, - "required": [ - "tag", - "name", - "value" - ], - "properties": { - "tag": { - "type": "string", - "default": "WithNamedFields", - "x-enumNames": ["WithNamedFields"], - "enum": ["WithNamedFields"] - }, - "name": { - "type": "string" - }, - "value": { - "type": "number", - "format": "double" - } - } - } - }, - "anyOf": [ - { - "$ref": "#/definitions/Case" - }, - { - "$ref": "#/definitions/WithOneField" - }, - { - "$ref": "#/definitions/WithNamedFields" - } - ] -}""" - - let ty = typeof - let actual = generator (ty) - "╰〳 ಠ 益 ಠೃ 〵╯" |> equal actual expected - return json actual + return generator typeof |> json } verify "Nested generates proper schema" { - let expected = - """{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Nested", - "definitions": { - "TestRecord": { - "title": "TestRecord", - "type": "object", - "additionalProperties": false, - "properties": { - "firstName": { - "type": "string" - }, - "lastName": { - "type": "string" - } - } - }, - "Rec": { - "type": "object", - "additionalProperties": false, - "required": [ - "tag", - "item" - ], - "properties": { - "tag": { - "type": "string", - "default": "Rec", - "x-enumNames": [ - "Rec" - ], - "enum": [ - "Rec" - ] - }, - "item": { - "$ref": "#/definitions/TestRecord" - } - } - }, - "TestDU": { - "title": "TestDU", - "definitions": { - "Case": { - "type": "string", - "default": "Case", - "additionalProperties": false, - "x-enumNames": [ - "Case" - ], - "enum": [ - "Case" - ] - }, - "WithOneField": { - "type": "object", - "additionalProperties": false, - "required": [ - "tag", - "item" - ], - "properties": { - "tag": { - "type": "string", - "default": "WithOneField", - "x-enumNames": [ - "WithOneField" - ], - "enum": [ - "WithOneField" - ] - }, - "item": { - "type": "integer", - "format": "int32" - } - } - }, - "WithNamedFields": { - "type": "object", - "additionalProperties": false, - "required": [ - "tag", - "name", - "value" - ], - "properties": { - "tag": { - "type": "string", - "default": "WithNamedFields", - "x-enumNames": [ - "WithNamedFields" - ], - "enum": [ - "WithNamedFields" - ] - }, - "name": { - "type": "string" - }, - "value": { - "type": "number", - "format": "double" - } - } - } - }, - "anyOf": [ - { - "$ref": "#/definitions/TestDU/definitions/Case" - }, - { - "$ref": "#/definitions/TestDU/definitions/WithOneField" - }, - { - "$ref": "#/definitions/TestDU/definitions/WithNamedFields" - } - ] - }, - "Du": { - "type": "object", - "additionalProperties": false, - "required": [ - "tag", - "item" - ], - "properties": { - "tag": { - "type": "string", - "default": "Du", - "x-enumNames": [ - "Du" - ], - "enum": [ - "Du" - ] - }, - "item": { - "$ref": "#/definitions/TestDU" - } - } - }, - "TestSingleDU": { - "title": "TestSingleDU", - "type": "string", - "additionalProperties": false, - "x-enumNames": [ - "Single", - "Double", - "Triple" - ], - "enum": [ - "Single", - "Double", - "Triple" - ] - }, - "SingleDu": { - "type": "object", - "additionalProperties": false, - "required": [ - "tag", - "item" - ], - "properties": { - "tag": { - "type": "string", - "default": "SingleDu", - "x-enumNames": [ - "SingleDu" - ], - "enum": [ - "SingleDu" - ] - }, - "item": { - "$ref": "#/definitions/TestSingleDU" - } - } - }, - "TestEnum": { - "title": "TestEnum", - "type": "string", - "description": "", - "x-enumNames": [ - "First", - "Second", - "Third" - ], - "enum": [ - "First", - "Second", - "Third" - ] - }, - "Enum": { - "type": "object", - "additionalProperties": false, - "required": [ - "tag", - "item" - ], - "properties": { - "tag": { - "type": "string", - "default": "Enum", - "x-enumNames": [ - "Enum" - ], - "enum": [ - "Enum" - ] - }, - "item": { - "$ref": "#/definitions/TestEnum" - } - } - }, - "TestClass": { - "title": "TestClass", - "type": "object", - "additionalProperties": false, - "properties": { - "firstName": { - "type": "string" - }, - "lastName": { - "type": "string" - } - } - }, - "Class": { - "type": "object", - "additionalProperties": false, - "required": [ - "tag", - "item" - ], - "properties": { - "tag": { - "type": "string", - "default": "Class", - "x-enumNames": [ - "Class" - ], - "enum": [ - "Class" - ] - }, - "item": { - "$ref": "#/definitions/TestClass" - } - } - }, - "Opt": { - "type": "object", - "additionalProperties": false, - "required": [ - "tag" - ], - "properties": { - "tag": { - "type": "string", - "default": "Opt", - "x-enumNames": [ - "Opt" - ], - "enum": [ - "Opt" - ] - }, - "item": { - "$ref": "#/definitions/TestRecord" - } - } - } - }, - "anyOf": [ - { - "$ref": "#/definitions/Rec" - }, - { - "$ref": "#/definitions/Du" - }, - { - "$ref": "#/definitions/SingleDu" - }, - { - "$ref": "#/definitions/Enum" - }, - { - "$ref": "#/definitions/Class" - }, - { - "$ref": "#/definitions/Opt" - } - ] -}""" - - let actual = generator (typeof) - "╰〳 ಠ 益 ಠೃ 〵╯" |> equal actual expected - return json actual + return generator typeof |> json } verify "RecWithOption generates proper schema" { - let expected = - """{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "RecWithOption", - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "description": { - "type": [ - "null", - "string" - ] - } - } -}""" - - let actual = generator (typeof) - "╰〳 ಠ 益 ಠೃ 〵╯" |> equal actual expected - return json actual + return generator typeof |> json + } + verify "RecWithNullable generates proper schema" { + return generator typeof |> json } verify "PaginatedResult<'T> generates proper schema" { - let expected = - """{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "PaginatedResultOfObject", - "type": "object", - "additionalProperties": false, - "properties": { - "page": { - "type": "integer", - "format":"int32" - }, - "perPage": { - "type": "integer", - "format": "int32" - }, - "total": { - "type": "integer", - "format": "int32" - }, - "results": { - "type": "array", - "items": {} - } - } -}""" - - let actual = generator (typeof>) - "╰〳 ಠ 益 ಠೃ 〵╯" |> equal actual expected - return json actual + return generator typeof> |> json } verify "FSharp list generates proper schema" { - let expected = """ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "TestList", - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "type": "integer", - "format": "int32" - }, - "name": { - "type": "string" - }, - "records": { - "type": "array", - "items": { - "$ref": "#/definitions/TestRecord" - } - } - }, - "definitions": { - "TestRecord": { - "type": "object", - "additionalProperties": false, - "properties": { - "firstName": { - "type": "string" - }, - "lastName": { - "type": "string" - } - } - } - } -} -""" - let actual = generator typeof - "╰〳 ಠ 益 ಠೃ 〵╯" |> equal actual expected - return json actual + return generator typeof |> json } + verify "FSharp decimal generates correct schema" { - let expected = """ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "TestDecimal", - "type": "object", - "additionalProperties": false, - "properties": { - "test": { - "$ref": "#/definitions/DuWithDecimal" - }, - "total": { - "type": "number", - "format": "decimal" - } - }, - "definitions": { - "DuWithDecimal": { - "definitions": { - "Nothing": { - "type": "string", - "default": "Nothing", - "additionalProperties": false, - "x-enumNames": [ - "Nothing" - ], - "enum": [ - "Nothing" - ] - }, - "Amount": { - "type": "object", - "additionalProperties": false, - "required": [ - "tag", - "item" - ], - "properties": { - "tag": { - "type": "string", - "default": "Amount", - "x-enumNames": [ - "Amount" - ], - "enum": [ - "Amount" - ] - }, - "item": { - "type": "number", - "format": "decimal" - } - } - } - }, - "anyOf": [ - { - "$ref": "#/definitions/DuWithDecimal/definitions/Nothing" - }, - { - "$ref": "#/definitions/DuWithDecimal/definitions/Amount" - } - ] - } - } -} -""" - let actual = generator typeof - "╰〳 ಠ 益 ಠೃ 〵╯" |> equal actual expected - return json actual + return generator typeof |> json } ] diff --git a/test/FSharp.Data.JsonSchema.Tests/JsonSerializationTests.fs b/test/FSharp.Data.JsonSchema.Tests/JsonSerializationTests.fs index 36e79df..d81e531 100644 --- a/test/FSharp.Data.JsonSchema.Tests/JsonSerializationTests.fs +++ b/test/FSharp.Data.JsonSchema.Tests/JsonSerializationTests.fs @@ -4,6 +4,10 @@ open System open FSharp.Data open Expecto +type RecWithSkippableSeq = + { Post : string + Likes : System.Text.Json.Serialization.Skippable } + [] let tests = testList @@ -265,4 +269,20 @@ let tests = let actual = Json.Deserialize("""{"name":"Ryan"}""") Expect.equal actual expected "Expected serializer to accept missing, optional field" + } + + test "Sequence field required to be explicitly empty" { + Expect.throws + (fun () -> Json.Deserialize>("""{"page":1,"perPage":10,"total":20}""") |> ignore) + "Expected serializer to enforce sequence field" + } + + test "Skippable sequence field not required to be explicitly empty" { + let expected = + { RecWithSkippableSeq.Post = "Hello" + Likes = System.Text.Json.Serialization.Skippable.Skip } + + let actual = Json.Deserialize("""{"post":"Hello"}""") + + Expect.equal actual expected "Expected serializer to accept missing, skippable sequence field" } ] diff --git a/test/FSharp.Data.JsonSchema.Tests/ValidationTests.fs b/test/FSharp.Data.JsonSchema.Tests/ValidationTests.fs index eb29b41..e1ace1f 100644 --- a/test/FSharp.Data.JsonSchema.Tests/ValidationTests.fs +++ b/test/FSharp.Data.JsonSchema.Tests/ValidationTests.fs @@ -57,13 +57,13 @@ let tests = "╰〳 ಠ 益 ಠೃ 〵╯" |> Expect.isOk actual } - test "Record missing array field validates against schema" { + test "Record missing array field does not validate against schema" { let schema = generator (typeof) let json = """{"id":1,"name":"Ryan"}""" let actual = Validation.validate schema json - "╰〳 ಠ 益 ಠೃ 〵╯" |> Expect.isOk actual + "╰〳 ಠ 益 ಠೃ 〵╯" |> Expect.isError actual } diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.FSharp decimal generates correct schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.FSharp decimal generates correct schema.verified.txt index f97a0a0..780f5a6 100644 --- a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.FSharp decimal generates correct schema.verified.txt +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.FSharp decimal generates correct schema.verified.txt @@ -3,6 +3,10 @@ "title": "TestDecimal", "type": "object", "additionalProperties": false, + "required": [ + "test", + "total" + ], "properties": { "test": { "$ref": "#/definitions/DuWithDecimal" diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.FSharp list generates proper schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.FSharp list generates proper schema.verified.txt index b67b8cc..2b9dbc7 100644 --- a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.FSharp list generates proper schema.verified.txt +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.FSharp list generates proper schema.verified.txt @@ -3,6 +3,11 @@ "title": "TestList", "type": "object", "additionalProperties": false, + "required": [ + "id", + "name", + "records" + ], "properties": { "id": { "type": "integer", @@ -22,6 +27,10 @@ "TestRecord": { "type": "object", "additionalProperties": false, + "required": [ + "firstName", + "lastName" + ], "properties": { "firstName": { "type": "string" diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Nested generates proper schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Nested generates proper schema.verified.txt index 89f185a..f7ea786 100644 --- a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Nested generates proper schema.verified.txt +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Nested generates proper schema.verified.txt @@ -6,6 +6,10 @@ "title": "TestRecord", "type": "object", "additionalProperties": false, + "required": [ + "firstName", + "lastName" + ], "properties": { "firstName": { "type": "string" diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.PaginatedResult__T_ generates proper schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.PaginatedResult__T_ generates proper schema.verified.txt index 43df531..ba83769 100644 --- a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.PaginatedResult__T_ generates proper schema.verified.txt +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.PaginatedResult__T_ generates proper schema.verified.txt @@ -3,6 +3,12 @@ "title": "PaginatedResultOfObject", "type": "object", "additionalProperties": false, + "required": [ + "page", + "perPage", + "total", + "results" + ], "properties": { "page": { "type": "integer", diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.RecWithNullable generates proper schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.RecWithNullable generates proper schema.verified.txt new file mode 100644 index 0000000..e321a86 --- /dev/null +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.RecWithNullable generates proper schema.verified.txt @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "RecWithNullable", + "type": "object", + "additionalProperties": false, + "required": [ + "need" + ], + "properties": { + "need": { + "type": "integer", + "format": "int32" + }, + "noNeed": { + "type": [ + "integer", + "null" + ], + "format": "int32" + } + } +} \ No newline at end of file diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.RecWithOption generates proper schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.RecWithOption generates proper schema.verified.txt index bd75363..347d076 100644 --- a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.RecWithOption generates proper schema.verified.txt +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.RecWithOption generates proper schema.verified.txt @@ -3,6 +3,9 @@ "title": "RecWithOption", "type": "object", "additionalProperties": false, + "required": [ + "name" + ], "properties": { "name": { "type": "string" diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Record generates proper schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Record generates proper schema.verified.txt index 7eb0bf2..1fd0fcb 100644 --- a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Record generates proper schema.verified.txt +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Record generates proper schema.verified.txt @@ -3,6 +3,10 @@ "title": "TestRecord", "type": "object", "additionalProperties": false, + "required": [ + "firstName", + "lastName" + ], "properties": { "firstName": { "type": "string" From 7c521c1f1d74d21e24f472fb510602c53077df81 Mon Sep 17 00:00:00 2001 From: Moises Nessim Date: Wed, 14 Jun 2023 23:04:50 -0500 Subject: [PATCH 06/26] Schema Generator: Do not require record properties that have OneOf Null [B1PC-4] --- src/FSharp.Data.JsonSchema/JsonSchema.fs | 7 +- test/FSharp.Data.JsonSchema.Tests/Bug10.fs | 6 - .../GeneratorTests.fs | 9 ++ .../FSharp.Data.JsonSchema.Tests/TestTypes.fs | 7 ++ ...ption generates proper schema.verified.txt | 23 ++++ ...ption generates proper schema.verified.txt | 104 ++++++++++++++++++ 6 files changed, 149 insertions(+), 7 deletions(-) create mode 100644 test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.RecWithArrayOption generates proper schema.verified.txt create mode 100644 test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.RecWithGenericOption generates proper schema.verified.txt diff --git a/src/FSharp.Data.JsonSchema/JsonSchema.fs b/src/FSharp.Data.JsonSchema/JsonSchema.fs index 5ce99a6..fa00c52 100644 --- a/src/FSharp.Data.JsonSchema/JsonSchema.fs +++ b/src/FSharp.Data.JsonSchema/JsonSchema.fs @@ -197,6 +197,11 @@ type MultiCaseDuSchemaProcessor(?casePropertyName) = type RecordSchemaProcessor() = + + let isNullableProperty(property: JsonSchemaProperty) = + property.Type.HasFlag JsonObjectType.Null + || property.OneOf |> Seq.exists (fun s -> s.Type.HasFlag JsonObjectType.Null) + member this.Process(context: SchemaProcessorContext) = if FSharpType.IsRecord(context.Type) @@ -204,7 +209,7 @@ type RecordSchemaProcessor() = let schema = context.Schema for KeyValue(propertyName, property) in schema.Properties do - if property.Type.HasFlag JsonObjectType.Null |> not then + if (not << isNullableProperty) property then property.IsRequired <- true interface ISchemaProcessor with diff --git a/test/FSharp.Data.JsonSchema.Tests/Bug10.fs b/test/FSharp.Data.JsonSchema.Tests/Bug10.fs index e9a096f..dd88b91 100644 --- a/test/FSharp.Data.JsonSchema.Tests/Bug10.fs +++ b/test/FSharp.Data.JsonSchema.Tests/Bug10.fs @@ -59,11 +59,6 @@ let tests = } }""" - """ - -{"$schema":"http://json-schema.org/draft-04/schema#","title":"outer","type":"object","additionalProperties":false,"required":["outer1"],"properties":{"outer1":{"oneOf":[{"type":"null"},{"$ref":"#/definitions/Inner"}]}},"definitions":{"Inner":{"type":"object","additionalProperties":false,"required":["inner1","inner2"],"properties":{"inner1":{"type":"integer","format":"int32"},"inner2":{"type":"string"}}}}} - """ - let gen = Generator.CreateMemoized("out") let actual = gen (typeof) equal actual expected "Expected detailed type definition in definitions." @@ -76,7 +71,6 @@ let tests = "title": "outer", "type": "object", "additionalProperties": false, - "required":["outer1"], "properties": { "outer1": { "oneOf": [ diff --git a/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs b/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs index 8e72463..f5c80e2 100644 --- a/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs +++ b/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs @@ -74,6 +74,15 @@ let tests = verify "RecWithOption generates proper schema" { return generator typeof |> json } + + verify "RecWithGenericOption generates proper schema" { + return generator typeof> |> json + } + + verify "RecWithArrayOption generates proper schema" { + return generator typeof |> json + } + verify "RecWithNullable generates proper schema" { return generator typeof |> json } diff --git a/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs b/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs index a83ed34..0702d71 100644 --- a/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs +++ b/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs @@ -47,6 +47,13 @@ type RecWithOption = { Name: string Description: string option } +type RecWithGenericOption<'T> = + { Car: string + CarType: 'T option } + +type RecWithArrayOption = + { Hey: string; Many: string array option } + type RecWithNullable = { Need: int NoNeed: System.Nullable } diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.RecWithArrayOption generates proper schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.RecWithArrayOption generates proper schema.verified.txt new file mode 100644 index 0000000..b3c097e --- /dev/null +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.RecWithArrayOption generates proper schema.verified.txt @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "RecWithArrayOption", + "type": "object", + "additionalProperties": false, + "required": [ + "hey" + ], + "properties": { + "hey": { + "type": "string" + }, + "many": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + } +} \ No newline at end of file diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.RecWithGenericOption generates proper schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.RecWithGenericOption generates proper schema.verified.txt new file mode 100644 index 0000000..083adbe --- /dev/null +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.RecWithGenericOption generates proper schema.verified.txt @@ -0,0 +1,104 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "RecWithGenericOptionOfTestDU", + "type": "object", + "additionalProperties": false, + "required": [ + "car" + ], + "properties": { + "car": { + "type": "string" + }, + "carType": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/definitions/TestDU" + } + ] + } + }, + "definitions": { + "TestDU": { + "definitions": { + "Case": { + "type": "string", + "default": "Case", + "additionalProperties": false, + "x-enumNames": [ + "Case" + ], + "enum": [ + "Case" + ] + }, + "WithOneField": { + "type": "object", + "additionalProperties": false, + "required": [ + "tag", + "item" + ], + "properties": { + "tag": { + "type": "string", + "default": "WithOneField", + "x-enumNames": [ + "WithOneField" + ], + "enum": [ + "WithOneField" + ] + }, + "item": { + "type": "integer", + "format": "int32" + } + } + }, + "WithNamedFields": { + "type": "object", + "additionalProperties": false, + "required": [ + "tag", + "name", + "value" + ], + "properties": { + "tag": { + "type": "string", + "default": "WithNamedFields", + "x-enumNames": [ + "WithNamedFields" + ], + "enum": [ + "WithNamedFields" + ] + }, + "name": { + "type": "string" + }, + "value": { + "type": "number", + "format": "double" + } + } + } + }, + "anyOf": [ + { + "$ref": "#/definitions/TestDU/definitions/Case" + }, + { + "$ref": "#/definitions/TestDU/definitions/WithOneField" + }, + { + "$ref": "#/definitions/TestDU/definitions/WithNamedFields" + } + ] + } + } +} \ No newline at end of file From ab1229e8b9ba4a6d464227033d67d60cfe3dcd03 Mon Sep 17 00:00:00 2001 From: Moises Nessim Date: Fri, 16 Jun 2023 15:11:52 -0500 Subject: [PATCH 07/26] Do not unwrap single case unions so that they can be validated and roundtripped correctly. [B1PC-4] --- src/FSharp.Data.JsonSchema/Serializer.fs | 1 + .../JsonSerializationTests.fs | 4 ---- test/FSharp.Data.JsonSchema.Tests/TestTypes.fs | 13 ++++++++++--- .../FSharp.Data.JsonSchema.Tests/ValidationTests.fs | 10 ++++++++++ 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/FSharp.Data.JsonSchema/Serializer.fs b/src/FSharp.Data.JsonSchema/Serializer.fs index 8b97a67..3383178 100644 --- a/src/FSharp.Data.JsonSchema/Serializer.fs +++ b/src/FSharp.Data.JsonSchema/Serializer.fs @@ -14,6 +14,7 @@ module private Defaults = .WithUnwrapOption() .WithSkippableOptionFields() .WithUnionUnwrapFieldlessTags() + .WithUnionUnwrapSingleCaseUnions(false) let mkOptions unionTagName = diff --git a/test/FSharp.Data.JsonSchema.Tests/JsonSerializationTests.fs b/test/FSharp.Data.JsonSchema.Tests/JsonSerializationTests.fs index d81e531..3e836be 100644 --- a/test/FSharp.Data.JsonSchema.Tests/JsonSerializationTests.fs +++ b/test/FSharp.Data.JsonSchema.Tests/JsonSerializationTests.fs @@ -4,10 +4,6 @@ open System open FSharp.Data open Expecto -type RecWithSkippableSeq = - { Post : string - Likes : System.Text.Json.Serialization.Skippable } - [] let tests = testList diff --git a/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs b/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs index 0702d71..5ae5c3e 100644 --- a/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs +++ b/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs @@ -58,12 +58,19 @@ type RecWithNullable = { Need: int NoNeed: System.Nullable } -module Util = - let stripWhitespace text = - System.Text.RegularExpressions.Regex.Replace(text, @"\s+", "") +type RecWithSkippableSeq = + { Post: string + Likes: System.Text.Json.Serialization.Skippable } type PaginatedResult<'T> = { Page: int PerPage: int Total: int Results: 'T seq } + +type SingleCaseDU = + | OnlyCase of onlyCase: TestRecord + +module Util = + let stripWhitespace text = + System.Text.RegularExpressions.Regex.Replace(text, @"\s+", "") diff --git a/test/FSharp.Data.JsonSchema.Tests/ValidationTests.fs b/test/FSharp.Data.JsonSchema.Tests/ValidationTests.fs index e1ace1f..9c6e595 100644 --- a/test/FSharp.Data.JsonSchema.Tests/ValidationTests.fs +++ b/test/FSharp.Data.JsonSchema.Tests/ValidationTests.fs @@ -164,4 +164,14 @@ let tests = let actual = Validation.validate schema json "╰〳 ಠ 益 ಠೃ 〵╯" |> Expect.equal actual (Ok()) } + + test "SingleCaseDU validates against schema and roundtrips" { + let schema = generator(typeof) + let expected = SingleCaseDU.OnlyCase {FirstName = "Ryan"; LastName = "Riley"} + let json = Json.Serialize(expected, "tag") + do Expect.wantOk (Validation.validate schema json) "Did not validate" + let actual = Json.Deserialize( json, "tag") + Expect.equal actual expected "Did not roundtrip" + } + ] From a1fa6f8df5eb1bf3dd17ae7554f6eafcbf1a3357 Mon Sep 17 00:00:00 2001 From: Moises Nessim Date: Mon, 19 Jun 2023 18:09:44 -0500 Subject: [PATCH 08/26] Use F# XmlDocumentation to generate documentation for the schema [B1PC-4] --- src/FSharp.Data.JsonSchema/JsonSchema.fs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/FSharp.Data.JsonSchema/JsonSchema.fs b/src/FSharp.Data.JsonSchema/JsonSchema.fs index fa00c52..5edb38c 100644 --- a/src/FSharp.Data.JsonSchema/JsonSchema.fs +++ b/src/FSharp.Data.JsonSchema/JsonSchema.fs @@ -266,7 +266,8 @@ type Generator private () = SerializerOptions = FSharp.Data.Json.DefaultOptions, DefaultReferenceTypeNullHandling = ReferenceTypeNullHandling.NotNull, ReflectionService = ReflectionService(), - SchemaNameGenerator = SchemaNameGenerator() + SchemaNameGenerator = SchemaNameGenerator(), + UseXmlDocumentation = true ) settings.SchemaProcessors.Add(OptionSchemaProcessor()) From 0ddc208ad100d6499a0bc6917e69aee380a6dcb5 Mon Sep 17 00:00:00 2001 From: Moises Nessim Date: Fri, 30 Jun 2023 09:59:56 -0500 Subject: [PATCH 09/26] Use context.ContextualType.Type intead of context.Type, since it is deprecated [B1PC-4] --- src/FSharp.Data.JsonSchema/JsonSchema.fs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/FSharp.Data.JsonSchema/JsonSchema.fs b/src/FSharp.Data.JsonSchema/JsonSchema.fs index 5edb38c..4707ea3 100644 --- a/src/FSharp.Data.JsonSchema/JsonSchema.fs +++ b/src/FSharp.Data.JsonSchema/JsonSchema.fs @@ -33,11 +33,11 @@ type OptionSchemaProcessor() = member this.Process(context: SchemaProcessorContext) = if - context.Type.IsGenericType - && optionTy.Equals(context.Type.GetGenericTypeDefinition()) + context.ContextualType.Type.IsGenericType + && optionTy.Equals(context.ContextualType.Type.GetGenericTypeDefinition()) then let schema = context.Schema - let cases = FSharpType.GetUnionCases(context.Type) + let cases = FSharpType.GetUnionCases(context.ContextualType.Type) let schemaType = [| for case in cases do @@ -70,12 +70,12 @@ type SingleCaseDuSchemaProcessor() = member this.Process(context: SchemaProcessorContext) = if - FSharpType.IsUnion(context.Type) - && Reflection.allCasesEmpty context.Type + FSharpType.IsUnion(context.ContextualType.Type) + && Reflection.allCasesEmpty context.ContextualType.Type then let schema = context.Schema schema.Type <- JsonObjectType.String - let cases = FSharpType.GetUnionCases(context.Type) + let cases = FSharpType.GetUnionCases(context.ContextualType.Type) for case in cases do schema.Enumeration.Add(case.Name) @@ -89,12 +89,12 @@ type MultiCaseDuSchemaProcessor(?casePropertyName) = member this.Process(context: SchemaProcessorContext) = if - FSharpType.IsUnion(context.Type) - && not (Reflection.allCasesEmpty context.Type) - && not (Reflection.isList context.Type) - && not (Reflection.isOption context.Type) + FSharpType.IsUnion(context.ContextualType.Type) + && not (Reflection.allCasesEmpty context.ContextualType.Type) + && not (Reflection.isList context.ContextualType.Type) + && not (Reflection.isOption context.ContextualType.Type) then - let cases = FSharpType.GetUnionCases(context.Type) + let cases = FSharpType.GetUnionCases(context.ContextualType.Type) // Set the core schema definition. let schema = context.Schema @@ -204,7 +204,7 @@ type RecordSchemaProcessor() = member this.Process(context: SchemaProcessorContext) = if - FSharpType.IsRecord(context.Type) + FSharpType.IsRecord(context.ContextualType.Type) then let schema = context.Schema From 18d7638b65b247bccda6ce6f4f99bcc18c4a9c64 Mon Sep 17 00:00:00 2001 From: Moises Nessim Date: Fri, 30 Jun 2023 10:39:10 -0500 Subject: [PATCH 10/26] Test that data annotations are generated properly for records [B1PC-4] --- .../GeneratorTests.fs | 6 ++++- .../FSharp.Data.JsonSchema.Tests/TestTypes.fs | 7 +++++ ...tions generates proper schema.verified.txt | 27 +++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Record with annotations generates proper schema.verified.txt diff --git a/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs b/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs index f5c80e2..2f0c1d2 100644 --- a/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs +++ b/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs @@ -97,4 +97,8 @@ let tests = verify "FSharp decimal generates correct schema" { return generator typeof |> json - } ] + } + + verify "Record with annotations generates proper schema" { + return generator typeof |> json + }] diff --git a/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs b/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs index 5ae5c3e..f12a17a 100644 --- a/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs +++ b/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs @@ -71,6 +71,13 @@ type PaginatedResult<'T> = type SingleCaseDU = | OnlyCase of onlyCase: TestRecord +open System.ComponentModel.DataAnnotations + +type RecordWithAnnotations = + { [] RegEx: string + [] MaxLength : string + [] Range: int } + module Util = let stripWhitespace text = System.Text.RegularExpressions.Regex.Replace(text, @"\s+", "") diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Record with annotations generates proper schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Record with annotations generates proper schema.verified.txt new file mode 100644 index 0000000..3f2fb83 --- /dev/null +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Record with annotations generates proper schema.verified.txt @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "RecordWithAnnotations", + "type": "object", + "additionalProperties": false, + "required": [ + "regEx", + "maxLength", + "range" + ], + "properties": { + "regEx": { + "type": "string", + "minLength": 1 + }, + "maxLength": { + "type": "string", + "maxLength": 10 + }, + "range": { + "type": "integer", + "format": "int32", + "maximum": 100.0, + "minimum": 0.0 + } + } +} \ No newline at end of file From ade2dc8d7303b53efe7007fceb481506ec126137 Mon Sep 17 00:00:00 2001 From: Moises Nessim Date: Mon, 17 Jul 2023 10:11:23 -0500 Subject: [PATCH 11/26] Add test for interdepenedent recursive types [B1PC-4] --- test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs | 4 ++++ test/FSharp.Data.JsonSchema.Tests/TestTypes.fs | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs b/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs index 2f0c1d2..67c6a2c 100644 --- a/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs +++ b/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs @@ -101,4 +101,8 @@ let tests = verify "Record with annotations generates proper schema" { return generator typeof |> json + } + + verify "Interdependent DUs generate proper schema" { + return generator typeof |> json }] diff --git a/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs b/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs index f12a17a..f4fdd53 100644 --- a/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs +++ b/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs @@ -78,6 +78,13 @@ type RecordWithAnnotations = [] MaxLength : string [] Range: int } +type Chicken = + | Have of Egg + | DontHaveEgg +and Egg = + | Have of Chicken + | DontHaveChicken + module Util = let stripWhitespace text = System.Text.RegularExpressions.Regex.Replace(text, @"\s+", "") From c2199492444a99c51c073bd95ab366cbe86d8bda Mon Sep 17 00:00:00 2001 From: Moises Nessim Date: Tue, 18 Jul 2023 12:03:25 -0500 Subject: [PATCH 12/26] Support mutually recursive MultiCaseDUs by passing the current context resolver to the Generator.Generate method Do not process reference schemas, to avoid infinite recursion Avoid double dictionary lookups by returning a tuple [B1PC-4] --- src/FSharp.Data.JsonSchema/JsonSchema.fs | 35 ++++--- .../GeneratorTests.fs | 5 + .../FSharp.Data.JsonSchema.Tests/TestTypes.fs | 5 + ...nt DUs generate proper schema.verified.txt | 94 +++++++++++++++++++ ...fields generate proper schema.verified.txt | 64 +++++++++++++ ...ested generates proper schema.verified.txt | 5 - 6 files changed, 184 insertions(+), 24 deletions(-) create mode 100644 test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Interdependent DUs generate proper schema.verified.txt create mode 100644 test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Interdependent DUs with optional fields generate proper schema.verified.txt diff --git a/src/FSharp.Data.JsonSchema/JsonSchema.fs b/src/FSharp.Data.JsonSchema/JsonSchema.fs index 4707ea3..6497b31 100644 --- a/src/FSharp.Data.JsonSchema/JsonSchema.fs +++ b/src/FSharp.Data.JsonSchema/JsonSchema.fs @@ -5,7 +5,6 @@ open System.Collections.Generic open Microsoft.FSharp.Reflection open Namotion.Reflection open NJsonSchema -open NJsonSchema.Annotations open NJsonSchema.Generation /// Microsoft.FSharp.Reflection helpers @@ -33,7 +32,8 @@ type OptionSchemaProcessor() = member this.Process(context: SchemaProcessorContext) = if - context.ContextualType.Type.IsGenericType + not context.Schema.HasReference + && context.ContextualType.Type.IsGenericType && optionTy.Equals(context.ContextualType.Type.GetGenericTypeDefinition()) then let schema = context.Schema @@ -70,7 +70,8 @@ type SingleCaseDuSchemaProcessor() = member this.Process(context: SchemaProcessorContext) = if - FSharpType.IsUnion(context.ContextualType.Type) + not context.Schema.HasReference + && FSharpType.IsUnion(context.ContextualType.Type) && Reflection.allCasesEmpty context.ContextualType.Type then let schema = context.Schema @@ -89,7 +90,8 @@ type MultiCaseDuSchemaProcessor(?casePropertyName) = member this.Process(context: SchemaProcessorContext) = if - FSharpType.IsUnion(context.ContextualType.Type) + not context.Schema.HasReference + && FSharpType.IsUnion(context.ContextualType.Type) && not (Reflection.allCasesEmpty context.ContextualType.Type) && not (Reflection.isList context.ContextualType.Type) && not (Reflection.isOption context.ContextualType.Type) @@ -147,38 +149,32 @@ type MultiCaseDuSchemaProcessor(?casePropertyName) = let innerTy = field.PropertyType.GetGenericArguments().[0] - let fieldSchema = + let fieldSchema, wasCached = match fieldSchemaCache.TryGetValue innerTy with - | true, fs -> fs - | _ -> context.Generator.Generate(innerTy) + | true, fs -> fs, true + | _ -> context.Generator.Generate(innerTy, context.Resolver), false let prop = if Reflection.isPrimitive innerTy then JsonSchemaProperty(Type = fieldSchema.Type) else - if not (fieldSchemaCache.ContainsKey innerTy) then - context.Resolver.AppendSchema(fieldSchema, typeNameHint = innerTy.Name) + if not wasCached then fieldSchemaCache.Add(innerTy, fieldSchema) JsonSchemaProperty(Reference = fieldSchema) s.Properties.Add(camelCaseFieldName, prop) else - let fieldSchema = + let fieldSchema, wasCached = match fieldSchemaCache.TryGetValue field.PropertyType with - | true, fs -> fs - | _ -> context.Generator.Generate(field.PropertyType) + | true, fs -> fs, true + | _ -> context.Generator.Generate(field.PropertyType, context.Resolver), false let prop = if Reflection.isPrimitive field.PropertyType then JsonSchemaProperty(Type = fieldSchema.Type, Format = fieldSchema.Format) else - if not (fieldSchemaCache.ContainsKey field.PropertyType) then - context.Resolver.AppendSchema( - fieldSchema, - typeNameHint = field.PropertyType.Name - ) - + if not wasCached then fieldSchemaCache.Add(field.PropertyType, fieldSchema) JsonSchemaProperty(Reference = fieldSchema) @@ -204,7 +200,8 @@ type RecordSchemaProcessor() = member this.Process(context: SchemaProcessorContext) = if - FSharpType.IsRecord(context.ContextualType.Type) + not context.Schema.HasReference + && FSharpType.IsRecord(context.ContextualType.Type) then let schema = context.Schema diff --git a/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs b/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs index 67c6a2c..543b93d 100644 --- a/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs +++ b/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs @@ -26,6 +26,7 @@ type VerifyBuilder(name,focusState) = member __.Return<'T>(v:'T) = Verifier.Verify(makeValidFilePath name, v,settings= verifySettings).Wait() let verify name = VerifyBuilder(name,FocusState.Normal) +let fverify name = VerifyBuilder(name,FocusState.Focused) let json ( schema: NJsonSchema.JsonSchema ) = schema.ToJson() @@ -105,4 +106,8 @@ let tests = verify "Interdependent DUs generate proper schema" { return generator typeof |> json + } + + verify "Interdependent DUs with optional fields generate proper schema" { + return generator typeof |> json }] diff --git a/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs b/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs index f4fdd53..0a4fb3f 100644 --- a/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs +++ b/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs @@ -85,6 +85,11 @@ and Egg = | Have of Chicken | DontHaveChicken +type Even = + | Even of Odd option +and Odd = + | Odd of Even option + module Util = let stripWhitespace text = System.Text.RegularExpressions.Regex.Replace(text, @"\s+", "") diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Interdependent DUs generate proper schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Interdependent DUs generate proper schema.verified.txt new file mode 100644 index 0000000..d9cd8eb --- /dev/null +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Interdependent DUs generate proper schema.verified.txt @@ -0,0 +1,94 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Chicken", + "definitions": { + "Egg": { + "definitions": { + "Have": { + "type": "object", + "additionalProperties": false, + "required": [ + "tag", + "item" + ], + "properties": { + "tag": { + "type": "string", + "default": "Have", + "x-enumNames": [ + "Have" + ], + "enum": [ + "Have" + ] + }, + "item": { + "$ref": "#" + } + } + }, + "DontHaveChicken": { + "type": "string", + "default": "DontHaveChicken", + "additionalProperties": false, + "x-enumNames": [ + "DontHaveChicken" + ], + "enum": [ + "DontHaveChicken" + ] + } + }, + "anyOf": [ + { + "$ref": "#/definitions/Egg/definitions/Have" + }, + { + "$ref": "#/definitions/Egg/definitions/DontHaveChicken" + } + ] + }, + "Have": { + "type": "object", + "additionalProperties": false, + "required": [ + "tag", + "item" + ], + "properties": { + "tag": { + "type": "string", + "default": "Have", + "x-enumNames": [ + "Have" + ], + "enum": [ + "Have" + ] + }, + "item": { + "$ref": "#/definitions/Egg" + } + } + }, + "DontHaveEgg": { + "type": "string", + "default": "DontHaveEgg", + "additionalProperties": false, + "x-enumNames": [ + "DontHaveEgg" + ], + "enum": [ + "DontHaveEgg" + ] + } + }, + "anyOf": [ + { + "$ref": "#/definitions/Have" + }, + { + "$ref": "#/definitions/DontHaveEgg" + } + ] +} \ No newline at end of file diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Interdependent DUs with optional fields generate proper schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Interdependent DUs with optional fields generate proper schema.verified.txt new file mode 100644 index 0000000..964ab23 --- /dev/null +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Interdependent DUs with optional fields generate proper schema.verified.txt @@ -0,0 +1,64 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Even", + "definitions": { + "Odd": { + "definitions": { + "Odd": { + "type": "object", + "additionalProperties": false, + "required": [ + "tag" + ], + "properties": { + "tag": { + "type": "string", + "default": "Odd", + "x-enumNames": [ + "Odd" + ], + "enum": [ + "Odd" + ] + }, + "item": { + "$ref": "#/definitions/Even" + } + } + } + }, + "anyOf": [ + { + "$ref": "#/definitions/Odd/definitions/Odd" + } + ] + }, + "Even": { + "type": "object", + "additionalProperties": false, + "required": [ + "tag" + ], + "properties": { + "tag": { + "type": "string", + "default": "Even", + "x-enumNames": [ + "Even" + ], + "enum": [ + "Even" + ] + }, + "item": { + "$ref": "#/definitions/Odd/definitions/Odd" + } + } + } + }, + "anyOf": [ + { + "$ref": "#/definitions/Even" + } + ] +} \ No newline at end of file diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Nested generates proper schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Nested generates proper schema.verified.txt index f7ea786..4e139a8 100644 --- a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Nested generates proper schema.verified.txt +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Nested generates proper schema.verified.txt @@ -3,7 +3,6 @@ "title": "Nested", "definitions": { "TestRecord": { - "title": "TestRecord", "type": "object", "additionalProperties": false, "required": [ @@ -43,7 +42,6 @@ } }, "TestDU": { - "title": "TestDU", "definitions": { "Case": { "type": "string", @@ -145,7 +143,6 @@ } }, "TestSingleDU": { - "title": "TestSingleDU", "type": "string", "additionalProperties": false, "x-enumNames": [ @@ -183,7 +180,6 @@ } }, "TestEnum": { - "title": "TestEnum", "type": "string", "description": "", "x-enumNames": [ @@ -221,7 +217,6 @@ } }, "TestClass": { - "title": "TestClass", "type": "object", "additionalProperties": false, "properties": { From 6ecc898bed0fea92d020abf384bb91f398de4945 Mon Sep 17 00:00:00 2001 From: Moises Nessim Date: Tue, 18 Jul 2023 12:13:16 -0500 Subject: [PATCH 13/26] Pass the current context.Resolver to the generator in the OptionSchemaProcessor [B1PC-4] --- src/FSharp.Data.JsonSchema/JsonSchema.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FSharp.Data.JsonSchema/JsonSchema.fs b/src/FSharp.Data.JsonSchema/JsonSchema.fs index 6497b31..3ab13e2 100644 --- a/src/FSharp.Data.JsonSchema/JsonSchema.fs +++ b/src/FSharp.Data.JsonSchema/JsonSchema.fs @@ -47,7 +47,7 @@ type OptionSchemaProcessor() = let field = case.GetFields() |> Array.head let schema = - context.Generator.Generate(field.PropertyType) + context.Generator.Generate(field.PropertyType, context.Resolver) match schema.Type with | JsonObjectType.None -> From c7b208fa9b0db3a8e9d770e2fe960fba6ac516c6 Mon Sep 17 00:00:00 2001 From: Moises Nessim Date: Tue, 18 Jul 2023 15:21:19 -0500 Subject: [PATCH 14/26] MultiCaseDU: Do not cache field schemas locally The generator will use the existent cache in the passed resolver [B1PC-4] --- src/FSharp.Data.JsonSchema/JsonSchema.fs | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src/FSharp.Data.JsonSchema/JsonSchema.fs b/src/FSharp.Data.JsonSchema/JsonSchema.fs index 3ab13e2..eac8a68 100644 --- a/src/FSharp.Data.JsonSchema/JsonSchema.fs +++ b/src/FSharp.Data.JsonSchema/JsonSchema.fs @@ -104,8 +104,6 @@ type MultiCaseDuSchemaProcessor(?casePropertyName) = schema.IsAbstract <- false schema.AllowAdditionalProperties <- true - let fieldSchemaCache = Dictionary() - // Add schemas for each case. for case in cases do let fields = case.GetFields() @@ -149,34 +147,22 @@ type MultiCaseDuSchemaProcessor(?casePropertyName) = let innerTy = field.PropertyType.GetGenericArguments().[0] - let fieldSchema, wasCached = - match fieldSchemaCache.TryGetValue innerTy with - | true, fs -> fs, true - | _ -> context.Generator.Generate(innerTy, context.Resolver), false + let fieldSchema = context.Generator.Generate(innerTy, context.Resolver) let prop = if Reflection.isPrimitive innerTy then JsonSchemaProperty(Type = fieldSchema.Type) else - if not wasCached then - fieldSchemaCache.Add(innerTy, fieldSchema) - JsonSchemaProperty(Reference = fieldSchema) s.Properties.Add(camelCaseFieldName, prop) else - let fieldSchema, wasCached = - match fieldSchemaCache.TryGetValue field.PropertyType with - | true, fs -> fs, true - | _ -> context.Generator.Generate(field.PropertyType, context.Resolver), false + let fieldSchema = context.Generator.Generate(field.PropertyType, context.Resolver) let prop = if Reflection.isPrimitive field.PropertyType then JsonSchemaProperty(Type = fieldSchema.Type, Format = fieldSchema.Format) else - if not wasCached then - fieldSchemaCache.Add(field.PropertyType, fieldSchema) - JsonSchemaProperty(Reference = fieldSchema) s.Properties.Add(camelCaseFieldName, prop) From bd0b6e117cb45f509469a1f6a06fbe0b2885acbb Mon Sep 17 00:00:00 2001 From: Moises Nessim Date: Tue, 18 Jul 2023 22:46:53 -0500 Subject: [PATCH 15/26] MultiCaseDU: Add generated schema to resolver if it has not been added yet. [B1PC-4] --- src/FSharp.Data.JsonSchema/JsonSchema.fs | 16 ++++-- .../GeneratorTests.fs | 8 +++ .../FSharp.Data.JsonSchema.Tests/TestTypes.fs | 5 ++ ...cords generates proper schema.verified.txt | 56 +++++++++++++++++++ ...cords generates proper schema.verified.txt | 35 ++++++++++++ 5 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.DU with array of records generates proper schema.verified.txt create mode 100644 test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Record with array of records generates proper schema.verified.txt diff --git a/src/FSharp.Data.JsonSchema/JsonSchema.fs b/src/FSharp.Data.JsonSchema/JsonSchema.fs index eac8a68..800ffa8 100644 --- a/src/FSharp.Data.JsonSchema/JsonSchema.fs +++ b/src/FSharp.Data.JsonSchema/JsonSchema.fs @@ -27,12 +27,16 @@ module Reflection = let isPrimitive (ty: Type) = ty.IsPrimitive || ty = typeof || ty = typeof + let isIntegerEnum (ty: Type) = + ty.IsEnum && ty.GetEnumUnderlyingType() = typeof + + type OptionSchemaProcessor() = static let optionTy = typedefof> member this.Process(context: SchemaProcessorContext) = if - not context.Schema.HasReference + isNull context.Schema.Reference && context.ContextualType.Type.IsGenericType && optionTy.Equals(context.ContextualType.Type.GetGenericTypeDefinition()) then @@ -70,7 +74,7 @@ type SingleCaseDuSchemaProcessor() = member this.Process(context: SchemaProcessorContext) = if - not context.Schema.HasReference + isNull context.Schema.Reference && FSharpType.IsUnion(context.ContextualType.Type) && Reflection.allCasesEmpty context.ContextualType.Type then @@ -90,7 +94,7 @@ type MultiCaseDuSchemaProcessor(?casePropertyName) = member this.Process(context: SchemaProcessorContext) = if - not context.Schema.HasReference + isNull context.Schema.Reference && FSharpType.IsUnion(context.ContextualType.Type) && not (Reflection.allCasesEmpty context.ContextualType.Type) && not (Reflection.isList context.ContextualType.Type) @@ -153,6 +157,8 @@ type MultiCaseDuSchemaProcessor(?casePropertyName) = if Reflection.isPrimitive innerTy then JsonSchemaProperty(Type = fieldSchema.Type) else + if not <| context.Resolver.HasSchema(innerTy, Reflection.isIntegerEnum innerTy) then + context.Resolver.AddSchema(innerTy, Reflection.isIntegerEnum innerTy,fieldSchema) JsonSchemaProperty(Reference = fieldSchema) s.Properties.Add(camelCaseFieldName, prop) @@ -163,6 +169,8 @@ type MultiCaseDuSchemaProcessor(?casePropertyName) = if Reflection.isPrimitive field.PropertyType then JsonSchemaProperty(Type = fieldSchema.Type, Format = fieldSchema.Format) else + if not <| context.Resolver.HasSchema(field.PropertyType, Reflection.isIntegerEnum field.PropertyType) then + context.Resolver.AddSchema(field.PropertyType, Reflection.isIntegerEnum field.PropertyType, fieldSchema) JsonSchemaProperty(Reference = fieldSchema) s.Properties.Add(camelCaseFieldName, prop) @@ -186,7 +194,7 @@ type RecordSchemaProcessor() = member this.Process(context: SchemaProcessorContext) = if - not context.Schema.HasReference + isNull context.Schema.Reference && FSharpType.IsRecord(context.ContextualType.Type) then let schema = context.Schema diff --git a/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs b/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs index 543b93d..017400c 100644 --- a/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs +++ b/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs @@ -108,6 +108,14 @@ let tests = return generator typeof |> json } + verify "DU with array of records generates proper schema" { + return generator typeof |> json + } + + verify "Record with array of records generates proper schema" { + return generator typeof |> json + } + verify "Interdependent DUs with optional fields generate proper schema" { return generator typeof |> json }] diff --git a/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs b/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs index 0a4fb3f..4699b80 100644 --- a/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs +++ b/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs @@ -90,6 +90,11 @@ type Even = and Odd = | Odd of Even option + +type RecWithRecArray = { V : TestRecord array } + +type DUWithRecArray = Records of TestRecord array + module Util = let stripWhitespace text = System.Text.RegularExpressions.Regex.Replace(text, @"\s+", "") diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.DU with array of records generates proper schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.DU with array of records generates proper schema.verified.txt new file mode 100644 index 0000000..ed688cd --- /dev/null +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.DU with array of records generates proper schema.verified.txt @@ -0,0 +1,56 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "DUWithRecArray", + "definitions": { + "TestRecord": { + "type": "object", + "additionalProperties": false, + "required": [ + "firstName", + "lastName" + ], + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + } + } + }, + "TestRecordOf": { + "type": "array", + "items": { + "$ref": "#/definitions/TestRecord" + } + }, + "Records": { + "type": "object", + "additionalProperties": false, + "required": [ + "tag", + "item" + ], + "properties": { + "tag": { + "type": "string", + "default": "Records", + "x-enumNames": [ + "Records" + ], + "enum": [ + "Records" + ] + }, + "item": { + "$ref": "#/definitions/TestRecordOf" + } + } + } + }, + "anyOf": [ + { + "$ref": "#/definitions/Records" + } + ] +} \ No newline at end of file diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Record with array of records generates proper schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Record with array of records generates proper schema.verified.txt new file mode 100644 index 0000000..514e653 --- /dev/null +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.Record with array of records generates proper schema.verified.txt @@ -0,0 +1,35 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "RecWithRecArray", + "type": "object", + "additionalProperties": false, + "required": [ + "v" + ], + "properties": { + "v": { + "type": "array", + "items": { + "$ref": "#/definitions/TestRecord" + } + } + }, + "definitions": { + "TestRecord": { + "type": "object", + "additionalProperties": false, + "required": [ + "firstName", + "lastName" + ], + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + } + } + } + } +} \ No newline at end of file From 5910089a08f2f45b6c8c02e1304b77a73743be70 Mon Sep 17 00:00:00 2001 From: Moises Nessim Date: Wed, 19 Jul 2023 10:30:53 -0500 Subject: [PATCH 16/26] MultiCaseDU: Make sure that all generated schemas are added to the resolver [B1PC-4] --- src/FSharp.Data.JsonSchema/JsonSchema.fs | 20 ++- .../GeneratorTests.fs | 4 + .../FSharp.Data.JsonSchema.Tests/TestTypes.fs | 4 +- ...f DUs generates proper schema.verified.txt | 118 ++++++++++++++++++ ...cords generates proper schema.verified.txt | 14 +++ 5 files changed, 153 insertions(+), 7 deletions(-) create mode 100644 test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.DU with array of DUs generates proper schema.verified.txt diff --git a/src/FSharp.Data.JsonSchema/JsonSchema.fs b/src/FSharp.Data.JsonSchema/JsonSchema.fs index 800ffa8..ac1b6dd 100644 --- a/src/FSharp.Data.JsonSchema/JsonSchema.fs +++ b/src/FSharp.Data.JsonSchema/JsonSchema.fs @@ -146,31 +146,39 @@ type MultiCaseDuSchemaProcessor(?casePropertyName) = string (Char.ToLowerInvariant field.Name.[0]) + field.Name.Substring(1) + let generate ( t : Type) = + let isIntegerEnum = Reflection.isIntegerEnum t + if context.Resolver.HasSchema(t, isIntegerEnum) then + context.Resolver.GetSchema(t, isIntegerEnum) + else + let s = context.Generator.Generate(t, context.Resolver) + if (not << Reflection.isPrimitive ) t + && not (context.Resolver.HasSchema(t, isIntegerEnum)) + then + context.Resolver.AddSchema(t, isIntegerEnum, s) + s + if field.PropertyType.IsGenericType && field.PropertyType.GetGenericTypeDefinition() = typedefof> then let innerTy = field.PropertyType.GetGenericArguments().[0] - let fieldSchema = context.Generator.Generate(innerTy, context.Resolver) + let fieldSchema = generate innerTy let prop = if Reflection.isPrimitive innerTy then JsonSchemaProperty(Type = fieldSchema.Type) else - if not <| context.Resolver.HasSchema(innerTy, Reflection.isIntegerEnum innerTy) then - context.Resolver.AddSchema(innerTy, Reflection.isIntegerEnum innerTy,fieldSchema) JsonSchemaProperty(Reference = fieldSchema) s.Properties.Add(camelCaseFieldName, prop) else - let fieldSchema = context.Generator.Generate(field.PropertyType, context.Resolver) + let fieldSchema = generate field.PropertyType let prop = if Reflection.isPrimitive field.PropertyType then JsonSchemaProperty(Type = fieldSchema.Type, Format = fieldSchema.Format) else - if not <| context.Resolver.HasSchema(field.PropertyType, Reflection.isIntegerEnum field.PropertyType) then - context.Resolver.AddSchema(field.PropertyType, Reflection.isIntegerEnum field.PropertyType, fieldSchema) JsonSchemaProperty(Reference = fieldSchema) s.Properties.Add(camelCaseFieldName, prop) diff --git a/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs b/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs index 017400c..4a2f3a6 100644 --- a/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs +++ b/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs @@ -112,6 +112,10 @@ let tests = return generator typeof |> json } + verify "DU with array of DUs generates proper schema" { + return generator typeof |> json + } + verify "Record with array of records generates proper schema" { return generator typeof |> json } diff --git a/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs b/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs index 4699b80..900512b 100644 --- a/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs +++ b/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs @@ -93,7 +93,9 @@ and Odd = type RecWithRecArray = { V : TestRecord array } -type DUWithRecArray = Records of TestRecord array +type DUWithRecArray = AA | Records of TestRecord array + +type DUWithDUArray = Dus of TestDU array module Util = let stripWhitespace text = diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.DU with array of DUs generates proper schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.DU with array of DUs generates proper schema.verified.txt new file mode 100644 index 0000000..b8b1206 --- /dev/null +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.DU with array of DUs generates proper schema.verified.txt @@ -0,0 +1,118 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "DUWithDUArray", + "definitions": { + "TestDU": { + "definitions": { + "Case": { + "type": "string", + "default": "Case", + "additionalProperties": false, + "x-enumNames": [ + "Case" + ], + "enum": [ + "Case" + ] + }, + "WithOneField": { + "type": "object", + "additionalProperties": false, + "required": [ + "tag", + "item" + ], + "properties": { + "tag": { + "type": "string", + "default": "WithOneField", + "x-enumNames": [ + "WithOneField" + ], + "enum": [ + "WithOneField" + ] + }, + "item": { + "type": "integer", + "format": "int32" + } + } + }, + "WithNamedFields": { + "type": "object", + "additionalProperties": false, + "required": [ + "tag", + "name", + "value" + ], + "properties": { + "tag": { + "type": "string", + "default": "WithNamedFields", + "x-enumNames": [ + "WithNamedFields" + ], + "enum": [ + "WithNamedFields" + ] + }, + "name": { + "type": "string" + }, + "value": { + "type": "number", + "format": "double" + } + } + } + }, + "anyOf": [ + { + "$ref": "#/definitions/TestDU/definitions/Case" + }, + { + "$ref": "#/definitions/TestDU/definitions/WithOneField" + }, + { + "$ref": "#/definitions/TestDU/definitions/WithNamedFields" + } + ] + }, + "TestDUOf": { + "type": "array", + "items": { + "$ref": "#/definitions/TestDU" + } + }, + "Dus": { + "type": "object", + "additionalProperties": false, + "required": [ + "tag", + "item" + ], + "properties": { + "tag": { + "type": "string", + "default": "Dus", + "x-enumNames": [ + "Dus" + ], + "enum": [ + "Dus" + ] + }, + "item": { + "$ref": "#/definitions/TestDUOf" + } + } + } + }, + "anyOf": [ + { + "$ref": "#/definitions/Dus" + } + ] +} \ No newline at end of file diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.DU with array of records generates proper schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.DU with array of records generates proper schema.verified.txt index ed688cd..fac3032 100644 --- a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.DU with array of records generates proper schema.verified.txt +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.DU with array of records generates proper schema.verified.txt @@ -2,6 +2,17 @@ "$schema": "http://json-schema.org/draft-04/schema#", "title": "DUWithRecArray", "definitions": { + "AA": { + "type": "string", + "default": "AA", + "additionalProperties": false, + "x-enumNames": [ + "AA" + ], + "enum": [ + "AA" + ] + }, "TestRecord": { "type": "object", "additionalProperties": false, @@ -49,6 +60,9 @@ } }, "anyOf": [ + { + "$ref": "#/definitions/AA" + }, { "$ref": "#/definitions/Records" } From f64b9f94afa1ee433a31f6e03d355038390488c8 Mon Sep 17 00:00:00 2001 From: Moises Nessim Date: Thu, 25 Jul 2024 09:40:25 -0500 Subject: [PATCH 17/26] Target net8.0 --- .github/workflows/ci.yml | 18 ++++++++++++++++-- .../FSharp.Data.JsonSchema.fsproj | 2 +- .../FSharp.Data.JsonSchema.Tests.fsproj | 4 ++-- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cccb927..a719374 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,7 @@ on: - v[0-9]+.[0-9]+.[0-9]+ branches: - master + - net8.0 pull_request: branches: - master @@ -16,18 +17,31 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Use dotnet CLI + + - name: Use dotnet CLI 6 uses: actions/setup-dotnet@v1 with: dotnet-version: "6.0.x" # SDK Version to use. + + - name: Use dotnet CLI 8 + uses: actions/setup-dotnet@v1 + with: + dotnet-version: "8.0.x" # SDK Version to use. + - name: Build run: | dotnet restore dotnet build -c Release --no-restore - - name: Test + + - name: Test net6.0 run: dotnet test -c Release --no-restore --no-build --framework net6.0 --logger "GitHubActions;report-warnings=false" + + - name: Test net8.0 + run: dotnet test -c Release --no-restore --no-build --framework net8.0 --logger "GitHubActions;report-warnings=false" + - name: Pack run: dotnet pack -c Release --no-restore --no-build --include-symbols -o out + - name: Upload Test Results if: failure() uses: actions/upload-artifact@v2 diff --git a/src/FSharp.Data.JsonSchema/FSharp.Data.JsonSchema.fsproj b/src/FSharp.Data.JsonSchema/FSharp.Data.JsonSchema.fsproj index d2087bb..4558477 100644 --- a/src/FSharp.Data.JsonSchema/FSharp.Data.JsonSchema.fsproj +++ b/src/FSharp.Data.JsonSchema/FSharp.Data.JsonSchema.fsproj @@ -1,7 +1,7 @@  - netstandard2.0;netstandard2.1;netcoreapp3.1;net6.0 + netstandard2.0;netstandard2.1;netcoreapp3.1;net6.0;net8.0 diff --git a/test/FSharp.Data.JsonSchema.Tests/FSharp.Data.JsonSchema.Tests.fsproj b/test/FSharp.Data.JsonSchema.Tests/FSharp.Data.JsonSchema.Tests.fsproj index 764c61f..8fb3481 100644 --- a/test/FSharp.Data.JsonSchema.Tests/FSharp.Data.JsonSchema.Tests.fsproj +++ b/test/FSharp.Data.JsonSchema.Tests/FSharp.Data.JsonSchema.Tests.fsproj @@ -1,7 +1,7 @@ Exe - netcoreapp3.1;net6.0 + netcoreapp3.1;net6.0;net8.0 false false @@ -25,4 +25,4 @@ - \ No newline at end of file + From a28417476ca639e23ee3b36664fb4b4c53241746 Mon Sep 17 00:00:00 2001 From: Moises Nessim Date: Thu, 25 Jul 2024 10:04:42 -0500 Subject: [PATCH 18/26] Conditionally change PackageId so I can test nuget package deployment --- .github/workflows/ci.yml | 1 - src/FSharp.Data.JsonSchema/FSharp.Data.JsonSchema.fsproj | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a719374..5aebeef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,6 @@ on: - v[0-9]+.[0-9]+.[0-9]+ branches: - master - - net8.0 pull_request: branches: - master diff --git a/src/FSharp.Data.JsonSchema/FSharp.Data.JsonSchema.fsproj b/src/FSharp.Data.JsonSchema/FSharp.Data.JsonSchema.fsproj index 4558477..928248a 100644 --- a/src/FSharp.Data.JsonSchema/FSharp.Data.JsonSchema.fsproj +++ b/src/FSharp.Data.JsonSchema/FSharp.Data.JsonSchema.fsproj @@ -2,6 +2,8 @@ netstandard2.0;netstandard2.1;netcoreapp3.1;net6.0;net8.0 + FSharp.Data.JsonSchema + $(GITHUB_REPOSITORY_OWNER).FSharp.Data.JsonSchema From 62be5982020611a37aa382e8f8d8e172220f730c Mon Sep 17 00:00:00 2001 From: Moises Nessim Date: Thu, 25 Jul 2024 10:21:42 -0500 Subject: [PATCH 19/26] Run CI on pre-release veriosn tags --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5aebeef..227152b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,7 +3,7 @@ name: CI on: push: tags: - - v[0-9]+.[0-9]+.[0-9]+ + - v[0-9]+.[0-9]+.[0-9]+* branches: - master pull_request: From f2c8776d99e674151c06672e0cafc9b32a8ef33e Mon Sep 17 00:00:00 2001 From: Moises Nessim Date: Thu, 25 Jul 2024 12:00:36 -0500 Subject: [PATCH 20/26] Fix fsproj packageid condition Set Version before build Use net8.0 in build.sh --- .github/workflows/ci.yml | 9 ++++++--- build.ps1 | 2 +- build.sh | 2 +- src/Directory.Build.props | 5 +++-- src/FSharp.Data.JsonSchema/FSharp.Data.JsonSchema.fsproj | 4 ++-- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 227152b..6bc5da5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,12 @@ jobs: with: dotnet-version: "8.0.x" # SDK Version to use. + - name: Set env + if: startsWith( github.ref, 'refs/tags/v' ) + run: | + echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV + echo "Version=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV + - name: Build run: | dotnet restore @@ -53,9 +59,6 @@ jobs: with: name: nupkg path: ./out - - name: Set env - if: startsWith( github.ref, 'refs/tags/v' ) - run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV - name: Push if: startsWith( github.ref, 'refs/tags/v' ) env: diff --git a/build.ps1 b/build.ps1 index acd306b..e9a7e60 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,4 +1,4 @@ dotnet restore dotnet build -c Release --no-restore -dotnet test -c Release --no-restore --no-build --framework net6.0 +dotnet test -c Release --no-restore --no-build --framework net8.0 dotnet pack -c Release -o bin --no-restore --no-build diff --git a/build.sh b/build.sh index bd41f2d..7878a27 100755 --- a/build.sh +++ b/build.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash dotnet restore dotnet build -c Release --no-restore -dotnet test -c Release --no-restore --no-build --framework net6.0 +dotnet test -c Release --no-restore --no-build --framework net8.0 dotnet pack -c Release -o bin --no-restore --no-build diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 0de1774..2eb047f 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,6 +1,7 @@ 2.1.2 + $(Version) Ryan Riley Ryan Riley https://github.com/panesofglass/FSharp.Data.JsonSchema @@ -16,10 +17,10 @@ true - + true - + true snupkg diff --git a/src/FSharp.Data.JsonSchema/FSharp.Data.JsonSchema.fsproj b/src/FSharp.Data.JsonSchema/FSharp.Data.JsonSchema.fsproj index 928248a..bb0cc8d 100644 --- a/src/FSharp.Data.JsonSchema/FSharp.Data.JsonSchema.fsproj +++ b/src/FSharp.Data.JsonSchema/FSharp.Data.JsonSchema.fsproj @@ -2,8 +2,8 @@ netstandard2.0;netstandard2.1;netcoreapp3.1;net6.0;net8.0 - FSharp.Data.JsonSchema - $(GITHUB_REPOSITORY_OWNER).FSharp.Data.JsonSchema + $(GITHUB_REPOSITORY_OWNER).FSharp.Data.JsonSchema + $(Version) From 8b9fb1d9b7f7a6f6835ce1be9f8c56122e9f2830 Mon Sep 17 00:00:00 2001 From: Moises Nessim Date: Thu, 25 Jul 2024 12:07:47 -0500 Subject: [PATCH 21/26] Fix Version --- .github/workflows/ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6bc5da5..e608923 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,8 +30,9 @@ jobs: - name: Set env if: startsWith( github.ref, 'refs/tags/v' ) run: | - echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV - echo "Version=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV + RELEASE_VERSION="${GITHUB_REF#refs/*/}" + echo "RELEASE_VERSION=${RELEASE_VERSION}" >> $GITHUB_ENV + echo "Version=${RELEASE_VERSION:1}" >> $GITHUB_ENV - name: Build run: | From de9ea79b3930ef51608d9336a6dd2c73c33b0f48 Mon Sep 17 00:00:00 2001 From: Moises Nessim Date: Thu, 25 Jul 2024 12:11:09 -0500 Subject: [PATCH 22/26] Push nupkg with optional prefix --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e608923..2620cc9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,4 +66,4 @@ jobs: API_KEY: ${{ secrets.NUGET_API_KEY }} SOURCE: "https://api.nuget.org/v3/index.json" run: | - dotnet nuget push ./out/FSharp.Data.JsonSchema.${RELEASE_VERSION:1}.nupkg --skip-duplicate --no-symbols true --source $SOURCE --api-key $API_KEY + dotnet nuget push ./out/*FSharp.Data.JsonSchema.${RELEASE_VERSION:1}.nupkg --skip-duplicate --no-symbols true --source $SOURCE --api-key $API_KEY From 24b2c85818a290ebd055fb93358515d01a1e260e Mon Sep 17 00:00:00 2001 From: Moises Nessim Date: Thu, 25 Jul 2024 12:23:44 -0500 Subject: [PATCH 23/26] Fix --no-symbols param by removing 'true' --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2620cc9..366d338 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,4 +66,4 @@ jobs: API_KEY: ${{ secrets.NUGET_API_KEY }} SOURCE: "https://api.nuget.org/v3/index.json" run: | - dotnet nuget push ./out/*FSharp.Data.JsonSchema.${RELEASE_VERSION:1}.nupkg --skip-duplicate --no-symbols true --source $SOURCE --api-key $API_KEY + dotnet nuget push ./out/*FSharp.Data.JsonSchema.${RELEASE_VERSION:1}.nupkg --skip-duplicate --no-symbols --source $SOURCE --api-key $API_KEY From 9cd155f5b10e710de514f18131c0c743546036ab Mon Sep 17 00:00:00 2001 From: Moises Nessim Date: Thu, 25 Jul 2024 17:45:30 -0500 Subject: [PATCH 24/26] Support parent DU case with same name --- src/FSharp.Data.JsonSchema/JsonSchema.fs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/FSharp.Data.JsonSchema/JsonSchema.fs b/src/FSharp.Data.JsonSchema/JsonSchema.fs index ac1b6dd..f34f6e4 100644 --- a/src/FSharp.Data.JsonSchema/JsonSchema.fs +++ b/src/FSharp.Data.JsonSchema/JsonSchema.fs @@ -23,13 +23,23 @@ module Reflection = let isOption (y: System.Type) = y.IsGenericType && typedefof<_ option> = y.GetGenericTypeDefinition() - + let isPrimitive (ty: Type) = ty.IsPrimitive || ty = typeof || ty = typeof let isIntegerEnum (ty: Type) = ty.IsEnum && ty.GetEnumUnderlyingType() = typeof +module Dictionary = + let getUniqueKey (dict: IDictionary) (key: string) = + let mutable i = 0 + let mutable newKey = key + + while dict.ContainsKey(newKey) do + i <- i + 1 + newKey <- sprintf "%s%d" key i + + newKey type OptionSchemaProcessor() = static let optionTy = typedefof> @@ -186,7 +196,9 @@ type MultiCaseDuSchemaProcessor(?casePropertyName) = s // Attach each case definition. - schema.Definitions.Add(case.Name, caseSchema) + let name = Dictionary.getUniqueKey schema.Definitions case.Name + // printfn "Adding case %s to dict: %A" name schema.Definitions + schema.Definitions.Add(name, caseSchema) // Add each schema to the anyOf collection. schema.AnyOf.Add(JsonSchema(Reference = caseSchema)) @@ -215,7 +227,7 @@ type RecordSchemaProcessor() = member this.Process(context) = this.Process(context) - + [] type internal SchemaNameGenerator() = From cd386edc398e58727933a9955660e10e6ada3811 Mon Sep 17 00:00:00 2001 From: Moises Nessim Date: Mon, 3 Mar 2025 14:00:32 -0500 Subject: [PATCH 25/26] Support ValueOption --- build.sh | 3 ++ src/FSharp.Data.JsonSchema/JsonSchema.fs | 27 ++++++----- .../FSharp.Data.JsonSchema.Tests.fsproj | 1 + .../GeneratorTests.fs | 19 ++++++-- .../JsonSerializationTests.fs | 30 ++++++++++++ .../FSharp.Data.JsonSchema.Tests/TestTypes.fs | 11 +++++ .../ValidationTests.fs | 46 ++++++++++++++++++- ...rates proper schema.DotNet6_0.verified.txt | 21 +++++++++ ...ption generates proper schema.verified.txt | 21 +++++++++ ...rates proper schema.DotNet6_0.verified.txt | 3 ++ ...n__a_ generates proper schema.verified.txt | 13 +----- ...rates proper schema.DotNet6_0.verified.txt | 3 ++ ...n__a_ generates proper schema.verified.txt | 3 ++ ...rates proper schema.DotNet6_0.verified.txt | 9 ++++ ..._int_ generates proper schema.verified.txt | 9 ++++ 15 files changed, 189 insertions(+), 30 deletions(-) create mode 100644 test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.RecWithValueOption generates proper schema.DotNet6_0.verified.txt create mode 100644 test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.RecWithValueOption generates proper schema.verified.txt create mode 100644 test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.option__a_ generates proper schema.DotNet6_0.verified.txt create mode 100644 test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.voption__a_ generates proper schema.DotNet6_0.verified.txt create mode 100644 test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.voption__a_ generates proper schema.verified.txt create mode 100644 test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.voption_int_ generates proper schema.DotNet6_0.verified.txt create mode 100644 test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.voption_int_ generates proper schema.verified.txt diff --git a/build.sh b/build.sh index 7878a27..de6a861 100755 --- a/build.sh +++ b/build.sh @@ -1,4 +1,7 @@ #!/usr/bin/env bash + +set -euo pipefail + dotnet restore dotnet build -c Release --no-restore dotnet test -c Release --no-restore --no-build --framework net8.0 diff --git a/src/FSharp.Data.JsonSchema/JsonSchema.fs b/src/FSharp.Data.JsonSchema/JsonSchema.fs index f34f6e4..6268890 100644 --- a/src/FSharp.Data.JsonSchema/JsonSchema.fs +++ b/src/FSharp.Data.JsonSchema/JsonSchema.fs @@ -22,7 +22,12 @@ module Reflection = let isOption (y: System.Type) = y.IsGenericType - && typedefof<_ option> = y.GetGenericTypeDefinition() + && + let def = y.GetGenericTypeDefinition() + def = typedefof<_ option> || def = typedefof> + + let isObjOption (y: System.Type) = + y = typedefof<_ option> || y = typedefof> let isPrimitive (ty: Type) = ty.IsPrimitive || ty = typeof || ty = typeof @@ -42,13 +47,10 @@ module Dictionary = newKey type OptionSchemaProcessor() = - static let optionTy = typedefof> - member this.Process(context: SchemaProcessorContext) = if isNull context.Schema.Reference - && context.ContextualType.Type.IsGenericType - && optionTy.Equals(context.ContextualType.Type.GetGenericTypeDefinition()) + && Reflection.isOption context.ContextualType.Type then let schema = context.Schema let cases = FSharpType.GetUnionCases(context.ContextualType.Type) @@ -56,7 +58,7 @@ type OptionSchemaProcessor() = let schemaType = [| for case in cases do match case.Name with - | "None" -> yield JsonObjectType.Null + | "None" | "ValueNone" -> yield JsonObjectType.Null | _ -> let field = case.GetFields() |> Array.head @@ -168,8 +170,7 @@ type MultiCaseDuSchemaProcessor(?casePropertyName) = context.Resolver.AddSchema(t, isIntegerEnum, s) s - if field.PropertyType.IsGenericType - && field.PropertyType.GetGenericTypeDefinition() = typedefof> then + if Reflection.isOption field.PropertyType then let innerTy = field.PropertyType.GetGenericArguments().[0] @@ -236,10 +237,9 @@ type internal SchemaNameGenerator() = override this.Generate(ty: Type) = let cachedType = ty.ToCachedType() - if cachedType.Type = typeof> then + if Reflection.isObjOption cachedType.Type then "Any" - elif cachedType.Type.IsGenericType - && cachedType.Type.GetGenericTypeDefinition() = typedefof> then + elif Reflection.isOption cachedType.Type then this.Generate(cachedType.GenericArguments.[0].OriginalType) else base.Generate(ty) @@ -249,10 +249,9 @@ type internal ReflectionService() = inherit DefaultReflectionService() override this.GetDescription(contextualType, defaultReferenceTypeNullHandling, settings) = - if contextualType.Type = typeof> then + if Reflection.isObjOption contextualType.Type then JsonTypeDescription.Create(contextualType, JsonObjectType.Object, true, null) - elif contextualType.Type.IsConstructedGenericType - && contextualType.Type.GetGenericTypeDefinition() = typedefof> then + elif Reflection.isOption contextualType.Type then let typeDescription = this.GetDescription( contextualType.OriginalGenericArguments.[0], diff --git a/test/FSharp.Data.JsonSchema.Tests/FSharp.Data.JsonSchema.Tests.fsproj b/test/FSharp.Data.JsonSchema.Tests/FSharp.Data.JsonSchema.Tests.fsproj index 8fb3481..1d9b6ab 100644 --- a/test/FSharp.Data.JsonSchema.Tests/FSharp.Data.JsonSchema.Tests.fsproj +++ b/test/FSharp.Data.JsonSchema.Tests/FSharp.Data.JsonSchema.Tests.fsproj @@ -2,6 +2,7 @@ Exe netcoreapp3.1;net6.0;net8.0 + net6.0 false false diff --git a/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs b/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs index 4a2f3a6..4f01026 100644 --- a/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs +++ b/test/FSharp.Data.JsonSchema.Tests/GeneratorTests.fs @@ -25,8 +25,9 @@ type VerifyBuilder(name,focusState) = member __.Return<'T>(v:'T) = Verifier.Verify(makeValidFilePath name, v,settings= verifySettings).Wait() -let verify name = VerifyBuilder(name,FocusState.Normal) -let fverify name = VerifyBuilder(name,FocusState.Focused) +let verify name = VerifyBuilder(name,Normal) +let fverify name = VerifyBuilder(name,Focused) +let pverify name = VerifyBuilder(name,Pending) let json ( schema: NJsonSchema.JsonSchema ) = schema.ToJson() @@ -56,10 +57,18 @@ let tests = return generator typeof> |> json } + verify "voption<'a> generates proper schema" { + return generator typeof> |> json + } + verify "option generates proper schema" { return generator typeof> |> json } + verify "voption generates proper schema" { + return generator typeof> |> json + } + verify "TestSingleDU generates proper schema" { return generator typeof |> json } @@ -76,6 +85,10 @@ let tests = return generator typeof |> json } + verify "RecWithValueOption generates proper schema" { + return generator typeof |> json + } + verify "RecWithGenericOption generates proper schema" { return generator typeof> |> json } @@ -91,7 +104,7 @@ let tests = verify "PaginatedResult<'T> generates proper schema" { return generator typeof> |> json } - + verify "FSharp list generates proper schema" { return generator typeof |> json } diff --git a/test/FSharp.Data.JsonSchema.Tests/JsonSerializationTests.fs b/test/FSharp.Data.JsonSchema.Tests/JsonSerializationTests.fs index 3e836be..edce554 100644 --- a/test/FSharp.Data.JsonSchema.Tests/JsonSerializationTests.fs +++ b/test/FSharp.Data.JsonSchema.Tests/JsonSerializationTests.fs @@ -38,6 +38,36 @@ let tests = Expect.equal actual expected "Expected serializer to convert option to unwrapped value" } + test "ValueOption.None should serialize as null" { + let expected = "null" + let actual = Json.Serialize(ValueNone, "tag") + Expect.equal actual expected "Expected serializer to convert ValueNone null" + } + + test "ValueOption.ValueNone should roundtrip" { + let expected = ValueNone + + let actual = + Json.Deserialize(Json.Serialize(expected, "tag"), "tag") + + Expect.equal actual expected "Expected serializer to convert ValueNone to null" + } + + test "ValueOption.ValueSome(1) should serialize as 1" { + let expected = "1" + let actual = Json.Serialize(ValueSome 1, "tag") + Expect.equal actual expected "Expected serializer to convert option to unwrapped value" + } + + test "ValueOption.ValueSome(1) should roundtrip" { + let expected = ValueSome 1 + + let actual = + Json.Deserialize(Json.Serialize(expected, "tag"), "tag") + + Expect.equal actual expected "Expected serializer to convert option to unwrapped value" + } + test "tuple should serialize as array" { let expected = """["2021-03-01T00:00:00",10.01]""" diff --git a/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs b/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs index 900512b..760f273 100644 --- a/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs +++ b/test/FSharp.Data.JsonSchema.Tests/TestTypes.fs @@ -6,6 +6,9 @@ type TestClass() = type TestRecord = { FirstName: string; LastName: string } +[] +type TestStructRecord = { A: int; B: float } + type TestList = { Id: int Name: string @@ -47,10 +50,18 @@ type RecWithOption = { Name: string Description: string option } +type RecWithValueOption = + { Count: int voption + Hey: string } + type RecWithGenericOption<'T> = { Car: string CarType: 'T option } +type RecWithGenericValueOption<'T> = + { Car: string + CarType: 'T voption } + type RecWithArrayOption = { Hey: string; Many: string array option } diff --git a/test/FSharp.Data.JsonSchema.Tests/ValidationTests.fs b/test/FSharp.Data.JsonSchema.Tests/ValidationTests.fs index 9c6e595..306d4c8 100644 --- a/test/FSharp.Data.JsonSchema.Tests/ValidationTests.fs +++ b/test/FSharp.Data.JsonSchema.Tests/ValidationTests.fs @@ -48,6 +48,15 @@ let tests = "╰〳 ಠ 益 ಠೃ 〵╯" |> Expect.isOk actual } + test "Record missing value optional field validates against schema" { + let schema = generator (typeof) + + let json = """{"hey":"Ryan"}""" + + let actual = Validation.validate schema json + "╰〳 ಠ 益 ಠೃ 〵╯" |> Expect.isOk actual + } + test "Record missing nullable field validates against schema" { let schema = generator (typeof) @@ -74,6 +83,13 @@ let tests = "╰〳 ಠ 益 ಠೃ 〵╯" |> Expect.equal actual (Ok()) } + test "ValueNone validates against schema for voption<_>" { + let schema = generator(typeof>) + let json = Json.Serialize(ValueNone, "tag") + let actual = Validation.validate schema json + "╰〳 ಠ 益 ಠೃ 〵╯" |> Expect.equal actual (Ok()) + } + test "None validates against schema for option" { let schema = generator(typeof>) let json = Json.Serialize(None, "tag") @@ -88,6 +104,13 @@ let tests = "╰〳 ಠ 益 ಠೃ 〵╯" |> Expect.equal actual (Ok()) } + test "ValueNone validates against schema for voption" { + let schema = generator(typeof>) + let json = Json.Serialize(ValueNone, "tag") + let actual = Validation.validate schema json + "╰〳 ಠ 益 ಠೃ 〵╯" |> Expect.equal actual (Ok()) + } + test "None validates against schema for option" { let schema = generator(typeof>) let json = Json.Serialize(None, "tag") @@ -95,6 +118,13 @@ let tests = "╰〳 ಠ 益 ಠೃ 〵╯" |> Expect.equal actual (Ok()) } + test "ValueNone validates against schema for voption" { + let schema = generator(typeof>) + let json = Json.Serialize(ValueNone, "tag") + let actual = Validation.validate schema json + "╰〳 ಠ 益 ಠೃ 〵╯" |> Expect.equal actual (Ok()) + } + test "Some \"test\" validates against schema for option<_>" { let schema = generator(typeof>) let json = Json.Serialize(Some "test", "tag") @@ -102,8 +132,15 @@ let tests = "╰〳 ಠ 益 ಠೃ 〵╯" |> Expect.equal actual (Ok()) } + test "ValueSome \"test\" validates against schema for voption<_>" { + let schema = generator(typeof>) + let json = Json.Serialize(ValueSome "test", "tag") + let actual = Validation.validate schema json + "╰〳 ಠ 益 ಠೃ 〵╯" |> Expect.equal actual (Ok()) + } + test "Some \"test\" validates against schema for option" { - let schema = generator(typeof>) + let schema = generator(typeof>) let json = Json.Serialize(Some "test", "tag") let actual = Validation.validate schema json "╰〳 ಠ 益 ಠೃ 〵╯" |> Expect.equal actual (Ok()) @@ -123,6 +160,13 @@ let tests = "╰〳 ಠ 益 ಠೃ 〵╯" |> Expect.equal actual (Ok()) } + test "ValueSome 1 validates against schema for voption" { + let schema = generator(typeof>) + let json = Json.Serialize(ValueSome 1, "tag") + let actual = Validation.validate schema json + "╰〳 ಠ 益 ಠೃ 〵╯" |> Expect.equal actual (Ok()) + } + test "TestSingleDU.Single validates against schema" { let schema = generator(typeof) let json = Json.Serialize(TestSingleDU.Single, "tag") diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.RecWithValueOption generates proper schema.DotNet6_0.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.RecWithValueOption generates proper schema.DotNet6_0.verified.txt new file mode 100644 index 0000000..45e4ab0 --- /dev/null +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.RecWithValueOption generates proper schema.DotNet6_0.verified.txt @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "RecWithValueOption", + "type": "object", + "additionalProperties": false, + "required": [ + "hey" + ], + "properties": { + "count": { + "type": [ + "integer", + "null" + ], + "format": "int32" + }, + "hey": { + "type": "string" + } + } +} \ No newline at end of file diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.RecWithValueOption generates proper schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.RecWithValueOption generates proper schema.verified.txt new file mode 100644 index 0000000..45e4ab0 --- /dev/null +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.RecWithValueOption generates proper schema.verified.txt @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "RecWithValueOption", + "type": "object", + "additionalProperties": false, + "required": [ + "hey" + ], + "properties": { + "count": { + "type": [ + "integer", + "null" + ], + "format": "int32" + }, + "hey": { + "type": "string" + } + } +} \ No newline at end of file diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.option__a_ generates proper schema.DotNet6_0.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.option__a_ generates proper schema.DotNet6_0.verified.txt new file mode 100644 index 0000000..f8c7798 --- /dev/null +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.option__a_ generates proper schema.DotNet6_0.verified.txt @@ -0,0 +1,3 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#" +} \ No newline at end of file diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.option__a_ generates proper schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.option__a_ generates proper schema.verified.txt index 2d81f00..f8c7798 100644 --- a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.option__a_ generates proper schema.verified.txt +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.option__a_ generates proper schema.verified.txt @@ -1,14 +1,3 @@ { - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Any", - "type": [ - "array", - "boolean", - "integer", - "null", - "number", - "object", - "string" - ], - "additionalProperties": false + "$schema": "http://json-schema.org/draft-04/schema#" } \ No newline at end of file diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.voption__a_ generates proper schema.DotNet6_0.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.voption__a_ generates proper schema.DotNet6_0.verified.txt new file mode 100644 index 0000000..f8c7798 --- /dev/null +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.voption__a_ generates proper schema.DotNet6_0.verified.txt @@ -0,0 +1,3 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#" +} \ No newline at end of file diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.voption__a_ generates proper schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.voption__a_ generates proper schema.verified.txt new file mode 100644 index 0000000..f8c7798 --- /dev/null +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.voption__a_ generates proper schema.verified.txt @@ -0,0 +1,3 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#" +} \ No newline at end of file diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.voption_int_ generates proper schema.DotNet6_0.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.voption_int_ generates proper schema.DotNet6_0.verified.txt new file mode 100644 index 0000000..f542b0c --- /dev/null +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.voption_int_ generates proper schema.DotNet6_0.verified.txt @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Integer", + "type": [ + "integer", + "null" + ], + "format": "int32" +} \ No newline at end of file diff --git a/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.voption_int_ generates proper schema.verified.txt b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.voption_int_ generates proper schema.verified.txt new file mode 100644 index 0000000..f542b0c --- /dev/null +++ b/test/FSharp.Data.JsonSchema.Tests/generator-verified/GeneratorTests.voption_int_ generates proper schema.verified.txt @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Integer", + "type": [ + "integer", + "null" + ], + "format": "int32" +} \ No newline at end of file From a0d8632fdb4655fedb4c84e6abae4cc3a3613c1f Mon Sep 17 00:00:00 2001 From: Moises Nessim Date: Mon, 3 Mar 2025 14:14:22 -0500 Subject: [PATCH 26/26] Use actions/upload-artifact@v4 --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 366d338..b2fd4c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,13 +50,13 @@ jobs: - name: Upload Test Results if: failure() - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: verify-test-results path: | **/*.received.* - name: Upload artifact - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v4 with: name: nupkg path: ./out