Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/All.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
</Folder>
<Folder Name="/HotChocolate/ApolloFederation/test/">
<Project Path="HotChocolate/ApolloFederation/test/ApolloFederation.Tests/HotChocolate.ApolloFederation.Tests.csproj" />
<Project Path="HotChocolate/ApolloFederation/test/ApolloFederation.ImplementationFirst/HotChocolate.ApolloFederation.ImplementationFirst.Tests.csproj" />
</Folder>
<Folder Name="/HotChocolate/AspNetCore/" />
<Folder Name="/HotChocolate/AspNetCore/benchmarks/" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
</Folder>
<Folder Name="/test/">
<Project Path="test/ApolloFederation.Tests/HotChocolate.ApolloFederation.Tests.csproj" />
<Project Path="test/ApolloFederation.ImplementationFirst/HotChocolate.ApolloFederation.ImplementationFirst.Tests.csproj" />
</Folder>
</Solution>
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,24 @@ public override IEnumerable<TypeReference> RegisterMoreTypes(
yield return _typeInspector.GetTypeRef(typeof(LinkDirective));
}

if (discoveryContexts.Any(t => t.Type is PageInfoType)
// The page info fields are made shareable in OnAfterCompleteName for any type
// named "PageInfo". That match is by name, so it also covers the page info type
// that is inferred from PageConnection<T> (runtime type PageInfo) and not just the
// built-in PageInfoType. We therefore have to register the shareable directive for
// any discovered page info object type, otherwise the directive cannot be resolved
// when the fields are completed.
if (discoveryContexts.Any(t => IsPageInfoType(t.Type))
&& discoveryContexts.All(t => t.Type is not DirectiveType<ShareableDirective>))
{
yield return _typeInspector.GetTypeRef(typeof(ShareableDirective));
}
}

private static bool IsPageInfoType(TypeSystemObject type)
=> type is PageInfoType
|| (type is IRuntimeTypeProvider { RuntimeType: { } runtimeType }
&& typeof(IPageInfo).IsAssignableFrom(runtimeType));

public override void OnBeforeCompleteName(
ITypeCompletionContext completionContext,
TypeSystemConfiguration configuration)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace HotChocolate.ApolloFederation;

