diff --git a/Directory.Build.props b/Directory.Build.props index 62d2954a..37bb94b2 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -11,7 +11,7 @@ - 3.1.0 + 3.1.1 diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 53fa5fda..73596c7f 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -240,6 +240,9 @@ * Changed `GraphQLOptionsDefaults.WebSocketConnectionInitTimeoutInMs` const type to `double` * Improved Relay XML documentation comments -### 3.1.1 - Unreleased +### 3.1.1 - 2026-04-03 -* Fixed planning phase crash when inline fragments reference types not included in union or interface definitions \ No newline at end of file +* Fixed GraphQL client provider handling for schema types that reuse reserved scalar names such as `Date`, so introspection kind now takes precedence over built-in scalar mappings. + +### 3.1.2 - Unreleased +* Fixed planning phase crash when inline fragments reference types not included in union or interface definitions diff --git a/src/FSharp.Data.GraphQL.Client.DesignTime/ProvidedTypesHelper.fs b/src/FSharp.Data.GraphQL.Client.DesignTime/ProvidedTypesHelper.fs index cfe2f421..621c5b2c 100644 --- a/src/FSharp.Data.GraphQL.Client.DesignTime/ProvidedTypesHelper.fs +++ b/src/FSharp.Data.GraphQL.Client.DesignTime/ProvidedTypesHelper.fs @@ -297,10 +297,6 @@ module internal ProvidedOperation = tdef.AddXmlDoc("Represents a GraphQL operation on the server.") tdef.AddMembersDelayed(fun _ -> let operationResultDef = ProvidedOperationResult.makeProvidedType(operationType) - let isScalar (typeName: string) = - match schemaTypes.TryFind typeName with - | Some introspectionType -> introspectionType.Kind = TypeKind.SCALAR - | None -> false let variables = let rec mapVariable (variableName : string) (variableType : InputType) = match variableType with @@ -309,11 +305,11 @@ module internal ProvidedOperation = | Some uploadInputTypeName when typeName = uploadInputTypeName -> struct (variableName, typeName, TypeMapping.makeOption typeof) | _ -> - match TypeMapping.scalar.TryFind(typeName) with + match TypeMapping.tryFindScalarType schemaTypes typeName with | Some t -> struct (variableName,typeName, TypeMapping.makeOption t) - | None when isScalar typeName -> struct (variableName, typeName, typeof) + | None when TypeMapping.isScalarTypeName schemaTypes typeName -> struct (variableName, typeName, typeof) | None -> - match schemaProvidedTypes.TryFind(typeName) with + match schemaProvidedTypes.TryFind typeName with | Some t -> struct (variableName, typeName, TypeMapping.makeOption t) | None -> failwith $"""Unable to find variable type "%s{typeName}" in the schema definition.""" | ListType itype -> @@ -381,7 +377,7 @@ module internal ProvidedOperation = tdef.DeclaredProperties |> Seq.exists ((fun p -> p.PropertyType) >> existsUploadType) variables |> Seq.exists (fun struct (_, _, t) -> existsUploadType t) || variables - |> Seq.where (fun struct (_, typeName, _) -> TypeMapping.scalar.TryGetValue typeName |> fst |> not) + |> Seq.where (fun struct (_, typeName, _) -> not (TypeMapping.isScalarTypeName schemaTypes typeName)) |> Seq.choose (fun struct (_, typeName, _) -> schemaProvidedTypes |> Map.tryFind typeName) |> Seq.exists existsUploadTypeDefinition let runMethodOverloads : MemberInfo list = diff --git a/src/FSharp.Data.GraphQL.Client/BaseTypes.fs b/src/FSharp.Data.GraphQL.Client/BaseTypes.fs index e1f4034f..e981d83c 100644 --- a/src/FSharp.Data.GraphQL.Client/BaseTypes.fs +++ b/src/FSharp.Data.GraphQL.Client/BaseTypes.fs @@ -169,6 +169,19 @@ module internal TypeMapping = "URI", typeof |] |> Map.ofArray + let isBuiltInScalarTypeName (name : string) = + scalar |> Map.containsKey name + + let isScalarTypeName (schemaTypes : Map) (name : string) = + match schemaTypes.TryFind name with + | Some schemaType -> schemaType.Kind = TypeKind.SCALAR + | None -> isBuiltInScalarTypeName name + + let tryFindScalarType (schemaTypes : Map) (name : string) = + if isScalarTypeName schemaTypes name + then scalar |> Map.tryFind name + else None + let getSchemaTypes (introspection : IntrospectionSchema) = let schemaTypeNames = [| "__TypeKind" @@ -179,13 +192,11 @@ module internal TypeMapping = "__EnumValue" "__Directive" "__Schema" |] - let isScalarType (name : string) = - scalar |> Map.containsKey name let isIntrospectionType (name : string) = schemaTypeNames |> Array.contains name introspection.Types |> Array.choose (fun t -> - if not (isIntrospectionType t.Name) && not (isScalarType t.Name) + if not (isIntrospectionType t.Name) && not (t.Kind = TypeKind.SCALAR && isBuiltInScalarTypeName t.Name) then Some(t.Name, t) else None) |> Map.ofArray diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/FSharp.Data.GraphQL.IntegrationTests.fsproj b/tests/FSharp.Data.GraphQL.IntegrationTests/FSharp.Data.GraphQL.IntegrationTests.fsproj index 55c630c3..5ef03404 100644 --- a/tests/FSharp.Data.GraphQL.IntegrationTests/FSharp.Data.GraphQL.IntegrationTests.fsproj +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/FSharp.Data.GraphQL.IntegrationTests.fsproj @@ -17,11 +17,14 @@ + + PreserveNewest + diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/ReservedScalarNameProviderTests.fs b/tests/FSharp.Data.GraphQL.IntegrationTests/ReservedScalarNameProviderTests.fs new file mode 100644 index 00000000..3a938226 --- /dev/null +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/ReservedScalarNameProviderTests.fs @@ -0,0 +1,47 @@ +module FSharp.Data.GraphQL.IntegrationTests.ReservedScalarNameProviderTests + +open Xunit +open FSharp.Data.GraphQL + +type ObjectDateProvider = GraphQLProvider<"reserved_scalar_object_date_introspection.json"> +type InputDateProvider = GraphQLProvider<"reserved_scalar_input_date_introspection.json"> + +module ObjectDateSchema = + type SchemaDate = ObjectDateProvider.Types.Date + + let operation = + ObjectDateProvider.Operation<"""query Q { + dateInfo { + value + category + } + }""">() + + let compileSmoke () = + let schemaDate = SchemaDate(value = "2026-04-03", category = "default") + let operationInstance : ObjectDateProvider.Operations.Q = operation + schemaDate |> ignore + operationInstance |> ignore + +module InputDateSchema = + type SchemaDate = InputDateProvider.Types.Date + + let operation = + InputDateProvider.Operation<"""query Q($input: Date) { + echoDate(input: $input) + }""">() + + let compileSmoke () = + let schemaDate = SchemaDate(value = "2026-04-03", category = "default") + let deferredRun : unit -> _ = + fun () -> operation.Run(Unchecked.defaultof, schemaDate) + schemaDate |> ignore + deferredRun |> ignore + +[] +let ``Should allow object types that reuse reserved scalar names`` () = + ObjectDateSchema.compileSmoke () + +[] +let ``Should allow input object types that reuse reserved scalar names`` () = + InputDateSchema.compileSmoke () diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/reserved_scalar_input_date_introspection.json b/tests/FSharp.Data.GraphQL.IntegrationTests/reserved_scalar_input_date_introspection.json new file mode 100644 index 00000000..c5c675f6 --- /dev/null +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/reserved_scalar_input_date_introspection.json @@ -0,0 +1,89 @@ +{ + "data": { + "__schema": { + "queryType": { + "name": "Query" + }, + "mutationType": null, + "subscriptionType": null, + "types": [ + { + "kind": "SCALAR", + "name": "String", + "description": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Query", + "description": null, + "fields": [ + { + "name": "echoDate", + "description": null, + "args": [ + { + "name": "input", + "description": null, + "type": { + "kind": "INPUT_OBJECT", + "name": "Date", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", + "name": "Date", + "description": null, + "fields": null, + "inputFields": [ + { + "name": "value", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "category", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + } + ], + "directives": [] + } + } +} diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/reserved_scalar_object_date_introspection.json b/tests/FSharp.Data.GraphQL.IntegrationTests/reserved_scalar_object_date_introspection.json new file mode 100644 index 00000000..7625cf9c --- /dev/null +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/reserved_scalar_object_date_introspection.json @@ -0,0 +1,82 @@ +{ + "data": { + "__schema": { + "queryType": { + "name": "Query" + }, + "mutationType": null, + "subscriptionType": null, + "types": [ + { + "kind": "SCALAR", + "name": "String", + "description": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Query", + "description": null, + "fields": [ + { + "name": "dateInfo", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "Date", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Date", + "description": null, + "fields": [ + { + "name": "value", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "category", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + } + ], + "directives": [] + } + } +}