diff --git a/src/FSharp.Data.GraphQL.Shared/SchemaDefinitions.fs b/src/FSharp.Data.GraphQL.Shared/SchemaDefinitions.fs index 51f8682c..bc33ab3e 100644 --- a/src/FSharp.Data.GraphQL.Shared/SchemaDefinitions.fs +++ b/src/FSharp.Data.GraphQL.Shared/SchemaDefinitions.fs @@ -392,17 +392,73 @@ module SchemaDefinitions = | false, _ -> getParseError destinationType s | InlineConstant value -> value.GetCoerceError destinationType - /// Wraps a GraphQL type definition, allowing defining field/argument - /// to take option of provided value. - let Nullable(innerDef : #TypeDef<'Val>) : NullableDef<'Val> = upcast { NullableDefinition.OfType = innerDef } - - /// Wraps a GraphQL type definition, allowing defining field/argument - /// to take voption of provided value. - let StructNullable(innerDef : #TypeDef<'Val>) : StructNullableDef<'Val> = upcast { StructNullableDefinition.OfType = innerDef } - - /// Wraps a GraphQL type definition, allowing defining field/argument - /// to take collection of provided value. - let ListOf(innerDef : #TypeDef<'Val>) : ListOfDef<'Val, 'Seq> = upcast { ListOfDefinition.OfType = innerDef } + type TypeWrapperStaticDispatch = + + static member Nullable<'Val>(innerDef : InputOutputDef<'Val>) : NullableDef<'Val> = + let ofType : TypeDef<'Val> = upcast innerDef + upcast { NullableDefinition.OfType = ofType } + + static member Nullable<'Val>(innerDef : InputDef<'Val>) : InputDef<'Val option> = + let ofType : TypeDef<'Val> = upcast innerDef + upcast { NullableDefinition.OfType = ofType } + + static member Nullable<'Val>(innerDef : OutputDef<'Val>) : OutputDef<'Val option> = + let ofType : TypeDef<'Val> = upcast innerDef + upcast { NullableDefinition.OfType = ofType } + + static member StructNullable<'Val>(innerDef : InputOutputDef<'Val>) : StructNullableDef<'Val> = + let ofType : TypeDef<'Val> = upcast innerDef + upcast { StructNullableDefinition.OfType = ofType } + + static member StructNullable<'Val>(innerDef : InputDef<'Val>) : InputDef<'Val voption> = + let ofType : TypeDef<'Val> = upcast innerDef + upcast { StructNullableDefinition.OfType = ofType } + + static member StructNullable<'Val>(innerDef : OutputDef<'Val>) : OutputDef<'Val voption> = + let ofType : TypeDef<'Val> = upcast innerDef + upcast { StructNullableDefinition.OfType = ofType } + + static member ListOf<'Val, 'Seq when 'Seq :> 'Val seq>(innerDef : InputOutputDef<'Val>) : ListOfDef<'Val, 'Seq> = + let ofType : TypeDef<'Val> = upcast innerDef + upcast { ListOfDefinition.OfType = ofType } + + static member ListOf<'Val, 'Seq when 'Seq :> 'Val seq>(innerDef : InputDef<'Val>) : InputDef<'Seq> = + let ofType : TypeDef<'Val> = upcast innerDef + upcast { ListOfDefinition.OfType = ofType } + + static member ListOf<'Val, 'Seq when 'Seq :> 'Val seq>(innerDef : OutputDef<'Val>) : OutputDef<'Seq> = + let ofType : TypeDef<'Val> = upcast innerDef + upcast { ListOfDefinition.OfType = ofType } + + /// Wraps a GraphQL input or output type definition, allowing defining field/argument + /// to take option of provided value while preserving input/output kind of wrapped type. + /// Input wrappers produce input definitions, output wrappers produce output definitions, + /// and wrappers over types implementing both kinds keep both capabilities. + /// Dispatch is selected at compile time via SRTP. + let inline Nullable< ^Def, ^Wrapped when (^Def or TypeWrapperStaticDispatch) : (static member Nullable : ^Def -> ^Wrapped) > + (innerDef : ^Def) + : ^Wrapped = + ((^Def or TypeWrapperStaticDispatch) : (static member Nullable : ^Def -> ^Wrapped) innerDef) + + /// Wraps a GraphQL input or output type definition, allowing defining field/argument + /// to take voption of provided value while preserving input/output kind of wrapped type. + /// Input wrappers produce input definitions, output wrappers produce output definitions, + /// and wrappers over types implementing both kinds keep both capabilities. + /// Dispatch is selected at compile time via SRTP. + let inline StructNullable< ^Def, ^Wrapped when (^Def or TypeWrapperStaticDispatch) : (static member StructNullable : ^Def -> ^Wrapped) > + (innerDef : ^Def) + : ^Wrapped = + ((^Def or TypeWrapperStaticDispatch) : (static member StructNullable : ^Def -> ^Wrapped) innerDef) + + /// Wraps a GraphQL input or output type definition, allowing defining field/argument + /// to take collection of provided value while preserving input/output kind of wrapped type. + /// Input wrappers produce input definitions, output wrappers produce output definitions, + /// and wrappers over types implementing both kinds keep both capabilities. + /// Dispatch is selected at compile time via SRTP. + let inline ListOf< ^Def, ^Wrapped when (^Def or TypeWrapperStaticDispatch) : (static member ListOf : ^Def -> ^Wrapped) > + (innerDef : ^Def) + : ^Wrapped = + ((^Def or TypeWrapperStaticDispatch) : (static member ListOf : ^Def -> ^Wrapped) innerDef) let internal variableOrElse other (_ : InputExecutionContextProvider) value (variables : IReadOnlyDictionary) = match value with @@ -1415,13 +1471,14 @@ module SchemaDefinitions = /// If defined, this value will be used when no matching input has been provided by the requester. /// Optional input description. Usefull for generating documentation. static member SkippableInput(name : string, typedef : #InputDef<'In>, ?description : string) : InputFieldDef = + let typedef : InputDef<'In> = upcast typedef upcast { InputFieldDefinition.Name = name Description = description |> Option.map (fun s -> s + " Skip this field if you want to avoid saving it") IsSkippable = true TypeDef = - match (box typedef) with - | :? NullableDef<'In> as n -> n - | _ -> Nullable typedef + match (box typedef) with + | :? NullableDef<'In> as n -> (n :> InputDef<'In option>) + | _ -> Nullable typedef DefaultValue = None ExecuteInput = Unchecked.defaultof } @@ -1539,4 +1596,3 @@ module SchemaDefinitions = Description = description FieldsFn = fun () -> fieldsFn() |> List.toArray ResolveType = resolveType } - diff --git a/src/FSharp.Data.GraphQL.Shared/SchemaDefinitionsExtensions.fs b/src/FSharp.Data.GraphQL.Shared/SchemaDefinitionsExtensions.fs index b1190964..1da3341c 100644 --- a/src/FSharp.Data.GraphQL.Shared/SchemaDefinitionsExtensions.fs +++ b/src/FSharp.Data.GraphQL.Shared/SchemaDefinitionsExtensions.fs @@ -27,8 +27,10 @@ type internal CustomFieldsObjectDefinition<'Val> (source : ObjectDef<'Val>, fiel member _.Implements = source.Implements member _.IsTypeOf = source.IsTypeOf interface TypeDef with - member this.MakeList () = upcast (ListOf this) - member this.MakeNullable () = upcast (Nullable this) + // We construct wrappers directly here because this API works with untyped TypeDef values. + // The public ListOf/Nullable helpers use SRTP dispatch and require statically known direction. + member this.MakeList () = upcast { ListOfDefinition.OfType = this } + member this.MakeNullable () = upcast { NullableDefinition.OfType = this } member _.Type = (source :> TypeDef).Type interface NamedDef with member _.Name = (source :> NamedDef).Name diff --git a/src/FSharp.Data.GraphQL.Shared/TypeSystem.fs b/src/FSharp.Data.GraphQL.Shared/TypeSystem.fs index 11006387..be8e79b5 100644 --- a/src/FSharp.Data.GraphQL.Shared/TypeSystem.fs +++ b/src/FSharp.Data.GraphQL.Shared/TypeSystem.fs @@ -609,6 +609,24 @@ and OutputDef<'Val> = inherit TypeDef<'Val> end +/// Representation of type definitions that can be used as both inputs and outputs +/// (for example scalars and enums). This marker is also used by SRTP wrapper dispatch. +and InputOutputDef = + interface + inherit InputDef + inherit OutputDef + end + +/// Representation of all type definitions, that can be used as both inputs and outputs +/// and are constrained to represent the provided .NET type. +and InputOutputDef<'Val> = + interface + inherit InputOutputDef + inherit TypeDef<'Val> + inherit InputDef<'Val> + inherit OutputDef<'Val> + end + /// Representation of leaf type definitions. Leaf types represents leafs /// of the GraphQL query tree. Each query path must end with a leaf. /// By default only scalars and enums are valid leaf types. @@ -1067,8 +1085,7 @@ and ScalarDef = abstract CoerceOutput : obj -> obj option inherit TypeDef inherit NamedDef - inherit InputDef - inherit OutputDef + inherit InputOutputDef inherit LeafDef end @@ -1098,6 +1115,7 @@ and [] ScalarDefinition<'Primitive, 'Val> = { interface InputDef interface OutputDef + interface InputOutputDef interface ScalarDef with member x.Name = x.Name @@ -1107,6 +1125,7 @@ and [] ScalarDefinition<'Primitive, 'Val> = { interface InputDef<'Val> interface OutputDef<'Val> + interface InputOutputDef<'Val> interface LeafDef interface NamedDef with @@ -1182,8 +1201,7 @@ and EnumDef = /// List of available enum cases. abstract Options : EnumVal[] inherit TypeDef - inherit InputDef - inherit OutputDef + inherit InputOutputDef inherit LeafDef inherit NamedDef end @@ -1197,8 +1215,7 @@ and EnumDef<'Val> = abstract Options : EnumValue<'Val>[] inherit EnumDef inherit TypeDef<'Val> - inherit InputDef<'Val> - inherit OutputDef<'Val> + inherit InputOutputDef<'Val> end and internal EnumDefinition<'Val> = { @@ -1212,6 +1229,7 @@ and internal EnumDefinition<'Val> = { interface InputDef interface OutputDef + interface InputOutputDef interface TypeDef with member _.Type = typeof<'Val> @@ -1523,8 +1541,7 @@ and ListOfDef<'Val, 'Seq when 'Seq :> 'Val seq> = /// GraphQL type definition of the container element type. abstract OfType : TypeDef<'Val> inherit TypeDef<'Seq> - inherit InputDef<'Seq> - inherit OutputDef<'Seq> + inherit InputOutputDef<'Seq> inherit ListOfDef end @@ -1574,8 +1591,7 @@ and NullableDef<'Val> = interface /// GraphQL type definition of the nested type. abstract OfType : TypeDef<'Val> - inherit InputDef<'Val option> - inherit OutputDef<'Val option> + inherit InputOutputDef<'Val option> inherit NullableDef end @@ -1614,8 +1630,7 @@ and StructNullableDef<'Val> = interface /// GraphQL type definition of the nested type. abstract OfType : TypeDef<'Val> - inherit InputDef<'Val voption> - inherit OutputDef<'Val voption> + inherit InputOutputDef<'Val voption> inherit NullableDef end diff --git a/tests/FSharp.Data.GraphQL.Tests/FSharp.Data.GraphQL.Tests.fsproj b/tests/FSharp.Data.GraphQL.Tests/FSharp.Data.GraphQL.Tests.fsproj index 763d841e..1bc22455 100644 --- a/tests/FSharp.Data.GraphQL.Tests/FSharp.Data.GraphQL.Tests.fsproj +++ b/tests/FSharp.Data.GraphQL.Tests/FSharp.Data.GraphQL.Tests.fsproj @@ -41,6 +41,16 @@ + + + + + + + + + + diff --git a/tests/FSharp.Data.GraphQL.Tests/PlanningTests.fs b/tests/FSharp.Data.GraphQL.Tests/PlanningTests.fs index 57b330da..ad7cf74d 100644 --- a/tests/FSharp.Data.GraphQL.Tests/PlanningTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/PlanningTests.fs @@ -139,7 +139,7 @@ let ``Planning must retain correct types for lists``() = } } }""" - let PersonList : ListOfDef = ListOf Person + let PersonList : OutputDef = ListOf Person let plan = schemaProcessor.CreateExecutionPlanOrFail(query) equals 1 plan.Fields.Length let listInfo = plan.Fields.Head @@ -178,7 +178,7 @@ let ``Planning must work with interfaces``() = }""" let plan = schemaProcessor.CreateExecutionPlanOrFail(query) equals 1 plan.Fields.Length - let INamedList : ListOfDef = ListOf INamed + let INamedList : OutputDef = ListOf INamed let listInfo = plan.Fields.Head listInfo.Identifier |> equals "names" listInfo.ReturnDef |> equals (upcast INamedList) @@ -215,7 +215,7 @@ let ``Planning must work with unions``() = let plan = schemaProcessor.CreateExecutionPlanOrFail(query) equals 1 plan.Fields.Length let listInfo = plan.Fields.Head - let UNamedList : ListOfDef = ListOf UNamed + let UNamedList : OutputDef = ListOf UNamed listInfo.Identifier |> equals "names" listInfo.ReturnDef |> equals (upcast UNamedList) let (ResolveCollection(info)) = listInfo.Kind @@ -309,7 +309,7 @@ let ``Planning must handle inline fragment with non-matching type condition in u // Verify the execution plan structure equals 1 plan.Fields.Length let listInfo = plan.Fields.Head - let UNamedList : ListOfDef = ListOf UNamed + let UNamedList : OutputDef = ListOf UNamed listInfo.Identifier |> equals "names" listInfo.ReturnDef |> equals (upcast UNamedList) let (ResolveCollection(info)) = listInfo.Kind diff --git a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/.gitignore b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/.gitignore new file mode 100644 index 00000000..a6b4595a --- /dev/null +++ b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/.gitignore @@ -0,0 +1 @@ +References.fsx diff --git a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/ListOf.InputAsOutput.fsx b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/ListOf.InputAsOutput.fsx new file mode 100644 index 00000000..c13f4322 --- /dev/null +++ b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/ListOf.InputAsOutput.fsx @@ -0,0 +1,15 @@ +#load "References.fsx" + +open FSharp.Data.GraphQL.Types + +type InputOnly = { Value: int } +type OutputOnly = { Value: int } + +let inputOnlyType = + Define.InputObject( + name = "InputOnlyType", + fields = [ Define.Input("value", IntType) ] + ) + +// This should fail: InputDef cannot be assigned to OutputDef +let _ : OutputDef = ListOf inputOnlyType diff --git a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/ListOf.OutputAsInput.fsx b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/ListOf.OutputAsInput.fsx new file mode 100644 index 00000000..00849d1d --- /dev/null +++ b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/ListOf.OutputAsInput.fsx @@ -0,0 +1,15 @@ +#load "References.fsx" + +open FSharp.Data.GraphQL.Types + +type InputOnly = { Value: int } +type OutputOnly = { Value: int } + +let outputOnlyType = + Define.Object( + name = "OutputOnlyType", + fields = [ Define.Field("value", IntType, fun _ x -> x.Value) ] + ) + +// This should fail: OutputDef cannot be assigned to InputDef +let _ : InputDef = ListOf outputOnlyType diff --git a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Nullable.InputAsOutput.fsx b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Nullable.InputAsOutput.fsx new file mode 100644 index 00000000..f94d9e29 --- /dev/null +++ b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Nullable.InputAsOutput.fsx @@ -0,0 +1,15 @@ +#load "References.fsx" + +open FSharp.Data.GraphQL.Types + +type InputOnly = { Value: int } +type OutputOnly = { Value: int } + +let inputOnlyType = + Define.InputObject( + name = "InputOnlyType", + fields = [ Define.Input("value", IntType) ] + ) + +// This should fail: InputDef cannot be assigned to OutputDef +let _ : OutputDef = Nullable inputOnlyType diff --git a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Nullable.OutputAsInput.fsx b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Nullable.OutputAsInput.fsx new file mode 100644 index 00000000..65a87635 --- /dev/null +++ b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Nullable.OutputAsInput.fsx @@ -0,0 +1,15 @@ +#load "References.fsx" + +open FSharp.Data.GraphQL.Types + +type InputOnly = { Value: int } +type OutputOnly = { Value: int } + +let outputOnlyType = + Define.Object( + name = "OutputOnlyType", + fields = [ Define.Field("value", IntType, fun _ x -> x.Value) ] + ) + +// This should fail: OutputDef cannot be assigned to InputDef +let _ : InputDef = Nullable outputOnlyType diff --git a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/StructNullable.InputAsOutput.fsx b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/StructNullable.InputAsOutput.fsx new file mode 100644 index 00000000..ffd651ea --- /dev/null +++ b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/StructNullable.InputAsOutput.fsx @@ -0,0 +1,15 @@ +#load "References.fsx" + +open FSharp.Data.GraphQL.Types + +type InputOnly = { Value: int } +type OutputOnly = { Value: int } + +let inputOnlyType = + Define.InputObject( + name = "InputOnlyType", + fields = [ Define.Input("value", IntType) ] + ) + +// This should fail: InputDef cannot be assigned to OutputDef +let _ : OutputDef = StructNullable inputOnlyType diff --git a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/StructNullable.OutputAsInput.fsx b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/StructNullable.OutputAsInput.fsx new file mode 100644 index 00000000..6ec0f495 --- /dev/null +++ b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/StructNullable.OutputAsInput.fsx @@ -0,0 +1,15 @@ +#load "References.fsx" + +open FSharp.Data.GraphQL.Types + +type InputOnly = { Value: int } +type OutputOnly = { Value: int } + +let outputOnlyType = + Define.Object( + name = "OutputOnlyType", + fields = [ Define.Field("value", IntType, fun _ x -> x.Value) ] + ) + +// This should fail: OutputDef cannot be assigned to InputDef +let _ : InputDef = StructNullable outputOnlyType diff --git a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Valid.fsx b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Valid.fsx new file mode 100644 index 00000000..74c56dce --- /dev/null +++ b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Valid.fsx @@ -0,0 +1,26 @@ +#load "References.fsx" + +open FSharp.Data.GraphQL.Types + +type InputOnly = { Value: int } +type OutputOnly = { Value: int } + +let inputOnlyType = + Define.InputObject( + name = "InputOnlyType", + fields = [ Define.Input("value", IntType) ] + ) + +let outputOnlyType = + Define.Object( + name = "OutputOnlyType", + fields = [ Define.Field("value", IntType, fun _ x -> x.Value) ] + ) + +// These are all valid assignments and must compile successfully +let _inputList : InputDef = ListOf inputOnlyType +let _outputList : OutputDef = ListOf outputOnlyType +let _inputNullable : InputDef = Nullable inputOnlyType +let _outputNullable : OutputDef = Nullable outputOnlyType +let _inputStruct : InputDef = StructNullable inputOnlyType +let _outputStruct : OutputDef = StructNullable outputOnlyType diff --git a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafetyTests.fs b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafetyTests.fs new file mode 100644 index 00000000..cececf2d --- /dev/null +++ b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafetyTests.fs @@ -0,0 +1,144 @@ +module FSharp.Data.GraphQL.Tests.TypeWrappersKindSafetyTests + +open System +open System.Diagnostics +open System.Threading.Tasks +open FSharp.Data.GraphQL.Types +open Xunit + +type private InputOnly = { Value : int } +type private OutputOnly = { Value : int } + +let private InputOnlyType = + Define.InputObject (name = "InputOnlyType", fields = [ Define.Input ("value", IntType) ]) + +let private OutputOnlyType = + Define.Object (name = "OutputOnlyType", fields = [ Define.Field ("value", IntType, fun _ x -> x.Value) ]) + +type TypeWrappersKindSafetyFixture () = + + let scriptsDir = IO.Path.Combine (AppContext.BaseDirectory, "TypeWrappersKindSafety") + let referencesPath = IO.Path.Combine (scriptsDir, "References.fsx") + let sourceProjectDir = + IO.Path.GetFullPath (IO.Path.Combine (AppContext.BaseDirectory, "..", "..", "..")) + let sourceScriptsDir = IO.Path.Combine (sourceProjectDir, "TypeWrappersKindSafety") + let sourceReferencesPath = IO.Path.Combine (sourceScriptsDir, "References.fsx") + + let ensureFileContentAsync (path : string) (content : string) : Task = task { + if IO.File.Exists (path) then + let! existing = IO.File.ReadAllTextAsync (path) + + if not (String.Equals (existing, content, StringComparison.Ordinal)) then + do! IO.File.WriteAllTextAsync (path, content) + else + do! IO.File.WriteAllTextAsync (path, content) + } + + member _.ScriptPath (name : string) = IO.Path.Combine (scriptsDir, name) + + member _.RunFsiCheckAsync (scriptPath : string) : Task = task { + let psi = + ProcessStartInfo ( + "dotnet", + sprintf "fsi --noninteractive \"%s\"" scriptPath, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + WorkingDirectory = AppContext.BaseDirectory + ) + + use proc = Process.Start (psi) + do! proc.WaitForExitAsync () + return proc.ExitCode + } + + member private _.ReferencesContent = + let sharedAssembly = IO.Path.Combine (AppContext.BaseDirectory, "FSharp.Data.GraphQL.Shared.dll") + let serverAssembly = IO.Path.Combine (AppContext.BaseDirectory, "FSharp.Data.GraphQL.Server.dll") + + [| sprintf "#r @\"%s\"" sharedAssembly; sprintf "#r @\"%s\"" serverAssembly |] + |> String.concat "\n" + + interface IAsyncLifetime with + + member this.InitializeAsync () : Task = + task { + IO.Directory.CreateDirectory (scriptsDir) |> ignore + IO.Directory.CreateDirectory (sourceScriptsDir) |> ignore + + let content = this.ReferencesContent + + do! ensureFileContentAsync referencesPath content + do! ensureFileContentAsync sourceReferencesPath content + } + + member _.DisposeAsync () = Task.CompletedTask + +type TypeWrappersKindSafetyTests (fixture : TypeWrappersKindSafetyFixture) = + interface IClassFixture + + [] + member _.``ListOf keeps input-output direction`` () : Task = task { + let inputList : InputDef = ListOf InputOnlyType + let outputList : OutputDef = ListOf OutputOnlyType + Assert.Equal ("[InputOnlyType!]!", inputList.ToString ()) + Assert.Equal ("[OutputOnlyType!]!", outputList.ToString ()) + } + + [] + member _.``Nullable keeps input-output direction`` () : Task = task { + let nullableInput : InputDef = Nullable InputOnlyType + let nullableOutput : OutputDef = Nullable OutputOnlyType + Assert.Equal ("InputOnlyType", nullableInput.ToString ()) + Assert.Equal ("OutputOnlyType", nullableOutput.ToString ()) + } + + [] + member _.``StructNullable keeps input-output direction`` () : Task = task { + let nullableInput : InputDef = StructNullable InputOnlyType + let nullableOutput : OutputDef = StructNullable OutputOnlyType + Assert.Equal ("InputOnlyType", nullableInput.ToString ()) + Assert.Equal ("OutputOnlyType", nullableOutput.ToString ()) + } + + [] + member _.``Valid script compiles successfully`` () : Task = task { + let! exitCode = fixture.RunFsiCheckAsync (fixture.ScriptPath ("Valid.fsx")) + Assert.Equal (0, exitCode) + } + + [] + member _.``ListOf rejects output type as input at compile time`` () : Task = task { + let! exitCode = fixture.RunFsiCheckAsync (fixture.ScriptPath ("ListOf.OutputAsInput.fsx")) + Assert.NotEqual (0, exitCode) + } + + [] + member _.``ListOf rejects input type as output at compile time`` () : Task = task { + let! exitCode = fixture.RunFsiCheckAsync (fixture.ScriptPath ("ListOf.InputAsOutput.fsx")) + Assert.NotEqual (0, exitCode) + } + + [] + member _.``Nullable rejects output type as input at compile time`` () : Task = task { + let! exitCode = fixture.RunFsiCheckAsync (fixture.ScriptPath ("Nullable.OutputAsInput.fsx")) + Assert.NotEqual (0, exitCode) + } + + [] + member _.``Nullable rejects input type as output at compile time`` () : Task = task { + let! exitCode = fixture.RunFsiCheckAsync (fixture.ScriptPath ("Nullable.InputAsOutput.fsx")) + Assert.NotEqual (0, exitCode) + } + + [] + member _.``StructNullable rejects output type as input at compile time`` () : Task = task { + let! exitCode = fixture.RunFsiCheckAsync (fixture.ScriptPath ("StructNullable.OutputAsInput.fsx")) + Assert.NotEqual (0, exitCode) + } + + [] + member _.``StructNullable rejects input type as output at compile time`` () : Task = task { + let! exitCode = fixture.RunFsiCheckAsync (fixture.ScriptPath ("StructNullable.InputAsOutput.fsx")) + Assert.NotEqual (0, exitCode) + } diff --git a/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputComplexTests.fs b/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputComplexTests.fs index 954ace7e..1e705357 100644 --- a/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputComplexTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputComplexTests.fs @@ -46,7 +46,7 @@ type TestInput = optArr : string option array option voptArr : string option array voption } // string voption array voption is too hard to implement -let InputArrayOf (innerDef : #TypeDef<'Val>) : ListOfDef<'Val, 'Val array> = ListOf innerDef +let InputArrayOf (innerDef : #InputDef<'Val>) : InputDef<'Val array> = ListOf (innerDef :> InputDef<'Val>) let TestInputObject = Define.InputObject ( diff --git a/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputNestedTests.fs b/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputNestedTests.fs index 69fc2b82..de573009 100644 --- a/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputNestedTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputNestedTests.fs @@ -14,7 +14,7 @@ open FSharp.Data.GraphQL.Types open FSharp.Data.GraphQL.Parser open FSharp.Data.GraphQL.Shared -let InputArrayOf (innerDef : #TypeDef<'Val>) : ListOfDef<'Val, 'Val array> = ListOf innerDef +let InputArrayOf (innerDef : #InputDef<'Val>) : InputDef<'Val array> = ListOf (innerDef :> InputDef<'Val>) let TestInputObject = InputComplexTests.TestInputObject