Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 61 additions & 17 deletions src/FSharp.Data.GraphQL.Client/ReflectionPatterns.fs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ module ReflectionPatterns =
let isOption (t : Type) =
t.IsGenericType && t.GetGenericTypeDefinition() = typedefof<_ option>

/// <summary>
/// Returns <see langword="true"/> when <paramref name="t"/> is an F# <c>voption</c> type.
/// </summary>
/// <param name="t">Type to inspect.</param>
let isValueOption (t : Type) =
t.IsGenericType && t.GetGenericTypeDefinition() = typedefof<_ voption>

let isMap (t : Type) =
t = typeof<Map<string, obj>>

Expand Down Expand Up @@ -70,49 +77,86 @@ module ReflectionPatterns =
let (_, none, _) = getOptionCases t
FSharpValue.MakeUnion(none, [||])

let private getValueOptionCases (t : Type) =
let votype = typedefof<_ voption>.MakeGenericType(t)
let cases = FSharpType.GetUnionCases (votype)
let valueSome = cases |> Array.find (fun c -> c.Name = "ValueSome")
let valueNone = cases |> Array.find (fun c -> c.Name = "ValueNone")
(valueSome, valueNone, votype)

/// <summary>
/// Builds a boxed <c>'T voption</c> value: <see langword="null"/> becomes <c>ValueNone</c>, otherwise the value is wrapped as <c>ValueSome</c>.
/// </summary>
let makeValueOption (t : Type) (value : obj) =
let (valueSome, valueNone, _) = getValueOptionCases t
if isNull value then
FSharpValue.MakeUnion (valueNone, [||])
else
FSharpValue.MakeUnion (valueSome, [| value |])

[<return: Struct>]
let (|Option|_|) t =
if isOption t then Some (Option (t.GetGenericArguments().[0]))
else None
if isOption t then ValueSome (Option (t.GetGenericArguments().[0]))
else ValueNone

/// <summary>
/// Matches F# <c>voption</c> types and extracts their generic value type.
/// </summary>
/// <param name="t">Type to inspect.</param>
[<return: Struct>]
let (|ValueOption|_|) t =
if isValueOption t then ValueSome (ValueOption (t.GetGenericArguments().[0]))
else ValueNone

let isType (expected : Type) (t : Type) =
match t with
| Option t -> t = expected
| ValueOption t -> t = expected
| _ -> t = expected

let isNumericType (t : Type) =
numericTypes |> Array.exists (fun expected -> isType expected t)

[<return: Struct>]
let (|Array|_|) (t : Type) =
if t.IsArray then Some (Array (t.GetElementType()))
else None
if t.IsArray then ValueSome (Array (t.GetElementType()))
else ValueNone

[<return: Struct>]
let (|List|_|) (t : Type) =
if isList t then Some (List (t.GetGenericArguments().[0]))
else None
if isList t then ValueSome (List (t.GetGenericArguments().[0]))
else ValueNone

[<return: Struct>]
let (|Seq|_|) (t : Type) =
if isSeq t then Some (Seq (t.GetGenericArguments().[0]))
else None
if isSeq t then ValueSome (Seq (t.GetGenericArguments().[0]))
else ValueNone

[<return: Struct>]
let (|EnumerableValue|_|) (x : obj) =
match x with
| :? IEnumerable as x -> Some (EnumerableValue (Seq.cast<obj> x |> Array.ofSeq))
| _ -> None
| :? IEnumerable as x -> ValueSome (EnumerableValue (Seq.cast<obj> x |> Array.ofSeq))
| _ -> ValueNone

[<return: Struct>]
let (|OptionValue|_|) (x : obj) =
let xtype = x.GetType()
let tryGetValue optionType =
match FSharpValue.GetUnionFields(x, optionType) with
| (_, [|value|]) -> ValueSome (OptionValue Some value)
| _ -> ValueSome (OptionValue None)
if isOption xtype
then
match FSharpValue.GetUnionFields(x, xtype) with
| (_, [|value|]) -> Some (OptionValue Some value)
| _ -> Some (OptionValue None)
else None
then tryGetValue xtype
elif isValueOption xtype
then tryGetValue xtype
else ValueNone
Comment thread
Copilot marked this conversation as resolved.

[<return: Struct>]
let (|EnumValue|_|) (x : obj) =
let xtype = x.GetType()
if xtype.IsEnum
then Some (x.ToString())
else None
then ValueSome (x.ToString())
else ValueNone

