Skip to content

Commit 27e39fc

Browse files
xperiandrivalberspanthus
authored
Updated docs about input object validator and scalar input coercion (#457)
* Added input object validator docs and scalar input coercion changes * Fixed `coerceValue` description in docs --------- Co-authored-by: Valber M. Silva de Souza <[email protected]> Co-authored-by: panthus <[email protected]>
1 parent fe8712c commit 27e39fc

File tree

2 files changed

+53
-16
lines changed

2 files changed

+53
-16
lines changed

docs/index.md

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
FSharp.Data.GraphQL
32
======================
43

docs/type-system.md

+53-15
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ Each schema definition requires to have something called *root query* - it's a G
77

88
GraphQL type system categorizes several custom types, that can be defined by programmer, including:
99

10-
- [Objects](#Defining-an-Object)
11-
- [Interfaces](#Defining-an-Interface)
12-
- [Unions](#Defining-a-Union)
13-
- [Enums](#Defining-an-Enum)
14-
- [Scalars](#Defining-a-Scalar)
15-
- [InputObjects](#Defining-an-Input-Object)
10+
- [Objects](#defining-an-object)
11+
- [Interfaces](#defining-an-interface)
12+
- [Unions](#defining-a-union)
13+
- [Enums](#defining-an-enum)
14+
- [Scalars](#defining-a-scalar)
15+
- [InputObjects](#defining-an-input-object)
1616

1717
Beside them, FSharp.Data.GraphQL defines two others:
1818

@@ -150,7 +150,8 @@ type Ord =
150150
let Ord = Define.Enum("Ord", [
151151
Define.EnumValue("Lesser", Ord.Lt)
152152
Define.EnumValue("Equal", Ord.Eq)
153-
Define.EnumValue("Greater", Ord.Greater) ])
153+
Define.EnumValue("Greater", Ord.Greater)
154+
])
154155
```
155156

156157
The major difference from .NET here is that GraphQL expects, that enum values are serialized as **strings**. Therefore, upon serialization, given enum value will be projected using `ToString()` method.
@@ -162,24 +163,61 @@ Just like enums, GraphQL scalars are leaf types, that should be able to be seria
162163
```fsharp
163164
let Guid = Define.Scalar(
164165
name = "Guid",
165-
coerceInput = (fun (StringValue s) -> match Guid.TryParse(s) with true, g -> Some g | false, _ -> None),
166-
coerceValue = fun v -> match v with | :? Guid g -> Some g | _ -> None)
166+
coerceInput = // Returns Result<Guid, IGQLError list>
167+
(fun value ->
168+
let destinationType = "GUID"
169+
function
170+
// Handle variable value as JsonElement
171+
| Variable e when e.ValueKind = JsonValueKind.String ->
172+
let s = e.GetString()
173+
match Guid.TryParse(s) with
174+
| true, guid -> Ok guid
175+
| false, _ -> e.GetDeserializeError destinationType
176+
| Variable e -> e.GetDeserializeError destinationType
177+
// Handle inline value as AST discriminated union object
178+
| InlineConstant (StringValue s) ->
179+
match Guid.TryParse(s) with
180+
| true, guid -> Ok guid
181+
| false, _ -> getParseError destinationType s
182+
| InlineConstant value -> value.GetCoerceError destinationType),
183+
coerceValue = // Returns Guid option
184+
fun v -> match v with | :? Guid g -> Some g | _ -> None
185+
)
167186
```
168187

169-
This examples shows how to create a scalar definition for .NET `Guid` type. It requires two functions to be defines:
188+
This example shows how to create a scalar definition for the .NET `Guid` type. It requires two functions to be defined:
170189

171-
1. `coerceInput` function, which will be used to resolve your scalar value directly from values encoded in GraphQL query string (in this case StringValue is just part of parsed query AST).
172-
2. `coerceValue` function, which will be used to apply something like "implicit casts" between two .NET types in conflicting cases - like when value is passed in variables query string part - which should be tolerated.
190+
1. `coerceInput` function, which will be used to resolve your scalar value from a variable or directly from a value encoded in a GraphQL query string (in this case StringValue is just a part of the parsed query AST).
191+
2. `coerceValue` function, which will return `Some` only if the value falls under the scalar's allowed values range, otherwise `None`. Applied while producing the return object graph from the resolver's output.
173192

174193
## Defining an Input Object
175194

176-
Just like objects, input objects describe a complex data types - however while Objects are designed to work as **output** types, Input Objects are **input** types only.
195+
Just like objects, input objects describe a complex data types - however while Objects are designed to work as **output** types, Input Objects are **input** types only.
177196

178197
```fsharp
179198
type CreateAccountData = { Email: string; Password: string }
180-
let CreateAccountData = Define.InputObject("CreateAccountData", [
199+
let CreateAccountDataType = Define.InputObject("CreateAccountData", [
181200
Define.Input("email", String)
182-
Define.Input("password", String) ])
201+
Define.Input("password", String)
202+
])
183203
```
184204

185205
Unlike the objects, you neither define input object field resolver nor provide any arguments for it. They also don't work together with abstract types like interfaces or unions.
206+
207+
Validate individual properties at scalar definition level, but use validator to validate the whole input object or several dependant properties like this:
208+
209+
```fsharp
210+
type InputAddress = { Country : string; ZipCode : string; City : string }
211+
let InputAddressType =
212+
Define.InputObject<InputAddress> ("InputAddress", [
213+
Define.Input ("country", StringType)
214+
Define.Input ("zipCode", StringType)
215+
Define.Input ("city", StringType)
216+
],
217+
fun inputAddress ->
218+
match inputAddress.Country with
219+
| "US" when inputAddress.ZipCode.Length <> 5 -> ValidationError <| createSingleError "ZipCode must be 5 characters for US"
220+
| "US" -> Success
221+
| _ -> ValidationError <| createSingleError "Unsupported country"
222+
)
223+
```

0 commit comments

Comments
 (0)