Skip to content

Support variables within inline object list filter #530

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ type internal ObjectListFilterMiddleware<'ObjectType, 'ListType>(reportToMetadat
|> Seq.map (fun x ->
match x.Name, x.Value with
| "filter", (VariableName variableName) -> Ok (ValueSome (ctx.Variables[variableName] :?> ObjectListFilter))
| "filter", inlineConstant -> ObjectListFilterType.CoerceInput (InlineConstant inlineConstant) |> Result.map ValueOption.ofObj
| "filter", inlineConstant -> ObjectListFilterType.CoerceInput (InlineConstant inlineConstant) ctx.Variables |> Result.map ValueOption.ofObj
| _ -> Ok ValueNone)
|> Seq.toList
match filterResults |> splitSeqErrorsList with
Expand Down
99 changes: 39 additions & 60 deletions src/FSharp.Data.GraphQL.Server.Middleware/SchemaDefinitions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
module FSharp.Data.GraphQL.Server.Middleware.SchemaDefinitions

open System
open System.Collections.Generic
open System.Collections.Immutable
open System.Text.Json
open FSharp.Data.GraphQL
open FSharp.Data.GraphQL.Types
open FSharp.Data.GraphQL.Ast
Expand All @@ -22,7 +19,7 @@ type private ComparisonOperator =
| LessThanOrEqual of string
| In of string

let rec private coerceObjectListFilterInput x : Result<ObjectListFilter voption, IGQLError list> =
let rec private coerceObjectListFilterInput (variables : Variables) inputValue : Result<ObjectListFilter voption, IGQLError list> =

let parseFieldCondition (s : string) =
let s = s.ToLowerInvariant ()
Expand Down Expand Up @@ -81,17 +78,17 @@ let rec private coerceObjectListFilterInput x : Result<ObjectListFilter voption,
| ValueSome acc -> build (ValueSome (Or (acc, x))) xs
build ValueNone x

let rec mapFilter (name : string, value : InputValue) =
let rec mapFilter (condition : ComparisonOperator) (value : InputValue) =
let mapFilters fields =
let coerceResults =
fields
|> Seq.map coerceObjectListFilterInput
|> Seq.map (coerceObjectListFilterInput variables)
|> Seq.toList
|> splitSeqErrorsList
match coerceResults with
| Error errs -> Error errs
| Ok coerced -> coerced |> Seq.vchoose id |> Seq.toList |> Ok
match parseFieldCondition name, value with
match condition, value with
| Equals "and", ListValue fields -> fields |> mapFilters |> Result.map buildAnd
| Equals "or", ListValue fields -> fields |> mapFilters |> Result.map buildOr
| Equals "not", ObjectValue value ->
Expand Down Expand Up @@ -126,76 +123,58 @@ let rec private coerceObjectListFilterInput x : Result<ObjectListFilter voption,
|> splitSeqErrors
return ValueSome (ObjectListFilter.In { FieldName = fname; Value = parsedValues |> Array.toList })
}
| condition, VariableName variableName ->
match variables.TryGetValue variableName with
| true, value -> mapFilter condition (value |> InputValue.OfObject)
| false, _ -> Errors.Variables.getVariableNotFoundError variableName
| _ -> Ok ValueNone

and mapInput value =
let filterResults =
value
|> Map.toSeq
|> Seq.map mapFilter
|> Seq.map (fun kvp -> mapFilter (parseFieldCondition kvp.Key) kvp.Value)
|> Seq.toList
|> splitSeqErrorsList
match filterResults with
| Error errs -> Error errs
| Ok filters -> filters |> Seq.vchoose id |> List.ofSeq |> buildAnd |> Ok

match x with
| ObjectValue x -> mapInput x
| NullValue -> ValueNone |> Ok
// TODO: Get union case
| _ ->
Error [
{ new IGQLError with
member _.Message = $"'ObjectListFilter' must be defined as object but got '{x.GetType ()}'"
}
]
let rec parse inputValue =
match inputValue with
| ObjectValue x -> mapInput x
| NullValue -> ValueNone |> Ok
| VariableName variableName ->
match variables.TryGetValue variableName with
| true, (:? ObjectListFilter as filter) -> ValueSome filter |> Ok
| true, value ->
System.Diagnostics.Debug.Fail "We expect the root value is parsed into ObjectListFilter"
value |> InputValue.OfObject |> parse
| false, _ -> Errors.Variables.getVariableNotFoundError variableName
// TODO: Get union case
| _ ->
Error [
{ new IGQLError with
member _.Message = $"'ObjectListFilter' must be defined as object but got '{inputValue.GetType ()}'"
}
]
parse inputValue