public sealed class Employee
{
public int EmpId { get; set; }

public string Name { get; set; } = default!;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using GreenDonut.Data;
using HotChocolate.Types;
using HotChocolate.Types.Pagination;

namespace HotChocolate.ApolloFederation;

[QueryType]
public static partial class EmployeeQuery
{
// Reproduces https://github.com/ChilliCream/graphql-platform/issues/9798:
// a source-generated connection field returning PageConnection<T> combined
// with Apollo Federation must not crash schema creation.
[UseConnection]
public static PageConnection<Employee> GetEmployeeByPageTest(PagingArguments args)
=> new(Page<Employee>.Empty);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<AssemblyName>HotChocolate.ApolloFederation.ImplementationFirst.Tests</AssemblyName>
<RootNamespace>HotChocolate.ApolloFederation</RootNamespace>
<InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);HotChocolate.Execution.Generated</InterceptorsPreviewNamespaces>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>.generated</CompilerGeneratedFilesOutputPath>
<!--Required since otherwise the XML docs won't be inferred by the source generator.-->
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\ApolloFederation\HotChocolate.ApolloFederation.csproj" />
<ProjectReference Include="..\..\..\Core\src\Types.Analyzers\HotChocolate.Types.Analyzers.csproj" OutputItemType="Analyzer" />
<ProjectReference Include="..\..\..\Data\src\Data\HotChocolate.Data.csproj" />
<ProjectReference Include="..\..\..\..\GreenDonut\src\GreenDonut.Data\GreenDonut.Data.csproj" />
</ItemGroup>

<ItemGroup>
<Compile Remove="$(CompilerGeneratedFilesOutputPath)/**/*.cs" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using HotChocolate;

[assembly: Module("ImplementationFirstTypes")]
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using HotChocolate.Execution;
using Microsoft.Extensions.DependencyInjection;

namespace HotChocolate.ApolloFederation;

// Counterpart to PageConnectionFederationTests: the composite schema path also makes
// connections and page info shareable (AddSourceSchemaDefaults turns on
// ApplyShareableToConnections, ApplyShareableToPageInfo and ApplyShareableToNodeFields).
// Unlike the Apollo Federation interceptor, every composite site registers the Shareable
// directive type before applying it, so building the schema must not crash.
public class PageConnectionCompositeSchemaTests
{
[Fact]
public async Task PageConnection_Field_With_CompositeSourceSchema_Builds_Schema()
{
// arrange & act
var schema = await new ServiceCollection()
.AddGraphQL()
.AddSourceSchemaDefaults()
.AddImplementationFirstTypes()
.AddPagingArguments()
.BuildSchemaAsync();

// assert
schema.MatchSnapshot();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using HotChocolate.Execution;
using Microsoft.Extensions.DependencyInjection;

namespace HotChocolate.ApolloFederation;

public class PageConnectionFederationTests
{
[Fact]
public async Task PageConnection_Field_With_ApolloFederation_Builds_Schema()
{
// arrange & act
var schema = await new ServiceCollection()
.AddGraphQL()
.AddApolloFederation()
.AddImplementationFirstTypes()
.AddPagingArguments()
.BuildSchemaAsync();

// assert
schema.MatchSnapshot();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
schema {
query: Query
}

type Query {
employeeByPageTest(
"Returns the first _n_ elements from the list."
first: Int
"Returns the elements in the list that come after the specified cursor."
after: String
"Returns the last _n_ elements from the list."
last: Int
"Returns the elements in the list that come before the specified cursor."
before: String
): EmployeeConnection!
}

type Employee {
empId: Int!
name: String!
}

"A connection to a list of items."
type EmployeeConnection @shareable {
"A list of edges."
edges: [EmployeeEdge!]
"A flattened list of the nodes"
nodes: [Employee!]
"Information to aid in pagination."
pageInfo: PageInfo!
"Identifies the total count of items in the connection."
totalCount: Int!
}

type EmployeeEdge @shareable {
"The item at the end of the edge."
node: Employee!
"A cursor for use in pagination."
cursor: String!
}

"A cursor that points to a specific page."
type PageCursor @shareable {
"The page number."
page: Int!
"The cursor."
cursor: String!
}

"Information about pagination in a connection."
type PageInfo @shareable {
"Indicates whether more edges exist following the set defined by the clients arguments."
hasNextPage: Boolean!
"Indicates whether more edges exist prior the set defined by the clients arguments."
hasPreviousPage: Boolean!
"When paginating backwards, the cursor to continue."
startCursor: String
"When paginating forwards, the cursor to continue."
endCursor: String
"A list of cursors to continue paginating forwards."
forwardCursors: [PageCursor!]!
"A list of cursors to continue paginating backwards."
backwardCursors: [PageCursor!]!
}

"""
By default, only a single source schema is allowed to contribute
a particular field to an object type.


This prevents source schemas from inadvertently defining similarly named
fields that are not semantically equivalent.


Fields must be explicitly marked as @shareable to allow multiple source
schemas to define them, ensuring that the decision to serve a field from
more than one source schema is intentional and coordinated.


directive @shareable repeatable on OBJECT | FIELD_DEFINITION
"""
directive @shareable repeatable on OBJECT | FIELD_DEFINITION
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
schema
@link(
url: "https://specs.apollo.dev/federation/v2.6"
import: ["@shareable", "@tag", "FieldSet"]
) {
query: Query
}

type Query {
employeeByPageTest(
"Returns the first _n_ elements from the list."
first: Int
"Returns the elements in the list that come after the specified cursor."
after: String
"Returns the last _n_ elements from the list."
last: Int
"Returns the elements in the list that come before the specified cursor."
before: String
): EmployeeConnection!
_service: _Service!
}

"This type provides a field named sdl: String! which exposes the SDL of the service's schema. This SDL (schema definition language) is a printed version of the service's schema including the annotations of federation directives. This SDL does not include the additions of the federation spec."
type _Service {
sdl: String!
}

type Employee {
empId: Int!
name: String!
}

"A connection to a list of items."
type EmployeeConnection {
"A list of edges."
edges: [EmployeeEdge!]
"A flattened list of the nodes"
nodes: [Employee!]
"Information to aid in pagination."
pageInfo: PageInfo!
"Identifies the total count of items in the connection."
totalCount: Int!
}

type EmployeeEdge {
"The item at the end of the edge."
node: Employee!
"A cursor for use in pagination."
cursor: String!
}

"A cursor that points to a specific page."
type PageCursor {
"The page number."
page: Int!
"The cursor."
cursor: String!
}

"Information about pagination in a connection."
type PageInfo {
"Indicates whether more edges exist following the set defined by the clients arguments."
hasNextPage: Boolean! @shareable
"Indicates whether more edges exist prior the set defined by the clients arguments."
hasPreviousPage: Boolean! @shareable
"When paginating backwards, the cursor to continue."
startCursor: String @shareable
"When paginating forwards, the cursor to continue."
endCursor: String @shareable
"A list of cursors to continue paginating forwards."
forwardCursors: [PageCursor!]! @shareable
"A list of cursors to continue paginating backwards."
backwardCursors: [PageCursor!]! @shareable
}

"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

"Scalar representing a set of fields."
scalar FieldSet

"Links definitions within the document to external schemas."
directive @link(
"Gets imported specification url."
url: String!
"Gets optional list of imported element names."
import: [String!]
) repeatable on SCHEMA

"Indicates that given object and/or field can be resolved by multiple subgraphs."
directive @shareable repeatable on OBJECT | FIELD_DEFINITION
Loading