Skip to content

Commit 6f71177

Browse files
VectorTetraViktor Tochonovxperiandri
authored
Support variables within inline object list filter (#530)
* Implemented reproduction test * Implemented `InputCustom` type for internal type definitions * Implemented parsing inline ObjectListFilter, which contains variables --------- Co-authored-by: Viktor Tochonov <[email protected]> Co-authored-by: Andrii Chebukin <[email protected]>
1 parent bbc6d3e commit 6f71177

File tree

10 files changed

+422
-170
lines changed

10 files changed

+422
-170
lines changed

src/FSharp.Data.GraphQL.Server.Middleware/MiddlewareDefinitions.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ type internal ObjectListFilterMiddleware<'ObjectType, 'ListType>(reportToMetadat
9393
|> Seq.map (fun x ->
9494
match x.Name, x.Value with
9595
| "filter", (VariableName variableName) -> Ok (ValueSome (ctx.Variables[variableName] :?> ObjectListFilter))
96-
| "filter", inlineConstant -> ObjectListFilterType.CoerceInput (InlineConstant inlineConstant) |> Result.map ValueOption.ofObj
96+
| "filter", inlineConstant -> ObjectListFilterType.CoerceInput (InlineConstant inlineConstant) ctx.Variables |> Result.map ValueOption.ofObj
9797
| _ -> Ok ValueNone)
9898
|> Seq.toList
9999
match filterResults |> splitSeqErrorsList with

src/FSharp.Data.GraphQL.Server.Middleware/SchemaDefinitions.fs

Lines changed: 39 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@
33
module FSharp.Data.GraphQL.Server.Middleware.SchemaDefinitions
44

55
open System
6-
open System.Collections.Generic
7-
open System.Collections.Immutable
8-
open System.Text.Json
96
open FSharp.Data.GraphQL
107
open FSharp.Data.GraphQL.Types
118
open FSharp.Data.GraphQL.Ast
@@ -22,7 +19,7 @@ type private ComparisonOperator =
2219
| LessThanOrEqual of string
2320
| In of string
2421

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

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

84-
let rec mapFilter (name : string, value : InputValue) =
81+
let rec mapFilter (condition : ComparisonOperator) (value : InputValue) =
8582
let mapFilters fields =
8683
let coerceResults =
8784
fields
88-
|> Seq.map coerceObjectListFilterInput
85+
|> Seq.map (coerceObjectListFilterInput variables)
8986
|> Seq.toList
9087
|> splitSeqErrorsList
9188
match coerceResults with
9289
| Error errs -> Error errs
9390
| Ok coerced -> coerced |> Seq.vchoose id |> Seq.toList |> Ok
94-
match parseFieldCondition name, value with
91+
match condition, value with
9592
| Equals "and", ListValue fields -> fields |> mapFilters |> Result.map buildAnd
9693
| Equals "or", ListValue fields -> fields |> mapFilters |> Result.map buildOr
9794
| Equals "not", ObjectValue value ->
@@ -126,76 +123,58 @@ let rec private coerceObjectListFilterInput x : Result<ObjectListFilter voption,
126123
|> splitSeqErrors
127124
return ValueSome (ObjectListFilter.In { FieldName = fname; Value = parsedValues |> Array.toList })
128125
}
126+
| condition, VariableName variableName ->
127+
match variables.TryGetValue variableName with
128+
| true, value -> mapFilter condition (value |> InputValue.OfObject)
129+
| false, _ -> Errors.Variables.getVariableNotFoundError variableName
129130
| _ -> Ok ValueNone
130131

131132
and mapInput value =
132133
let filterResults =
133134
value
134-
|> Map.toSeq
135-
|> Seq.map mapFilter
135+
|> Seq.map (fun kvp -> mapFilter (parseFieldCondition kvp.Key) kvp.Value)
136136
|> Seq.toList
137137
|> splitSeqErrorsList
138138
match filterResults with
139139
| Error errs -> Error errs
140140
| Ok filters -> filters |> Seq.vchoose id |> List.ofSeq |> buildAnd |> Ok
141141

142-
match x with
143-
| ObjectValue x -> mapInput x
144-
| NullValue -> ValueNone |> Ok
145-
// TODO: Get union case
146-
| _ ->
147-
Error [
148-
{ new IGQLError with
149-
member _.Message = $"'ObjectListFilter' must be defined as object but got '{x.GetType ()}'"
150-
}
151-
]
142+
let rec parse inputValue =
143+
match inputValue with
144+
| ObjectValue x -> mapInput x
145+
| NullValue -> ValueNone |> Ok
146+
| VariableName variableName ->
147+
match variables.TryGetValue variableName with
148+
| true, (:? ObjectListFilter as filter) -> ValueSome filter |> Ok
149+
| true, value ->
150+
System.Diagnostics.Debug.Fail "We expect the root value is parsed into ObjectListFilter"
151+
value |> InputValue.OfObject |> parse
152+
| false, _ -> Errors.Variables.getVariableNotFoundError variableName
153+
// TODO: Get union case
154+
| _ ->
155+
Error [
156+
{ new IGQLError with
157+
member _.Message = $"'ObjectListFilter' must be defined as object but got '{inputValue.GetType ()}'"
158+
}
159+
]
160+
parse inputValue
152161

153-
let private coerceObjectListFilterValue (x : obj) : ObjectListFilter option =
154-
match x with
155-
| :? ObjectListFilter as x -> Some x
156-
| _ -> None
157-
//let private coerceObjectListFilterValue (x : obj) =
158-
// match x with
159-
// | :? ObjectListFilter as x -> Ok x
160-
// | _ -> Error [{ new IGQLError with member _.Message = $"Cannot coerce ObjectListFilter output. '%s{x.GetType().FullName}' is not 'ObjectListFilter'" }]
161-
162-
// TODO: Move to shared and make public
163-
let rec private jsonElementToInputValue (element : JsonElement) =
164-
match element.ValueKind with
165-
| JsonValueKind.Null -> NullValue
166-
| JsonValueKind.True -> BooleanValue true
167-
| JsonValueKind.False -> BooleanValue false
168-
| JsonValueKind.String -> StringValue (element.GetString ())
169-
| JsonValueKind.Number -> FloatValue (element.GetDouble ())
170-
| JsonValueKind.Array ->
171-
ListValue (
172-
element.EnumerateArray ()
173-
|> Seq.map jsonElementToInputValue
174-
|> List.ofSeq
175-
)
176-
| JsonValueKind.Object ->
177-
ObjectValue (
178-
element.EnumerateObject ()
179-
|> Seq.map (fun p -> p.Name, jsonElementToInputValue p.Value)
180-
|> Map.ofSeq
181-
)
182-
| _ -> raise (NotSupportedException "Unsupported JSON element type")
183162

184163
/// Defines an object list filter for use as an argument for filter list of object fields.
185-
let ObjectListFilterType : ScalarDefinition<ObjectListFilter> = {
164+
let ObjectListFilterType : InputCustomDefinition<ObjectListFilter> = {
186165
Name = "ObjectListFilter"
187166
Description =
188167
Some
189168
"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."
190169
CoerceInput =
191-
(function
192-
| InlineConstant c ->
193-
coerceObjectListFilterInput c
194-
|> Result.map ValueOption.toObj
195-
| Variable json ->
196-
json
197-
|> jsonElementToInputValue
198-
|> coerceObjectListFilterInput
199-
|> Result.map ValueOption.toObj)
200-
CoerceOutput = coerceObjectListFilterValue
170+
(fun input variables ->
171+
match input with
172+
| InlineConstant c ->
173+
(coerceObjectListFilterInput variables c)
174+
|> Result.map ValueOption.toObj
175+
| Variable json ->
176+
json
177+
|> InputValue.OfJsonElement
178+
|> (coerceObjectListFilterInput variables)
179+
|> Result.map ValueOption.toObj)
201180
}

src/FSharp.Data.GraphQL.Server/Execution.fs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,16 @@ let internal coerceVariables (variables: VarDef list) (vars: ImmutableDictionary
599599
fun (acc : Result<ImmutableDictionary<string, obj>.Builder, IGQLError list>) struct(varDef, jsonElement) -> validation {
600600
let! value =
601601
let varTypeDef = varDef.TypeDef
602-
coerceVariableValue false [] ValueNone (varTypeDef, varTypeDef) varDef jsonElement
602+
let ctx = {
603+
IsNullable = false
604+
InputObjectPath = []
605+
ObjectFieldErrorDetails = ValueNone
606+
OriginalTypeDef = varTypeDef
607+
TypeDef = varTypeDef
608+
VarDef = varDef
609+
Input = jsonElement
610+
}
611+
coerceVariableValue ctx
603612
|> Result.mapError (
604613
List.map (fun err ->
605614
match err with

src/FSharp.Data.GraphQL.Server/Schema.fs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,8 @@ type Schema<'Root> (query: ObjectDef<'Root>, ?mutation: ObjectDef<'Root>, ?subsc
321321
getPossibleTypes idef
322322
|> Array.map (fun tdef -> Map.find tdef.Name namedTypes)
323323
IntrospectionType.Interface(idef.Name, idef.Description, fields, possibleTypes)
324+
| InputCustom inCustDef ->
325+
IntrospectionType.InputObject(inCustDef.Name, inCustDef.Description, [||])
324326
| _ -> failwithf "Unexpected value of typedef: %O" typedef
325327

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

0 commit comments

Comments
 (0)