Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrated Apollo Federation Support in Build #4650

Merged
merged 5 commits into from Jan 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .build/Build.PublicApiAnalyzer.cs
Expand Up @@ -7,11 +7,11 @@
using System.Threading.Tasks;
using Nuke.Common;
using Nuke.Common.IO;
using Nuke.Common.ProjectModel;
using Nuke.Common.Tools.DotNet;
using static Nuke.Common.Tools.DotNet.DotNetTasks;
using static Nuke.Common.Tools.Git.GitTasks;
using static Helpers;
using Nuke.Common.ProjectModel;

partial class Build
{
Expand Down
2 changes: 1 addition & 1 deletion .build/Build.Sonar.cs
Expand Up @@ -2,10 +2,10 @@
using Nuke.Common.Tooling;
using Nuke.Common.Tools.DotNet;
using Nuke.Common.Tools.SonarScanner;
using static System.IO.Path;
using static Nuke.Common.Tools.DotNet.DotNetTasks;
using static Nuke.Common.Tools.SonarScanner.SonarScannerTasks;
using static Helpers;
using static System.IO.Path;

partial class Build
{
Expand Down
4 changes: 2 additions & 2 deletions .build/Build.Tests.cs
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
Expand All @@ -17,8 +19,6 @@
using static Nuke.Common.Tools.ReportGenerator.ReportGeneratorTasks;
using static Nuke.Common.Tools.Codecov.CodecovTasks;
using static Helpers;
using System;
using System.Diagnostics;

partial class Build
{
Expand Down
1 change: 1 addition & 0 deletions .build/Helpers.cs
Expand Up @@ -12,6 +12,7 @@ class Helpers
{
"GreenDonut",
Path.Combine("HotChocolate", "Analyzers"),
Path.Combine("HotChocolate", "ApolloFederation"),
Path.Combine("HotChocolate", "AspNetCore"),
Path.Combine("HotChocolate", "AzureFunctions"),
Path.Combine("HotChocolate", "Core"),
Expand Down
Expand Up @@ -69,7 +69,7 @@ protected override Representation ParseLiteral(ObjectValueNode valueSyntax)
{
return new Representation()
{
Typename = s.Value,
TypeName = s.Value,
Data = valueSyntax
};
}
Expand Down Expand Up @@ -123,7 +123,7 @@ public override bool TryDeserialize(object? resultValue, out object? runtimeValu
runtimeValue = new Representation
{
Data = ovn,
Typename = svn.Value
TypeName = svn.Value
};
return true;
}
Expand Down
Expand Up @@ -12,41 +12,44 @@ internal static class EntitiesResolver
{
public static async Task<List<object?>> _Entities(
ISchema schema,
IReadOnlyList<Representation> representations, IResolverContext c)
IReadOnlyList<Representation> representations,
IResolverContext context)
{
var ret = new List<object?>();
foreach (Representation? representation in representations)
var entities = new List<object?>();

foreach (Representation representation in representations)
{
INamedType? representationType = schema.Types
.SingleOrDefault(type =>
type.Name == representation.Typename &&
type.ContextData.ContainsKey(ExtendMarker));
if (schema.TryGetType<INamedType>(representation.TypeName, out var entityType) &&
!entityType.ContextData.ContainsKey(ExtendMarker))
{
entityType = null;
}

if (representationType != null)
if (entityType != null)
{
if (representationType.ContextData.TryGetValue(EntityResolver, out var obj) &&
obj is Func<object, object?> d)
if (entityType.ContextData.TryGetValue(EntityResolver, out var value) &&
value is Func<object, object?> d)
{
ret.Add(d(representation));
entities.Add(d(representation));
}
else
{
throw ThrowHelper.EntityResolver_NoResolverFound();
}
}
else if (schema.TryGetType<ObjectType>(representation.Typename, out ObjectType? type) &&
type.ContextData.TryGetValue(EntityResolver, out object? o) &&
o is FieldResolverDelegate resolver)
else if (schema.TryGetType<ObjectType>(representation.TypeName, out var objectType) &&
objectType.ContextData.TryGetValue(EntityResolver, out var value) &&
value is FieldResolverDelegate resolver)
{
c.SetLocalValue("data", representation.Data);
ret.Add(await resolver.Invoke(c).ConfigureAwait(false));
context.SetLocalValue("data", representation.Data);
entities.Add(await resolver.Invoke(context).ConfigureAwait(false));
}
else
{
throw ThrowHelper.EntityResolver_NoResolverFound();
}
}

return ret;
return entities;
}
}
Expand Up @@ -4,7 +4,7 @@ namespace HotChocolate.ApolloFederation;

public class Representation
{
public NameString Typename { get; set; }
public NameString TypeName { get; set; }

public ObjectValueNode Data { get; set; } = default!;
}
Expand Up @@ -35,7 +35,7 @@ public void Deserialize()
Assert.IsType<Representation>(representationObject);
if (representationObject is Representation representation)
{
Assert.Equal("test", representation.Typename);
Assert.Equal("test", representation.TypeName);
Assert.Collection(representation.Data.Fields,
node =>
{
Expand Down Expand Up @@ -149,7 +149,7 @@ public void Serialize()
);
var representation = new Representation
{
Typename = "test",
TypeName = "test",
Data = objectValueNode
};

Expand Down Expand Up @@ -190,7 +190,7 @@ public void TrySerialize()
);
var representation = new Representation
{
Typename = "test",
TypeName = "test",
Data = objectValueNode
};

Expand Down Expand Up @@ -247,7 +247,7 @@ public void ParseValue()
);
var representation = new Representation
{
Typename = "test",
TypeName = "test",
Data = objectValueNode
};

Expand Down Expand Up @@ -283,7 +283,7 @@ public void ParseLiteral()
Representation? parsedRepresentation = Assert.IsType<Representation>(valueSyntax);
Assert.Equal(
"test",
parsedRepresentation.Typename);
parsedRepresentation.TypeName);
Assert.Equal(
objectValueNode,
parsedRepresentation.Data);
Expand Down Expand Up @@ -319,7 +319,7 @@ public void ParseResult()
);
var representation = new Representation
{
Typename = "test",
TypeName = "test",
Data = objectValueNode
};

Expand Down
Expand Up @@ -104,19 +104,10 @@ public void AnnotateProvidesToFieldCodeFirst()
.Argument("a", a => a.Type<IntType>())
.Type("Review");
}))
.AddQueryType(
new ObjectType(
o =>
{
o.Name("Query");
o.Field("someField")
.Argument("a", a => a.Type<IntType>())
.Type("Review");
}))
.AddType<FieldSetType>()
.AddDirectiveType<KeyDirectiveType>()
.AddDirectiveType<ProvidesDirectiveType>()
.Use(next => context => default)
.Use(_ => _ => default)
.Create();

