Skip to content

Commit d889e68

Browse files
xperiandriclaude
andcommitted
Use [<Struct>] ? optional parameters and voption in Define/DefineRec and the introspection chain
Convert the optional-value plumbing fed by `Define`/`DefineRec` in SchemaDefinitions.fs from `option`/`Option` to `voption`/`ValueOption`, per the project convention of preferring `voption` to avoid an allocation per optional value: - All 47 `?description`, `?deprecationReason`, `?isTypeOf`, `?interfaces`, `?args`, `?defaultValue` and `?resolveType` parameters across `Define`/`DefineRec` are now `[<Struct>] ?...` (`?batching` already was). `defaultArg` became `defaultValueArg` where these parameters are unwrapped. - The type-system records and interfaces that store these values (`FieldDef(inition)`, `ScalarDef(inition)`, `EnumVal`/`EnumValue`, `EnumDef(inition)`, `ObjectDef(inition)`, `InterfaceDef(inition)`, `UnionDef(inition)`, `InputObjectDef(inition)`, `InputFieldDef(inition)`, `InputCustomDef(inition)`, `SubscriptionFieldDefinition`, `SubscriptionObjectDefinition`, `DirectiveDef`) now expose `voption` instead of `option`. - The full introspection schema (`IntrospectionDirective`, `IntrospectionType`, `IntrospectionTypeRef`, `IntrospectionInputVal`, `IntrospectionEnumVal`, `IntrospectionField`, `IntrospectionSchema`) moves to `voption` for every optional field, not only Description/DeprecationReason/DefaultValue, so the chain from `Define.*` down to the client type provider needs no `Option.toValueOption`/`ValueOption.ofOption`/`Option.map` adapters. The `__type`/`__schema` introspection resolvers in Introspection.fs switch from `Nullable` to `StructNullable` accordingly. - Server `Execution.fs`, `Values.fs`, `Linq.fs`, `Schema.fs` and `Planning.fs`, and Shared `Validation.fs` (`SchemaInfo.QueryType`/`MutationType`/`SubscriptionType`, `TryGetTypeByRef`, `TryGetOperationType`, `tryFindInArrayOption`) follow the same members through to `voption`. - The client type provider learns `voption`: `Client/BaseTypes.fs` and `Client/Serialization.fs` (new `ReflectionPatterns.isValueOption` / `(|ValueOption|_|)` / `makeValueOption` helpers, added alongside the existing `option`-only ones since `GraphQLResponse.Errors` and provided-type runtime values still use `option`), and `Client.DesignTime/ProvidedTypesHelper.fs` (`ProvidedTypeMetadata.Description`, `RecordPropertyMetadata.Description`/`DeprecationReason`). - `Server.Middleware/SchemaDefinitions.fs` (`ObjectListFilterType`) and the test helper `OptionalsNormalizationTests.ValidString.fs` (`Define.ValidStringScalar`, which forwards `?description`) follow. - Ran `dotnet fantomas` on every changed file; several of them had pre-existing formatting drift from the pinned Fantomas version, which the formatter now corrects alongside the semantic changes. This is a source-breaking change for callers of the affected `Define`/`DefineRec` members (`?description = someOption`-style forwarding must now pass a `voption`) and for code that pattern- matches `Some`/`None` on `.Description`, `.DeprecationReason`, `.DefaultValue`, `.IsTypeOf`, `.ResolveType`, or on any field of the `Introspection*` types (must switch to `ValueSome`/`ValueNone`). Out of scope (left for a follow-up): `Schema`'s `?mutation`/ `?subscription`/`?config` constructor parameters, `TypeMap`'s `?overwrite`/`?includeDefaultTypes`, `SchemaConfig.SetExecute`'s `?overwrite`, the Middleware `Define.*Middleware` helpers, `ObjectListFilter.Apply`, `ValidationError.AsResult`'s `?path`, and the client's `Locations.Create`'s `?folderPath`. Verified: `dotnet build` on both `FSharp.Data.GraphQL.slnx` and `FSharp.Data.GraphQL.Integration.slnx` succeeds with 0 errors/warnings; `FSharp.Data.GraphQL.Tests` (627 passed, 5 pre-existing skips) and `FSharp.Data.GraphQL.IntegrationTests` (105 passed, exercising the client type provider's schema deserialization and `voption` quotation end to end) both pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent e17cccc commit d889e68

17 files changed

Lines changed: 448 additions & 406 deletions

File tree

‎src/FSharp.Data.GraphQL.Client.DesignTime/ProvidedTypesHelper.fs‎

Lines changed: 35 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ module internal QuotationHelpers =
3535
let exprs = coerceValues (fun _ -> typ) instance
3636
Expr.NewArray(typ, exprs)
3737
let tupleExpr (tupleType : Type) (v : obj) =
38-
let typ = FSharpType.GetTupleElements tupleType |> Array.mapi (fun i t -> i, t) |> Map.ofArray
38+
let typ = FSharpType.GetTupleElements tupleType |> Seq.mapi (fun i t -> i, t) |> Map.ofSeq
3939
let fieldTypeLookup i = typ.[i]
4040
let fields = FSharpValue.GetTupleFields v
4141
let exprs = coerceValues fieldTypeLookup fields
@@ -107,19 +107,19 @@ module internal ProvidedEnum =
107107

108108
type internal ProvidedTypeMetadata =
109109
{ Name : string
110-
Description : string option }
110+
Description : string voption }
111111

