Skip to content

Commit 531d84c

Browse files
committed
Refactor union type handling and interface name generation
- Track union case names in NamedType.UnionType for better type resolution - Add dedicated DotnetName field to Interface for proper "I" prefix handling - Update AddUnion signature to use proper generic constraint on query builder - Rename the generated `AddUnionXYZ` methods to `AddUnionCaseXYZ` type: refactor scope: graphql-parser
1 parent 2055b9f commit 531d84c

File tree

6 files changed

+75
-69
lines changed

6 files changed

+75
-69
lines changed

ShopifySharp.GraphQL.Parser/AstNodeMapper.fs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,12 @@ module AstNodeMapper =
140140
let private mapGraphTypeToNamedType (context: IParsedContext) (namedType: GraphQLNamedType): NamedType =
141141
let typeName = namedType.Name.StringValue
142142
match context.TryFindDocumentNode namedType.Name.Value with
143-
| Some (:? GraphQLUnionTypeDefinition) -> NamedType.UnionType typeName
143+
| Some (:? GraphQLUnionTypeDefinition as unionType) ->
144+
let caseTypes =
145+
unionType.Types.Items
146+
|> Seq.map _.Name.StringValue
147+
|> Array.ofSeq
148+
NamedType.UnionType (typeName, caseTypes)
144149
| Some (:? GraphQLInputObjectTypeDefinition) -> NamedType.InputObject typeName
145150
| Some (:? GraphQLEnumTypeDefinition) -> NamedType.Enum typeName
146151
| Some (:? GraphQLInterfaceTypeDefinition) -> NamedType.Interface typeName
@@ -299,7 +304,8 @@ module AstNodeMapper =
299304
InheritedTypeNames = mapToInheritedTypeNames objectTypeDefinition.Interfaces }
300305