// act
Expand Down Expand Up @@ -176,7 +167,7 @@ public void AnnotateProvidesToClassAttributePureCodeFirst()

public class Query
{
public Review someField(int id) => default!;
public Review SomeField(int id) => default!;
}

public class Review
Expand Down
Expand Up @@ -24,7 +24,7 @@ public void AddRequiresDirective_EnsureAvailableInSchema()
Assert.IsType<RequiresDirectiveType>(directive);
Assert.Equal(WellKnownTypeNames.Requires, directive!.Name);
Assert.Single(directive.Arguments);
this.AssertDirectiveHasFieldsArgument(directive);
AssertDirectiveHasFieldsArgument(directive);
Assert.Collection(
directive.Locations,
t => Assert.Equal(DirectiveLocation.FieldDefinition, t));
Expand Down Expand Up @@ -54,7 +54,7 @@ public void AnnotateProvidesToFieldSchemaFirst()
.AddDirectiveType<KeyDirectiveType>()
.AddDirectiveType<RequiresDirectiveType>()
.AddType<FieldSetType>()
.Use(next => context => default)
.Use(_ => _ => default)
.Create();

// act
Expand Down Expand Up @@ -107,12 +107,12 @@ public void AnnotateProvidesToFieldCodeFirst()
o.Field("someField").Argument("a", a => a.Type<IntType>()).Type(reviewType);
});

