Skip to content

Commit

Permalink
Refactor IFederationResolver; add FederationResolverAttribute (#3963)
Browse files Browse the repository at this point in the history
  • Loading branch information
Shane32 committed Jun 20, 2024
1 parent 4023803 commit 8c612dd
Show file tree
Hide file tree
Showing 17 changed files with 1,524 additions and 141 deletions.
169 changes: 169 additions & 0 deletions docs2/site/docs/migrations/migration8.md
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,175 @@ Note: the feature is still a draft and has not made it into the official GraphQL
It is expected to be added once it has been implemented in multiple libraries and proven to be useful.
It is not expected to change from the current draft.

### 16. Federation entity resolver configuration methods and attributes added for code-first and type-first schemas

Extension methods have been added for defining entity resolvers in code-first and type-first schemas
for GraphQL Federation.

Code-first sample 1: (uses entity type for representation)

```cs
public class WidgetType : ObjectGraphType<Widget>
{
public WidgetType()
{
// configure federation key fields
this.Key("id");

// configure federation resolver
this.ResolveReference(async (context, widget) =>
{
// pull the id from the representation
var id = widget.Id;

// resolve the entity reference
var widgetData = context.RequestServices!.GetRequiredService<WidgetRepository>();
return await widgetData.GetWidgetByIdAsync(id, context.CancellationToken);
});

// configure fields
Field(x => x.Id, type: typeof(NonNullGraphType<IdGraphType>));
Field(x => x.Name);
}
}

public class Widget
{
public string Id { get; set; }
public string Name { get; set; }
}
```

Code-first sample 2: (uses custom type for representation)

```cs
public class WidgetType : ObjectGraphType<Widget>
{
public WidgetType()
{
// configure federation key fields
this.Key("id");

// configure federation resolver
this.ResolveReference<WidgetRepresentation, Widget>(async (context, widget) =>
{
// pull the id from the representation
var id = widget.Id;

// resolve the entity reference
var widgetData = context.RequestServices!.GetRequiredService<WidgetRepository>();
return await widgetData.GetWidgetByIdAsync(id, context.CancellationToken);
});

// configure fields
Field(x => x.Id, type: typeof(NonNullGraphType<IdGraphType>));
Field(x => x.Name);
}
}

public class Widget
{
public string Id { get; set; }
public string Name { get; set; }
}

public class WidgetRepresentation
{
public string Id { get; set; }
}
```

Type-first sample 1: (static method; uses method arguments for representation)

```cs
// configure federation key fields
[Key("id")]
public class Widget
{
// configure fields
[Id]
public string Id { get; set; }
public string Name { get; set; }

// configure federation resolver
[FederationResolver]
public static async Task<Widget> ResolveReference([FromServices] WidgetRepository widgetData, [Id] string id, CancellationToken token)
{
// resolve the entity reference
return await widgetData.GetWidgetByIdAsync(id, token);
}
}
```

Type-first sample 2: (instance method; uses instance for representation)

```cs
// configure federation key fields
[Key("id")]
public class Widget
{
// configure fields
[Id]
public string Id { get; set; }
public string Name { get; set; }

// configure federation resolver
[FederationResolver]
public async Task<Widget> ResolveReference([FromServices] WidgetRepository widgetData, CancellationToken token)
{
// pull the id from the representation
var id = Id;

// resolve the entity reference
return await widgetData.GetWidgetByIdAsync(id, token);
}
}
```

Note that you may apply the `[Key]` attribute multiple times to define multiple sets of key fields, pursuant to the
GraphQL Federation specification. You may define multiple resolvers when using static methods in a type-first schema.
Otherwise your method will need to decide which set of key fields to use for resolution, as demonstrated in the
code-first sample below:

```cs
public class WidgetType : ObjectGraphType<Widget>
{
public WidgetType()
{
// configure federation key fields
this.Key("id");
this.Key("sku");

// configure federation resolver
this.ResolveReference(async (context, widget) =>
{
// pull the key values from the representation
var id = widget.Id;
var sku = widget.Sku;

// resolve the entity reference
var widgetData = context.RequestServices!.GetRequiredService<WidgetRepository>();
if (id != null)
return await widgetData.GetWidgetByIdAsync(id, context.CancellationToken);
else
return await widgetData.GetWidgetBySkuAsync(sku, context.CancellationToken);
});

// configure fields
Field(x => x.Id, type: typeof(NonNullGraphType<IdGraphType>));
Field(x => x.Sku);
Field(x => x.Name);
}
}

public class Widget
{
public string Id { get; set; }
public string Sku { get; set; }
public string Name { get; set; }
}
```

## Breaking Changes

### 1. Query type is required
Expand Down
43 changes: 35 additions & 8 deletions src/GraphQL.ApiTests/net50/GraphQL.approved.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1528,9 +1528,12 @@ namespace GraphQL.Federation
public static class FederationGraphTypeExtensions
{
public static void ResolveReference<TSourceType>(this GraphQL.Types.ObjectGraphType<TSourceType> graphType, GraphQL.Federation.Resolvers.IFederationResolver resolver) { }
public static void ResolveReference<TSourceType, TReturnType>(this GraphQL.Types.ObjectGraphType<TSourceType> graphType, System.Func<GraphQL.IResolveFieldContext, TSourceType, GraphQL.DataLoader.IDataLoaderResult<TReturnType?>> resolver) { }
public static void ResolveReference<TSourceType, TReturnType>(this GraphQL.Types.ObjectGraphType<TSourceType> graphType, System.Func<GraphQL.IResolveFieldContext, TSourceType, System.Threading.Tasks.Task<TReturnType?>> resolver) { }
public static void ResolveReference<TSourceType, TReturnType>(this GraphQL.Types.ObjectGraphType<TSourceType> graphType, System.Func<GraphQL.IResolveFieldContext, TSourceType, TReturnType?> resolver) { }
public static void ResolveReference<TSourceType>(this GraphQL.Types.ObjectGraphType<TSourceType> graphType, System.Func<GraphQL.IResolveFieldContext, TSourceType, GraphQL.DataLoader.IDataLoaderResult<TSourceType?>> resolver) { }
public static void ResolveReference<TSourceType>(this GraphQL.Types.ObjectGraphType<TSourceType> graphType, System.Func<GraphQL.IResolveFieldContext, TSourceType, System.Threading.Tasks.Task<TSourceType?>> resolver) { }
public static void ResolveReference<TSourceType>(this GraphQL.Types.ObjectGraphType<TSourceType> graphType, System.Func<GraphQL.IResolveFieldContext, TSourceType, TSourceType?> resolver) { }
public static void ResolveReference<TSourceType, TReturnType>(this GraphQL.Types.ObjectGraphType<TReturnType> graphType, System.Func<GraphQL.IResolveFieldContext, TSourceType, GraphQL.DataLoader.IDataLoaderResult<TReturnType?>> resolver) { }
public static void ResolveReference<TSourceType, TReturnType>(this GraphQL.Types.ObjectGraphType<TReturnType> graphType, System.Func<GraphQL.IResolveFieldContext, TSourceType, System.Threading.Tasks.Task<TReturnType?>> resolver) { }
public static void ResolveReference<TSourceType, TReturnType>(this GraphQL.Types.ObjectGraphType<TReturnType> graphType, System.Func<GraphQL.IResolveFieldContext, TSourceType, TReturnType?> resolver) { }
}
public static class FederationInterfaceMetadataExtensions
{
Expand All @@ -1555,6 +1558,14 @@ namespace GraphQL.Federation
public static TMetadataWriter Shareable<TMetadataWriter>(this TMetadataWriter graphType)
where TMetadataWriter : GraphQL.Types.IMetadataWriter, GraphQL.Types.IObjectGraphType { }
}
[System.AttributeUsage(System.AttributeTargets.Method)]
public class FederationResolverAttribute : GraphQL.GraphQLAttribute
{
public FederationResolverAttribute() { }
public override void Modify(GraphQL.Types.TypeInformation typeInformation) { }
public override void Modify(GraphQL.Types.IGraphType graphType, System.Reflection.MemberInfo memberInfo, GraphQL.Types.FieldType fieldType, bool isInputType, ref bool ignore) { }
public override bool ShouldInclude(System.Reflection.MemberInfo memberInfo, bool? isInputType) { }
}
[System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Enum | System.AttributeTargets.Method | System.AttributeTargets.Property | System.AttributeTargets.Field | System.AttributeTargets.Interface | System.AttributeTargets.Parameter)]
public class InaccessibleAttribute : GraphQL.GraphQLAttribute
{
Expand Down Expand Up @@ -1627,24 +1638,40 @@ namespace GraphQL.Federation.Resolvers
public System.Collections.Generic.IEnumerable<GraphQL.Federation.Resolvers.Representation> ConvertRepresentations(GraphQL.Types.ISchema schema, System.Collections.IList representations) { }
public System.Threading.Tasks.ValueTask<object?> ResolveAsync(GraphQL.IResolveFieldContext context) { }
}
public abstract class FederationResolverBase : GraphQL.Federation.Resolvers.IFederationResolver
{
protected FederationResolverBase() { }
public abstract System.Type SourceType { get; }
public virtual bool MatchKeys(System.Collections.Generic.IDictionary<string, object?> representation) { }
public object ParseRepresentation(GraphQL.Types.IObjectGraphType graphType, System.Collections.Generic.IDictionary<string, object?> representation) { }
public abstract System.Threading.Tasks.ValueTask<object?> ResolveAsync(GraphQL.IResolveFieldContext context, GraphQL.Types.IObjectGraphType graphType, object parsedRepresentation);
}
public abstract class FederationResolverBase<TParsedType> : GraphQL.Federation.Resolvers.FederationResolverBase
{
protected FederationResolverBase() { }
public override System.Type SourceType { get; }
public override System.Threading.Tasks.ValueTask<object?> ResolveAsync(GraphQL.IResolveFieldContext context, GraphQL.Types.IObjectGraphType graphType, object parsedRepresentation) { }
public abstract System.Threading.Tasks.ValueTask<object?> ResolveAsync(GraphQL.IResolveFieldContext context, GraphQL.Types.IObjectGraphType graphType, TParsedType parsedRepresentation);
}
public class FederationResolver<TClrType> : GraphQL.Federation.Resolvers.FederationResolver<TClrType, TClrType>
{
public FederationResolver(System.Func<GraphQL.IResolveFieldContext, TClrType, GraphQL.DataLoader.IDataLoaderResult<TClrType?>> resolveFunc) { }
public FederationResolver(System.Func<GraphQL.IResolveFieldContext, TClrType, System.Threading.Tasks.Task<TClrType?>> resolveFunc) { }
public FederationResolver(System.Func<GraphQL.IResolveFieldContext, TClrType, TClrType?> resolveFunc) { }
}
public class FederationResolver<TSourceType, TReturnType> : GraphQL.Federation.Resolvers.IFederationResolver
public class FederationResolver<TSourceType, TReturnType> : GraphQL.Federation.Resolvers.FederationResolverBase<TSourceType>
{
public FederationResolver(System.Func<GraphQL.IResolveFieldContext, TSourceType, GraphQL.DataLoader.IDataLoaderResult<TReturnType?>> resolveFunc) { }
public FederationResolver(System.Func<GraphQL.IResolveFieldContext, TSourceType, System.Threading.Tasks.Task<TReturnType?>> resolveFunc) { }
public FederationResolver(System.Func<GraphQL.IResolveFieldContext, TSourceType, TReturnType?> resolveFunc) { }
public System.Type SourceType { get; }
public System.Threading.Tasks.ValueTask<object?> ResolveAsync(GraphQL.IResolveFieldContext context, object source) { }
public override System.Type SourceType { get; }
public override System.Threading.Tasks.ValueTask<object?> ResolveAsync(GraphQL.IResolveFieldContext context, GraphQL.Types.IObjectGraphType graphType, TSourceType source) { }
}
public interface IFederationResolver
{
System.Type SourceType { get; }
System.Threading.Tasks.ValueTask<object?> ResolveAsync(GraphQL.IResolveFieldContext context, object source);
bool MatchKeys(System.Collections.Generic.IDictionary<string, object?> representation);
object ParseRepresentation(GraphQL.Types.IObjectGraphType graphType, System.Collections.Generic.IDictionary<string, object?> representation);
System.Threading.Tasks.ValueTask<object?> ResolveAsync(GraphQL.IResolveFieldContext context, GraphQL.Types.IObjectGraphType graphType, object parsedRepresentation);
}
public class Representation : System.IEquatable<GraphQL.Federation.Resolvers.Representation>
{
Expand Down
Loading

0 comments on commit 8c612dd

Please sign in to comment.