301306
let mapInterfaceTypeDefinition context (interfaceTypeDefinition: GraphQLInterfaceTypeDefinition): Interface =
302-
{ Name = strToInterfaceName interfaceTypeDefinition.Name.StringValue
307+
{ Name = interfaceTypeDefinition.Name.StringValue
308+
DotnetName = strToInterfaceName interfaceTypeDefinition.Name.StringValue
303309
XmlSummary = mapDescriptionToXmlSummary interfaceTypeDefinition.Description
304310
Deprecation = getDeprecationMessage interfaceTypeDefinition.Directives
305311
Fields = mapToFields context (ObjectFields interfaceTypeDefinition.Fields)

ShopifySharp.GraphQL.Parser/Domain.fs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,15 @@ type NamedType =
4444
| Interface of name: string
4545
| Enum of name: string
4646
| InputObject of name: string
47-
| UnionType of name: string
47+
| UnionType of name: string * caseNames: string[]
4848
with
4949
member this.Name: string =
5050
match this with
5151
| Class name -> name
5252
| Interface name -> name
5353
| Enum name -> name
5454
| InputObject name -> name
55-
| UnionType name -> name
55+
| UnionType (name, _) -> name
5656
override this.ToString (): string =
5757
this.Name
5858

@@ -104,6 +104,8 @@ type Field =
104104

105105
type Interface =
106106
{ Name: string
107+
/// The preferred type name when this interface is transformed to dotnet, i.e. the type name prefixed with "I".
108+
DotnetName: string
107109
XmlSummary: string[]
108110
Deprecation: string option
109111
Fields: Field[]

ShopifySharp.GraphQL.Parser/QueryBuilderWriter.fs

Lines changed: 59 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ module rec QueryBuilderWriter =
1010
let [<Literal>] private QueryRootObjectName = "QueryRoot"
1111
let [<Literal>] private MutationRootObjectName = "Mutation"
1212

13+
// Fully qualify class names which might collide with System types
14+
let qualifiedPascalTypeName className =
15+
match toCasing Pascal className with
16+
| "Attribute" -> "ShopifySharp.GraphQL.Attribute"
17+
| pascalGenericType -> pascalGenericType
18+
1319
let private canAddFields = function
1420
| VisitedTypes.Class _ -> true
1521
| VisitedTypes.Interface _ -> true
@@ -22,17 +28,18 @@ module rec QueryBuilderWriter =
2228
let private canAddArguments (type': VisitedTypes) =
2329
type'.IsOperation
2430

25-
let private writeUnionTypeMutationJoins (pascalParentClassName: string) (unionCaseName: string) (_: IParsedContext) writer: ValueTask =
31+
let private writeUnionCaseJoin (pascalParentClassName: string) (unionCaseName: string) (fieldName: string) (_: IParsedContext) writer: ValueTask =
2632
pipeWriter writer {
2733
let pascalUnionCaseName = toCasing Pascal unionCaseName
28-
let camelUnionCaseName = toCasing Camel unionCaseName
34+
let pascalFieldName = toCasing Pascal fieldName
35+
let camelFieldName = toCasing Camel fieldName
2936
let unionCaseQueryBuilderName = $"{pascalUnionCaseName}QueryBuilder"
3037

31-
do! Indented + $"public {pascalParentClassName} AddUnion{pascalUnionCaseName}(Func<{unionCaseQueryBuilderName}, {unionCaseQueryBuilderName}> build)"
38+
do! Indented + $"public {pascalParentClassName} AddUnionCase{pascalFieldName}(Func<{unionCaseQueryBuilderName}, {unionCaseQueryBuilderName}> build)"
3239
do! NewLine
3340
do! Indented + "{"
3441
do! NewLine
35-
do! DoubleIndented + $"AddUnion<{pascalUnionCaseName}>(\"{camelUnionCaseName}\", build);"
42+
do! DoubleIndented + $"AddUnion<{pascalUnionCaseName}, {unionCaseQueryBuilderName}>(\"{camelFieldName}\", build);"
3643
do! NewLine
3744
do! DoubleIndented + "return this;"
3845
do! NewLine
@@ -50,48 +57,47 @@ module rec QueryBuilderWriter =
5057
let camelFieldName = toCasing Camel fieldName
5158

5259
pipeWriter writer {
53-
// TODO: does the context actually know about union cases/named types now that we parse the document definitions directly?
54-
// (i.e. now that we aren't using the visitor pattern)
55-
if context.TypeIsKnownUnionCase fieldName || context.IsNamedType (NamedType.UnionType fieldName) then
56-
yield! writeUnionTypeMutationJoins pascalClassName fieldName context
57-
else
58-
match AstNodeMapper.unwrapFieldType fieldType with
59-
| FieldValueType.GraphObjectType (NamedType.UnionType graphObjectTypeName) ->
60-
printfn $"{graphObjectTypeName} should have procced the writeUnionTypeMutationJoins fn"
61-
yield! writeUnionTypeMutationJoins pascalClassName fieldName context
62-
| FieldValueType.GraphObjectType (NamedType.Class graphObjectTypeName)
63-
| FieldValueType.GraphObjectType (NamedType.Interface graphObjectTypeName)
64-
| FieldValueType.GraphObjectType (NamedType.InputObject graphObjectTypeName) ->
65-
let pascalTypeName = toCasing Pascal graphObjectTypeName
66-
let queryBuilderName = $"{toCasing Pascal graphObjectTypeName}QueryBuilder"
67-
68-
// TODO: if this is a collection type (not fieldType.IsFieldValueType), use the AddField collection overload
69-
70-
yield! writeDeprecationAttribute Indented None
71-
do! Indented + $"public {pascalClassName} AddField{pascalFieldName}(Func<{queryBuilderName}, {queryBuilderName}> build)"
72-
do! NewLine
73-
do! DoubleIndented + "{"
74-
do! NewLine
75-
do! TripleIndented + $"AddField<{pascalTypeName}, {queryBuilderName}>(\"{camelFieldName}\", build);"
76-
do! NewLine
77-
do! TripleIndented + "return this;"
78-
do! NewLine
79-
do! DoubleIndented + "}"
80-
do! NewLine
81-
| _ ->
82-
// TODO: if this is a collection type (not fieldType.IsFieldValueType), use the AddField collection overload
83-
84-
yield! writeDeprecationAttribute Indented deprecationWarning
85-
do! Indented + $"public {pascalClassName} AddField{pascalFieldName}()"
86-
do! NewLine
87-
do! DoubleIndented + "{"
88-
do! NewLine
89-
do! DoubleIndented + $"AddField(\"{camelFieldName}\");"
90-
do! NewLine
91-
do! TripleIndented + "return this;"
92-
do! NewLine
93-
do! DoubleIndented + "}"
94-
do! NewLine
60+
match AstNodeMapper.unwrapFieldType fieldType with
61+
| FieldValueType.GraphObjectType (NamedType.UnionType (_, unionCaseNames)) ->
62+
// yield! writeUnionCaseJoin pascalClassName unionTypeName fieldName context
63+
for unionCaseName in unionCaseNames do
64+
yield! writeUnionCaseJoin pascalClassName unionCaseName fieldName context
65+
| FieldValueType.GraphObjectType (NamedType.Class graphObjectTypeName as namedType)
66+
| FieldValueType.GraphObjectType (NamedType.Interface graphObjectTypeName as namedType)
67+
| FieldValueType.GraphObjectType (NamedType.InputObject graphObjectTypeName as namedType) ->
68+
let pascalTypeName =
69+
if namedType.IsInterface
70+
then toCasing Pascal (mapStrToInterfaceName graphObjectTypeName)
71+
else qualifiedPascalTypeName graphObjectTypeName
72+
let queryBuilderName = $"{toCasing Pascal graphObjectTypeName}QueryBuilder"
73+
74+
// TODO: if this is a collection type (not fieldType.IsFieldValueType), use the AddField collection overload
75+
76+
yield! writeDeprecationAttribute Indented None
77+
do! Indented + $"public {pascalClassName} AddField{pascalFieldName}(Func<{queryBuilderName}, {queryBuilderName}> build)"
78+
do! NewLine
79+
do! DoubleIndented + "{"
80+
do! NewLine
81+
do! TripleIndented + $"AddField<{pascalTypeName}, {queryBuilderName}>(\"{camelFieldName}\", build);"
82+
do! NewLine
83+
do! TripleIndented + "return this;"
84+
do! NewLine
85+
do! DoubleIndented + "}"
86+
do! NewLine
87+
| _ ->
88+
// TODO: if this is a collection type (not fieldType.IsFieldValueType), use the AddField collection overload
89+
90+
yield! writeDeprecationAttribute Indented deprecationWarning
91+
do! Indented + $"public {pascalClassName} AddField{pascalFieldName}()"
92+
do! NewLine
93+
do! DoubleIndented + "{"
94+
do! NewLine
95+
do! DoubleIndented + $"AddField(\"{camelFieldName}\");"
96+
do! NewLine
97+
do! TripleIndented + "return this;"
98+
do! NewLine
99+
do! DoubleIndented + "}"
100+
do! NewLine
95101
}
96102

97103
let private writeQueryBuilderAddFieldMethods (pascalClassName: string) (type': VisitedTypes) (context: IParsedContext) writer: ValueTask =
@@ -114,7 +120,8 @@ module rec QueryBuilderWriter =
114120
match visitedType with
115121
| VisitedTypes.UnionType unionType ->
116122
for unionCase in unionType.Cases do
117-
yield! writeUnionTypeMutationJoins pascalClassName unionCase.Name context
123+
yield! writeUnionCaseJoin pascalClassName unionCase.Name unionCase.Name context
124+
//yield! writeUnionTypeJoin pascalClassName unionCase.Name context
118125
| visitedType ->
119126
let fields =
120127
match visitedType with
@@ -190,23 +197,20 @@ module rec QueryBuilderWriter =
190197

191198
let genericType =
192199
match type' with
200+
| VisitedTypes.Interface interface' ->
201+
interface'.DotnetName
193202
| VisitedTypes.Operation operation ->
194203
match operation.ReturnType with
204+
| ReturnType.VisitedType (VisitedTypes.Interface interface') ->
205+
interface'.DotnetName
195206
| ReturnType.VisitedType visitedTypes ->
196207
visitedTypes.Name
197208
| ReturnType.FieldType fieldType ->
198209
// Use the wrapper function to ensure primitives are wrapped in GraphQLValue<T> or GraphQLCollection<T>
199210
AstNodeMapper.mapFieldTypeToStringWithPrimitiveWrapper context.AssumeNullability fieldType
200211
| x -> x.Name
201212

202-
// Fully qualify class names that might collide with System types
203-
let qualifiedGenericType =
204-
let pascalGenericType = toCasing Pascal genericType
205-
// The type name should already have the "I" prefix if it's an interface
206-
// (it comes from VisitedTypes.Name which includes the prefix)
207-
match pascalGenericType with
208-
| "Attribute" -> "ShopifySharp.GraphQL.Attribute"
209-
| _ -> pascalGenericType
213+
let qualifiedGenericType = qualifiedPascalTypeName genericType
210214

211215
do! $"public class {pascalClassName}(): GraphQueryBuilder<{qualifiedGenericType}>(\"{camelTypeName}\")"
212216

ShopifySharp.GraphQL.Parser/VisitedTypeWriter.fs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ let private writeFields (context: IParsedContext) shouldSkipWritingField parentT
210210
| NamedType.InputObject _ ->
211211
yield! writeDefaultValueForFieldType field.ValueType context.AssumeNullability
212212
| NamedType.Enum p
213-
| NamedType.UnionType p ->
213+
| NamedType.UnionType (p, _) ->
214214
failwith $"Parent \"{p}\" is unsupported type {parentType.GetType()}"
215215

216216
do! NewLine
@@ -245,7 +245,7 @@ let private writeInterface (interface': Interface) (context: IParsedContext) (wr
245245
yield! writeDeprecationAttribute Outdented interface'.Deprecation
246246
yield! writeJsonDerivedTypeAttributes2 interface'.Name (context.GetInterfaceImplementationTypeNames interface'.Name)
247247

248-
do! $"public interface {interface'.Name}: IGraphQLObject"
248+
do! $"public interface {interface'.DotnetName}: IGraphQLObject"
249249

250250
if Array.length interface'.InheritedTypeNames > 0 then
251251
do! ", "

ShopifySharp.GraphQL.Parser/Visitor.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ type Visitor() =
9090
VisitedTypes.UnionType unionType
9191
|> context.SetVisitedType
9292

93-
NamedType.UnionType unionType.Name
93+
NamedType.UnionType (unionType.Name, unionType.Cases |> Array.map _.Name)
9494
|> context.AddNamedType
9595

9696
unionType.Cases

ShopifySharp/Infrastructure/GraphQueryBuilder.cs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public void AddField<TField, TGraphQueryBuilder>(string name, Func<TGraphQueryBu
5151
Query.AddUnion(field.Query);
5252
}
5353

54-
public void AddUnion<TUnionCase, TGraphQueryBuilder>(string name, Func<GraphQueryBuilder<TUnionCase>, GraphQueryBuilder<TUnionCase>> build)
54+
public void AddUnion<TUnionCase, TGraphQueryBuilder>(string name, Func<TGraphQueryBuilder, TGraphQueryBuilder> build)
5555
where TUnionCase : class, IGraphQLUnionCase, IGraphQLObject
5656
where TGraphQueryBuilder : GraphQueryBuilder<TUnionCase>, new()
5757
{
@@ -60,10 +60,4 @@ public void AddUnion<TUnionCase, TGraphQueryBuilder>(string name, Func<GraphQuer
6060

6161
Query.AddUnion(union.Query);
6262
}
63-
64-
public void AddUnion<TUnionCase>(string name, GraphQueryBuilder<TUnionCase> union)
65-
where TUnionCase : class, IGraphQLUnionCase, IGraphQLObject
66-
{
67-
Query.AddUnion(union.Query);
68-
}
6963
}

0 commit comments

Comments
 (0)