let makeValue (t : Type) (value : obj) =
let isOption = isOption t
Expand Down
14 changes: 10 additions & 4 deletions src/FSharp.Data.GraphQL.Client/Serialization.fs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ module Serialization =
let private downcastNone<'T> t =
match t with
| Option t -> downcast (makeOption t null)
| ValueOption t -> downcast (makeValueOption t null)
| _ -> failwith $"Error parsing JSON value: %O{t} is not an option value."

let private downcastType (t : Type) x =
match t with
| Option t -> downcast (makeOption t (Convert.ChangeType(x, t)))
| ValueOption t -> downcast (makeValueOption t (Convert.ChangeType(x, t)))
| _ -> downcast (Convert.ChangeType(x, t))

let private isStringType = isType typeof<string>
Expand All @@ -37,7 +39,7 @@ module Serialization =
let private isUriType = isType typeof<Uri>
let private isGuidType = isType typeof<Guid>
let private isBooleanType = isType typeof<bool>
let private isEnumType = function (Option t | t) when t.IsEnum -> true | _ -> false
let private isEnumType = function (Option t | ValueOption t | t) when t.IsEnum -> true | _ -> false

let private downcastString (t : Type) (s : string) =
match t with
Expand All @@ -60,7 +62,7 @@ module Serialization =
| _ -> failwith $"Error parsing JSON value: %O{t} is a Guid type, but parsing of value \"%s{s}\" failed."
| t when isEnumType t ->
match t with
| (Option et | et) ->
| (Option et | ValueOption et | et) ->
try Enum.Parse(et, s) |> downcastType t
with _ -> failwith $"Error parsing JSON value: %O{t} is a Enum type, but parsing of value \"%s{s}\" failed."
| _ -> failwith $"Error parsing JSON value: %O{t} is not a string type."
Expand Down Expand Up @@ -92,6 +94,7 @@ module Serialization =
Tracer.runAndMeasureExecutionTime "Converted Array JsonValue to CLR array" (fun _ ->
match t with
| Option t -> getArrayValue t converter items |> makeOption t
| ValueOption t -> getArrayValue t converter items |> makeValueOption t
| Array itype | Seq itype -> items |> Array.map (converter itype) |> castArray itype
| List itype -> items |> Array.map (converter itype) |> Array.toList |> castList itype
| _ -> failwith $"Error parsing JSON value: %O{t} is not an array type.")
Expand Down Expand Up @@ -121,9 +124,12 @@ module Serialization =
|> Array.map (fun (n, t) ->
match Map.tryFind n jprops with
| Some p -> n, convert t p
| None -> n, makeOption t null)
| None ->
match t with
| ValueOption vt -> n, makeValueOption vt null
| _ -> n, makeOption t null)
let rcrd =
let t = match t with Option t -> t | _ -> t
let t = match t with Option t | ValueOption t -> t | _ -> t
let vals = vals t
if isMap t
then Map.ofArray vals |> box
Expand Down
6 changes: 4 additions & 2 deletions src/FSharp.Data.GraphQL.Client/TextConversions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ open System.Text.RegularExpressions
module private TextConversionHelpers =
let asOption = function true, v -> Some v | _ -> None

[<return: Struct>]
let (|StringEqualsIgnoreCase|_|) (s1:string) s2 =
if s1.Equals(s2, StringComparison.OrdinalIgnoreCase)
then Some () else None
then ValueSome () else ValueNone

[<return: Struct>]
let (|OneOfIgnoreCase|_|) set str =
if Array.exists (fun s -> StringComparer.OrdinalIgnoreCase.Compare(s, str) = 0) set then Some() else None
if Array.exists (fun s -> StringComparer.OrdinalIgnoreCase.Compare(s, str) = 0) set then ValueSome() else ValueNone

let msDateRegex = lazy Regex(@"^/Date\((-?\d+)([-+]\d+)?\)/$", RegexOptions.Compiled)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,11 @@ module internal IncrementalPayloadSplitting =

/// Matches a path ending in a list of indices, such as the path of a batched deferred payload, returning the
/// path of the batch's own field and the indices of its items.
[<return: Struct>]
let (|BatchPath|_|) (path : obj list) =
match List.rev path with
| (:? (obj list) as indices) :: fieldPathRev -> Some (List.rev fieldPathRev, indices)
| _ -> None
| (:? (obj list) as indices) :: fieldPathRev -> ValueSome (List.rev fieldPathRev, indices)
| _ -> ValueNone