ISchema? schema = SchemaBuilder.New()
ISchema schema = SchemaBuilder.New()
.AddQueryType(queryType)
.AddType<FieldSetType>()
.AddDirectiveType<KeyDirectiveType>()
.AddDirectiveType<RequiresDirectiveType>()
.Use(next => context => default)
.Use(_ => _ => default)
.Create();

// act
Expand Down Expand Up @@ -164,7 +164,7 @@ public void AnnotateProvidesToClassAttributePureCodeFirst()

public class Query
{
public Review someField(int id) => default!;
public Review SomeField(int id) => default!;
}

public class Review
Expand Down
Expand Up @@ -9,17 +9,8 @@ type Query {
"The `@defer` directive may be provided for fragment spreads and inline fragments to inform the executor to delay the execution of the current fragment to indicate deprioritization of the current fragment. A query with `@defer` directive will cause the request to potentially return multiple responses, where non-deferred data is delivered in the initial response and data deferred is delivered in a subsequent response. `@include` and `@skip` take precedence over `@defer`."
directive @defer("If this argument label has a value other than null, it will be passed on to the result of this defer directive. This label is intended to give client applications a way to identify to which fragment a deferred result belongs to." label: String "Deferred when true." if: Boolean) on FRAGMENT_SPREAD | INLINE_FRAGMENT

"The @deprecated directive is used within the type system definition language to indicate deprecated portions of a GraphQL service’s schema,such as deprecated fields on a type or deprecated enum values."
directive @deprecated("Deprecations include a reason for why it is deprecated, which is formatted using Markdown syntax (as specified by CommonMark)." reason: String = "No longer supported") on FIELD_DEFINITION | ENUM_VALUE

"Directive to indicate that a field is owned by another service, for example via Apollo federation."
directive @external on FIELD_DEFINITION

"Directs the executor to include this field or fragment only when the `if` argument is true."
directive @include("Included when true." if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

"Directs the executor to skip this field or fragment when the `if` argument is true."
directive @skip("Skipped when true." if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

"The `@stream` directive may be provided for a field of `List` type so that the backend can leverage technology such as asynchronous iterators to provide a partial list in the initial response, and additional list items in subsequent responses. `@include` and `@skip` take precedence over `@stream`."
directive @stream("If this argument label has a value other than null, it will be passed on to the result of this stream directive. This label is intended to give client applications a way to identify to which fragment a streamed result belongs to." label: String "The initial elements that shall be send down to the consumer." initialCount: Int! "Streamed when true." if: Boolean!) on FIELD
directive @stream("If this argument label has a value other than null, it will be passed on to the result of this stream directive. This label is intended to give client applications a way to identify to which fragment a streamed result belongs to." label: String "The initial elements that shall be send down to the consumer." initialCount: Int! = 0 "Streamed when true." if: Boolean) on FIELD
Expand Up @@ -24,15 +24,9 @@ union _Entity = User
"The `@defer` directive may be provided for fragment spreads and inline fragments to inform the executor to delay the execution of the current fragment to indicate deprioritization of the current fragment. A query with `@defer` directive will cause the request to potentially return multiple responses, where non-deferred data is delivered in the initial response and data deferred is delivered in a subsequent response. `@include` and `@skip` take precedence over `@defer`."
directive @defer("If this argument label has a value other than null, it will be passed on to the result of this defer directive. This label is intended to give client applications a way to identify to which fragment a deferred result belongs to." label: String "Deferred when true." if: Boolean) on FRAGMENT_SPREAD | INLINE_FRAGMENT

"The @deprecated directive is used within the type system definition language to indicate deprecated portions of a GraphQL service’s schema,such as deprecated fields on a type or deprecated enum values."
directive @deprecated("Deprecations include a reason for why it is deprecated, which is formatted using Markdown syntax (as specified by CommonMark)." reason: String = "No longer supported") on FIELD_DEFINITION | ENUM_VALUE

"Directive to indicate that a field is owned by another service, for example via Apollo federation."
directive @external on FIELD_DEFINITION

"Directs the executor to include this field or fragment only when the `if` argument is true."
directive @include("Included when true." if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

"Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface."
directive @key(fields: _FieldSet!) on OBJECT | INTERFACE

Expand All @@ -42,11 +36,8 @@ directive @provides(fields: _FieldSet!) on FIELD_DEFINITION
"Used to annotate the required input fieldset from a base type for a resolver."
directive @requires(fields: _FieldSet!) on FIELD_DEFINITION

"Directs the executor to skip this field or fragment when the `if` argument is true."
directive @skip("Skipped when true." if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

"The `@stream` directive may be provided for a field of `List` type so that the backend can leverage technology such as asynchronous iterators to provide a partial list in the initial response, and additional list items in subsequent responses. `@include` and `@skip` take precedence over `@stream`."
directive @stream("If this argument label has a value other than null, it will be passed on to the result of this stream directive. This label is intended to give client applications a way to identify to which fragment a streamed result belongs to." label: String "The initial elements that shall be send down to the consumer." initialCount: Int! "Streamed when true." if: Boolean!) on FIELD
directive @stream("If this argument label has a value other than null, it will be passed on to the result of this stream directive. This label is intended to give client applications a way to identify to which fragment a streamed result belongs to." label: String "The initial elements that shall be send down to the consumer." initialCount: Int! = 0 "Streamed when true." if: Boolean) on FIELD

"The _Any scalar is used to pass representations of entities from external services into the root _entities field for execution. Validation of the _Any scalar is done by matching the __typename and @external fields defined in the schema."
scalar _Any
Expand Down
Expand Up @@ -9,17 +9,8 @@ type Query {
"The `@defer` directive may be provided for fragment spreads and inline fragments to inform the executor to delay the execution of the current fragment to indicate deprioritization of the current fragment. A query with `@defer` directive will cause the request to potentially return multiple responses, where non-deferred data is delivered in the initial response and data deferred is delivered in a subsequent response. `@include` and `@skip` take precedence over `@defer`."
directive @defer("If this argument label has a value other than null, it will be passed on to the result of this defer directive. This label is intended to give client applications a way to identify to which fragment a deferred result belongs to." label: String "Deferred when true." if: Boolean) on FRAGMENT_SPREAD | INLINE_FRAGMENT

"The @deprecated directive is used within the type system definition language to indicate deprecated portions of a GraphQL service’s schema,such as deprecated fields on a type or deprecated enum values."
directive @deprecated("Deprecations include a reason for why it is deprecated, which is formatted using Markdown syntax (as specified by CommonMark)." reason: String = "No longer supported") on FIELD_DEFINITION | ENUM_VALUE

"Directive to indicate that a field is owned by another service, for example via Apollo federation."
directive @external on FIELD_DEFINITION

"Directs the executor to include this field or fragment only when the `if` argument is true."
directive @include("Included when true." if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

"Directs the executor to skip this field or fragment when the `if` argument is true."
directive @skip("Skipped when true." if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

"The `@stream` directive may be provided for a field of `List` type so that the backend can leverage technology such as asynchronous iterators to provide a partial list in the initial response, and additional list items in subsequent responses. `@include` and `@skip` take precedence over `@stream`."
directive @stream("If this argument label has a value other than null, it will be passed on to the result of this stream directive. This label is intended to give client applications a way to identify to which fragment a streamed result belongs to." label: String "The initial elements that shall be send down to the consumer." initialCount: Int! "Streamed when true." if: Boolean!) on FIELD
directive @stream("If this argument label has a value other than null, it will be passed on to the result of this stream directive. This label is intended to give client applications a way to identify to which fragment a streamed result belongs to." label: String "The initial elements that shall be send down to the consumer." initialCount: Int! = 0 "Streamed when true." if: Boolean) on FIELD