diff --git a/src/FSharp.Data.GraphQL.Server/Schema.fs b/src/FSharp.Data.GraphQL.Server/Schema.fs index a23b341a..67146948 100644 --- a/src/FSharp.Data.GraphQL.Server/Schema.fs +++ b/src/FSharp.Data.GraphQL.Server/Schema.fs @@ -186,6 +186,7 @@ type Schema<'Root> (query: ObjectDef<'Root>, ?mutation: ObjectDef<'Root>, ?subsc IDType DateTimeOffsetType DateOnlyType + TimeOnlyType UriType __Schema query ] diff --git a/src/FSharp.Data.GraphQL.Shared/SchemaDefinitions.fs b/src/FSharp.Data.GraphQL.Shared/SchemaDefinitions.fs index ec60993c..51f8682c 100644 --- a/src/FSharp.Data.GraphQL.Shared/SchemaDefinitions.fs +++ b/src/FSharp.Data.GraphQL.Shared/SchemaDefinitions.fs @@ -159,6 +159,18 @@ module SchemaDefinitions = | false, _ -> None | other -> None + /// Tries to convert any value to TimeOnly. + let coerceTimeOnlyValue (x : obj) : TimeOnly option = + match x with + | null -> None + | :? TimeOnly as d -> Some d + | :? DateTime as d -> Some (TimeOnly.FromDateTime d) + | :? string as s -> + match TimeOnly.TryParse(s) with + | true, time -> Some time + | false, _ -> None + | other -> None + /// Tries to convert any value to Guid. let coerceGuidValue (x : obj) : Guid option = match x with @@ -348,6 +360,22 @@ module SchemaDefinitions = | false, _ -> getParseRangeError(destinationType, DateOnly.MinValue, DateOnly.MaxValue) s | InlineConstant value -> value.GetCoerceRangeError(destinationType, DateOnly.MinValue, DateOnly.MaxValue) + /// Tries to resolve AST query input to TimeOnly. + let coerceTimeOnlyInput = + let destinationType = "time" + function + | Variable e when e.ValueKind = JsonValueKind.String -> + let s = e.GetString() + match TimeOnly.TryParse(s) with + | true, time -> Ok time + | false, _ -> e.GetDeserializeError destinationType + | Variable e -> e.GetDeserializeError destinationType + | InlineConstant (StringValue s) -> + match TimeOnly.TryParse(s) with + | true, time -> Ok time + | false, _ -> getParseRangeError(destinationType, TimeOnly.MinValue, TimeOnly.MaxValue) s + | InlineConstant value -> value.GetCoerceRangeError(destinationType, TimeOnly.MinValue, TimeOnly.MaxValue) + /// Tries to resolve AST query input to Guid. let coerceGuidInput = let destinationType = "GUID" @@ -473,6 +501,15 @@ module SchemaDefinitions = CoerceInput = coerceDateOnlyInput CoerceOutput = coerceDateOnlyValue } + /// GraphQL type for System.TimeOnly + let TimeOnlyType : ScalarDefinition = + { Name = "TimeOnly" + Description = + Some + "The `TimeOnly` scalar type represents a Time value without Date component. The `TimeOnly` type appears in a JSON response as a `String` representation of full-time value as specified by [IETF 3339](https://www.ietf.org/rfc/rfc3339.txt)." + CoerceInput = coerceTimeOnlyInput + CoerceOutput = coerceTimeOnlyValue } + /// GraphQL type for System.Guid let GuidType : ScalarDefinition = { Name = "Guid" @@ -759,6 +796,7 @@ module SchemaDefinitions = /// /// Type name. Must be unique in scope of the current schema. /// List of input fields defined by the current input object. + /// Object validator. /// Optional input object description. Useful for generating documentation. static member InputObject(name : string, fields : InputFieldDef list, validator: GQLValidator<'Out>, ?description : string) : InputObjectDefinition<'Out> = { Name = name diff --git a/tests/FSharp.Data.GraphQL.Tests/IntrospectionTests.fs b/tests/FSharp.Data.GraphQL.Tests/IntrospectionTests.fs index fa33e314..b04010b9 100644 --- a/tests/FSharp.Data.GraphQL.Tests/IntrospectionTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/IntrospectionTests.fs @@ -691,6 +691,16 @@ let ``Introspection executes an introspection query`` () = "enumValues", null "possibleTypes", null ] + box <| NameValueLookup.ofList [ + "kind", upcast "SCALAR" + "name", upcast "TimeOnly" + "description", upcast "The `TimeOnly` scalar type represents a Time value without Date component. The `TimeOnly` type appears in a JSON response as a `String` representation of full-time value as specified by [IETF 3339](https://www.ietf.org/rfc/rfc3339.txt)." + "fields", null + "inputFields", null + "interfaces", null + "enumValues", null + "possibleTypes", null + ] box <| NameValueLookup.ofList [ "kind", upcast "SCALAR" "name", upcast "URI"