112112
module internal ProvidedInterface =
113113
let makeProvidedType(metadata : ProvidedTypeMetadata) =
114114
let tdef = ProvidedTypeDefinition("I" + metadata.Name.FirstCharUpper(), None, nonNullable = true, isInterface = true)
115-
metadata.Description |> Option.iter tdef.AddXmlDoc
115+
metadata.Description |> ValueOption.iter tdef.AddXmlDoc
116116
tdef
117117

118118
type internal RecordPropertyMetadata =
119119
{ Name : string
120120
Alias : string voption
121-
Description : string option
122-
DeprecationReason : string option
121+
Description : string voption
122+
DeprecationReason : string voption
123123
Type : Type }
124124
member x.AliasOrName =
125125
match x.Alias with
@@ -154,8 +154,8 @@ module internal ProvidedRecord =
154154
| Some prop -> prop.Value
155155
| None -> failwith $"""Expected to find property "%s{pname}", but the property was not found.""" @@>
156156
let pdef = ProvidedProperty(pname, metadata.Type, getterCode)
157-
metadata.Description |> Option.iter pdef.AddXmlDoc
158-
metadata.DeprecationReason |> Option.iter pdef.AddObsoleteAttribute
157+
metadata.Description |> ValueOption.iter pdef.AddXmlDoc
158+
metadata.DeprecationReason |> ValueOption.iter pdef.AddObsoleteAttribute
159159
pdef))
160160
let addConstructorDelayed (propertiesGetter : unit -> (string * string voption * Type) list) =
161161
tdef.AddMembersDelayed(fun _ ->
@@ -529,7 +529,7 @@ module internal Provider =
529529
else
530530
let getIntrospectionFields typeName =
531531
match schemaTypes.TryGetValue typeName with
532-
| true, schematype -> schematype.Fields |> Option.defaultValue [||]
532+
| true, schematype -> schematype.Fields |> ValueOption.defaultValue [||]
533533
| false, _ -> failwith $"""Could not find a schema type based on a type reference. The reference is to a "%s{typeName}" type, but that type was not found in the schema types."""
534534
let getPropertyMetadata typeName (info : AstFieldInfo) : RecordPropertyMetadata =
535535
let ifield =
@@ -587,11 +587,11 @@ module internal Provider =
587587
let schemaTypes = TypeMapping.getSchemaTypes schema
588588
let getSchemaType (tref : IntrospectionTypeRef) =
589589
match tref.Name with
590-
| Some name ->
590+
| ValueSome name ->
591591
match schemaTypes.TryFind(name) with
592592
| Some itype -> itype
593593
| None -> failwithf "Type \"%s\" was not found on the schema custom types." name
594-
| None -> failwith "Expected schema type to have a name, but it does not have one."
594+
| ValueNone -> failwith "Expected schema type to have a name, but it does not have one."
595595
let typeModifier (modifier : Type -> Type) (metadata : RecordPropertyMetadata) = { metadata with Type = modifier metadata.Type }
596596
let makeOption = typeModifier TypeMapping.makeOption
597597
let makeArrayOption = typeModifier (TypeMapping.makeArray >> TypeMapping.makeOption)
@@ -629,7 +629,7 @@ module internal Provider =
629629
{ Name = field.Name
630630
Alias = ValueNone
631631
Description = field.Description
632-
DeprecationReason = None
632+
DeprecationReason = ValueNone
633633
Type = providedType }
634634
|> makeOption
635635
| _ when uploadInputTypeName.IsSome && field.Type.Name.IsSome && uploadInputTypeName.Value = field.Type.Name.Value && field.Type.Kind <> TypeKind.INPUT_OBJECT ->
@@ -646,7 +646,7 @@ module internal Provider =
646646
{ Name = field.Name
647647
Alias = ValueNone
648648
Description = field.Description
649-
DeprecationReason = None
649+
DeprecationReason = ValueNone
650650
Type = providedType }
651651
|> makeOption
652652
| _ -> failwith "Could not find a schema type based on a type reference. The reference has an invalid or unsupported combination of Name, Kind and OfType fields."
@@ -661,7 +661,7 @@ module internal Provider =
661661
providedTypes.Value <- providedTypes.Value.Add(itype.Name, tdef)
662662
let properties =
663663
itype.Fields
664-
|> Option.defaultValue [||]
664+
|> ValueOption.defaultValue [||]
665665
|> Seq.map resolveFieldMetadata
666666
|> Seq.toList
667667
upcast ProvidedRecord.makeProvidedType(tdef, properties, explicitOptionalParameters)
@@ -670,7 +670,7 @@ module internal Provider =
670670
providedTypes.Value <- providedTypes.Value.Add(itype.Name, tdef)
671671
let properties =
672672
itype.InputFields
673-
|> Option.defaultValue [||]
673+
|> ValueOption.defaultValue [||]
674674
|> Seq.map resolveInputFieldMetadata
675675
|> Seq.toList
676676
upcast ProvidedRecord.makeProvidedType(tdef, properties, explicitOptionalParameters)
@@ -681,8 +681,8 @@ module internal Provider =
681681
| TypeKind.ENUM ->
682682
let items =
683683
match itype.EnumValues with
684-
| Some values -> values |> Array.map (fun value -> value.Name)
685-
| None -> [||]
684+
| ValueSome values -> values |> Array.map (fun value -> value.Name)
685+
| ValueNone -> [||]
686686
let tdef = ProvidedEnum.makeProvidedType(itype.Name, items)
687687
providedTypes.Value <- providedTypes.Value.Add(itype.Name, tdef)
688688
tdef
@@ -691,8 +691,8 @@ module internal Provider =
691691
schemaTypes |> Map.iter (fun _ itype -> if not (List.contains itype.Kind ignoredKinds) then resolveProvidedType itype |> ignore)
692692
let possibleTypes (itype : IntrospectionType) =
693693
match itype.PossibleTypes with
694-
| Some trefs -> trefs |> Array.map (getSchemaType >> resolveProvidedType)
695-
| None -> [||]
694+
| ValueSome trefs -> trefs |> Array.map (getSchemaType >> resolveProvidedType)
695+
| ValueNone -> [||]
696696
let getProvidedType typeName =
697697
match providedTypes.Value.TryFind(typeName) with
698698
| Some ptype -> ptype
@@ -833,16 +833,16 @@ module internal Provider =
833833
| Query -> schema.QueryType
834834
| Mutation ->
835835
match schema.MutationType with
836-
| Some tref -> tref
837-
| None -> failwith "The operation is a mutation operation, but the schema does not have a mutation type."
836+
| ValueSome tref -> tref
837+
| ValueNone -> failwith "The operation is a mutation operation, but the schema does not have a mutation type."
838838
| Subscription ->
839839
match schema.SubscriptionType with
840-
| Some tref -> tref
841-
| None -> failwithf "The operation is a subscription operation, but the schema does not have a subscription type."
840+
| ValueSome tref -> tref
841+
| ValueNone -> failwithf "The operation is a subscription operation, but the schema does not have a subscription type."
842842
let tinst =
843843
match tref.Name with
844-
| Some name -> schema.Types |> Array.tryFind (fun t -> t.Name = name)
845-
| None -> None
844+
| ValueSome name -> schema.Types |> Array.tryFind (fun t -> t.Name = name)
845+
| ValueNone -> None
846846
match tinst with
847847
| Some t -> { tref with Kind = t.Kind }
848848
| None -> failwith "The operation was found in the schema, but it does not have a name."
@@ -857,8 +857,8 @@ module internal Provider =
857857
let metadata = getOperationMetadata(schemaTypes, uploadInputTypeName, enumProvidedTypes, operationAstFields, operationTypeRef, explicitOptionalParameters)
858858
let operationTypeName : TypeName =
859859
match operationTypeRef.Name with
860-
| Some name -> name
861-
| None -> failwith "Error parsing query. Operation type does not have a name."
860+
| ValueSome name -> name
861+
| ValueNone -> failwith "Error parsing query. Operation type does not have a name."
862862
let rec getKind (tref : IntrospectionTypeRef) =
863863
match tref.Kind with
864864
| TypeKind.NON_NULL | TypeKind.LIST when tref.OfType.IsSome -> getKind tref.OfType.Value
@@ -868,8 +868,8 @@ module internal Provider =
868868
| TypeKind.NON_NULL | TypeKind.LIST when tref.OfType.IsSome -> getTypeName tref.OfType.Value
869869
| _ ->
870870
match tref.Name with
871-
| Some tname -> tname
872-
| None -> failwithf "Expected type kind \"%s\" to have a name, but it does not have a name." (tref.Kind.ToString())
871+
| ValueSome tname -> tname
872+
| ValueNone -> failwithf "Expected type kind \"%s\" to have a name, but it does not have a name." (tref.Kind.ToString())
873873
let rec getIntrospectionType (tref : IntrospectionTypeRef) =
874874
match tref.Kind with
875875
| TypeKind.NON_NULL | TypeKind.LIST when tref.OfType.IsSome -> getIntrospectionType tref.OfType.Value
@@ -891,7 +891,7 @@ module internal Provider =
891891
| FragmentField fragf ->
892892
let fragmentType =
893893
let tref =
894-
Option.defaultValue [||] introspectionType.PossibleTypes
894+
ValueOption.defaultValue [||] introspectionType.PossibleTypes
895895
|> Array.map getIntrospectionType
896896
|> Array.append [|introspectionType|]
897897
|> Array.tryFind (fun pt -> pt.Name = fragf.TypeCondition)
@@ -900,19 +900,17 @@ module internal Provider =
900900
| None -> failwithf "Fragment field defines a type condition \"%s\", but that type was not found in the schema definition." fragf.TypeCondition
901901
let field =
902902
fragmentType.Fields
903-
|> Option.map (Array.tryFind (fun f -> f.Name = fragf.Name))
904-
|> Option.flatten
903+
|> ValueOption.bind (Array.tryFind (fun f -> f.Name = fragf.Name) >> ValueOption.ofOption)
905904
match field with
906-
| Some f -> f.Type
907-
| None -> throw fragmentType.Name
905+
| ValueSome f -> f.Type
906+
| ValueNone -> throw fragmentType.Name
908907
| TypeField typef ->
909908
let field =
910909
introspectionType.Fields
911-
|> Option.map (Array.tryFind (fun f -> f.Name = typef.Name))
912-
|> Option.flatten
910+
|> ValueOption.bind (Array.tryFind (fun f -> f.Name = typef.Name) >> ValueOption.ofOption)
913911
match field with
914-
| Some f -> f.Type
915-
| None -> throw introspectionType.Name
912+
| ValueSome f -> f.Type
913+
| ValueNone -> throw introspectionType.Name
916914
let fields =
917915
match getKind tref with
918916
| TypeKind.OBJECT | TypeKind.INTERFACE | TypeKind.UNION ->

0 commit comments

Comments
 (0)