/// Splits a batch's data (an array with one element per index, in the same order) and errors (each carrying the
/// full path of the item it belongs to, since every error of a batch originates from resolving one specific
Expand Down
4 changes: 2 additions & 2 deletions src/FSharp.Data.GraphQL.Server/Execution.fs
Original file line number Diff line number Diff line change
Expand Up @@ -412,8 +412,8 @@ and private executeResolvers (inputContext : InputExecutionContextProvider) (ctx

let rec innerListDef = function
| Nullable (Output innerDef) -> innerListDef innerDef
| List (Output innerDef) -> Some innerDef
| _ -> None
| List (Output innerDef) -> ValueSome innerDef
| _ -> ValueNone

let (|HasList|_|) = innerListDef

Expand Down
5 changes: 3 additions & 2 deletions src/FSharp.Data.GraphQL.Server/ObservableExtensions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,11 @@ module internal Observable =
/// Distinguishes a linked-token cancellation, typically caused by a resolution failure, from the
/// subscription itself being disposed.
/// </remarks>
[<return: Struct>]
let (|CanceledIndependently|_|) (cancellationToken : CancellationToken) (ex : exn) =
match ex with
| :? OperationCanceledException when not cancellationToken.IsCancellationRequested -> Some ()
| _ -> None
| :? OperationCanceledException when not cancellationToken.IsCancellationRequested -> ValueSome ()
| _ -> ValueNone

/// <summary>
/// Creates a cold observable, which enumerates the asynchronous sequence for every subscription.
Expand Down
30 changes: 18 additions & 12 deletions src/FSharp.Data.GraphQL.Server/ReflectionHelper.fs
Original file line number Diff line number Diff line change
Expand Up @@ -24,40 +24,46 @@ type internal Methods =

module internal Gen =

[<return: Struct>]
let (|List|_|) (t: Type) =
let typeParam = t.GetGenericArguments().[0]
let tList = typedefof<_ list>.MakeGenericType [| typeParam |]
if t = tList then Some typeParam
else None
if t = tList then ValueSome typeParam
else ValueNone

[<return: Struct>]
let (|Array|_|) (t: Type) =
if t.IsArray then Some (t.GetGenericArguments().[0])
else None
if t.IsArray then ValueSome (t.GetGenericArguments().[0])
else ValueNone

[<return: Struct>]
let (|Set|_|) (t: Type) =
let typeParam = t.GetGenericArguments().[0]
let tArray = typedefof<Set<_>>.MakeGenericType [| typeParam |]
if t = tArray then Some typeParam
else None
if t = tArray then ValueSome typeParam
else ValueNone

let inline defaultArgOnNull a b = if isNull a |> not then a else b
let private optionType = typedefof<option<_>>
[<return: Struct>]
let (|Option|_|) (t: Type) =
if t.IsGenericType && t.GetGenericTypeDefinition() = optionType
then Some (t.GetGenericArguments().[0])
else None
then ValueSome (t.GetGenericArguments().[0])
else ValueNone

[<return: Struct>]
let (|Enumerable|_|) t =
if typeof<System.Collections.IEnumerable>.IsAssignableFrom t
then
let e = defaultArgOnNull (t.GetInterface("IEnumerable`1")) t
Some (e.GetGenericArguments().[0])
else None
ValueSome (e.GetGenericArguments().[0])
else ValueNone

[<return: Struct>]
let (|Queryable|_|) t =
if typeof<IQueryable>.IsAssignableFrom t
then Some (Queryable (t.GetInterface("IQueryable`1").GetGenericArguments().[0]))
else None
then ValueSome (Queryable (t.GetInterface("IQueryable`1").GetGenericArguments().[0]))
else ValueNone

let genericType<'t> typeParams = typedefof<'t>.MakeGenericType typeParams

Expand Down
6 changes: 4 additions & 2 deletions src/FSharp.Data.GraphQL.Shared/AsyncVal.fs
Original file line number Diff line number Diff line change
Expand Up @@ -230,10 +230,12 @@ module AsyncExtensions =
let asyncVal = AsyncValBuilder ()

/// Active pattern used for checking if AsyncVal contains immediate value.
let (|Immediate|_|) (x : AsyncVal<'T>) = match x with | Value v -> Some v | _ -> None
[<return: Struct>]
let (|Immediate|_|) (x : AsyncVal<'T>) = match x with | Value v -> ValueSome v | _ -> ValueNone

/// Active patter used for checking if AsyncVal wraps an Async computation.
let (|Async|_|) (x : AsyncVal<'T>) = match x with | Async a -> Some a | _ -> None
[<return: Struct>]
let (|Async|_|) (x : AsyncVal<'T>) = match x with | Async a -> ValueSome a | _ -> ValueNone

type Microsoft.FSharp.Control.AsyncBuilder with

Expand Down
27 changes: 15 additions & 12 deletions src/FSharp.Data.GraphQL.Shared/Helpers/Reflection.fs
Original file line number Diff line number Diff line change
Expand Up @@ -171,28 +171,31 @@ module Helpers =

let rec internal moduleType = ReflectionHelper.getModuleType <@ moduleType @>

/// <summary>
/// Casts a <see cref="System.Object"/> to a <see cref="option{System.Object}"/>.
/// </summary>
let optionCast (value: obj) =
if isNull value then None
let private objectOptionCast (value: obj) =
if isNull value then ValueNone
else
let t = value.GetType()
if t.FullName.StartsWith ReflectionHelper.OptionTypeName then
let p = t.GetProperty("Value")
Some (p.GetValue(value, [||]))
ValueSome (p.GetValue(value, [||]))
elif t.FullName.StartsWith ReflectionHelper.ValueOptionTypeName then
if value = Activator.CreateInstance t then None
if value = Activator.CreateInstance t then ValueNone
else
let p = t.GetProperty("Value")
Some (p.GetValue(value, [||]))
else None
ValueSome (p.GetValue(value, [||]))
else ValueNone

/// <summary>
/// Casts a <see cref="System.Object"/> to a <see cref="option{System.Object}"/>.
/// </summary>
let optionCast (value: obj) = objectOptionCast value |> ValueOption.toOption

/// <summary>
/// Matches a System.Object with an option.
/// If the object is an <see cref="Option{T}", returns it as Some, otherwise, return <see cref="None"/>.
/// Matches a <see cref="System.Object"/> containing a boxed <see cref="Option{T}"/> or <see cref="ValueOption{T}"/>.
/// Returns the wrapped value for <see cref="Some"/> and <see cref="ValueSome"/>, and does not match for <see cref="None"/>, <see cref="ValueNone"/>, or non-option values.
/// </summary>
let (|ObjectOption|_|) = optionCast
[<return: Struct>]
let (|ObjectOption|_|) (value : obj) = objectOptionCast value
Comment thread
xperiandri marked this conversation as resolved.

/// <summary>
/// Lifts a <see cref="System.Object"/> to an <see cref="option{System.Object}"/>, unless it is already an <see cref="option{System.Object}"/>.
Expand Down
5 changes: 3 additions & 2 deletions src/FSharp.Data.GraphQL.Shared/Output.fs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ type NameValueLookup(keyValues: KeyValuePair<string, obj> []) =
else i <- i+1
let getValue key = (kvals |> Array.find (fun kv -> kv.Key = key)).Value

[<return: Struct>]
let (|BoxedSeq|_|) (xs : obj) =
match xs with
| (:? System.Collections.IEnumerable as enumerable) -> Some (Seq.cast<obj> enumerable)
| _ -> None
| (:? System.Collections.IEnumerable as enumerable) -> ValueSome (Seq.cast<obj> enumerable)
| _ -> ValueNone

let rec structEq (x: NameValueLookup) (y: NameValueLookup) =
if Object.ReferenceEquals(x, y) then true
Expand Down
7 changes: 4 additions & 3 deletions src/FSharp.Data.GraphQL.Shared/SchemaDefinitions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -183,13 +183,14 @@ module SchemaDefinitions =
| other -> None

/// Check if provided obj value is an Option and extract its wrapped value as object if possible
[<return: Struct>]
let private (|Option|_|) (x : obj) =
if isNull x then None
if isNull x then ValueNone
else
let t = x.GetType().GetTypeInfo()
if t.IsGenericType && t.GetGenericTypeDefinition() = typedefof<option<_>> then
t.GetDeclaredProperty("Value").GetValue(x) |> Some
else None
t.GetDeclaredProperty("Value").GetValue(x) |> ValueSome
else ValueNone

/// Tries to convert any value to string.
let coerceStringValue (x : obj) : string option =
Expand Down
Loading
Loading