let private coerceObjectListFilterValue (x : obj) : ObjectListFilter option =
match x with
| :? ObjectListFilter as x -> Some x
| _ -> None
//let private coerceObjectListFilterValue (x : obj) =
// match x with
// | :? ObjectListFilter as x -> Ok x
// | _ -> Error [{ new IGQLError with member _.Message = $"Cannot coerce ObjectListFilter output. '%s{x.GetType().FullName}' is not 'ObjectListFilter'" }]

// TODO: Move to shared and make public
let rec private jsonElementToInputValue (element : JsonElement) =
match element.ValueKind with
| JsonValueKind.Null -> NullValue
| JsonValueKind.True -> BooleanValue true
| JsonValueKind.False -> BooleanValue false
| JsonValueKind.String -> StringValue (element.GetString ())
| JsonValueKind.Number -> FloatValue (element.GetDouble ())
| JsonValueKind.Array ->
ListValue (
element.EnumerateArray ()
|> Seq.map jsonElementToInputValue
|> List.ofSeq
)
| JsonValueKind.Object ->
ObjectValue (
element.EnumerateObject ()
|> Seq.map (fun p -> p.Name, jsonElementToInputValue p.Value)
|> Map.ofSeq
)
| _ -> raise (NotSupportedException "Unsupported JSON element type")

/// Defines an object list filter for use as an argument for filter list of object fields.
let ObjectListFilterType : ScalarDefinition<ObjectListFilter> = {
let ObjectListFilterType : InputCustomDefinition<ObjectListFilter> = {
Name = "ObjectListFilter"
Description =
Some
"The `Filter` scalar type represents a filter on one or more fields of an object in an object list. The filter is represented by a JSON object where the fields are the complemented by specific suffixes to represent a query."
CoerceInput =
(function
| InlineConstant c ->
coerceObjectListFilterInput c
|> Result.map ValueOption.toObj
| Variable json ->
json
|> jsonElementToInputValue
|> coerceObjectListFilterInput
|> Result.map ValueOption.toObj)
CoerceOutput = coerceObjectListFilterValue
(fun input variables ->
match input with
| InlineConstant c ->
(coerceObjectListFilterInput variables c)
|> Result.map ValueOption.toObj
| Variable json ->
json
|> InputValue.OfJsonElement
|> (coerceObjectListFilterInput variables)
|> Result.map ValueOption.toObj)
}
11 changes: 10 additions & 1 deletion src/FSharp.Data.GraphQL.Server/Execution.fs
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,16 @@ let internal coerceVariables (variables: VarDef list) (vars: ImmutableDictionary
fun (acc : Result<ImmutableDictionary<string, obj>.Builder, IGQLError list>) struct(varDef, jsonElement) -> validation {
let! value =
let varTypeDef = varDef.TypeDef
coerceVariableValue false [] ValueNone (varTypeDef, varTypeDef) varDef jsonElement
let ctx = {
IsNullable = false
InputObjectPath = []
ObjectFieldErrorDetails = ValueNone
OriginalTypeDef = varTypeDef
TypeDef = varTypeDef
VarDef = varDef
Input = jsonElement
}
coerceVariableValue ctx
|> Result.mapError (
List.map (fun err ->
match err with
Expand Down
3 changes: 3 additions & 0 deletions src/FSharp.Data.GraphQL.Server/Schema.fs
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,8 @@ type Schema<'Root> (query: ObjectDef<'Root>, ?mutation: ObjectDef<'Root>, ?subsc
getPossibleTypes idef
|> Array.map (fun tdef -> Map.find tdef.Name namedTypes)
IntrospectionType.Interface(idef.Name, idef.Description, fields, possibleTypes)
| InputCustom inCustDef ->
IntrospectionType.InputObject(inCustDef.Name, inCustDef.Description, [||])
| _ -> failwithf "Unexpected value of typedef: %O" typedef

let introspectSchema (types : TypeMap) : IntrospectionSchema =
Expand All @@ -334,6 +336,7 @@ type Schema<'Root> (query: ObjectDef<'Root>, ?mutation: ObjectDef<'Root>, ?subsc
| Union x -> typeName, { Kind = TypeKind.UNION; Name = Some typeName; Description = x.Description; OfType = None }
| Enum x -> typeName, { Kind = TypeKind.ENUM; Name = Some typeName; Description = x.Description; OfType = None }
| Interface x -> typeName, { Kind = TypeKind.INTERFACE; Name = Some typeName; Description = x.Description; OfType = None }
| InputCustom x -> typeName, { Kind = TypeKind.INPUT_OBJECT; Name = Some typeName; Description = x.Description; OfType = None }
| _ -> failwithf "Unexpected value of typedef: %O" typedef)
|> Map.ofSeq
let itypes =
Expand Down
Loading