Skip to content

Commit 2d968df

Browse files
committed
Wrap primitive/primitive collection return types in an IGraphQLObject wrapper
This change refactors how the "primitive" (i.e. string, int, bool, etc.) return types of operations are handled. Such operations will now have their return types wrapped in either a `GraphQLValue<T>` or `GraphQLCollection<T>` wrapper class, which both satisfy the `IGraphQLObject` constraint required by the `GraphQueryBuilder`. type: feature scope: sourcegen, graphql, parser
1 parent de7fd64 commit 2d968df

File tree

5 files changed

+83
-3
lines changed

5 files changed

+83
-3
lines changed

ShopifySharp.GraphQL.Parser/AstNodeMapper.fs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ module AstNodeMapper =
3939
| NonNullableType valueType -> unwrapFieldType valueType
4040
| CollectionType collectionType -> unwrapFieldType collectionType
4141

42+
/// Maps a field type to a string representation.
4243
let rec mapFieldTypeToString (isNamedType: NamedType -> bool) assumeNullability (valueType: FieldType) (collectionHandling: FieldTypeCollectionHandling) =
4344
let maybeWriteNullability isNullable fieldStr =
4445
fieldStr + (if isNullable then "?" else "")
@@ -67,6 +68,40 @@ module AstNodeMapper =
6768

6869
unwrapType false valueType
6970

71+
/// <summary>
72+
/// Maps a field type to a string representation, wrapping primitives in <see cref="GraphQLValue<T>" /> or
73+
/// <see cref="GraphQLCollection<T>" /> to ensure the type implements IGraphQLObject. Used for operation return types.
74+
/// </summary>
75+
let rec mapFieldTypeToStringWithPrimitiveWrapper (isNamedType: NamedType -> bool) assumeNullability (valueType: FieldType) =
76+
let rec isCollectionType = function
77+
| CollectionType _ -> true
78+
| NonNullableType type' -> isCollectionType type'
79+
| NullableType type' -> isCollectionType type'
80+
| _ -> false
81+
82+
let rec isPrimitiveType = function
83+
| ValueType valueType ->
84+
match valueType with
85+
| FieldValueType.GraphObjectType _ -> false
86+
| _ -> true
87+
| NonNullableType type' -> isPrimitiveType type'
88+
| NullableType type' -> isPrimitiveType type'
89+
| CollectionType type' -> isPrimitiveType type'
90+
91+
// Determine if we need to wrap this type
92+
match valueType with
93+
| _ when isCollectionType valueType && isPrimitiveType valueType ->
94+
// It's a collection of primitives, wrap it in GraphQLCollection<T>
95+
let elementType = mapFieldTypeToString isNamedType false valueType FieldTypeCollectionHandling.UnwrapCollection
96+
$"GraphQLCollection<{elementType}>"
97+
| _ when isPrimitiveType valueType ->
98+
// It's a single primitive, wrap it in GraphQLValue<T>
99+
let primitiveType = mapFieldTypeToString isNamedType false valueType FieldTypeCollectionHandling.KeepCollection
100+
$"GraphQLValue<{primitiveType}>"
101+
| _ ->
102+
// It's GraphQL object, no wrapper needed. Return the base type string without a wrapper
103+
mapFieldTypeToString isNamedType assumeNullability valueType FieldTypeCollectionHandling.KeepCollection
104+
70105
let rec private mapGraphTypeToName (fieldType: GraphQLType): string =
71106
match fieldType with
72107
| :? GraphQLNamedType as namedType ->

ShopifySharp.GraphQL.Parser/QueryBuilderWriter.fs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,8 @@ module rec QueryBuilderWriter =
158158
| ReturnType.VisitedType visitedTypes ->
159159
visitedTypes.Name
160160
| ReturnType.FieldType fieldType ->
161-
AstNodeMapper.mapFieldTypeToString context.IsNamedType context.AssumeNullability fieldType FieldTypeCollectionHandling.KeepCollection
161+
// Use the wrapper function to ensure primitives are wrapped in GraphQLValue<T> or GraphQLCollection<T>
162+
AstNodeMapper.mapFieldTypeToStringWithPrimitiveWrapper context.IsNamedType context.AssumeNullability fieldType
162163
| x -> x.Name
163164

164165
// Fully qualify class names that might collide with System types

ShopifySharp/Entities/GraphQL/Generated/QueryBuilders/ArticleTagsQueryBuilder.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace ShopifySharp.Services.Generated;
99
using ShopifySharp.Infrastructure;
1010
using ShopifySharp.Infrastructure.Serialization.Json;
1111

12-
public class ArticleTagsQueryBuilder() : GraphQueryBuilder<ICollection<string>?>("query articleTags")
12+
public class ArticleTagsQueryBuilder() : GraphQueryBuilder<GraphQLCollection<string>>("query articleTags")
1313
{
1414
public ArticleTagsQueryBuilder AddReturnValue()
1515
{
@@ -28,4 +28,4 @@ public ArticleTagsQueryBuilder AddArgumentSort(ArticleTagSort? sort)
2828
AddArgument("sort", sort);
2929
return this;
3030
}
31-
}
31+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace ShopifySharp.GraphQL;
2+
3+
using System.Collections.Generic;
4+
5+
/// <summary>
6+
/// Wraps a collection of "primitive" values (i.e. strings, numbers, bools) returned from a GraphQL query or mutation.
7+
/// This allows collection return types to implement IGraphQLObject while maintaining type safety.
8+
/// </summary>
9+
/// <typeparam name="T">The type of the collection's elements</typeparam>
10+
public record GraphQLCollection<T> : IGraphQLObject
11+
{
12+
public ICollection<T> Value { get; init; } = new List<T>();
13+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
namespace ShopifySharp.GraphQL;
2+
3+
using System.Collections.Generic;
4+
5+
/// <summary>
6+
/// Wraps a "primitive" value (i.e. string, int, bool) returned from a GraphQL query or mutation.
7+
/// This allows primitive return types to implement IGraphQLObject while maintaining type safety.
8+
/// </summary>
9+
/// <typeparam name="T">The underlying primitive type (string, int, bool, etc.)</typeparam>
10+
public record GraphQLValue<T> : IGraphQLObject
11+
{
12+
/// <summary>
13+
/// The underlying primitive value.
14+
/// </summary>
15+
public T Value { get; init; }
16+
17+
public GraphQLValue(T value)
18+
{
19+
Value = value;
20+
}
21+
22+
/// <summary>
23+
/// Implicit conversion to the underlying type.
24+
/// </summary>
25+
public static implicit operator T(GraphQLValue<T> wrapper) => wrapper.Value;
26+
27+
/// <summary>
28+
/// Implicit conversion from the underlying type.
29+
/// </summary>
30+
public static implicit operator GraphQLValue<T>(T value) => new(value);
31+
}

0 commit comments

Comments
 (0)