From fae7aa7092b61fa0151bf4dc6dea241b12798c8c Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Thu, 1 Dec 2022 22:09:19 +0100 Subject: [PATCH] Migrated Legacy Stitching to Version 13 (#5537) --- ...hocolateAuthorizeRequestExecutorBuilder.cs | 4 - .../src/Benchmarks.Execution/Program.cs | 2 - .../DefaultExecutionPipelineBenchmark.cs | 2 +- .../Core/src/Abstractions/NameString.cs.txt | 203 -- .../ExecutionRequestExecutorExtensions.cs | 2 +- .../src/Execution/Processing/Selection.cs | 9 +- .../Properties/InternalsVisibleTo.cs | 1 + .../DataLoaderParameterExpressionBuilder.cs | 3 +- .../DataLoaderResolverContextExtensions.cs | 23 +- .../ObjectFieldDataLoaderExtensions.cs | 17 +- .../src/Types/Resolvers/IResolverContext.cs | 2 +- .../Core/src/Types/Types/FieldCollection.cs | 4 +- ...ate_ShortNullableIn_Expression_NET7_0.snap | 2 +- ...ortNullableNotEqual_Expression_NET7_0.snap | 4 +- ..._ShortNullableNotIn_Expression_NET7_0.snap | 6 +- ...eate_NullableEnumIn_Expression_NET7_0.snap | 2 +- ...ullableEnumNotEqual_Expression_NET7_0.snap | 4 +- ...e_NullableEnumNotIn_Expression_NET7_0.snap | 6 +- ...ngEqual_Expression_CustomAllow_NET7_0.snap | 6 +- ...llObjectStringEqual_Expression_NET7_0.snap | 6 +- ...neObjectStringEqual_Expression_NET7_0.snap | 2 +- ...StringEqualWithNull_Expression_NET7_0.snap | 2 +- ...bjectNullableEnumIn_Expression_NET7_0.snap | 2 +- ...jectNullableShortIn_Expression_NET7_0.snap | 2 +- ...lableStringContains_Expression_NET7_0.snap | 4 +- ...lableStringEndsWith_Expression_NET7_0.snap | 4 +- ...te_NullableStringIn_Expression_NET7_0.snap | 2 +- ...bleStringNoContains_Expression_NET7_0.snap | 4 +- ...leStringNotEndsWith_Expression_NET7_0.snap | 4 +- ...lableStringNotEqual_Expression_NET7_0.snap | 4 +- ...NullableStringNotIn_Expression_NET7_0.snap | 6 +- ...StringNotStartsWith_Expression_NET7_0.snap | 4 +- ...bleStringStartsWith_Expression_NET7_0.snap | 4 +- ...ts.Integration_Create_Boolean_OrderBy.snap | 4 +- ...ration_Create_Boolean_OrderBy_NonNull.snap | 4 +- .../src/Stitching.Types/BindingList.cs | 18 +- .../Bindings/DependenciesBinding.cs | 0 .../Stitching.Types/Bindings/FetchBinding.cs | 0 .../Stitching.Types/Bindings/SourceBinding.cs | 0 .../DefaultObjectTypeDefinitionMerger.cs | 4 +- .../HotChocolate.Stitching.Types.csproj | 0 .../src/Stitching.Types/IBinding.cs | 0 .../src/Stitching.Types/ITypeDefinition.cs | 0 .../Stitching.Types/ITypeDefinitionMerger.cs | 0 .../Stitching.Types/ObjectTypeDefinition.cs | 4 +- .../ApplyComplexTypeExtension.cs | 22 +- .../ApplyExtensions/ApplyExtension.cs | 0 .../ApplyExtensionsMiddleware.cs | 16 +- .../ApplyInterfaceTypeExtension.cs | 0 .../ApplyObjectTypeExtension.cs | 0 .../ApplyExtensions/IApplyExtension.cs | 0 .../ApplyMissingBindingsMiddleware.cs | 4 +- .../ApplyMissingBindings/BindingContext.cs | 0 .../ApplyMissingBindings/BindingRewriter.cs | 0 .../ApplyRenaming/ApplyRenamingMiddleware.cs | 2 +- .../Pipeline/ApplyRenaming/RenameContext.cs | 0 .../Pipeline/ApplyRenaming/RenameIndexer.cs | 6 +- .../Pipeline/ApplyRenaming/RenameInfo.cs | 0 .../Pipeline/ApplyRenaming/RenameRewriter.cs | 22 +- .../Stitching.Types/Pipeline/BindDirective.cs | 4 +- .../Pipeline/ISchemaMergeContext.cs | 0 .../Stitching.Types/Pipeline/MergeSchema.cs | 0 .../Pipeline/MergeSchemaMiddleware.cs | 0 .../PrepareDocumentsMiddleware.cs | 6 +- .../SquashDocumentsMiddleware.cs | 2 +- .../Pipeline/RenameDirective.cs | 4 +- .../Pipeline/SchemaMergeContext.cs | 0 .../Pipeline/SchemaMergePipelineBuilder.cs | 0 .../Pipeline/ServiceConfiguration.cs | 0 .../src/Stitching.Types/ThrowHelper.cs | 0 .../Stitching.Types/TypeDefinitionMerger.cs | 0 .../DefaultObjectTypeDefinitionMergerTests.cs | 11 +- .../DefaultObjectTypeDefinitionTests.cs | 2 +- .../HotChocolate.Stitching.Types.Tests.csproj | 0 .../ApplyExtensionsMiddlewareTests.cs | 32 +- .../ApplyLocalRenamingMiddlewareTests.cs | 30 +- .../ApplyMissingBindingsMiddlewareTests.cs | 10 +- .../Pipeline/FullPipelineTests.cs | 14 +- ...iddlewareTests.Apply_Local_Remove.graphql} | 4 +- ...ply_Object_Extension_Is_Preserved.graphql} | 4 +- ...Merge_Directives_2_Single_Document.graphql | 7 + ..._Merge_Directives_Single_Document.graphql} | 4 +- ..._Field_Directives_Single_Document.graphql} | 4 +- ..._Object_Extension_Single_Document.graphql} | 4 +- ...iddlewareTests.Apply_Local_Rename.graphql} | 4 +- ...pply_Local_Rename_Interface_Field.graphql} | 4 +- ...ly_Local_Rename_Interface_Field_2.graphql} | 4 +- ...Apply_Local_Rename_Interface_Name.graphql} | 4 +- ...Rename_Object_And_Refactor_Usages.graphql} | 4 +- ...l_Rename_Scalar_And_Update_Usages.graphql} | 4 +- ...ewareTests.Apply_Missing_Bindings.graphql} | 4 +- ...lPipelineTests.Apply_Local_Remove.graphql} | 4 +- ...finitionMergerTests.Merge_Simple_Type.snap | 4 +- ...sts.Same_Field_In_Each_Schema_Version.snap | 3 + .../Utf8GraphQLParser.Utilities.cs | 15 +- .../Language.Utf8/Utf8GraphQLParser.Values.cs | 3 +- .../src/Language.Utf8/Utf8GraphQLParser.cs | 11 + .../src/Language.Visitors/SyntaxRewriter~1.cs | 4 +- .../Stitching/HotChocolate.Stitching.sln | 86 +- ...HotChocolate.Stitching.Abstractions.csproj | 16 + .../PublicAPI.Shipped.txt | 6 + .../PublicAPI.Unshipped.txt | 0 .../RemoteSchemaDefinition.cs | 39 + ...ishSchemaDefinitionDescriptorExtensions.cs | 31 + ...ngRedisRequestExecutorBuilderExtensions.cs | 41 + .../HotChocolate.Stitching.Redis.csproj | 20 + .../src/Stitching.Redis/PublicAPI.Shipped.txt | 8 + .../Stitching.Redis/PublicAPI.Unshipped.txt | 0 .../RedisExecutorOptionsProvider.cs | 170 ++ .../RedisSchemaDefinitionPublisher.cs | 50 + .../Stitching.Redis/SchemaDefinitionDto.cs | 13 + .../Delegation/CollectUsedVariableVisitor.cs | 31 + .../DelegateToRemoteSchemaMiddleware.cs | 512 +++++ .../Delegation/DictionaryDeserializer.cs | 159 ++ .../Delegation/DictionaryResultMiddleware.cs | 48 + ...ExtractFieldQuerySyntaxRewriter.Context.cs | 53 + .../ExtractFieldQuerySyntaxRewriter.cs | 454 ++++ .../Stitching/Delegation/ExtractedField.cs | 24 + .../Stitching/Delegation/RemoteFieldHelper.cs | 29 + .../Delegation/RemoteQueryBuilder.cs | 326 +++ .../ArgumentScopedVariableResolver.cs | 54 + .../ContextDataScopedVariableResolver.cs | 59 + .../FieldScopedVariableResolver.cs | 70 + .../IScopedVariableResolver.cs | 12 + .../RootScopedVariableResolver.cs | 47 + .../Delegation/ScopedVariables/ScopeNames.cs | 9 + ...ScopedContextDataScopedVariableResolver.cs | 58 + .../ScopedVariables/ScopedVariableValue.cs | 30 + .../Stitching/Delegation/SerializedData.cs | 11 + ...equestExecutorExtensions.DomainServices.cs | 30 + ...olateStitchingRequestExecutorExtensions.cs | 1049 +++++++++ .../Stitching/Directives/ComputedDirective.cs | 6 + .../Directives/ComputedDirectiveType.cs | 21 + .../Stitching/Directives/DelegateDirective.cs | 8 + .../Directives/DelegateDirectiveType.cs | 28 + .../Directives/DirectiveFieldNames.cs | 26 + .../Stitching/Directives/DirectiveNames.cs | 18 + .../Directives/ScopedVariableNode.cs | 182 ++ .../Directives/SelectionPathComponent.cs | 61 + .../Directives/SelectionPathParser.cs | 134 ++ .../Stitching/Directives/SourceDirective.cs | 8 + .../Directives/SourceDirectiveType.cs | 42 + .../Stitching/src/Stitching/ErrorHelper.cs | 17 + .../Extensions/ContextDataExtensions.cs | 375 ++++ .../Extensions/HasDirectiveExtensions.cs | 33 + .../Stitching/HotChocolate.Stitching.csproj | 35 + .../src/Stitching/InternalsVisibleTo.cs | 3 + ...AddSchemaExtensionRewriter.MergeContext.cs | 27 + .../Merge/AddSchemaExtensionRewriter.cs | 549 +++++ .../src/Stitching/Merge/DirectiveTypeInfo.cs | 18 + .../src/Stitching/Merge/EnumTypeInfo.cs | 14 + .../Merge/Extensions/EnumerableExtensions.cs | 25 + .../Extensions/MergeSyntaxNodeExtensions.cs | 489 +++++ .../Merge/Extensions/SchemaInfoExtensions.cs | 17 + .../Extensions/SchemaMergerExtensions.cs | 188 ++ .../Merge/Extensions/TypeInfoExtensions.cs | 37 + .../Merge/Handlers/ComplexTypeMergeHelpers.cs | 96 + .../Handlers/DirectiveTypeMergeHandler.cs | 103 + .../Merge/Handlers/EnumTypeMergeHandler.cs | 121 ++ .../Handlers/InputObjectTypeMergeHandler.cs | 242 +++ .../Handlers/InterfaceTypeMergeHandler.cs | 36 + .../Merge/Handlers/ObjectTypeMergeHandler.cs | 37 + .../Merge/Handlers/RootTypeMergeHandler.cs | 91 + .../Merge/Handlers/ScalarTypeMergeHandler.cs | 23 + .../Merge/Handlers/TypeMergeHandlerBase.cs | 68 + .../Merge/Handlers/TypeMergeHelpers.cs | 80 + .../Merge/Handlers/UnionTypeMergeHandler.cs | 38 + .../Stitching/Merge/IDirectiveMergeHandler.cs | 10 + .../src/Stitching/Merge/IDirectiveTypeInfo.cs | 9 + .../src/Stitching/Merge/ISchemaInfo.cs | 27 + .../Stitching/Merge/ISchemaMergeContext.cs | 14 + .../src/Stitching/Merge/ISchemaMerger.cs | 23 + .../src/Stitching/Merge/ITypeInfo.cs | 13 + .../src/Stitching/Merge/ITypeInfo~1.cs | 11 + .../src/Stitching/Merge/ITypeMergeHandler.cs | 10 + .../Stitching/Merge/InputObjectTypeInfo.cs | 14 + .../src/Stitching/Merge/InterfaceTypeInfo.cs | 14 + .../Merge/MergeDirectiveRuleDelegate.cs | 10 + .../src/Stitching/Merge/MergeTypeDelegate.cs | 10 + .../src/Stitching/Merge/ObjectTypeInfo.cs | 14 + .../Rewriters/DelegateDocumentRewriter.cs | 22 + .../Merge/Rewriters/DelegateTypeRewriter.cs | 22 + .../Merge/Rewriters/IDocumentRewriter.cs | 8 + .../Merge/Rewriters/ITypeRewriter.cs | 8 + .../Merge/Rewriters/RemoveFieldRewriter.cs | 84 + .../Merge/Rewriters/RemoveRootTypeRewriter.cs | 46 + .../Merge/Rewriters/RemoveTypeRewriter.cs | 38 + .../Rewriters/RenameFieldArgumentRewriter.cs | 104 + .../Merge/Rewriters/RenameFieldRewriter.cs | 93 + .../Merge/Rewriters/RenameTypeRewriter.cs | 41 + .../Merge/Rewriters/RewriteFieldsDelegate.cs | 8 + .../src/Stitching/Merge/ScalarTypeInfo.cs | 14 + .../src/Stitching/Merge/SchemaInfo.cs | 186 ++ .../src/Stitching/Merge/SchemaMergeContext.cs | 68 + .../Stitching/Merge/SchemaMergeException.cs | 32 + .../src/Stitching/Merge/SchemaMerger.cs | 405 ++++ .../Stitching/src/Stitching/Merge/TypeInfo.cs | 48 + .../src/Stitching/Merge/TypeInfo~1.cs | 18 + .../Stitching/Merge/TypeReferenceRewriter.cs | 337 +++ .../src/Stitching/Merge/UnionTypeInfo.cs | 14 + .../Stitching/Pipeline/HttpRequestClient.cs | 332 +++ .../Pipeline/HttpRequestMiddleware.cs | 31 + .../Pipeline/HttpResponseDeserializer.cs | 127 ++ .../HttpStitchingRequestInterceptor.cs | 28 + .../IHttpStitchingRequestInterceptor.cs | 22 + .../Properties/StitchingResources.Designer.cs | 300 +++ .../Properties/StitchingResources.resx | 246 +++ .../src/Stitching/PublicAPI.Shipped.txt | 377 ++++ .../src/Stitching/PublicAPI.Unshipped.txt | 0 .../src/Stitching/Requests/BufferedRequest.cs | 155 ++ .../Requests/IRemoteRequestExecutor.cs | 51 + .../Stitching/Requests/IStitchingContext.cs | 8 + .../Stitching/Requests/MergeRequestHelper.cs | 306 +++ .../Requests/MergeRequestRewriter.cs | 147 ++ .../src/Stitching/Requests/MergeUtils.cs | 12 + .../Requests/RemoteRequestExecutor.cs | 153 ++ .../Stitching/Requests/StitchingContext.cs | 55 + .../IPublishSchemaDefinitionDescriptor.cs | 46 + .../ISchemaDefinitionPublisher.cs | 11 + .../PublishSchemaDefinitionDescriptor.cs | 183 ++ .../SchemaDefinitionFieldNames.cs | 7 + .../SchemaDefinitionSchemaInterceptor.cs | 29 + .../SchemaDefinitions/SchemaDefinitionType.cs | 54 + .../SchemaDefinitionTypeInterceptor.cs | 65 + .../Stitching/src/Stitching/ThrowHelper.cs | 111 + .../CopySchemaDefinitionTypeInterceptor.cs | 20 + .../Stitching/Utilities/FieldDependency.cs | 45 + .../Utilities/FieldDependencyResolver.cs | 267 +++ .../Utilities/IQueryDelegationRewriter.cs | 57 + .../Utilities/IntrospectionHelper.cs | 142 ++ .../Utilities/QueryDelegationRewriterBase.cs | 59 + .../Utilities/SchemaExtensionsRewriter.cs | 69 + .../Utilities/StitchingSchemaInterceptor.cs | 281 +++ .../Utilities/StitchingTypeInterceptor.cs | 107 + .../src/Stitching/WellKnownContextData.cs | 19 + .../src/Stitching/WellKnownFieldNames.cs | 6 + .../ContextDataExtensionsTest.cs | 72 + ...StitchingRequestExecutorExtensionsTests.cs | 94 + .../ArgumentScopedVariableResolverTests.cs | 190 ++ .../ContextDataScopedVariableResolverTests.cs | 184 ++ .../Delegation/DelegateDirectiveTests.cs | 30 + .../Delegation/DictionaryDeserializerTests.cs | 725 +++++++ .../FieldScopedVariableResolverTests.cs | 180 ++ .../Delegation/RemoteQueryBuilderTests.cs | 97 + ...dContextDataScopedVariableResolverTests.cs | 188 ++ ...ests.Directive_Definition_PrintIsMtch.snap | 2 + ...oteQueryBuilderTests.BuildRemoteQuery.snap | 15 + ...ldRemoteQueryCanOverrideOperationName.snap | 15 + ...ewareTests.ExecuteQueryOnRemoteSchema.snap | 18 + .../Directives/SelectionPathParserTests.cs | 177 ++ .../HotChocolate.Stitching.Tests.csproj | 36 + .../Stitching.Tests/Integration/BaseTests.cs | 948 ++++++++ .../Integration/FederatedRedisSchemaTests.cs | 429 ++++ .../Integration/FederatedSchemaErrorTests.cs | 238 ++ .../Integration/FederatedSchemaTests.cs | 271 +++ .../Integration/StitchingTestContext.cs | 69 + .../BaseTests.AddLocalSchema.snap | 24 + .../BaseTests.AutoMerge_Execute.snap | 18 + ...BaseTests.AutoMerge_Execute_Arguments.snap | 34 + .../BaseTests.AutoMerge_Execute_Computed.snap | 7 + ...er_DoesNotExist_And_Is_Correctly_Null.snap | 5 + ...AutoMerge_Execute_Fragment_Definition.snap | 24 + ...BaseTests.AutoMerge_Execute_GuidField.snap | 7 + ...sts.AutoMerge_Execute_Inline_Fragment.snap | 24 + .../BaseTests.AutoMerge_Execute_IntField.snap | 7 + ...s.AutoMerge_Execute_List_Aggregations.snap | 11 + ...AutoMerge_Execute_Object_Aggregations.snap | 7 + ...eTests.AutoMerge_Execute_RenameScalar.snap | 31 + ...AutoMerge_Execute_Scalar_Aggregations.snap | 5 + ...ts.AutoMerge_Execute_Schema_GuidField.snap | 177 ++ .../BaseTests.AutoMerge_Execute_Union.snap | 27 + ...BaseTests.AutoMerge_Execute_Variables.snap | 25 + .../BaseTests.AutoMerge_Schema.snap | 176 ++ .../BaseTests.Directive_Delegation.snap | 27 + .../BaseTests.LocalField_Execute.snap | 19 + .../BaseTests.Schema_AddResolver.snap | 19 + ...ests.AutoMerge_AddLocal_Field_Execute.snap | 23 + ...tedRedisSchemaTests.AutoMerge_Execute.snap | 22 + ...atedRedisSchemaTests.AutoMerge_Schema.snap | 60 + ...atedSchemaErrorTests.AutoMerge_Schema.snap | 63 + ...rror_StatusCode_On_DownStream_Request.snap | 13 + ...ests.AutoMerge_AddLocal_Field_Execute.snap | 23 + ...chemaTests.AutoMerge_CompileOperation.snap | 21 + ...ederatedSchemaTests.AutoMerge_Execute.snap | 22 + ...FederatedSchemaTests.AutoMerge_Schema.snap | 60 + ...ive_Variables_Are_Correctly_Rewritten.snap | 23 + .../Merge/AddSchemaExtensionRewriterTests.cs | 494 +++++ .../DirectiveTypeMergeHandlerTests.cs | 174 ++ .../Handlers/EnumTypeMergeHandlerTests.cs | 187 ++ .../InputObjectTypeMergeHandlerTests.cs | 119 + .../InterfaceTypeMergeHandlerTests.cs | 119 + .../Handlers/ObjectTypeMergeHandlerTests.cs | 156 ++ .../Handlers/RootTypeMergeHandlerTests.cs | 75 + .../Handlers/UnionTypeMergeHandlerTests.cs | 43 + ..._SimpleIdenticalDirectives_TypeMerges.snap | 1 + ...ereTwoAreIdentical_TwoTypesAfterMerge.snap | 3 + ...MergeHandlerTests.MergeIdenticalEnums.snap | 4 + ...calEnumsTakeDescriptionFromSecondType.snap | 5 + ...geHandlerTests.MergeNonIdenticalEnums.snap | 10 + ...eHandlerTests.MergeNonIdenticalEnums2.snap | 10 + ...ntTypes_InputMergesLeftoversArePassed.snap | 221 ++ ...ntTypes_InputMergesLeftoversArePassed.snap | 224 ++ ...erge_SimpleIdenticalInputs_TypeMerges.snap | 3 + ...ereTwoAreIdentical_TwoTypesAfterMerge.snap | 8 + ...tTypes_InterfMergesLeftoversArePassed.snap | 224 ++ ..._SimpleIdenticalInterfaces_TypeMerges.snap | 3 + ...ereTwoAreIdentical_TwoTypesAfterMerge.snap | 7 + ...tTypes_ObjectMergesLeftoversArePassed.snap | 224 ++ ...ectWithDifferentInterfaces_TypesMerge.snap | 3 + ...rge_SimpleIdenticalObjects_TypeMerges.snap | 3 + ...ereTwoAreIdentical_TwoTypesAfterMerge.snap | 7 + ...hCollisions_CollidingFieldsAreRenamed.snap | 4 + ...e_RootTypeWithNoCollisions_TypeMerges.snap | 4 + ...TypeMergeHandlerTests.MergeUnionTypes.snap | 3 + .../Merge/MergeSyntaxNodeExtensionsTests.cs | 372 ++++ .../Stitching.Tests/Merge/SchemaInfoTests.cs | 91 + .../Merge/SchemaMergerTests.cs | 594 +++++ .../Merge/TypeInfoExtensionsTests.cs | 116 + ...nRewriterTests.EnumType_AddDirectives.snap | 6 + ...Tests.EnumType_AddDuplicateDirectives.snap | 1 + ...ests.EnumType_AddUndeclaredDirectives.snap | 1 + ...ensionRewriterTests.EnumType_AddValue.snap | 5 + ...onRewriterTests.EnumType_TypeMismatch.snap | 1 + ...erTests.InputObjectType_AddDirectives.snap | 5 + ...nputObjectType_AddDuplicateDirectives.snap | 1 + ...rTests.InputObjectType_AddObjectField.snap | 8 + ...rTests.InputObjectType_AddScalarField.snap | 4 + ...putObjectType_AddUndeclaredDirectives.snap | 1 + ...iterTests.InterfaceType_AddDirectives.snap | 5 + ....InterfaceType_AddDuplicateDirectives.snap | 1 + ...terTests.InterfaceType_AddObjectField.snap | 8 + ...terTests.InterfaceType_AddScalarField.snap | 4 + ...InterfaceType_AddUndeclaredDirectives.snap | 1 + ...ewriterTests.ObjectType_AddDirectives.snap | 5 + ...Tests.ObjectType_AddDirectivesToField.snap | 5 + ...sts.ObjectType_AddDuplicateDirectives.snap | 1 + ...writerTests.ObjectType_AddObjectField.snap | 8 + ...writerTests.ObjectType_AddScalarField.snap | 4 + ...iterTests.ObjectType_AddScalarField_2.snap | 8 + ...ts.ObjectType_AddUndeclaredDirectives.snap | 1 + ...tType_DirectiveDeclaredInExtensionDoc.snap | 5 + ...RewriterTests.UnionType_AddDirectives.snap | 11 + ...ests.UnionType_AddDuplicateDirectives.snap | 1 + ...ensionRewriterTests.UnionType_AddType.snap | 13 + ...sts.UnionType_AddUndeclaredDirectives.snap | 1 + ....AddDelegationPath_MultipleComponents.snap | 3 + ...tionPath_MultipleComponents_EmptyPath.snap | 3 + ...th_MultipleComponents_SingleComponent.snap | 3 + ...ionsTests.AddDelegationPath_Overwrite.snap | 3 + ...sts.AddDelegationPath_SingleComponent.snap | 3 + ...elegationPath_SingleComponent_TwoArgs.snap | 3 + ...eldDefinitionDoesNotHaveSameTypeShape.snap | 17 + ...hemaMergerTests.LastFieldRenameWins_A.snap | 24 + ...hemaMergerTests.LastFieldRenameWins_B.snap | 24 + ...sts.MergeDemoSchemaAndRemoveRootTypes.snap | 34 + ...DemoSchemaAndRemoveRootTypesOnSchemaA.snap | 40 + ...sts.MergeDemoSchemaWithDefaultHandler.snap | 42 + ...ests.MergeDirectivesWithCustomHandler.snap | 1 + ...erTests.MergeDirectivesWithCustomRule.snap | 1 + ...rgeSchemaAndRemoveFieldB1OnAllSchemas.snap | 11 + ....MergeSchemaAndRemoveFieldB1OnSchemaA.snap | 12 + ...MergeSchemaAndRemoveTypeAOnAllSchemas.snap | 3 + ...ts.MergeSchemaAndRemoveTypeAOnSchemaA.snap | 7 + ...hemaAndRenameFieldB1toB11OnAllSchemas.snap | 13 + ...eSchemaAndRenameFieldB1toB11OnSchemaA.snap | 13 + ...SchemaAndRenameTypeAtoXyzOnAllSchemas.snap | 13 + ...rgeSchemaAndRenameTypeAtoXyzOnSchemaA.snap | 13 + ...s.MergeSimpleSchemaWithDefaultHandler.snap | 5 + ...ergerTests.MergeTypeWithCustomHandler.snap | 4 + ...maMergerTests.MergeTypeWithCustomRule.snap | 4 + ...chemaMergerTests.RenameInterfaceField.snap | 24 + .../SchemaMergerTests.RenameObjectField.snap | 20 + ...ameObjectFieldThatImplementsInterface.snap | 24 + ...maMergerTests.RenameReferencingType_A.snap | 20 + ...maMergerTests.RenameReferencingType_B.snap | 20 + ...sts.Rename_Type_With_Various_Variants.snap | 14 + .../Middleware/ConfigurationApiTests.txt | 83 + .../DelegateToRemoteSchemaMiddlewareTests.txt | 935 ++++++++ .../Middleware/ErrorBehaviour.txt | 120 ++ .../Middleware/HttpInterceptorTests.txt | 93 + .../Middleware/InputObjectDelegationTests.txt | 114 + .../Middleware/ScalarTests.txt | 270 +++ .../Middleware/SchemaCreationTests.txt | 46 + .../Middleware/SchemaMergeTests.txt | 126 ++ .../Middleware/StitchingTestBase.txt | 59 + .../Middleware/TypeExtensionTests.txt | 102 + .../Middleware/TypeRewriterTests.txt | 128 ++ .../Middleware/TypeSystemDirectivesTests.txt | 100 + .../Middleware/VariableDelegationTests.txt | 167 ++ ...eSchemaMiddlewareTests.AddErrorFilter.snap | 93 + ...lewareTests.CustomDirectiveIsPassedOn.snap | 9 + ...ewareTests.DateTimeIsHandledCorrectly.snap | 10 + ...reTests.DelegateWithGuidFieldArgument.snap | 10 + ...areTests.DelegateWithIntFieldArgument.snap | 10 + ...wareTests.ExecuteStitchedQueryBuilder.snap | 35 + ...StitchedQueryBuilderVariableArguments.snap | 23 + ...teStitchedQueryBuilderWithLocalSchema.snap | 25 + ...teStitchedQueryBuilderWithRenamedType.snap | 24 + ...ExecuteStitchedQueryWithComputedField.snap | 10 + ...s.ExecuteStitchingQueryDeepObjectPath.snap | 10 + ...s.ExecuteStitchingQueryDeepScalarPath.snap | 8 + ...ts.ExecuteStitchingQueryWithArguments.snap | 18 + ...eStitchingQueryWithFragmentDefinition.snap | 27 + ...ecuteStitchingQueryWithInlineFragment.snap | 27 + ...eTests.ExecuteStitchingQueryWithUnion.snap | 30 + ...ts.ExecuteStitchingQueryWithVariables.snap | 28 + ...Tests.ExtendedScalarAsInAndOutputType.snap | 9 + ...MiddlewareTests.HttpErrorsHavePathSet.snap | 45 + ...ithEnumArgument_EnumIsCorrectlyPassed.snap | 15 + ...oteSchemaMiddlewareTests.ReplaceField.snap | 10 + ...chemaMiddlewareTests.StitchedMutation.snap | 13 + ...tchedMutationWithRenamedFieldArgument.snap | 13 + ...StitchedMutationWithRenamedInputField.snap | 13 + ...ationWithRenamedInputFieldInVariables.snap | 13 + ...nWithRenamedInputFieldInVariablesList.snap | 21 + ...chedMutationWithRenamedInputFieldList.snap | 21 + ....StitchedMutationWithRenamedInputType.snap | 13 + .../ErrorBehaviour.ConnectionLost.snap | 17 + .../Errorbehavior.ConnectionLost.snap | 17 + ...nterceptHttpRequestAndDelegateHeaders.snap | 10 + ...lowInputObjectTypesAsArguments_result.snap | 8 + ...lowInputObjectTypesAsArguments_schema.snap | 23 + ...ests.Custom_Scalar_Delegated_Argument.snap | 8 + ...ustom_Scalar_Delegated_Input_Argument.snap | 8 + ...r_Delegated_Input_Argument_Unstitched.snap | 8 + .../ScalarTests.Custom_Scalar_Types.snap | 8 + ...And_Deserializes_Correctly_byte_field.snap | 10 + ...And_Deserializes_Correctly_date_field.snap | 10 + ...eserializes_Correctly_date_time_field.snap | 10 + ..._Deserializes_Correctly_decimal_field.snap | 10 + ...nd_Deserializes_Correctly_float_field.snap | 10 + ...s_And_Deserializes_Correctly_id_field.snap | 10 + ..._And_Deserializes_Correctly_int_field.snap | 10 + ...And_Deserializes_Correctly_long_field.snap | 10 + ...d_Deserializes_Correctly_string_field.snap | 10 + ...ExtensionTests.AddObjectTypeExtension.snap | 8 + .../TypeExtensionTests.UseSchemaBuilder.snap | 8 + ...ts.CustomRewriterTakesPriority_result.snap | 8 + ...ts.CustomRewriterTakesPriority_schema.snap | 15 + ...eSchemaHasTypeSystemDirectives_result.snap | 8 + ...eSchemaHasTypeSystemDirectives_schema.snap | 17 + ....ListVariableIsCorrectlyPassed_result.snap | 15 + ....ListVariableIsCorrectlyPassed_schema.snap | 179 ++ ...FieldVariableIsCorrectlyPassed_result.snap | 13 + ...FieldVariableIsCorrectlyPassed_schema.snap | 179 ++ ...dListVariableIsCorrectlyPassed_result.snap | 15 + ...dListVariableIsCorrectlyPassed_schema.snap | 180 ++ .../Requests/BufferedRequestTests.cs | 215 ++ .../Requests/MergeRequestHelperTests.cs | 199 ++ ...estHelperTests.Create_BufferedRequest.snap | 8 + ....Create_BufferedRequest_AutoGenerated.snap | 8 + ...BufferedRequest_With_Mixed_Operations.snap | 16 + ...Requests_With_Variables_On_Directives.snap | 8 + ...sSchemaRequestExecutorBuilderExtensions.cs | 17 + .../Stitching.Tests/Schemas/Accounts/Query.cs | 12 + .../Stitching.Tests/Schemas/Accounts/User.cs | 22 + .../Schemas/Accounts/UserRepository.cs | 23 + ...tSchemaRequestExecutorBuilderExtensions.cs | 22 + .../Schemas/Contracts/ContractStorage.cs | 53 + .../Schemas/Contracts/ContractType.cs | 15 + .../Schemas/Contracts/CustomDirectiveType.cs | 20 + .../Schemas/Contracts/IContract.cs | 8 + .../Contracts/LifeInsuranceContract.cs | 10 + .../Contracts/LifeInsuranceContractType.cs | 69 + .../Schemas/Contracts/Query.cs | 54 + .../Schemas/Contracts/QueryType.cs | 28 + .../Schemas/Contracts/SomeOtherContract.cs | 12 + .../Contracts/SomeOtherContractType.cs | 30 + .../Schemas/Customers/ComplexInput.cs | 12 + .../Schemas/Customers/ComplexInputType.cs | 16 + .../Schemas/Customers/Consultant.cs | 7 + .../Schemas/Customers/ConsultantType.cs | 33 + .../Schemas/Customers/CreateCustomerInput.cs | 8 + .../Customers/CreateCustomerPayload.cs | 6 + .../Schemas/Customers/Customer.cs | 12 + .../Schemas/Customers/CustomerKind.cs | 7 + .../Customers/CustomerOrConsultantType.cs | 14 + .../Schemas/Customers/CustomerRepository.cs | 51 + .../Schemas/Customers/CustomerResolver.cs | 16 + ...rSchemaRequestExecutorBuilderExtensions.cs | 20 + .../Schemas/Customers/CustomerType.cs | 41 + .../Customers/ICustomerOrConsultant.cs | 5 + .../Schemas/Customers/Mutation.cs | 39 + .../Schemas/Customers/MutationType.cs | 7 + .../Schemas/Customers/Query.cs | 51 + .../Schemas/Customers/QueryType.cs | 31 + .../Schemas/Customers/SayInput.cs | 6 + .../Schemas/Inventory/InventoryInfo.cs | 14 + .../Inventory/InventoryInfoRepository.cs | 21 + ...ySchemaRequestExecutorBuilderExtensions.cs | 17 + .../Schemas/Inventory/Query.cs | 12 + .../Schemas/Products/Product.cs | 17 + .../Schemas/Products/ProductRepository.cs | 25 + ...sSchemaRequestExecutorBuilderExtensions.cs | 17 + .../Stitching.Tests/Schemas/Products/Query.cs | 16 + .../Stitching.Tests/Schemas/Reviews/Author.cs | 13 + .../Stitching.Tests/Schemas/Reviews/Query.cs | 20 + .../Stitching.Tests/Schemas/Reviews/Review.cs | 17 + .../Schemas/Reviews/ReviewRepository.cs | 40 + ...sSchemaRequestExecutorBuilderExtensions.cs | 17 + .../Stitching.Tests/Schemas/SchemaTests.cs | 37 + .../SpecialCases/ContractSchemaFactory.txt | 124 ++ .../SchemaTests.ContractSchemaSnapshot.snap | 76 + .../SchemaTests.CustomerSchemaSnapshot.snap | 114 + .../AccountSchemaDefinition.json | 8 + .../__resources__/AdvisorClient.graphql | 116 + .../__resources__/Contract.graphql | 18 + .../__resources__/ContractClient.graphql | 1920 +++++++++++++++++ .../__resources__/Customer.graphql | 26 + .../__resources__/DocumentClient.graphql | 339 +++ .../__resources__/DummyDirective.graphql | 1 + .../IntrospectionWithDefaultValues.json | 1003 +++++++++ .../MergeQueryWithVariable.graphql | 21 + .../SchemaInfoTests_Schema.graphql | 17 + .../StarWarsIntrospectionResult.json | 1778 +++++++++++++++ .../__resources__/Stitching.graphql | 56 + .../__resources__/StitchingComputed.graphql | 49 + .../__resources__/StitchingExtensions.graphql | 13 + .../__resources__/StitchingQuery.graphql | 17 + .../StitchingQueryWithSkip.graphql | 17 + .../StitchingQueryWithTypename.graphql | 6 + ...st_Empty_QueryRequestBuilderException.snap | 1 + ...st_OnlyQueryIsSet_RequestHasOnlyQuery.snap | 65 + ...ueryAndAddProperties_RequestIsCreated.snap | 68 + ...QueryAndAddVariables_RequestIsCreated.snap | 68 + ...QueryAndInitialValue_RequestIsCreated.snap | 67 + ...st_QueryAndOperation_RequestIsCreated.snap | 74 + ...eryAndResetOperation_RequestIsCreated.snap | 65 + ...ryAndResetProperties_RequestIsCreated.snap | 65 + ...eryAndResetVariables_RequestIsCreated.snap | 65 + ...est_QueryAndServices_RequestIsCreated.snap | 65 + ...ueryAndSetProperties_RequestIsCreated.snap | 67 + ...QueryAndSetVariables_RequestIsCreated.snap | 67 + ....BuildRequest_SetAll_RequestIsCreated.snap | 80 + ...ingBuilderTests.AddExtensionsFromFile.snap | 4 + ...gBuilderTests.AddExtensionsFromString.snap | 4 + .../StitchingBuilderTests.AddSchema.snap | 12 + ...itchingBuilderTests.AddSchemaFromFile.snap | 42 + ...itchingBuilderTests.AddSchemaFromHttp.snap | 143 ++ ...chingBuilderTests.AddSchemaFromString.snap | 42 + .../StitchingBuilderTests.AddSchema_2.snap | 132 ++ .../test/Stitching.Tests/xunit.runner.json | 3 + ...on_Merge_Directives_2_Single_Document.snap | 7 - ...ingMiddlewareTests.Apply_Local_Rename.snap | 9 - ...sts.Same_Field_In_Each_Schema_Version.snap | 3 - .../Utilities.Introspection/BuiltInTypes.cs | 4 +- .../IntrospectionClient.cs | 18 +- .../IntrospectionDeserializer.cs | 16 +- .../IntrospectionQueryHelper.cs | 20 +- .../Utilities.Introspection/SchemaFeatures.cs | 4 +- 550 files changed, 34014 insertions(+), 512 deletions(-) delete mode 100644 src/HotChocolate/Core/src/Abstractions/NameString.cs.txt rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/BindingList.cs (80%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/Bindings/DependenciesBinding.cs (100%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/Bindings/FetchBinding.cs (100%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/Bindings/SourceBinding.cs (100%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/DefaultObjectTypeDefinitionMerger.cs (83%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/HotChocolate.Stitching.Types.csproj (100%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/IBinding.cs (100%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/ITypeDefinition.cs (100%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/ITypeDefinitionMerger.cs (100%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/ObjectTypeDefinition.cs (90%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/Pipeline/ApplyExtensions/ApplyComplexTypeExtension.cs (89%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/Pipeline/ApplyExtensions/ApplyExtension.cs (100%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/Pipeline/ApplyExtensions/ApplyExtensionsMiddleware.cs (92%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/Pipeline/ApplyExtensions/ApplyInterfaceTypeExtension.cs (100%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/Pipeline/ApplyExtensions/ApplyObjectTypeExtension.cs (100%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/Pipeline/ApplyExtensions/IApplyExtension.cs (100%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/Pipeline/ApplyMissingBindings/ApplyMissingBindingsMiddleware.cs (88%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/Pipeline/ApplyMissingBindings/BindingContext.cs (100%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/Pipeline/ApplyMissingBindings/BindingRewriter.cs (100%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/Pipeline/ApplyRenaming/ApplyRenamingMiddleware.cs (97%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/Pipeline/ApplyRenaming/RenameContext.cs (100%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/Pipeline/ApplyRenaming/RenameIndexer.cs (94%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/Pipeline/ApplyRenaming/RenameInfo.cs (100%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/Pipeline/ApplyRenaming/RenameRewriter.cs (90%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/Pipeline/BindDirective.cs (96%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/Pipeline/ISchemaMergeContext.cs (100%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/Pipeline/MergeSchema.cs (100%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/Pipeline/MergeSchemaMiddleware.cs (100%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/Pipeline/PrepareDocuments/PrepareDocumentsMiddleware.cs (85%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/Pipeline/PrepareDocuments/SquashDocumentsMiddleware.cs (92%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/Pipeline/RenameDirective.cs (92%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/Pipeline/SchemaMergeContext.cs (100%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/Pipeline/SchemaMergePipelineBuilder.cs (100%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/Pipeline/ServiceConfiguration.cs (100%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/ThrowHelper.cs (100%) rename src/HotChocolate/{Stitching => Fusion}/src/Stitching.Types/TypeDefinitionMerger.cs (100%) rename src/HotChocolate/{Stitching => Fusion}/test/Stitching.Types.Tests/DefaultObjectTypeDefinitionMergerTests.cs (90%) rename src/HotChocolate/{Stitching => Fusion}/test/Stitching.Types.Tests/DefaultObjectTypeDefinitionTests.cs (91%) rename src/HotChocolate/{Stitching => Fusion}/test/Stitching.Types.Tests/HotChocolate.Stitching.Types.Tests.csproj (100%) rename src/HotChocolate/{Stitching => Fusion}/test/Stitching.Types.Tests/Pipeline/ApplyExtensionsMiddlewareTests.cs (84%) rename src/HotChocolate/{Stitching => Fusion}/test/Stitching.Types.Tests/Pipeline/ApplyLocalRenamingMiddlewareTests.cs (86%) rename src/HotChocolate/{Stitching => Fusion}/test/Stitching.Types.Tests/Pipeline/ApplyMissingBindingsMiddlewareTests.cs (89%) rename src/HotChocolate/{Stitching => Fusion}/test/Stitching.Types.Tests/Pipeline/FullPipelineTests.cs (70%) rename src/HotChocolate/{Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Local_Remove.snap => Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Local_Remove.graphql} (50%) rename src/HotChocolate/{Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Is_Preserved.snap => Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Is_Preserved.graphql} (77%) create mode 100644 src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Merge_Directives_2_Single_Document.graphql rename src/HotChocolate/{Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Merge_Directives_Single_Document.snap => Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Merge_Directives_Single_Document.graphql} (50%) rename src/HotChocolate/{Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Merge_Field_Directives_Single_Document.snap => Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Merge_Field_Directives_Single_Document.graphql} (50%) rename src/HotChocolate/{Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Single_Document.snap => Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Single_Document.graphql} (51%) rename src/HotChocolate/{Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename.snap => Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename.graphql} (74%) rename src/HotChocolate/{Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Interface_Field.snap => Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Interface_Field.graphql} (77%) rename src/HotChocolate/{Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Interface_Field_2.snap => Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Interface_Field_2.graphql} (86%) rename src/HotChocolate/{Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Interface_Name.snap => Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Interface_Name.graphql} (73%) rename src/HotChocolate/{Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Object_And_Refactor_Usages.snap => Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Object_And_Refactor_Usages.graphql} (57%) rename src/HotChocolate/{Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Scalar_And_Update_Usages.snap => Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Scalar_And_Update_Usages.graphql} (85%) rename src/HotChocolate/{Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyMissingBindingsMiddlewareTests.Apply_Missing_Bindings.snap => Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyMissingBindingsMiddlewareTests.Apply_Missing_Bindings.graphql} (88%) rename src/HotChocolate/{Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/FullPipelineTests.Apply_Local_Remove.snap => Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/FullPipelineTests.Apply_Local_Remove.graphql} (77%) rename src/HotChocolate/{Stitching => Fusion}/test/Stitching.Types.Tests/__snapshots__/DefaultObjectTypeDefinitionMergerTests.Merge_Simple_Type.snap (60%) create mode 100644 src/HotChocolate/Fusion/test/Stitching.Types.Tests/__snapshots__/DefaultObjectTypeDefinitionMergerTests.Same_Field_In_Each_Schema_Version.snap create mode 100644 src/HotChocolate/Stitching/src/Stitching.Abstractions/HotChocolate.Stitching.Abstractions.csproj create mode 100644 src/HotChocolate/Stitching/src/Stitching.Abstractions/PublicAPI.Shipped.txt create mode 100644 src/HotChocolate/Stitching/src/Stitching.Abstractions/PublicAPI.Unshipped.txt create mode 100644 src/HotChocolate/Stitching/src/Stitching.Abstractions/RemoteSchemaDefinition.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching.Redis/DependencyInjection/HotChocolateStitchingRedisPublishSchemaDefinitionDescriptorExtensions.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching.Redis/DependencyInjection/HotChocolateStitchingRedisRequestExecutorBuilderExtensions.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching.Redis/HotChocolate.Stitching.Redis.csproj create mode 100644 src/HotChocolate/Stitching/src/Stitching.Redis/PublicAPI.Shipped.txt create mode 100644 src/HotChocolate/Stitching/src/Stitching.Redis/PublicAPI.Unshipped.txt create mode 100644 src/HotChocolate/Stitching/src/Stitching.Redis/RedisExecutorOptionsProvider.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching.Redis/RedisSchemaDefinitionPublisher.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching.Redis/SchemaDefinitionDto.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Delegation/CollectUsedVariableVisitor.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Delegation/DelegateToRemoteSchemaMiddleware.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Delegation/DictionaryDeserializer.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Delegation/DictionaryResultMiddleware.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Delegation/ExtractFieldQuerySyntaxRewriter.Context.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Delegation/ExtractFieldQuerySyntaxRewriter.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Delegation/ExtractedField.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Delegation/RemoteFieldHelper.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Delegation/RemoteQueryBuilder.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Delegation/ScopedVariables/ArgumentScopedVariableResolver.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Delegation/ScopedVariables/ContextDataScopedVariableResolver.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Delegation/ScopedVariables/FieldScopedVariableResolver.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Delegation/ScopedVariables/IScopedVariableResolver.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Delegation/ScopedVariables/RootScopedVariableResolver.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Delegation/ScopedVariables/ScopeNames.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Delegation/ScopedVariables/ScopedContextDataScopedVariableResolver.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Delegation/ScopedVariables/ScopedVariableValue.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Delegation/SerializedData.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/DependencyInjection/HotChocolateStitchingRequestExecutorExtensions.DomainServices.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/DependencyInjection/HotChocolateStitchingRequestExecutorExtensions.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Directives/ComputedDirective.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Directives/ComputedDirectiveType.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Directives/DelegateDirective.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Directives/DelegateDirectiveType.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Directives/DirectiveFieldNames.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Directives/DirectiveNames.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Directives/ScopedVariableNode.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Directives/SelectionPathComponent.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Directives/SelectionPathParser.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Directives/SourceDirective.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Directives/SourceDirectiveType.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/ErrorHelper.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Extensions/ContextDataExtensions.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Extensions/HasDirectiveExtensions.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/HotChocolate.Stitching.csproj create mode 100644 src/HotChocolate/Stitching/src/Stitching/InternalsVisibleTo.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/AddSchemaExtensionRewriter.MergeContext.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/AddSchemaExtensionRewriter.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/DirectiveTypeInfo.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/EnumTypeInfo.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/Extensions/EnumerableExtensions.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/Extensions/MergeSyntaxNodeExtensions.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/Extensions/SchemaInfoExtensions.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/Extensions/SchemaMergerExtensions.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/Extensions/TypeInfoExtensions.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/ComplexTypeMergeHelpers.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/DirectiveTypeMergeHandler.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/EnumTypeMergeHandler.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/InputObjectTypeMergeHandler.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/InterfaceTypeMergeHandler.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/ObjectTypeMergeHandler.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/RootTypeMergeHandler.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/ScalarTypeMergeHandler.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/TypeMergeHandlerBase.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/TypeMergeHelpers.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/UnionTypeMergeHandler.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/IDirectiveMergeHandler.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/IDirectiveTypeInfo.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/ISchemaInfo.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/ISchemaMergeContext.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/ISchemaMerger.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/ITypeInfo.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/ITypeInfo~1.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/ITypeMergeHandler.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/InputObjectTypeInfo.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/InterfaceTypeInfo.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/MergeDirectiveRuleDelegate.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/MergeTypeDelegate.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/ObjectTypeInfo.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/DelegateDocumentRewriter.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/DelegateTypeRewriter.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/IDocumentRewriter.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/ITypeRewriter.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/RemoveFieldRewriter.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/RemoveRootTypeRewriter.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/RemoveTypeRewriter.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/RenameFieldArgumentRewriter.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/RenameFieldRewriter.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/RenameTypeRewriter.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/RewriteFieldsDelegate.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/ScalarTypeInfo.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/SchemaInfo.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/SchemaMergeContext.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/SchemaMergeException.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/SchemaMerger.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/TypeInfo.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/TypeInfo~1.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/TypeReferenceRewriter.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Merge/UnionTypeInfo.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Pipeline/HttpRequestClient.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Pipeline/HttpRequestMiddleware.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Pipeline/HttpResponseDeserializer.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Pipeline/HttpStitchingRequestInterceptor.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Pipeline/IHttpStitchingRequestInterceptor.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Properties/StitchingResources.Designer.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Properties/StitchingResources.resx create mode 100644 src/HotChocolate/Stitching/src/Stitching/PublicAPI.Shipped.txt create mode 100644 src/HotChocolate/Stitching/src/Stitching/PublicAPI.Unshipped.txt create mode 100644 src/HotChocolate/Stitching/src/Stitching/Requests/BufferedRequest.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Requests/IRemoteRequestExecutor.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Requests/IStitchingContext.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Requests/MergeRequestHelper.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Requests/MergeRequestRewriter.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Requests/MergeUtils.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Requests/RemoteRequestExecutor.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Requests/StitchingContext.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/SchemaDefinitions/IPublishSchemaDefinitionDescriptor.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/SchemaDefinitions/ISchemaDefinitionPublisher.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/SchemaDefinitions/PublishSchemaDefinitionDescriptor.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/SchemaDefinitions/SchemaDefinitionFieldNames.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/SchemaDefinitions/SchemaDefinitionSchemaInterceptor.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/SchemaDefinitions/SchemaDefinitionType.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/SchemaDefinitions/SchemaDefinitionTypeInterceptor.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/ThrowHelper.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Utilities/CopySchemaDefinitionTypeInterceptor.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Utilities/FieldDependency.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Utilities/FieldDependencyResolver.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Utilities/IQueryDelegationRewriter.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Utilities/IntrospectionHelper.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Utilities/QueryDelegationRewriterBase.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Utilities/SchemaExtensionsRewriter.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Utilities/StitchingSchemaInterceptor.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/Utilities/StitchingTypeInterceptor.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/WellKnownContextData.cs create mode 100644 src/HotChocolate/Stitching/src/Stitching/WellKnownFieldNames.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Configuration/ContextDataExtensionsTest.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Configuration/HotChocolateStitchingRequestExecutorExtensionsTests.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/ArgumentScopedVariableResolverTests.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/ContextDataScopedVariableResolverTests.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/DelegateDirectiveTests.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/DictionaryDeserializerTests.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/FieldScopedVariableResolverTests.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/RemoteQueryBuilderTests.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/ScopedContextDataScopedVariableResolverTests.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/__snapshots__/DelegateDirectiveTests.Directive_Definition_PrintIsMtch.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/__snapshots__/RemoteQueryBuilderTests.BuildRemoteQuery.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/__snapshots__/RemoteQueryBuilderTests.BuildRemoteQueryCanOverrideOperationName.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/__snapshots__/RemoteQueryMiddlewareTests.ExecuteQueryOnRemoteSchema.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Directives/SelectionPathParserTests.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/HotChocolate.Stitching.Tests.csproj create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/BaseTests.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/FederatedRedisSchemaTests.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/FederatedSchemaErrorTests.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/FederatedSchemaTests.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/StitchingTestContext.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AddLocalSchema.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Arguments.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Computed.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Customer_DoesNotExist_And_Is_Correctly_Null.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Fragment_Definition.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_GuidField.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Inline_Fragment.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_IntField.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_List_Aggregations.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Object_Aggregations.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_RenameScalar.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Scalar_Aggregations.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Schema_GuidField.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Union.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Variables.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Schema.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.Directive_Delegation.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.LocalField_Execute.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.Schema_AddResolver.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedRedisSchemaTests.AutoMerge_AddLocal_Field_Execute.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedRedisSchemaTests.AutoMerge_Execute.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedRedisSchemaTests.AutoMerge_Schema.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaErrorTests.AutoMerge_Schema.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaErrorTests.Execute_Error_StatusCode_On_DownStream_Request.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaTests.AutoMerge_AddLocal_Field_Execute.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaTests.AutoMerge_CompileOperation.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaTests.AutoMerge_Execute.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaTests.AutoMerge_Schema.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaTests.Directive_Variables_Are_Correctly_Rewritten.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/AddSchemaExtensionRewriterTests.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/DirectiveTypeMergeHandlerTests.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/EnumTypeMergeHandlerTests.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/InputObjectTypeMergeHandlerTests.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/InterfaceTypeMergeHandlerTests.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/ObjectTypeMergeHandlerTests.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/RootTypeMergeHandlerTests.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/UnionTypeMergeHandlerTests.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/DirectiveTypeMergeHandlerTests.Merge_SimpleIdenticalDirectives_TypeMerges.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/DirectiveTypeMergeHandlerTests.Merge_ThreeDirectivessWhereTwoAreIdentical_TwoTypesAfterMerge.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/EnumTypeMergeHandlerTests.MergeIdenticalEnums.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/EnumTypeMergeHandlerTests.MergeIdenticalEnumsTakeDescriptionFromSecondType.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/EnumTypeMergeHandlerTests.MergeNonIdenticalEnums.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/EnumTypeMergeHandlerTests.MergeNonIdenticalEnums2.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/EnumTypeMergeHandlerTests.Merge_DifferentTypes_InputMergesLeftoversArePassed.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/InputObjectTypeMergeHandlerTests.Merge_DifferentTypes_InputMergesLeftoversArePassed.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/InputObjectTypeMergeHandlerTests.Merge_SimpleIdenticalInputs_TypeMerges.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/InputObjectTypeMergeHandlerTests.Merge_ThreeInputsWhereTwoAreIdentical_TwoTypesAfterMerge.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/InterfaceTypeMergeHandlerTests.Merge_DifferentTypes_InterfMergesLeftoversArePassed.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/InterfaceTypeMergeHandlerTests.Merge_SimpleIdenticalInterfaces_TypeMerges.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/InterfaceTypeMergeHandlerTests.Merge_ThreeInterfWhereTwoAreIdentical_TwoTypesAfterMerge.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/ObjectTypeMergeHandlerTests.Merge_DifferentTypes_ObjectMergesLeftoversArePassed.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/ObjectTypeMergeHandlerTests.Merge_ObjectWithDifferentInterfaces_TypesMerge.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/ObjectTypeMergeHandlerTests.Merge_SimpleIdenticalObjects_TypeMerges.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/ObjectTypeMergeHandlerTests.Merge_ThreeObjectsWhereTwoAreIdentical_TwoTypesAfterMerge.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/RootTypeMergeHandlerTests.Merge_RootTypeWithCollisions_CollidingFieldsAreRenamed.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/RootTypeMergeHandlerTests.Merge_RootTypeWithNoCollisions_TypeMerges.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/UnionTypeMergeHandlerTests.MergeUnionTypes.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/MergeSyntaxNodeExtensionsTests.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/SchemaInfoTests.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/SchemaMergerTests.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/TypeInfoExtensionsTests.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.EnumType_AddDirectives.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.EnumType_AddDuplicateDirectives.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.EnumType_AddUndeclaredDirectives.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.EnumType_AddValue.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.EnumType_TypeMismatch.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InputObjectType_AddDirectives.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InputObjectType_AddDuplicateDirectives.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InputObjectType_AddObjectField.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InputObjectType_AddScalarField.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InputObjectType_AddUndeclaredDirectives.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InterfaceType_AddDirectives.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InterfaceType_AddDuplicateDirectives.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InterfaceType_AddObjectField.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InterfaceType_AddScalarField.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InterfaceType_AddUndeclaredDirectives.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_AddDirectives.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_AddDirectivesToField.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_AddDuplicateDirectives.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_AddObjectField.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_AddScalarField.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_AddScalarField_2.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_AddUndeclaredDirectives.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_DirectiveDeclaredInExtensionDoc.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.UnionType_AddDirectives.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.UnionType_AddDuplicateDirectives.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.UnionType_AddType.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.UnionType_AddUndeclaredDirectives.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/MergeSyntaxNodeExtensionsTests.AddDelegationPath_MultipleComponents.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/MergeSyntaxNodeExtensionsTests.AddDelegationPath_MultipleComponents_EmptyPath.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/MergeSyntaxNodeExtensionsTests.AddDelegationPath_MultipleComponents_SingleComponent.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/MergeSyntaxNodeExtensionsTests.AddDelegationPath_Overwrite.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/MergeSyntaxNodeExtensionsTests.AddDelegationPath_SingleComponent.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/MergeSyntaxNodeExtensionsTests.AddDelegationPath_SingleComponent_TwoArgs.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.FieldDefinitionDoesNotHaveSameTypeShape.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.LastFieldRenameWins_A.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.LastFieldRenameWins_B.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeDemoSchemaAndRemoveRootTypes.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeDemoSchemaAndRemoveRootTypesOnSchemaA.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeDemoSchemaWithDefaultHandler.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeDirectivesWithCustomHandler.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeDirectivesWithCustomRule.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSchemaAndRemoveFieldB1OnAllSchemas.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSchemaAndRemoveFieldB1OnSchemaA.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSchemaAndRemoveTypeAOnAllSchemas.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSchemaAndRemoveTypeAOnSchemaA.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSchemaAndRenameFieldB1toB11OnAllSchemas.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSchemaAndRenameFieldB1toB11OnSchemaA.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSchemaAndRenameTypeAtoXyzOnAllSchemas.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSchemaAndRenameTypeAtoXyzOnSchemaA.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSimpleSchemaWithDefaultHandler.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeTypeWithCustomHandler.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeTypeWithCustomRule.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.RenameInterfaceField.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.RenameObjectField.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.RenameObjectFieldThatImplementsInterface.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.RenameReferencingType_A.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.RenameReferencingType_B.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.Rename_Type_With_Various_Variants.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/ConfigurationApiTests.txt create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/DelegateToRemoteSchemaMiddlewareTests.txt create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/ErrorBehaviour.txt create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/HttpInterceptorTests.txt create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/InputObjectDelegationTests.txt create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/ScalarTests.txt create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/SchemaCreationTests.txt create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/SchemaMergeTests.txt create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/StitchingTestBase.txt create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/TypeExtensionTests.txt create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/TypeRewriterTests.txt create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/TypeSystemDirectivesTests.txt create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/VariableDelegationTests.txt create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.AddErrorFilter.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.CustomDirectiveIsPassedOn.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.DateTimeIsHandledCorrectly.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.DelegateWithGuidFieldArgument.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.DelegateWithIntFieldArgument.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchedQueryBuilder.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchedQueryBuilderVariableArguments.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchedQueryBuilderWithLocalSchema.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchedQueryBuilderWithRenamedType.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchedQueryWithComputedField.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchingQueryDeepObjectPath.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchingQueryDeepScalarPath.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchingQueryWithArguments.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchingQueryWithFragmentDefinition.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchingQueryWithInlineFragment.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchingQueryWithUnion.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchingQueryWithVariables.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExtendedScalarAsInAndOutputType.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.HttpErrorsHavePathSet.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.Query_WithEnumArgument_EnumIsCorrectlyPassed.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ReplaceField.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.StitchedMutation.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.StitchedMutationWithRenamedFieldArgument.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.StitchedMutationWithRenamedInputField.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.StitchedMutationWithRenamedInputFieldInVariables.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.StitchedMutationWithRenamedInputFieldInVariablesList.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.StitchedMutationWithRenamedInputFieldList.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.StitchedMutationWithRenamedInputType.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ErrorBehaviour.ConnectionLost.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/Errorbehavior.ConnectionLost.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/HttpInterceptorTests.InterceptHttpRequestAndDelegateHeaders.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/InputObjectDelegationTests.AllowInputObjectTypesAsArguments_result.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/InputObjectDelegationTests.AllowInputObjectTypesAsArguments_schema.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Custom_Scalar_Delegated_Argument.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Custom_Scalar_Delegated_Input_Argument.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Custom_Scalar_Delegated_Input_Argument_Unstitched.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Custom_Scalar_Types.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_byte_field.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_date_field.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_date_time_field.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_decimal_field.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_float_field.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_id_field.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_int_field.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_long_field.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_string_field.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/TypeExtensionTests.AddObjectTypeExtension.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/TypeExtensionTests.UseSchemaBuilder.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/TypeRewriterTests.CustomRewriterTakesPriority_result.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/TypeRewriterTests.CustomRewriterTakesPriority_schema.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/TypeSystemDirectivesTests.SourceSchemaHasTypeSystemDirectives_result.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/TypeSystemDirectivesTests.SourceSchemaHasTypeSystemDirectives_schema.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/VariableDelegationTests.ListVariableIsCorrectlyPassed_result.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/VariableDelegationTests.ListVariableIsCorrectlyPassed_schema.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/VariableDelegationTests.ObjectFieldVariableIsCorrectlyPassed_result.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/VariableDelegationTests.ObjectFieldVariableIsCorrectlyPassed_schema.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/VariableDelegationTests.ScopedListVariableIsCorrectlyPassed_result.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/VariableDelegationTests.ScopedListVariableIsCorrectlyPassed_schema.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Requests/BufferedRequestTests.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Requests/MergeRequestHelperTests.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Requests/__snapshots__/MergeRequestHelperTests.Create_BufferedRequest.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Requests/__snapshots__/MergeRequestHelperTests.Create_BufferedRequest_AutoGenerated.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Requests/__snapshots__/MergeRequestHelperTests.Create_BufferedRequest_With_Mixed_Operations.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Requests/__snapshots__/MergeRequestHelperTests.Merge_Requests_With_Variables_On_Directives.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Accounts/AccountsSchemaRequestExecutorBuilderExtensions.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Accounts/Query.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Accounts/User.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Accounts/UserRepository.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/ContractSchemaRequestExecutorBuilderExtensions.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/ContractStorage.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/ContractType.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/CustomDirectiveType.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/IContract.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/LifeInsuranceContract.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/LifeInsuranceContractType.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/Query.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/QueryType.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/SomeOtherContract.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/SomeOtherContractType.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/ComplexInput.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/ComplexInputType.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/Consultant.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/ConsultantType.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/CreateCustomerInput.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/CreateCustomerPayload.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/Customer.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/CustomerKind.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/CustomerOrConsultantType.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/CustomerRepository.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/CustomerResolver.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/CustomerSchemaRequestExecutorBuilderExtensions.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/CustomerType.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/ICustomerOrConsultant.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/Mutation.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/MutationType.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/Query.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/QueryType.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/SayInput.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Inventory/InventoryInfo.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Inventory/InventoryInfoRepository.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Inventory/InventorySchemaRequestExecutorBuilderExtensions.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Inventory/Query.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Products/Product.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Products/ProductRepository.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Products/ProductsSchemaRequestExecutorBuilderExtensions.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Products/Query.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/Author.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/Query.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/Review.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/ReviewRepository.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/ReviewsSchemaRequestExecutorBuilderExtensions.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/SchemaTests.cs create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/SpecialCases/ContractSchemaFactory.txt create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/__snapshots__/SchemaTests.ContractSchemaSnapshot.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/__snapshots__/SchemaTests.CustomerSchemaSnapshot.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/AccountSchemaDefinition.json create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/AdvisorClient.graphql create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/Contract.graphql create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/ContractClient.graphql create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/Customer.graphql create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/DocumentClient.graphql create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/DummyDirective.graphql create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/IntrospectionWithDefaultValues.json create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/MergeQueryWithVariable.graphql create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/SchemaInfoTests_Schema.graphql create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/StarWarsIntrospectionResult.json create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/Stitching.graphql create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/StitchingComputed.graphql create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/StitchingExtensions.graphql create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/StitchingQuery.graphql create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/StitchingQueryWithSkip.graphql create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/StitchingQueryWithTypename.graphql create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_Empty_QueryRequestBuilderException.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_OnlyQueryIsSet_RequestHasOnlyQuery.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndAddProperties_RequestIsCreated.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndAddVariables_RequestIsCreated.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndInitialValue_RequestIsCreated.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndOperation_RequestIsCreated.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndResetOperation_RequestIsCreated.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndResetProperties_RequestIsCreated.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndResetVariables_RequestIsCreated.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndServices_RequestIsCreated.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndSetProperties_RequestIsCreated.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndSetVariables_RequestIsCreated.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_SetAll_RequestIsCreated.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/StitchingBuilderTests.AddExtensionsFromFile.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/StitchingBuilderTests.AddExtensionsFromString.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/StitchingBuilderTests.AddSchema.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/StitchingBuilderTests.AddSchemaFromFile.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/StitchingBuilderTests.AddSchemaFromHttp.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/StitchingBuilderTests.AddSchemaFromString.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/StitchingBuilderTests.AddSchema_2.snap create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/xunit.runner.json delete mode 100644 src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Merge_Directives_2_Single_Document.snap delete mode 100644 src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyRenamingMiddlewareTests.Apply_Local_Rename.snap delete mode 100644 src/HotChocolate/Stitching/test/Stitching.Types.Tests/__snapshots__/DefaultObjectTypeDefinitionMergerTests.Same_Field_In_Each_Schema_Version.snap diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/HotChocolateAuthorizeRequestExecutorBuilder.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/HotChocolateAuthorizeRequestExecutorBuilder.cs index 2d1662c423e..ac39b62530f 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/HotChocolateAuthorizeRequestExecutorBuilder.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/HotChocolateAuthorizeRequestExecutorBuilder.cs @@ -44,11 +44,7 @@ public static class HotChocolateAuthorizeRequestExecutorBuilder var jsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, -#if NET5_0_OR_GREATER DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull -#else - IgnoreNullValues = true -#endif }; jsonOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase, false)); o.JsonSerializerOptions = jsonOptions; diff --git a/src/HotChocolate/Benchmarks/src/Benchmarks.Execution/Program.cs b/src/HotChocolate/Benchmarks/src/Benchmarks.Execution/Program.cs index 63a57a834eb..475b3070d8b 100644 --- a/src/HotChocolate/Benchmarks/src/Benchmarks.Execution/Program.cs +++ b/src/HotChocolate/Benchmarks/src/Benchmarks.Execution/Program.cs @@ -25,11 +25,9 @@ private static async Task Run() Console.WriteLine("Warmup 1"); await queryBench.Sessions_DataLoader_Large().ConfigureAwait(false); - ; Console.WriteLine("Warmup 2"); await queryBench.Sessions_DataLoader_Large().ConfigureAwait(false); - ; Console.WriteLine("Run"); diff --git a/src/HotChocolate/Core/benchmark/Execution.Benchmarks/DefaultExecutionPipelineBenchmark.cs b/src/HotChocolate/Core/benchmark/Execution.Benchmarks/DefaultExecutionPipelineBenchmark.cs index aa509aee7f0..db80501ea49 100644 --- a/src/HotChocolate/Core/benchmark/Execution.Benchmarks/DefaultExecutionPipelineBenchmark.cs +++ b/src/HotChocolate/Core/benchmark/Execution.Benchmarks/DefaultExecutionPipelineBenchmark.cs @@ -138,7 +138,7 @@ public Task LargeQueryFiveParallelRequests() } // var jsonWriter = new HotChocolate.Execution.Serialization.JsonQueryResultSerializer(true); - // Console.WriteLine(jsonWriter.Serialize((IReadOnlyQueryResult)result)); + // Console.WriteLine(jsonWriter.Serialize((IQueryResult)result)); await result.DisposeAsync(); } diff --git a/src/HotChocolate/Core/src/Abstractions/NameString.cs.txt b/src/HotChocolate/Core/src/Abstractions/NameString.cs.txt deleted file mode 100644 index 57e80fdc6ac..00000000000 --- a/src/HotChocolate/Core/src/Abstractions/NameString.cs.txt +++ /dev/null @@ -1,203 +0,0 @@ -using System; -using System.ComponentModel; -using System.Globalization; -using HotChocolate.Language; -using HotChocolate.Properties; - -namespace HotChocolate; - -/// -/// The type name string guarantees that a string adheres to the -/// GraphQL spec rules: /[_A-Za-z][_0-9A-Za-z]*/ -/// -[TypeConverter(typeof(NameStringConverter))] -public readonly struct NameString - : IEquatable - , IComparable -{ - /// - /// Initializes a new instance of the struct. - /// - /// The actual type name string - /// - public NameString(string value) - { - if (!NameUtils.IsValidGraphQLName(value)) - { - throw new ArgumentException( - string.Format(CultureInfo.InvariantCulture, - AbstractionResources.Type_NameIsNotValid, - value ?? "null"), - nameof(value)); - } - Value = value; - } - - /// - /// The name value. - /// - public string Value { get; } - - /// - /// true if the name is not empty - /// - public bool HasValue => !IsEmpty; - - public bool IsEmpty => string.IsNullOrEmpty(Value); - - /// - /// Provides the name string. - /// - /// The name string value - public override string ToString() => Value; - - /// - /// Appends a to this - /// instance and returns a new instance of - /// representing the combined . - /// - /// The combined . - public NameString Add(NameString other) - => new(Value + other.Value); - - /// - /// Compares this value to another value - /// using a specific type. - /// - /// - /// The second for comparison. - /// - /// - /// The type to use. - /// - /// - /// true if both values are equal. - /// - public bool Equals(NameString other, StringComparison comparisonType) - => (!HasValue && !other.HasValue) || string.Equals(Value, other.Value, comparisonType); - - /// - /// Compares this value to another value using - /// comparison type. - /// - /// - /// The second for comparison. - /// - /// - /// true if both values are equal. - /// - public bool Equals(NameString other) => - Equals(other, StringComparison.Ordinal); - - /// - /// Compares this value to another value using - /// comparison. - /// - /// - /// The second for comparison. - /// - /// - /// true if both values are equal. - /// - public override bool Equals(object? obj) - { - if (obj is null) - { - return IsEmpty; - } - return obj is NameString n && Equals(n); - } - -#if NET5_0_OR_GREATER - public int CompareTo(NameString other) - => string.CompareOrdinal(Value, other.Value); -#else - public int CompareTo(NameString other) - => string.Compare(Value, other.Value, StringComparison.Ordinal); -#endif - - /// - /// Serves as a hash function for a object. - /// - /// - /// A hash code for this instance that is suitable for use in hashing - /// algorithms and data structures such as a hash table. - /// - public override int GetHashCode() - => HasValue ? StringComparer.Ordinal.GetHashCode(Value) : 0; - - /// - /// Operator call through to Equals - /// - /// The left parameter - /// The right parameter - /// - /// true if both values are equal. - /// - public static bool operator ==(NameString left, NameString right) - => left.Equals(right); - - /// - /// Operator call through to Equals - /// - /// The left parameter - /// The right parameter - /// - /// true if both values are not equal. - /// - public static bool operator !=(NameString left, NameString right) - => !left.Equals(right); - - /// - /// Concatenates a with a . - /// - /// The left parameter - /// The right parameter - /// The ToString combination of both values - public static string operator +(string left, NameString right) - // This overload exists to prevent the implicit string<->NameString - // converter from trying to call the NameString+NameString operator - // for things that are not name strings. - => string.Concat(left, right.Value); - - /// - /// Concatenates a with a . - /// - /// The left parameter - /// The right parameter - /// The ToString combination of both values - public static string operator +(NameString left, string right) - // This overload exists to prevent the implicit string<->NameString - // converter from trying to call the NameString+NameString operator - // for things that are not name strings. - => string.Concat(left.Value, right); - - /// - /// Operator call through to Add - /// - /// The left parameter - /// The right parameter - /// - /// The combination of both values - /// - public static NameString operator +(NameString left, NameString right) - => left.Add(right); - - /// - /// Implicitly creates a new from - /// the given string. - /// - public static implicit operator NameString(string s) - => ConvertFromString(s); - - /// - /// Implicitly calls ToString(). - /// - public static implicit operator string(NameString name) - => name.ToString(); - - internal static NameString ConvertFromString(string s) - => string.IsNullOrEmpty(s) - ? new NameString() - : new NameString(s); -} diff --git a/src/HotChocolate/Core/src/Execution/Extensions/ExecutionRequestExecutorExtensions.cs b/src/HotChocolate/Core/src/Execution/Extensions/ExecutionRequestExecutorExtensions.cs index 4a42105c67c..2cf9adb3a83 100644 --- a/src/HotChocolate/Core/src/Execution/Extensions/ExecutionRequestExecutorExtensions.cs +++ b/src/HotChocolate/Core/src/Execution/Extensions/ExecutionRequestExecutorExtensions.cs @@ -74,7 +74,7 @@ public static class ExecutionRequestExecutorExtensions public static Task ExecuteAsync( this IRequestExecutor executor, string query, - IReadOnlyDictionary variableValues) + Dictionary variableValues) { if (executor is null) { diff --git a/src/HotChocolate/Core/src/Execution/Processing/Selection.cs b/src/HotChocolate/Core/src/Execution/Processing/Selection.cs index e2fb426a691..6714658c023 100644 --- a/src/HotChocolate/Core/src/Execution/Processing/Selection.cs +++ b/src/HotChocolate/Core/src/Execution/Processing/Selection.cs @@ -6,6 +6,7 @@ using HotChocolate.Language; using HotChocolate.Resolvers; using HotChocolate.Types; +using Microsoft.Extensions.ObjectPool; namespace HotChocolate.Execution.Processing; @@ -46,7 +47,9 @@ public class Selection : ISelection _includeConditions = includeConditions ?? Array.Empty(); - _flags = isInternal ? Flags.Internal : Flags.None; + _flags = isInternal + ? Flags.Internal + : Flags.None; if (Type.IsListType()) { @@ -162,6 +165,7 @@ public bool IsIncluded(long includeFlags, bool allowInternals = false) // if there are flags in most cases we just have one so we can // check the first and optimize for this. var includeCondition = _includeConditions[0]; + if ((includeFlags & includeCondition) == includeCondition) { return !IsInternal || allowInternals; @@ -177,6 +181,7 @@ public bool IsIncluded(long includeFlags, bool allowInternals = false) for (var i = 1; i < _includeConditions.Length; i++) { includeCondition = _includeConditions[i]; + if ((includeFlags & includeCondition) == includeCondition) { return !IsInternal || allowInternals; @@ -247,7 +252,7 @@ internal void AddSelection(FieldNode selectionSyntax, long includeCondition = 0) { var selections = new ISelectionNode[ selectionSet.Selections.Count + - other.SelectionSet.Selections.Count]; + other.SelectionSet.Selections.Count]; var next = 0; for (var i = 0; i < selectionSet.Selections.Count; i++) diff --git a/src/HotChocolate/Core/src/Execution/Properties/InternalsVisibleTo.cs b/src/HotChocolate/Core/src/Execution/Properties/InternalsVisibleTo.cs index 41be87ee15b..cd7f2c603b1 100644 --- a/src/HotChocolate/Core/src/Execution/Properties/InternalsVisibleTo.cs +++ b/src/HotChocolate/Core/src/Execution/Properties/InternalsVisibleTo.cs @@ -2,4 +2,5 @@ [assembly: InternalsVisibleTo("HotChocolate.Execution.Tests")] [assembly: InternalsVisibleTo("HotChocolate.Execution.Benchmarks")] +[assembly: InternalsVisibleTo("HotChocolate.Stitching")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] diff --git a/src/HotChocolate/Core/src/Fetching/DataLoaderParameterExpressionBuilder.cs b/src/HotChocolate/Core/src/Fetching/DataLoaderParameterExpressionBuilder.cs index f70e42e22e9..a221e9f29a3 100644 --- a/src/HotChocolate/Core/src/Fetching/DataLoaderParameterExpressionBuilder.cs +++ b/src/HotChocolate/Core/src/Fetching/DataLoaderParameterExpressionBuilder.cs @@ -7,6 +7,7 @@ namespace HotChocolate.Fetching; +// ReSharper disable once ClassNeverInstantiated.Global public sealed class DataLoaderParameterExpressionBuilder : CustomParameterExpressionBuilder { private static readonly MethodInfo _dataLoader; @@ -29,7 +30,7 @@ public override bool CanHandle(ParameterInfo parameter) public override Expression Build(ParameterInfo parameter, Expression context) { - DataLoaderAttribute? attribute = parameter.GetCustomAttribute(); + var attribute = parameter.GetCustomAttribute(); return string.IsNullOrEmpty(attribute?.Key) ? Expression.Call( diff --git a/src/HotChocolate/Core/src/Fetching/Extensions/DataLoaderResolverContextExtensions.cs b/src/HotChocolate/Core/src/Fetching/Extensions/DataLoaderResolverContextExtensions.cs index 44256402560..564f4b469b7 100644 --- a/src/HotChocolate/Core/src/Fetching/Extensions/DataLoaderResolverContextExtensions.cs +++ b/src/HotChocolate/Core/src/Fetching/Extensions/DataLoaderResolverContextExtensions.cs @@ -10,6 +10,7 @@ #nullable enable +// ReSharper disable once CheckNamespace namespace HotChocolate.Types; public static class DataLoaderResolverContextExtensions @@ -74,8 +75,8 @@ public static class DataLoaderResolverContextExtensions throw new ArgumentNullException(nameof(fetch)); } - IServiceProvider services = context.Services; - IDataLoaderRegistry reg = services.GetRequiredService(); + var services = context.Services; + var reg = services.GetRequiredService(); FetchBatchDataLoader Loader() => new( dataLoaderName ?? "default", @@ -180,8 +181,8 @@ public static class DataLoaderResolverContextExtensions throw new ArgumentNullException(nameof(fetch)); } - IServiceProvider services = context.Services; - IDataLoaderRegistry reg = services.GetRequiredService(); + var services = context.Services; + var reg = services.GetRequiredService(); FetchGroupedDataLoader Loader() => new( dataLoaderName ?? "default", @@ -271,8 +272,8 @@ public static class DataLoaderResolverContextExtensions throw new ArgumentNullException(nameof(fetch)); } - IServiceProvider services = context.Services; - IDataLoaderRegistry reg = services.GetRequiredService(); + var services = context.Services; + var reg = services.GetRequiredService(); FetchCacheDataLoader Loader() => new( key ?? "default", @@ -359,8 +360,8 @@ public static T DataLoader(this IResolverContext context, string key) throw new ArgumentNullException(nameof(key)); } - IServiceProvider services = context.Services; - IDataLoaderRegistry reg = services.GetRequiredService(); + var services = context.Services; + var reg = services.GetRequiredService(); return reg.GetOrRegister(key, () => CreateDataLoader(services)); } @@ -373,15 +374,15 @@ public static T DataLoader(this IResolverContext context) throw new ArgumentNullException(nameof(context)); } - IServiceProvider services = context.Services; - IDataLoaderRegistry reg = services.GetRequiredService(); + var services = context.Services; + var reg = services.GetRequiredService(); return reg.GetOrRegister(() => CreateDataLoader(services)); } private static T CreateDataLoader(IServiceProvider services) where T : IDataLoader { - T registeredDataLoader = services.GetService(); + var registeredDataLoader = services.GetService(); if (registeredDataLoader is null) { diff --git a/src/HotChocolate/Core/src/Fetching/Extensions/ObjectFieldDataLoaderExtensions.cs b/src/HotChocolate/Core/src/Fetching/Extensions/ObjectFieldDataLoaderExtensions.cs index 038c59a28ec..bfd2f2f0dd4 100644 --- a/src/HotChocolate/Core/src/Fetching/Extensions/ObjectFieldDataLoaderExtensions.cs +++ b/src/HotChocolate/Core/src/Fetching/Extensions/ObjectFieldDataLoaderExtensions.cs @@ -12,6 +12,7 @@ #nullable enable +// ReSharper disable once CheckNamespace namespace HotChocolate.Types; public static class DataLoaderObjectFieldExtensions @@ -27,7 +28,7 @@ public static class DataLoaderObjectFieldExtensions { FieldMiddlewareDefinition placeholder = new(_ => _ => default, key: DataLoader); - if (!TryGetDataLoaderTypes(dataLoaderType, out Type? keyType, out Type? valueType)) + if (!TryGetDataLoaderTypes(dataLoaderType, out var keyType, out var valueType)) { throw DataLoader_InvalidType(dataLoaderType); } @@ -42,7 +43,7 @@ public static class DataLoaderObjectFieldExtensions IExtendedType schemaType; if (!valueType.IsArray) { - IExtendedType resolverType = + var resolverType = c.TypeInspector.GetType(definition.ResultType!); schemaType = c.TypeInspector.GetType(resolverType.IsArrayOrList @@ -94,7 +95,7 @@ public static class DataLoaderObjectFieldExtensions .MakeGenericType(dataLoaderType, keyType, valueType); } - FieldMiddleware middleware = FieldClassMiddlewareFactory.Create(middlewareType); + var middleware = FieldClassMiddlewareFactory.Create(middlewareType); var index = definition.MiddlewareDefinitions.IndexOf(placeholder); definition.MiddlewareDefinitions[index] = new(middleware, key: DataLoader); } @@ -104,11 +105,11 @@ public static class DataLoaderObjectFieldExtensions [NotNullWhen(true)] out Type? key, [NotNullWhen(true)] out Type? value) { - foreach (Type interfaceType in type.GetInterfaces()) + foreach (var interfaceType in type.GetInterfaces()) { if (interfaceType.IsGenericType) { - Type typeDefinition = interfaceType.GetGenericTypeDefinition(); + var typeDefinition = interfaceType.GetGenericTypeDefinition(); if (typeof(IDataLoader<,>) == typeDefinition) { key = interfaceType.GetGenericArguments()[0]; @@ -136,13 +137,13 @@ public GroupedDataLoaderMiddleware(FieldDelegate next) public async Task InvokeAsync(IMiddlewareContext context) { - TDataLoader dataloader = context.DataLoader(); + var dataloader = context.DataLoader(); await _next(context).ConfigureAwait(false); if (context.Result is IReadOnlyCollection values) { - IReadOnlyList data = await dataloader + var data = await dataloader .LoadAsync(values, context.RequestAborted) .ConfigureAwait(false); @@ -179,7 +180,7 @@ public DataLoaderMiddleware(FieldDelegate next) public async Task InvokeAsync(IMiddlewareContext context) { - TDataLoader dataloader = context.DataLoader(); + var dataloader = context.DataLoader(); await _next(context).ConfigureAwait(false); diff --git a/src/HotChocolate/Core/src/Types/Resolvers/IResolverContext.cs b/src/HotChocolate/Core/src/Types/Resolvers/IResolverContext.cs index 3f00a50cb30..ac1a594df5d 100644 --- a/src/HotChocolate/Core/src/Types/Resolvers/IResolverContext.cs +++ b/src/HotChocolate/Core/src/Types/Resolvers/IResolverContext.cs @@ -37,7 +37,7 @@ public interface IResolverContext : IPureResolverContext /// resolvers to store and retrieve data during execution scoped to the /// hierarchy. /// - IImmutableDictionary ScopedContextData { get; set; } + new IImmutableDictionary ScopedContextData { get; set; } /// /// The local context data dictionary can be used by middlewares and diff --git a/src/HotChocolate/Core/src/Types/Types/FieldCollection.cs b/src/HotChocolate/Core/src/Types/Types/FieldCollection.cs index 4fdc3562433..de9d833ec29 100644 --- a/src/HotChocolate/Core/src/Types/Types/FieldCollection.cs +++ b/src/HotChocolate/Core/src/Types/Types/FieldCollection.cs @@ -7,7 +7,7 @@ namespace HotChocolate.Types; -public class FieldCollection : IFieldCollection where T : class, IField +public sealed class FieldCollection : IFieldCollection where T : class, IField { private readonly Dictionary _fieldsLookup; private readonly T[] _fields; @@ -79,7 +79,7 @@ public FieldEnumerator(T[] fields) public T Current { get; private set; } = default!; - object? IEnumerator.Current => Current; + object IEnumerator.Current => Current; public bool MoveNext() { diff --git a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorComparableTests.Create_ShortNullableIn_Expression_NET7_0.snap b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorComparableTests.Create_ShortNullableIn_Expression_NET7_0.snap index 3c995fc3596..54355f3ca88 100644 --- a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorComparableTests.Create_ShortNullableIn_Expression_NET7_0.snap +++ b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorComparableTests.Create_ShortNullableIn_Expression_NET7_0.snap @@ -64,5 +64,5 @@ WHERE "d"."BarShort" IN (13, 14) --------------- SELECT "d"."Id", "d"."BarShort" FROM "Data" AS "d" -WHERE "d"."BarShort" = 13 OR "d"."BarShort" IS NULL +WHERE "d"."BarShort" = 13 OR ("d"."BarShort" IS NULL) --------------- diff --git a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorComparableTests.Create_ShortNullableNotEqual_Expression_NET7_0.snap b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorComparableTests.Create_ShortNullableNotEqual_Expression_NET7_0.snap index b3b7074d1e5..5eef8b4803a 100644 --- a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorComparableTests.Create_ShortNullableNotEqual_Expression_NET7_0.snap +++ b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorComparableTests.Create_ShortNullableNotEqual_Expression_NET7_0.snap @@ -23,7 +23,7 @@ SELECT "d"."Id", "d"."BarShort" FROM "Data" AS "d" -WHERE "d"."BarShort" <> @__p_0 OR "d"."BarShort" IS NULL +WHERE "d"."BarShort" <> @__p_0 OR ("d"."BarShort" IS NULL) --------------- 13 Result: @@ -51,7 +51,7 @@ WHERE "d"."BarShort" <> @__p_0 OR "d"."BarShort" IS NULL SELECT "d"."Id", "d"."BarShort" FROM "Data" AS "d" -WHERE "d"."BarShort" <> @__p_0 OR "d"."BarShort" IS NULL +WHERE "d"."BarShort" <> @__p_0 OR ("d"."BarShort" IS NULL) --------------- null Result: diff --git a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorComparableTests.Create_ShortNullableNotIn_Expression_NET7_0.snap b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorComparableTests.Create_ShortNullableNotIn_Expression_NET7_0.snap index 79d6d075a3a..87d54ac87eb 100644 --- a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorComparableTests.Create_ShortNullableNotIn_Expression_NET7_0.snap +++ b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorComparableTests.Create_ShortNullableNotIn_Expression_NET7_0.snap @@ -18,7 +18,7 @@ --------------- SELECT "d"."Id", "d"."BarShort" FROM "Data" AS "d" -WHERE "d"."BarShort" NOT IN (12, 13) OR "d"."BarShort" IS NULL +WHERE "d"."BarShort" NOT IN (12, 13) OR ("d"."BarShort" IS NULL) --------------- 13and14 Result: @@ -41,7 +41,7 @@ WHERE "d"."BarShort" NOT IN (12, 13) OR "d"."BarShort" IS NULL --------------- SELECT "d"."Id", "d"."BarShort" FROM "Data" AS "d" -WHERE "d"."BarShort" NOT IN (13, 14) OR "d"."BarShort" IS NULL +WHERE "d"."BarShort" NOT IN (13, 14) OR ("d"."BarShort" IS NULL) --------------- 13andNull Result: @@ -64,5 +64,5 @@ WHERE "d"."BarShort" NOT IN (13, 14) OR "d"."BarShort" IS NULL --------------- SELECT "d"."Id", "d"."BarShort" FROM "Data" AS "d" -WHERE "d"."BarShort" <> 13 AND "d"."BarShort" IS NOT NULL +WHERE "d"."BarShort" <> 13 AND ("d"."BarShort" IS NOT NULL) --------------- diff --git a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorEnumTests.Create_NullableEnumIn_Expression_NET7_0.snap b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorEnumTests.Create_NullableEnumIn_Expression_NET7_0.snap index 81c46827556..573a3ca4549 100644 --- a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorEnumTests.Create_NullableEnumIn_Expression_NET7_0.snap +++ b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorEnumTests.Create_NullableEnumIn_Expression_NET7_0.snap @@ -61,5 +61,5 @@ nullAndFoo SQL: --------------- SELECT "d"."Id", "d"."BarEnum" FROM "Data" AS "d" -WHERE "d"."BarEnum" = 0 OR "d"."BarEnum" IS NULL +WHERE "d"."BarEnum" = 0 OR ("d"."BarEnum" IS NULL) --------------- diff --git a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorEnumTests.Create_NullableEnumNotEqual_Expression_NET7_0.snap b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorEnumTests.Create_NullableEnumNotEqual_Expression_NET7_0.snap index 944014046d2..a441f406122 100644 --- a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorEnumTests.Create_NullableEnumNotEqual_Expression_NET7_0.snap +++ b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorEnumTests.Create_NullableEnumNotEqual_Expression_NET7_0.snap @@ -26,7 +26,7 @@ BAR SQL: SELECT "d"."Id", "d"."BarEnum" FROM "Data" AS "d" -WHERE "d"."BarEnum" <> @__p_0 OR "d"."BarEnum" IS NULL +WHERE "d"."BarEnum" <> @__p_0 OR ("d"."BarEnum" IS NULL) --------------- FOO Result: @@ -57,7 +57,7 @@ FOO SQL: SELECT "d"."Id", "d"."BarEnum" FROM "Data" AS "d" -WHERE "d"."BarEnum" <> @__p_0 OR "d"."BarEnum" IS NULL +WHERE "d"."BarEnum" <> @__p_0 OR ("d"."BarEnum" IS NULL) --------------- null Result: diff --git a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorEnumTests.Create_NullableEnumNotIn_Expression_NET7_0.snap b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorEnumTests.Create_NullableEnumNotIn_Expression_NET7_0.snap index 1f89fa29530..d84aa9a8e3e 100644 --- a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorEnumTests.Create_NullableEnumNotIn_Expression_NET7_0.snap +++ b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorEnumTests.Create_NullableEnumNotIn_Expression_NET7_0.snap @@ -21,7 +21,7 @@ BarAndFoo SQL: --------------- SELECT "d"."Id", "d"."BarEnum" FROM "Data" AS "d" -WHERE "d"."BarEnum" NOT IN (1, 0) OR "d"."BarEnum" IS NULL +WHERE "d"."BarEnum" NOT IN (1, 0) OR ("d"."BarEnum" IS NULL) --------------- FOO Result: @@ -50,7 +50,7 @@ FOO SQL: --------------- SELECT "d"."Id", "d"."BarEnum" FROM "Data" AS "d" -WHERE "d"."BarEnum" <> 0 OR "d"."BarEnum" IS NULL +WHERE "d"."BarEnum" <> 0 OR ("d"."BarEnum" IS NULL) --------------- nullAndFoo Result: @@ -76,5 +76,5 @@ nullAndFoo SQL: --------------- SELECT "d"."Id", "d"."BarEnum" FROM "Data" AS "d" -WHERE "d"."BarEnum" <> 0 AND "d"."BarEnum" IS NOT NULL +WHERE "d"."BarEnum" <> 0 AND ("d"."BarEnum" IS NOT NULL) --------------- diff --git a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorListTests.Create_ArrayAllObjectStringEqual_Expression_CustomAllow_NET7_0.snap b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorListTests.Create_ArrayAllObjectStringEqual_Expression_CustomAllow_NET7_0.snap index 5d6f8c0f35a..5272280dae6 100644 --- a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorListTests.Create_ArrayAllObjectStringEqual_Expression_CustomAllow_NET7_0.snap +++ b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorListTests.Create_ArrayAllObjectStringEqual_Expression_CustomAllow_NET7_0.snap @@ -30,7 +30,7 @@ FROM "Data" AS "d" WHERE NOT EXISTS ( SELECT 1 FROM "FooNested" AS "f" - WHERE "d"."Id" = "f"."FooId" AND ("f"."Bar" <> @__p_0 OR "f"."Bar" IS NULL)) + WHERE "d"."Id" = "f"."FooId" AND ("f"."Bar" <> @__p_0 OR ("f"."Bar" IS NULL))) --------------- d Result: @@ -51,7 +51,7 @@ FROM "Data" AS "d" WHERE NOT EXISTS ( SELECT 1 FROM "FooNested" AS "f" - WHERE "d"."Id" = "f"."FooId" AND ("f"."Bar" <> @__p_0 OR "f"."Bar" IS NULL)) + WHERE "d"."Id" = "f"."FooId" AND ("f"."Bar" <> @__p_0 OR ("f"."Bar" IS NULL))) --------------- null Result: @@ -70,5 +70,5 @@ FROM "Data" AS "d" WHERE NOT EXISTS ( SELECT 1 FROM "FooNested" AS "f" - WHERE "d"."Id" = "f"."FooId" AND "f"."Bar" IS NOT NULL) + WHERE "d"."Id" = "f"."FooId" AND ("f"."Bar" IS NOT NULL)) --------------- diff --git a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorListTests.Create_ArrayAllObjectStringEqual_Expression_NET7_0.snap b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorListTests.Create_ArrayAllObjectStringEqual_Expression_NET7_0.snap index 5d6f8c0f35a..5272280dae6 100644 --- a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorListTests.Create_ArrayAllObjectStringEqual_Expression_NET7_0.snap +++ b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorListTests.Create_ArrayAllObjectStringEqual_Expression_NET7_0.snap @@ -30,7 +30,7 @@ FROM "Data" AS "d" WHERE NOT EXISTS ( SELECT 1 FROM "FooNested" AS "f" - WHERE "d"."Id" = "f"."FooId" AND ("f"."Bar" <> @__p_0 OR "f"."Bar" IS NULL)) + WHERE "d"."Id" = "f"."FooId" AND ("f"."Bar" <> @__p_0 OR ("f"."Bar" IS NULL))) --------------- d Result: @@ -51,7 +51,7 @@ FROM "Data" AS "d" WHERE NOT EXISTS ( SELECT 1 FROM "FooNested" AS "f" - WHERE "d"."Id" = "f"."FooId" AND ("f"."Bar" <> @__p_0 OR "f"."Bar" IS NULL)) + WHERE "d"."Id" = "f"."FooId" AND ("f"."Bar" <> @__p_0 OR ("f"."Bar" IS NULL))) --------------- null Result: @@ -70,5 +70,5 @@ FROM "Data" AS "d" WHERE NOT EXISTS ( SELECT 1 FROM "FooNested" AS "f" - WHERE "d"."Id" = "f"."FooId" AND "f"."Bar" IS NOT NULL) + WHERE "d"."Id" = "f"."FooId" AND ("f"."Bar" IS NOT NULL)) --------------- diff --git a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorListTests.Create_ArrayNoneObjectStringEqual_Expression_NET7_0.snap b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorListTests.Create_ArrayNoneObjectStringEqual_Expression_NET7_0.snap index b78bf1a0dee..2e406a3f6ea 100644 --- a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorListTests.Create_ArrayNoneObjectStringEqual_Expression_NET7_0.snap +++ b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorListTests.Create_ArrayNoneObjectStringEqual_Expression_NET7_0.snap @@ -163,5 +163,5 @@ FROM "Data" AS "d" WHERE NOT (EXISTS ( SELECT 1 FROM "FooNested" AS "f" - WHERE "d"."Id" = "f"."FooId" AND "f"."Bar" IS NULL)) + WHERE "d"."Id" = "f"."FooId" AND ("f"."Bar" IS NULL))) --------------- diff --git a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorListTests.Create_ArraySomeObjectStringEqualWithNull_Expression_NET7_0.snap b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorListTests.Create_ArraySomeObjectStringEqualWithNull_Expression_NET7_0.snap index c29562a4db0..53984b1028d 100644 --- a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorListTests.Create_ArraySomeObjectStringEqualWithNull_Expression_NET7_0.snap +++ b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorListTests.Create_ArraySomeObjectStringEqualWithNull_Expression_NET7_0.snap @@ -150,5 +150,5 @@ FROM "Data" AS "d" WHERE EXISTS ( SELECT 1 FROM "FooNested" AS "f" - WHERE "d"."Id" = "f"."FooId" AND "f"."Bar" IS NULL) + WHERE "d"."Id" = "f"."FooId" AND ("f"."Bar" IS NULL)) --------------- diff --git a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorObjectTests.Create_ObjectNullableEnumIn_Expression_NET7_0.snap b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorObjectTests.Create_ObjectNullableEnumIn_Expression_NET7_0.snap index a2b98c66259..7d678f3c178 100644 --- a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorObjectTests.Create_ObjectNullableEnumIn_Expression_NET7_0.snap +++ b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorObjectTests.Create_ObjectNullableEnumIn_Expression_NET7_0.snap @@ -74,5 +74,5 @@ nullAndFoo SQL: SELECT "d"."Id", "d"."FooId" FROM "Data" AS "d" LEFT JOIN "FooNullable" AS "f" ON "d"."FooId" = "f"."Id" -WHERE "f"."BarEnum" = 0 OR "f"."BarEnum" IS NULL +WHERE "f"."BarEnum" = 0 OR ("f"."BarEnum" IS NULL) --------------- diff --git a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorObjectTests.Create_ObjectNullableShortIn_Expression_NET7_0.snap b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorObjectTests.Create_ObjectNullableShortIn_Expression_NET7_0.snap index 4d6b526af12..31e48b2a439 100644 --- a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorObjectTests.Create_ObjectNullableShortIn_Expression_NET7_0.snap +++ b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorObjectTests.Create_ObjectNullableShortIn_Expression_NET7_0.snap @@ -94,5 +94,5 @@ WHERE "f"."BarShort" IN (13, 14) SELECT "d"."Id", "d"."FooId" FROM "Data" AS "d" LEFT JOIN "FooNullable" AS "f" ON "d"."FooId" = "f"."Id" -WHERE "f"."BarShort" = 13 OR "f"."BarShort" IS NULL +WHERE "f"."BarShort" = 13 OR ("f"."BarShort" IS NULL) --------------- diff --git a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringContains_Expression_NET7_0.snap b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringContains_Expression_NET7_0.snap index 9e5adf030cd..49d3f8a2060 100644 --- a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringContains_Expression_NET7_0.snap +++ b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringContains_Expression_NET7_0.snap @@ -17,7 +17,7 @@ a SQL: SELECT "d"."Id", "d"."Bar" FROM "Data" AS "d" -WHERE "d"."Bar" IS NOT NULL AND (@__p_0 = '' OR instr("d"."Bar", @__p_0) > 0) +WHERE ("d"."Bar" IS NOT NULL) AND (@__p_0 = '' OR instr("d"."Bar", @__p_0) > 0) --------------- b Result: @@ -39,7 +39,7 @@ b SQL: SELECT "d"."Id", "d"."Bar" FROM "Data" AS "d" -WHERE "d"."Bar" IS NOT NULL AND (@__p_0 = '' OR instr("d"."Bar", @__p_0) > 0) +WHERE ("d"."Bar" IS NOT NULL) AND (@__p_0 = '' OR instr("d"."Bar", @__p_0) > 0) --------------- null diff --git a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringEndsWith_Expression_NET7_0.snap b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringEndsWith_Expression_NET7_0.snap index 218abf10539..ed69058e16b 100644 --- a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringEndsWith_Expression_NET7_0.snap +++ b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringEndsWith_Expression_NET7_0.snap @@ -17,7 +17,7 @@ atest SQL: SELECT "d"."Id", "d"."Bar" FROM "Data" AS "d" -WHERE "d"."Bar" IS NOT NULL AND (@__p_0 = '' OR substr("d"."Bar", -length(@__p_0)) = @__p_0 OR @__p_0 = '') +WHERE ("d"."Bar" IS NOT NULL) AND (@__p_0 = '' OR substr("d"."Bar", -length(@__p_0)) = @__p_0 OR @__p_0 = '') --------------- btest Result: @@ -39,7 +39,7 @@ btest SQL: SELECT "d"."Id", "d"."Bar" FROM "Data" AS "d" -WHERE "d"."Bar" IS NOT NULL AND (@__p_0 = '' OR substr("d"."Bar", -length(@__p_0)) = @__p_0 OR @__p_0 = '') +WHERE ("d"."Bar" IS NOT NULL) AND (@__p_0 = '' OR substr("d"."Bar", -length(@__p_0)) = @__p_0 OR @__p_0 = '') --------------- null diff --git a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringIn_Expression_NET7_0.snap b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringIn_Expression_NET7_0.snap index f06accb8139..b08dc4ef220 100644 --- a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringIn_Expression_NET7_0.snap +++ b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringIn_Expression_NET7_0.snap @@ -41,7 +41,7 @@ testbtestAndNull SQL: --------------- SELECT "d"."Id", "d"."Bar" FROM "Data" AS "d" -WHERE "d"."Bar" = 'testbtest' OR "d"."Bar" IS NULL +WHERE "d"."Bar" = 'testbtest' OR ("d"."Bar" IS NULL) --------------- testatest Result: diff --git a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringNoContains_Expression_NET7_0.snap b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringNoContains_Expression_NET7_0.snap index fac0647e374..d4f74c6d66e 100644 --- a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringNoContains_Expression_NET7_0.snap +++ b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringNoContains_Expression_NET7_0.snap @@ -20,7 +20,7 @@ a SQL: SELECT "d"."Id", "d"."Bar" FROM "Data" AS "d" -WHERE "d"."Bar" IS NULL OR NOT (@__p_0 = '' OR instr("d"."Bar", @__p_0) > 0) +WHERE ("d"."Bar" IS NULL) OR NOT (@__p_0 = '' OR instr("d"."Bar", @__p_0) > 0) --------------- b Result: @@ -45,7 +45,7 @@ b SQL: SELECT "d"."Id", "d"."Bar" FROM "Data" AS "d" -WHERE "d"."Bar" IS NULL OR NOT (@__p_0 = '' OR instr("d"."Bar", @__p_0) > 0) +WHERE ("d"."Bar" IS NULL) OR NOT (@__p_0 = '' OR instr("d"."Bar", @__p_0) > 0) --------------- null diff --git a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringNotEndsWith_Expression_NET7_0.snap b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringNotEndsWith_Expression_NET7_0.snap index 37cacbdc317..65b93a20ee8 100644 --- a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringNotEndsWith_Expression_NET7_0.snap +++ b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringNotEndsWith_Expression_NET7_0.snap @@ -20,7 +20,7 @@ atest SQL: SELECT "d"."Id", "d"."Bar" FROM "Data" AS "d" -WHERE "d"."Bar" IS NULL OR (@__p_0 <> '' AND substr("d"."Bar", -length(@__p_0)) <> @__p_0 AND @__p_0 <> '') +WHERE ("d"."Bar" IS NULL) OR (@__p_0 <> '' AND substr("d"."Bar", -length(@__p_0)) <> @__p_0 AND @__p_0 <> '') --------------- btest Result: @@ -45,7 +45,7 @@ btest SQL: SELECT "d"."Id", "d"."Bar" FROM "Data" AS "d" -WHERE "d"."Bar" IS NULL OR (@__p_0 <> '' AND substr("d"."Bar", -length(@__p_0)) <> @__p_0 AND @__p_0 <> '') +WHERE ("d"."Bar" IS NULL) OR (@__p_0 <> '' AND substr("d"."Bar", -length(@__p_0)) <> @__p_0 AND @__p_0 <> '') --------------- null diff --git a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringNotEqual_Expression_NET7_0.snap b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringNotEqual_Expression_NET7_0.snap index 53b3004c365..de1be6d3f2d 100644 --- a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringNotEqual_Expression_NET7_0.snap +++ b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringNotEqual_Expression_NET7_0.snap @@ -20,7 +20,7 @@ testatest SQL: SELECT "d"."Id", "d"."Bar" FROM "Data" AS "d" -WHERE "d"."Bar" <> @__p_0 OR "d"."Bar" IS NULL +WHERE "d"."Bar" <> @__p_0 OR ("d"."Bar" IS NULL) --------------- testbtest Result: @@ -45,7 +45,7 @@ testbtest SQL: SELECT "d"."Id", "d"."Bar" FROM "Data" AS "d" -WHERE "d"."Bar" <> @__p_0 OR "d"."Bar" IS NULL +WHERE "d"."Bar" <> @__p_0 OR ("d"."Bar" IS NULL) --------------- null Result: diff --git a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringNotIn_Expression_NET7_0.snap b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringNotIn_Expression_NET7_0.snap index 22d7f0ab28e..47664990538 100644 --- a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringNotIn_Expression_NET7_0.snap +++ b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringNotIn_Expression_NET7_0.snap @@ -15,7 +15,7 @@ testatestAndtestb SQL: --------------- SELECT "d"."Id", "d"."Bar" FROM "Data" AS "d" -WHERE "d"."Bar" NOT IN ('testatest', 'testbtest') OR "d"."Bar" IS NULL +WHERE "d"."Bar" NOT IN ('testatest', 'testbtest') OR ("d"."Bar" IS NULL) --------------- testbtestAndNull Result: @@ -35,7 +35,7 @@ testbtestAndNull SQL: --------------- SELECT "d"."Id", "d"."Bar" FROM "Data" AS "d" -WHERE "d"."Bar" <> 'testbtest' AND "d"."Bar" IS NOT NULL +WHERE "d"."Bar" <> 'testbtest' AND ("d"."Bar" IS NOT NULL) --------------- testatest Result: @@ -58,5 +58,5 @@ testatest SQL: --------------- SELECT "d"."Id", "d"."Bar" FROM "Data" AS "d" -WHERE "d"."Bar" <> 'testatest' OR "d"."Bar" IS NULL +WHERE "d"."Bar" <> 'testatest' OR ("d"."Bar" IS NULL) --------------- diff --git a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringNotStartsWith_Expression_NET7_0.snap b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringNotStartsWith_Expression_NET7_0.snap index bc5e00b6840..b763a55a9f1 100644 --- a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringNotStartsWith_Expression_NET7_0.snap +++ b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringNotStartsWith_Expression_NET7_0.snap @@ -20,7 +20,7 @@ testa SQL: SELECT "d"."Id", "d"."Bar" FROM "Data" AS "d" -WHERE "d"."Bar" IS NULL OR (@__p_0 <> '' AND (NOT ("d"."Bar" LIKE @__p_0 || '%') OR substr("d"."Bar", 1, length(@__p_0)) <> @__p_0) AND @__p_0 <> '') +WHERE ("d"."Bar" IS NULL) OR (@__p_0 <> '' AND (NOT ("d"."Bar" LIKE @__p_0 || '%') OR substr("d"."Bar", 1, length(@__p_0)) <> @__p_0) AND @__p_0 <> '') --------------- testb Result: @@ -45,7 +45,7 @@ testb SQL: SELECT "d"."Id", "d"."Bar" FROM "Data" AS "d" -WHERE "d"."Bar" IS NULL OR (@__p_0 <> '' AND (NOT ("d"."Bar" LIKE @__p_0 || '%') OR substr("d"."Bar", 1, length(@__p_0)) <> @__p_0) AND @__p_0 <> '') +WHERE ("d"."Bar" IS NULL) OR (@__p_0 <> '' AND (NOT ("d"."Bar" LIKE @__p_0 || '%') OR substr("d"."Bar", 1, length(@__p_0)) <> @__p_0) AND @__p_0 <> '') --------------- null diff --git a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringStartsWith_Expression_NET7_0.snap b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringStartsWith_Expression_NET7_0.snap index 7e86531136f..33bd84ca56e 100644 --- a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringStartsWith_Expression_NET7_0.snap +++ b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/__snapshots__/QueryableFilterVisitorStringTests.Create_NullableStringStartsWith_Expression_NET7_0.snap @@ -17,7 +17,7 @@ testa SQL: SELECT "d"."Id", "d"."Bar" FROM "Data" AS "d" -WHERE "d"."Bar" IS NOT NULL AND (@__p_0 = '' OR (("d"."Bar" LIKE @__p_0 || '%') AND substr("d"."Bar", 1, length(@__p_0)) = @__p_0) OR @__p_0 = '') +WHERE ("d"."Bar" IS NOT NULL) AND (@__p_0 = '' OR (("d"."Bar" LIKE @__p_0 || '%') AND substr("d"."Bar", 1, length(@__p_0)) = @__p_0) OR @__p_0 = '') --------------- testb Result: @@ -39,7 +39,7 @@ testb SQL: SELECT "d"."Id", "d"."Bar" FROM "Data" AS "d" -WHERE "d"."Bar" IS NOT NULL AND (@__p_0 = '' OR (("d"."Bar" LIKE @__p_0 || '%') AND substr("d"."Bar", 1, length(@__p_0)) = @__p_0) OR @__p_0 = '') +WHERE ("d"."Bar" IS NOT NULL) AND (@__p_0 = '' OR (("d"."Bar" LIKE @__p_0 || '%') AND substr("d"."Bar", 1, length(@__p_0)) = @__p_0) OR @__p_0 = '') --------------- null diff --git a/src/HotChocolate/Data/test/Data.Sorting.InMemory.Tests/__snapshots__/QueryableSortVisitorVariablesTests.Integration_Create_Boolean_OrderBy.snap b/src/HotChocolate/Data/test/Data.Sorting.InMemory.Tests/__snapshots__/QueryableSortVisitorVariablesTests.Integration_Create_Boolean_OrderBy.snap index 798e018358e..f42bc79c5af 100644 --- a/src/HotChocolate/Data/test/Data.Sorting.InMemory.Tests/__snapshots__/QueryableSortVisitorVariablesTests.Integration_Create_Boolean_OrderBy.snap +++ b/src/HotChocolate/Data/test/Data.Sorting.InMemory.Tests/__snapshots__/QueryableSortVisitorVariablesTests.Integration_Create_Boolean_OrderBy.snap @@ -1,7 +1,7 @@ ASC --------------- { - "ContentType": "application/json; charset=utf-8", + "ContentType": "application/graphql-response+json; charset=utf-8", "StatusCode": "OK", "Data": { "root": [ @@ -21,7 +21,7 @@ ASC DESC --------------- { - "ContentType": "application/json; charset=utf-8", + "ContentType": "application/graphql-response+json; charset=utf-8", "StatusCode": "OK", "Data": { "root": [ diff --git a/src/HotChocolate/Data/test/Data.Sorting.InMemory.Tests/__snapshots__/QueryableSortVisitorVariablesTests.Integration_Create_Boolean_OrderBy_NonNull.snap b/src/HotChocolate/Data/test/Data.Sorting.InMemory.Tests/__snapshots__/QueryableSortVisitorVariablesTests.Integration_Create_Boolean_OrderBy_NonNull.snap index 798e018358e..f42bc79c5af 100644 --- a/src/HotChocolate/Data/test/Data.Sorting.InMemory.Tests/__snapshots__/QueryableSortVisitorVariablesTests.Integration_Create_Boolean_OrderBy_NonNull.snap +++ b/src/HotChocolate/Data/test/Data.Sorting.InMemory.Tests/__snapshots__/QueryableSortVisitorVariablesTests.Integration_Create_Boolean_OrderBy_NonNull.snap @@ -1,7 +1,7 @@ ASC --------------- { - "ContentType": "application/json; charset=utf-8", + "ContentType": "application/graphql-response+json; charset=utf-8", "StatusCode": "OK", "Data": { "root": [ @@ -21,7 +21,7 @@ ASC DESC --------------- { - "ContentType": "application/json; charset=utf-8", + "ContentType": "application/graphql-response+json; charset=utf-8", "StatusCode": "OK", "Data": { "root": [ diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/BindingList.cs b/src/HotChocolate/Fusion/src/Stitching.Types/BindingList.cs similarity index 80% rename from src/HotChocolate/Stitching/src/Stitching.Types/BindingList.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/BindingList.cs index 91504000dd3..846ded0dd04 100644 --- a/src/HotChocolate/Stitching/src/Stitching.Types/BindingList.cs +++ b/src/HotChocolate/Fusion/src/Stitching.Types/BindingList.cs @@ -18,7 +18,7 @@ public void Add(IBinding item) { _count++; - if (!_bindings.TryGetValue(item.Target, out IBinding[]? bindings)) + if (!_bindings.TryGetValue(item.Target, out var bindings)) { bindings = new[] { item }; _bindings.Add(item.Target, bindings); @@ -38,7 +38,7 @@ public bool Remove(IBinding item) SchemaCoordinate target, [NotNullWhen(true)] out IReadOnlyList? bindings) { - if (_bindings.TryGetValue(target, out IBinding[]? b)) + if (_bindings.TryGetValue(target, out var b)) { bindings = b; return true; @@ -50,7 +50,7 @@ public bool Remove(IBinding item) public bool Contains(IBinding item) { - if (_bindings.TryGetValue(item.Target, out IBinding[]? bindings)) + if (_bindings.TryGetValue(item.Target, out var bindings)) { return bindings.Length is 0 ? bindings[0].Equals(item) @@ -65,9 +65,9 @@ public bool Contains(IBinding item) public void CopyTo(IBinding[] array, int arrayIndex) { var i = arrayIndex; - foreach (IBinding[] bindings in _bindings.Values) + foreach (var bindings in _bindings.Values) { - foreach (IBinding binding in bindings) + foreach (var binding in bindings) { array[i++] = binding; } @@ -76,9 +76,9 @@ public void CopyTo(IBinding[] array, int arrayIndex) public void CopyTo(BindingList bindings) { - foreach (KeyValuePair item in _bindings) + foreach (var item in _bindings) { - if (!bindings._bindings.TryGetValue(item.Key, out IBinding[]? b)) + if (!bindings._bindings.TryGetValue(item.Key, out var b)) { var copy = new IBinding[item.Value.Length]; Array.Copy(item.Value, 0, copy, 0, copy.Length); @@ -97,9 +97,9 @@ public void CopyTo(BindingList bindings) public IEnumerator GetEnumerator() { - foreach (IBinding[] bindings in _bindings.Values) + foreach (var bindings in _bindings.Values) { - foreach (IBinding binding in bindings) + foreach (var binding in bindings) { yield return binding; } diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/Bindings/DependenciesBinding.cs b/src/HotChocolate/Fusion/src/Stitching.Types/Bindings/DependenciesBinding.cs similarity index 100% rename from src/HotChocolate/Stitching/src/Stitching.Types/Bindings/DependenciesBinding.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/Bindings/DependenciesBinding.cs diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/Bindings/FetchBinding.cs b/src/HotChocolate/Fusion/src/Stitching.Types/Bindings/FetchBinding.cs similarity index 100% rename from src/HotChocolate/Stitching/src/Stitching.Types/Bindings/FetchBinding.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/Bindings/FetchBinding.cs diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/Bindings/SourceBinding.cs b/src/HotChocolate/Fusion/src/Stitching.Types/Bindings/SourceBinding.cs similarity index 100% rename from src/HotChocolate/Stitching/src/Stitching.Types/Bindings/SourceBinding.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/Bindings/SourceBinding.cs diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/DefaultObjectTypeDefinitionMerger.cs b/src/HotChocolate/Fusion/src/Stitching.Types/DefaultObjectTypeDefinitionMerger.cs similarity index 83% rename from src/HotChocolate/Stitching/src/Stitching.Types/DefaultObjectTypeDefinitionMerger.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/DefaultObjectTypeDefinitionMerger.cs index 8db70e482bd..2f8e2870b42 100644 --- a/src/HotChocolate/Stitching/src/Stitching.Types/DefaultObjectTypeDefinitionMerger.cs +++ b/src/HotChocolate/Fusion/src/Stitching.Types/DefaultObjectTypeDefinitionMerger.cs @@ -10,13 +10,13 @@ protected override void MergeInto(ObjectTypeDefinition source, ObjectTypeDefinit var processed = new HashSet(); var temp = new List(); - foreach (FieldDefinitionNode field in target.Definition.Fields) + foreach (var field in target.Definition.Fields) { temp.Add(field); processed.Add(field.Name.Value); } - foreach (FieldDefinitionNode targetField in source.Definition.Fields) + foreach (var targetField in source.Definition.Fields) { if (processed.Add(targetField.Name.Value)) { diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/HotChocolate.Stitching.Types.csproj b/src/HotChocolate/Fusion/src/Stitching.Types/HotChocolate.Stitching.Types.csproj similarity index 100% rename from src/HotChocolate/Stitching/src/Stitching.Types/HotChocolate.Stitching.Types.csproj rename to src/HotChocolate/Fusion/src/Stitching.Types/HotChocolate.Stitching.Types.csproj diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/IBinding.cs b/src/HotChocolate/Fusion/src/Stitching.Types/IBinding.cs similarity index 100% rename from src/HotChocolate/Stitching/src/Stitching.Types/IBinding.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/IBinding.cs diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/ITypeDefinition.cs b/src/HotChocolate/Fusion/src/Stitching.Types/ITypeDefinition.cs similarity index 100% rename from src/HotChocolate/Stitching/src/Stitching.Types/ITypeDefinition.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/ITypeDefinition.cs diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/ITypeDefinitionMerger.cs b/src/HotChocolate/Fusion/src/Stitching.Types/ITypeDefinitionMerger.cs similarity index 100% rename from src/HotChocolate/Stitching/src/Stitching.Types/ITypeDefinitionMerger.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/ITypeDefinitionMerger.cs diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/ObjectTypeDefinition.cs b/src/HotChocolate/Fusion/src/Stitching.Types/ObjectTypeDefinition.cs similarity index 90% rename from src/HotChocolate/Stitching/src/Stitching.Types/ObjectTypeDefinition.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/ObjectTypeDefinition.cs index 418988212b1..4e7c80b1184 100644 --- a/src/HotChocolate/Stitching/src/Stitching.Types/ObjectTypeDefinition.cs +++ b/src/HotChocolate/Fusion/src/Stitching.Types/ObjectTypeDefinition.cs @@ -14,7 +14,7 @@ public ObjectTypeDefinition(ObjectTypeDefinitionNode definition, string schemaNa Definition = definition ?? throw new ArgumentNullException(nameof(definition)); Name = definition.Name.Value; - foreach (FieldDefinitionNode field in Definition.Fields) + foreach (var field in Definition.Fields) { Bindings.Add(new SourceBinding(new(Name, field.Name.Value), schemaName)); } @@ -45,7 +45,7 @@ public ObjectTypeDefinition(ObjectTypeDefinitionNode definition, string schemaNa try { - ObjectTypeDefinitionNode definitionNode = ParseObjectTypeDefinition(s); + var definitionNode = ParseObjectTypeDefinition(s); definition = new ObjectTypeDefinition(definitionNode, schemaName); return true; } diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyExtensions/ApplyComplexTypeExtension.cs b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyExtensions/ApplyComplexTypeExtension.cs similarity index 89% rename from src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyExtensions/ApplyComplexTypeExtension.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyExtensions/ApplyComplexTypeExtension.cs index 77eaafe4105..da7b64d0426 100644 --- a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyExtensions/ApplyComplexTypeExtension.cs +++ b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyExtensions/ApplyComplexTypeExtension.cs @@ -13,9 +13,9 @@ internal abstract class ApplyComplexTypeExtension { protected override TDef Apply(TDef definition, TExt extension) { - IReadOnlyList directives = definition.Directives; - IReadOnlyList interfaces = definition.Interfaces; - IReadOnlyList fields = definition.Fields; + var directives = definition.Directives; + var interfaces = definition.Interfaces; + var fields = definition.Fields; if (extension.Directives.Count > 0) { @@ -29,7 +29,7 @@ protected override TDef Apply(TDef definition, TExt extension) var names = definition.Interfaces.Select(t => t.Name.Value).ToHashSet(); List? temp = null; - foreach (NamedTypeNode type in extension.Interfaces) + foreach (var type in extension.Interfaces) { if (names.Add(type.Name.Value)) { @@ -51,14 +51,14 @@ protected override TDef Apply(TDef definition, TExt extension) t))); var touched = false; - foreach (FieldDefinitionNode extensionField in extension.Fields) + foreach (var extensionField in extension.Fields) { // By default extensions would only allow to insert new fields. // We also use extensions as a vehicle to annotate fields. // So if we see that there is already a field we will try to merge it. - if (map.TryGetValue(extensionField.Name.Value, out FieldDefinitionNode? field)) + if (map.TryGetValue(extensionField.Name.Value, out var field)) { - FieldDefinitionNode temp = + var temp = ApplyExtensions(definition.Name.Value, field, extensionField); if (!touched) @@ -111,8 +111,8 @@ protected override TDef Apply(TDef definition, TExt extension) throw ApplyExtensionsMiddleware_ArgumentCountMismatch(typeName, definition, extension); } - IReadOnlyList arguments = definition.Arguments; - IReadOnlyList directives = definition.Directives; + var arguments = definition.Arguments; + var directives = definition.Directives; if (definition.Arguments.Count > 0) { @@ -120,8 +120,8 @@ protected override TDef Apply(TDef definition, TExt extension) for (var i = 0; i < definition.Arguments.Count; i++) { - InputValueDefinitionNode arg = definition.Arguments[i]; - InputValueDefinitionNode argExt = definition.Arguments[i]; + var arg = definition.Arguments[i]; + var argExt = definition.Arguments[i]; if (!arg.Name.Equals(argExt.Name, SyntaxComparison.Syntax)) { diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyExtensions/ApplyExtension.cs b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyExtensions/ApplyExtension.cs similarity index 100% rename from src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyExtensions/ApplyExtension.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyExtensions/ApplyExtension.cs diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyExtensions/ApplyExtensionsMiddleware.cs b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyExtensions/ApplyExtensionsMiddleware.cs similarity index 92% rename from src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyExtensions/ApplyExtensionsMiddleware.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyExtensions/ApplyExtensionsMiddleware.cs index a6ff033d00f..9b221625742 100644 --- a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyExtensions/ApplyExtensionsMiddleware.cs +++ b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyExtensions/ApplyExtensionsMiddleware.cs @@ -29,11 +29,11 @@ public async ValueTask InvokeAsync(ISchemaMergeContext context) for (var i = 0; i < context.Documents.Count; i++) { - Document document = context.Documents[i]; + var document = context.Documents[i]; CollectTypeDefinitions(definitions, extensions, document.SyntaxTree); CollectTypeExtensions(extensions, document.SyntaxTree); - DocumentNode rewritten = ApplyExtensions(definitions, extensions); + var rewritten = ApplyExtensions(definitions, extensions); document = new Document(document.Name, rewritten); context.Documents = context.Documents.SetItem(i, document); @@ -49,7 +49,7 @@ public async ValueTask InvokeAsync(ISchemaMergeContext context) List extensions, DocumentNode document) { - foreach (IDefinitionNode definition in document.Definitions) + foreach (var definition in document.Definitions) { if (definition is ITypeSystemDefinitionNode typeDef) { @@ -75,7 +75,7 @@ public async ValueTask InvokeAsync(ISchemaMergeContext context) List extensions, DocumentNode document) { - foreach (IDefinitionNode definition in document.Definitions) + foreach (var definition in document.Definitions) { if (definition is ITypeSystemExtensionNode typeExt) { @@ -90,7 +90,7 @@ public async ValueTask InvokeAsync(ISchemaMergeContext context) { var preserved = new List(); - foreach (ITypeSystemExtensionNode extension in extensions) + foreach (var extension in extensions) { if (extension is SchemaExtensionNode schemaExt) { @@ -122,7 +122,7 @@ public async ValueTask InvokeAsync(ISchemaMergeContext context) Dictionary definitions, SchemaExtensionNode extension) { - if (definitions.TryGetValue(_schema, out ITypeSystemDefinitionNode? def) && + if (definitions.TryGetValue(_schema, out var def) && def is SchemaDefinitionNode schemaDef) { var directives = schemaDef.Directives.ToList(); @@ -143,12 +143,12 @@ public async ValueTask InvokeAsync(ISchemaMergeContext context) Dictionary definitions, ITypeExtensionNode extension) { - if (definitions.TryGetValue(extension.Name.Value, out ITypeSystemDefinitionNode? node) && + if (definitions.TryGetValue(extension.Name.Value, out var node) && node is ITypeDefinitionNode typeDef) { for (var i = 0; i < _applyExtensions.Length; i++) { - ITypeDefinitionNode? merged = _applyExtensions[i].TryApply(typeDef, extension); + var merged = _applyExtensions[i].TryApply(typeDef, extension); if (merged is not null) { diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyExtensions/ApplyInterfaceTypeExtension.cs b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyExtensions/ApplyInterfaceTypeExtension.cs similarity index 100% rename from src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyExtensions/ApplyInterfaceTypeExtension.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyExtensions/ApplyInterfaceTypeExtension.cs diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyExtensions/ApplyObjectTypeExtension.cs b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyExtensions/ApplyObjectTypeExtension.cs similarity index 100% rename from src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyExtensions/ApplyObjectTypeExtension.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyExtensions/ApplyObjectTypeExtension.cs diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyExtensions/IApplyExtension.cs b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyExtensions/IApplyExtension.cs similarity index 100% rename from src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyExtensions/IApplyExtension.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyExtensions/IApplyExtension.cs diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyMissingBindings/ApplyMissingBindingsMiddleware.cs b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyMissingBindings/ApplyMissingBindingsMiddleware.cs similarity index 88% rename from src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyMissingBindings/ApplyMissingBindingsMiddleware.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyMissingBindings/ApplyMissingBindingsMiddleware.cs index a017c2510b2..0a246de9ba5 100644 --- a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyMissingBindings/ApplyMissingBindingsMiddleware.cs +++ b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyMissingBindings/ApplyMissingBindingsMiddleware.cs @@ -17,8 +17,8 @@ public async ValueTask InvokeAsync(ISchemaMergeContext context) { for (var i = 0; i < context.Documents.Count; i++) { - Document document = context.Documents[i]; - DocumentNode syntaxTree = document.SyntaxTree; + var document = context.Documents[i]; + var syntaxTree = document.SyntaxTree; var bindContext = new BindingContext(document.Name); syntaxTree = (DocumentNode)_rewriter.Rewrite(syntaxTree, bindContext); diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyMissingBindings/BindingContext.cs b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyMissingBindings/BindingContext.cs similarity index 100% rename from src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyMissingBindings/BindingContext.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyMissingBindings/BindingContext.cs diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyMissingBindings/BindingRewriter.cs b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyMissingBindings/BindingRewriter.cs similarity index 100% rename from src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyMissingBindings/BindingRewriter.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyMissingBindings/BindingRewriter.cs diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyRenaming/ApplyRenamingMiddleware.cs b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyRenaming/ApplyRenamingMiddleware.cs similarity index 97% rename from src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyRenaming/ApplyRenamingMiddleware.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyRenaming/ApplyRenamingMiddleware.cs index e6177222ef4..15aba9dce30 100644 --- a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyRenaming/ApplyRenamingMiddleware.cs +++ b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyRenaming/ApplyRenamingMiddleware.cs @@ -20,7 +20,7 @@ public async ValueTask InvokeAsync(ISchemaMergeContext context) { for (var i = 0; i < context.Documents.Count; i++) { - Document doc = context.Documents[i]; + var doc = context.Documents[i]; var renameContext = new RenameContext(doc.Name); _indexer.Visit(doc.SyntaxTree, renameContext); diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyRenaming/RenameContext.cs b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyRenaming/RenameContext.cs similarity index 100% rename from src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyRenaming/RenameContext.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyRenaming/RenameContext.cs diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyRenaming/RenameIndexer.cs b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyRenaming/RenameIndexer.cs similarity index 94% rename from src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyRenaming/RenameIndexer.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyRenaming/RenameIndexer.cs index 46c6a102478..a05ba179030 100644 --- a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyRenaming/RenameIndexer.cs +++ b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyRenaming/RenameIndexer.cs @@ -68,7 +68,7 @@ protected override ISyntaxVisitorAction Enter(ISyntaxNode node, RenameContext co case FieldDefinitionNode field when TryGetRenameInformation(field.Directives, out var dn, out var to): - SchemaCoordinateNode coordinateNode = context.Navigator.CreateCoordinate(); + var coordinateNode = context.Navigator.CreateCoordinate(); context.RenamedFields[coordinateNode] = new RenameInfo(to, dn); break; } @@ -96,8 +96,8 @@ protected override ISyntaxVisitorAction Leave(ISyntaxNode node, RenameContext co for (var i = 0; i < directives.Count; i++) { - DirectiveNode node = directives[0]; - if (RenameDirective.TryParse(node, out RenameDirective? rename)) + var node = directives[0]; + if (RenameDirective.TryParse(node, out var rename)) { directive = node; to = rename.To; diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyRenaming/RenameInfo.cs b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyRenaming/RenameInfo.cs similarity index 100% rename from src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyRenaming/RenameInfo.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyRenaming/RenameInfo.cs diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyRenaming/RenameRewriter.cs b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyRenaming/RenameRewriter.cs similarity index 90% rename from src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyRenaming/RenameRewriter.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyRenaming/RenameRewriter.cs index 93581f1178f..c47ebdd6ec6 100644 --- a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ApplyRenaming/RenameRewriter.cs +++ b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ApplyRenaming/RenameRewriter.cs @@ -33,7 +33,7 @@ protected override void OnLeave(ISyntaxNode node, RenameContext context) return default; } - if (context.RenamedTypes.TryGetValue(originalName, out RenameInfo? _)) + if (context.RenamedTypes.TryGetValue(originalName, out var _)) { rewrittenNode = ApplyBindDirective( rewrittenNode, @@ -57,7 +57,7 @@ protected override void OnLeave(ISyntaxNode node, RenameContext context) return default; } - if (context.RenamedTypes.TryGetValue(originalName, out RenameInfo? _)) + if (context.RenamedTypes.TryGetValue(originalName, out var _)) { rewrittenNode = ApplyBindDirective( rewrittenNode, @@ -81,7 +81,7 @@ protected override void OnLeave(ISyntaxNode node, RenameContext context) return default; } - if (context.RenamedTypes.TryGetValue(originalName, out RenameInfo? _)) + if (context.RenamedTypes.TryGetValue(originalName, out var _)) { rewrittenNode = ApplyBindDirective( rewrittenNode, @@ -105,7 +105,7 @@ protected override void OnLeave(ISyntaxNode node, RenameContext context) return default; } - if (context.RenamedTypes.TryGetValue(originalName, out RenameInfo? _)) + if (context.RenamedTypes.TryGetValue(originalName, out var _)) { rewrittenNode = ApplyBindDirective( rewrittenNode, @@ -129,7 +129,7 @@ protected override void OnLeave(ISyntaxNode node, RenameContext context) return default; } - if (context.RenamedTypes.TryGetValue(originalName, out RenameInfo? _)) + if (context.RenamedTypes.TryGetValue(originalName, out var _)) { rewrittenNode = ApplyBindDirective( rewrittenNode, @@ -153,7 +153,7 @@ protected override void OnLeave(ISyntaxNode node, RenameContext context) return default; } - if (context.RenamedTypes.TryGetValue(originalName, out RenameInfo? _)) + if (context.RenamedTypes.TryGetValue(originalName, out var _)) { rewrittenNode = ApplyBindDirective( rewrittenNode, @@ -175,7 +175,7 @@ protected override void OnLeave(ISyntaxNode node, RenameContext context) return default; } - if (context.Navigator.TryPeek(1, out ISyntaxNode? parent) && + if (context.Navigator.TryPeek(1, out var parent) && parent.Kind is SyntaxKind.ObjectTypeDefinition or InterfaceTypeDefinition && context.TypesWithFieldRenames.Contains(((INamedSyntaxNode)parent).Name.Value)) { @@ -206,7 +206,7 @@ protected override void OnLeave(ISyntaxNode node, RenameContext context) protected override NameNode RewriteName(NameNode node, RenameContext context) { - if (!context.Navigator.TryPeek(1, out ISyntaxNode? parent)) + if (!context.Navigator.TryPeek(1, out var parent)) { return base.RewriteName(node, context); } @@ -218,12 +218,12 @@ InputObjectTypeDefinition or EnumTypeDefinition or ScalarTypeDefinition or NamedType && - context.RenamedTypes.TryGetValue(node.Value, out RenameInfo? value)) + context.RenamedTypes.TryGetValue(node.Value, out var value)) { return node.WithValue(value.Name); } - if (!context.Navigator.TryPeek(2, out ISyntaxNode? grandParent)) + if (!context.Navigator.TryPeek(2, out var grandParent)) { return base.RewriteName(node, context); } @@ -258,7 +258,7 @@ ScalarTypeDefinition or var copy = node.Directives.ToList(); - foreach (DirectiveNode directive in node.Directives) + foreach (var directive in node.Directives) { if (RenameDirective.IsOfType(directive)) { diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/BindDirective.cs b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/BindDirective.cs similarity index 96% rename from src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/BindDirective.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/BindDirective.cs index 23de4f34d66..918dced00d6 100644 --- a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/BindDirective.cs +++ b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/BindDirective.cs @@ -29,7 +29,7 @@ public static bool TryParse(DirectiveNode syntax, [NotNullWhen(true)] out BindDi string? toValue = null; string? asValue = null; - foreach (ArgumentNode argument in syntax.Arguments) + foreach (var argument in syntax.Arguments) { switch (argument.Name.Value) { @@ -82,7 +82,7 @@ public static bool IsOfType(DirectiveNode syntax) bool hasTo = false, hasAs = false; - foreach (ArgumentNode argument in syntax.Arguments) + foreach (var argument in syntax.Arguments) { if (argument.Name.Value.EqualsOrdinal("to") && argument.Value is StringValueNode { Value.Length: > 0 }) diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ISchemaMergeContext.cs b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ISchemaMergeContext.cs similarity index 100% rename from src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ISchemaMergeContext.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ISchemaMergeContext.cs diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/MergeSchema.cs b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/MergeSchema.cs similarity index 100% rename from src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/MergeSchema.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/MergeSchema.cs diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/MergeSchemaMiddleware.cs b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/MergeSchemaMiddleware.cs similarity index 100% rename from src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/MergeSchemaMiddleware.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/MergeSchemaMiddleware.cs diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/PrepareDocuments/PrepareDocumentsMiddleware.cs b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/PrepareDocuments/PrepareDocumentsMiddleware.cs similarity index 85% rename from src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/PrepareDocuments/PrepareDocumentsMiddleware.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/PrepareDocuments/PrepareDocumentsMiddleware.cs index 7a9c5b80918..a892f7e0edf 100644 --- a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/PrepareDocuments/PrepareDocumentsMiddleware.cs +++ b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/PrepareDocuments/PrepareDocumentsMiddleware.cs @@ -16,15 +16,15 @@ public PrepareDocumentsMiddleware(MergeSchema next) public async ValueTask InvokeAsync(ISchemaMergeContext context) { - foreach (ServiceConfiguration configuration in context.Configurations) + foreach (var configuration in context.Configurations) { var definitions = new List(); RegisterServiceInfo(definitions, configuration); - foreach (DocumentNode document in configuration.Documents) + foreach (var document in configuration.Documents) { - foreach (IDefinitionNode definition in document.Definitions) + foreach (var definition in document.Definitions) { definitions.Add(definition); } diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/PrepareDocuments/SquashDocumentsMiddleware.cs b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/PrepareDocuments/SquashDocumentsMiddleware.cs similarity index 92% rename from src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/PrepareDocuments/SquashDocumentsMiddleware.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/PrepareDocuments/SquashDocumentsMiddleware.cs index 2487541c134..450b0d83aa9 100644 --- a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/PrepareDocuments/SquashDocumentsMiddleware.cs +++ b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/PrepareDocuments/SquashDocumentsMiddleware.cs @@ -18,7 +18,7 @@ public async ValueTask InvokeAsync(ISchemaMergeContext context) { var definitions = new List(); - foreach (Document document in context.Documents) + foreach (var document in context.Documents) { definitions.AddRange(document.SyntaxTree.Definitions); } diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/RenameDirective.cs b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/RenameDirective.cs similarity index 92% rename from src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/RenameDirective.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/RenameDirective.cs index 2687c2a9a6f..1f89fe549db 100644 --- a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/RenameDirective.cs +++ b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/RenameDirective.cs @@ -21,7 +21,7 @@ public RenameDirective(string to) { if (syntax.Arguments.Count == 1) { - ArgumentNode argument = syntax.Arguments[0]; + var argument = syntax.Arguments[0]; if (argument.Name.Value.EqualsOrdinal("to") && argument.Value is StringValueNode sv && sv.Value.Length > 0) @@ -42,7 +42,7 @@ public static bool IsOfType(DirectiveNode syntax) { if (syntax.Arguments.Count == 1) { - ArgumentNode argument = syntax.Arguments[0]; + var argument = syntax.Arguments[0]; if (argument.Name.Value.EqualsOrdinal("to")) { return true; diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/SchemaMergeContext.cs b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/SchemaMergeContext.cs similarity index 100% rename from src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/SchemaMergeContext.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/SchemaMergeContext.cs diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/SchemaMergePipelineBuilder.cs b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/SchemaMergePipelineBuilder.cs similarity index 100% rename from src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/SchemaMergePipelineBuilder.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/SchemaMergePipelineBuilder.cs diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ServiceConfiguration.cs b/src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ServiceConfiguration.cs similarity index 100% rename from src/HotChocolate/Stitching/src/Stitching.Types/Pipeline/ServiceConfiguration.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/Pipeline/ServiceConfiguration.cs diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/ThrowHelper.cs b/src/HotChocolate/Fusion/src/Stitching.Types/ThrowHelper.cs similarity index 100% rename from src/HotChocolate/Stitching/src/Stitching.Types/ThrowHelper.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/ThrowHelper.cs diff --git a/src/HotChocolate/Stitching/src/Stitching.Types/TypeDefinitionMerger.cs b/src/HotChocolate/Fusion/src/Stitching.Types/TypeDefinitionMerger.cs similarity index 100% rename from src/HotChocolate/Stitching/src/Stitching.Types/TypeDefinitionMerger.cs rename to src/HotChocolate/Fusion/src/Stitching.Types/TypeDefinitionMerger.cs diff --git a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/DefaultObjectTypeDefinitionMergerTests.cs b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/DefaultObjectTypeDefinitionMergerTests.cs similarity index 90% rename from src/HotChocolate/Stitching/test/Stitching.Types.Tests/DefaultObjectTypeDefinitionMergerTests.cs rename to src/HotChocolate/Fusion/test/Stitching.Types.Tests/DefaultObjectTypeDefinitionMergerTests.cs index 6c5748e99b9..f3fc93e45de 100644 --- a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/DefaultObjectTypeDefinitionMergerTests.cs +++ b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/DefaultObjectTypeDefinitionMergerTests.cs @@ -1,6 +1,5 @@ +using CookieCrumble; using HotChocolate.Stitching.Types.Bindings; -using Snapshooter.Xunit; -using Xunit; namespace HotChocolate.Stitching.Types; @@ -15,14 +14,14 @@ public void Merge_Simple_Type() a: String }", "Abc", - out ObjectTypeDefinition? a); + out var a); ObjectTypeDefinition.TryParse( @"type Foo { b: String }", "Def", - out ObjectTypeDefinition? b); + out var b); // act var merger = new DefaultObjectTypeDefinitionMerger(); @@ -45,14 +44,14 @@ public void Same_Field_In_Each_Schema_Version() a: String }", "Abc", - out ObjectTypeDefinition? a); + out var a); ObjectTypeDefinition.TryParse( @"type Foo { a: String }", "Def", - out ObjectTypeDefinition? b); + out var b); // act var merger = new DefaultObjectTypeDefinitionMerger(); diff --git a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/DefaultObjectTypeDefinitionTests.cs b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/DefaultObjectTypeDefinitionTests.cs similarity index 91% rename from src/HotChocolate/Stitching/test/Stitching.Types.Tests/DefaultObjectTypeDefinitionTests.cs rename to src/HotChocolate/Fusion/test/Stitching.Types.Tests/DefaultObjectTypeDefinitionTests.cs index 6df12742178..2d9b641f523 100644 --- a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/DefaultObjectTypeDefinitionTests.cs +++ b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/DefaultObjectTypeDefinitionTests.cs @@ -14,7 +14,7 @@ public void TryParse() a: String }", "Abc", - out ObjectTypeDefinition? definition); + out var definition); Assert.True(success); Assert.NotNull(definition); diff --git a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/HotChocolate.Stitching.Types.Tests.csproj b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/HotChocolate.Stitching.Types.Tests.csproj similarity index 100% rename from src/HotChocolate/Stitching/test/Stitching.Types.Tests/HotChocolate.Stitching.Types.Tests.csproj rename to src/HotChocolate/Fusion/test/Stitching.Types.Tests/HotChocolate.Stitching.Types.Tests.csproj diff --git a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/ApplyExtensionsMiddlewareTests.cs b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/ApplyExtensionsMiddlewareTests.cs similarity index 84% rename from src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/ApplyExtensionsMiddlewareTests.cs rename to src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/ApplyExtensionsMiddlewareTests.cs index 1ca6e2e764a..26c0fed6c43 100644 --- a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/ApplyExtensionsMiddlewareTests.cs +++ b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/ApplyExtensionsMiddlewareTests.cs @@ -1,10 +1,6 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using CookieCrumble; using HotChocolate.Stitching.Types.Pipeline.ApplyExtensions; using HotChocolate.Stitching.Types.Pipeline.PrepareDocuments; -using Snapshooter.Xunit; -using Xunit; using static HotChocolate.Language.Utf8GraphQLParser; namespace HotChocolate.Stitching.Types.Pipeline; @@ -15,7 +11,7 @@ public class ApplyExtensionsMiddlewareTests public async Task Apply_Object_Extension_Single_Document() { // arrange - MergeSchema pipeline = CreatePipeline(); + var pipeline = CreatePipeline(); var service = new ServiceConfiguration( "abc", @@ -34,14 +30,14 @@ public async Task Apply_Object_Extension_Single_Document() await pipeline(context); // assert - context.Documents.Single().SyntaxTree.ToString().MatchSnapshot(); + context.Documents.Single().SyntaxTree.MatchSnapshot(); } [Fact] public async Task Apply_Object_Extension_Is_Preserved() { // arrange - MergeSchema pipeline = CreatePipeline(); + var pipeline = CreatePipeline(); var service = new ServiceConfiguration( "abc", @@ -60,14 +56,14 @@ public async Task Apply_Object_Extension_Is_Preserved() await pipeline(context); // assert - context.Documents.Single().SyntaxTree.ToString().MatchSnapshot(); + context.Documents.Single().SyntaxTree.MatchSnapshot(); } [Fact] public async Task Apply_Object_Extension_Merge_Field_Directives_Single_Document() { // arrange - MergeSchema pipeline = CreatePipeline(); + var pipeline = CreatePipeline(); var service = new ServiceConfiguration( "abc", @@ -86,14 +82,14 @@ public async Task Apply_Object_Extension_Merge_Field_Directives_Single_Document( await pipeline(context); // assert - context.Documents.Single().SyntaxTree.ToString().MatchSnapshot(); + context.Documents.Single().SyntaxTree.MatchSnapshot(); } [Fact] public async Task Apply_Object_Extension_Merge_Directives_Single_Document() { // arrange - MergeSchema pipeline = CreatePipeline(); + var pipeline = CreatePipeline(); var service = new ServiceConfiguration( "abc", @@ -110,14 +106,14 @@ public async Task Apply_Object_Extension_Merge_Directives_Single_Document() await pipeline(context); // assert - context.Documents.Single().SyntaxTree.ToString().MatchSnapshot(); + context.Documents.Single().SyntaxTree.MatchSnapshot(); } [Fact] public async Task Apply_Object_Extension_Merge_Directives_2_Single_Document() { // arrange - MergeSchema pipeline = CreatePipeline(); + var pipeline = CreatePipeline(); var service = new ServiceConfiguration( "abc", @@ -134,14 +130,14 @@ public async Task Apply_Object_Extension_Merge_Directives_2_Single_Document() await pipeline(context); // assert - context.Documents.Single().SyntaxTree.ToString().MatchSnapshot(); + context.Documents.Single().SyntaxTree.MatchSnapshot(); } [Fact(Skip = "This needs to be fixed.")] public async Task Apply_Object_Extension_Field_Type_Mismatch() { // arrange - MergeSchema pipeline = CreatePipeline(); + var pipeline = CreatePipeline(); var service = new ServiceConfiguration( "abc", @@ -167,7 +163,7 @@ public async Task Apply_Object_Extension_Field_Type_Mismatch() public async Task Apply_Local_Remove() { // arrange - MergeSchema pipeline = CreatePipeline(); + var pipeline = CreatePipeline(); var service = new ServiceConfiguration( "abc", @@ -186,7 +182,7 @@ public async Task Apply_Local_Remove() await pipeline(context); // assert - context.Documents.Single().SyntaxTree.ToString().MatchSnapshot(); + context.Documents.Single().SyntaxTree.MatchSnapshot(); } private MergeSchema CreatePipeline() diff --git a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/ApplyLocalRenamingMiddlewareTests.cs b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/ApplyLocalRenamingMiddlewareTests.cs similarity index 86% rename from src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/ApplyLocalRenamingMiddlewareTests.cs rename to src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/ApplyLocalRenamingMiddlewareTests.cs index 8e95e500d7d..576866cfbe8 100644 --- a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/ApplyLocalRenamingMiddlewareTests.cs +++ b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/ApplyLocalRenamingMiddlewareTests.cs @@ -1,12 +1,8 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using CookieCrumble; using HotChocolate.Language; using HotChocolate.Stitching.Types.Pipeline.ApplyExtensions; using HotChocolate.Stitching.Types.Pipeline.ApplyRenaming; using HotChocolate.Stitching.Types.Pipeline.PrepareDocuments; -using Snapshooter.Xunit; -using Xunit; namespace HotChocolate.Stitching.Types.Pipeline; @@ -16,7 +12,7 @@ public class ApplyLocalRenamingMiddlewareTests public async Task Apply_Local_Rename() { // arrange - MergeSchema pipeline = CreatePipeline(); + var pipeline = CreatePipeline(); var service = new ServiceConfiguration( "abc", @@ -38,14 +34,14 @@ public async Task Apply_Local_Rename() await pipeline.Invoke(context); // assert - context.Documents.Single().SyntaxTree.ToString().MatchSnapshot(); + context.Documents.Single().SyntaxTree.MatchSnapshot(); } [Fact] public async Task Apply_Local_Rename_Interface_Name() { // arrange - MergeSchema pipeline = CreatePipeline(); + var pipeline = CreatePipeline(); var service = new ServiceConfiguration( "abc", @@ -67,14 +63,14 @@ interface IFoo { await pipeline.Invoke(context); // assert - context.Documents.Single().SyntaxTree.ToString().MatchSnapshot(); + context.Documents.Single().SyntaxTree.MatchSnapshot(); } [Fact] public async Task Apply_Local_Rename_Object_And_Refactor_Usages() { // arrange - MergeSchema pipeline = CreatePipeline(); + var pipeline = CreatePipeline(); var service = new ServiceConfiguration( "abc", @@ -96,14 +92,14 @@ public async Task Apply_Local_Rename_Object_And_Refactor_Usages() await pipeline.Invoke(context); // assert - context.Documents.Single().SyntaxTree.ToString().MatchSnapshot(); + context.Documents.Single().SyntaxTree.MatchSnapshot(); } [Fact] public async Task Apply_Local_Rename_Scalar_And_Update_Usages() { // arrange - MergeSchema pipeline = CreatePipeline(); + var pipeline = CreatePipeline(); var service = new ServiceConfiguration( "abc", @@ -132,14 +128,14 @@ public async Task Apply_Local_Rename_Scalar_And_Update_Usages() await pipeline.Invoke(context); // assert - context.Documents.Single().SyntaxTree.ToString().MatchSnapshot(); + context.Documents.Single().SyntaxTree.MatchSnapshot(); } [Fact] public async Task Apply_Local_Rename_Interface_Field() { // arrange - MergeSchema pipeline = CreatePipeline(); + var pipeline = CreatePipeline(); var service = new ServiceConfiguration( "SchemaName", @@ -163,14 +159,14 @@ interface IFoo { await pipeline.Invoke(context); // assert - context.Documents.Single().SyntaxTree.ToString().MatchSnapshot(); + context.Documents.Single().SyntaxTree.MatchSnapshot(); } [Fact] public async Task Apply_Local_Rename_Interface_Field_2() { // arrange - MergeSchema pipeline = CreatePipeline(); + var pipeline = CreatePipeline(); var service = new ServiceConfiguration( "SchemaName", @@ -200,7 +196,7 @@ interface IFooExt implements IFoo { await pipeline.Invoke(context); // assert - context.Documents.Single().SyntaxTree.ToString().MatchSnapshot(); + context.Documents.Single().SyntaxTree.MatchSnapshot(); } private MergeSchema CreatePipeline() diff --git a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/ApplyMissingBindingsMiddlewareTests.cs b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/ApplyMissingBindingsMiddlewareTests.cs similarity index 89% rename from src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/ApplyMissingBindingsMiddlewareTests.cs rename to src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/ApplyMissingBindingsMiddlewareTests.cs index 08bcd12383c..853afb17350 100644 --- a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/ApplyMissingBindingsMiddlewareTests.cs +++ b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/ApplyMissingBindingsMiddlewareTests.cs @@ -1,13 +1,9 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using CookieCrumble; using HotChocolate.Language; using HotChocolate.Stitching.Types.Pipeline.ApplyExtensions; using HotChocolate.Stitching.Types.Pipeline.ApplyMissingBindings; using HotChocolate.Stitching.Types.Pipeline.ApplyRenaming; using HotChocolate.Stitching.Types.Pipeline.PrepareDocuments; -using Snapshooter.Xunit; -using Xunit; namespace HotChocolate.Stitching.Types.Pipeline; @@ -17,7 +13,7 @@ public class ApplyMissingBindingsMiddlewareTests public async Task Apply_Missing_Bindings() { // arrange - MergeSchema pipeline = CreatePipeline(); + var pipeline = CreatePipeline(); var service = new ServiceConfiguration( "SchemaName", @@ -47,7 +43,7 @@ interface IFooExt implements IFoo { await pipeline.Invoke(context); // assert - context.Documents.Single().SyntaxTree.ToString().MatchSnapshot(); + context.Documents.Single().SyntaxTree.MatchSnapshot(); } private MergeSchema CreatePipeline() diff --git a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/FullPipelineTests.cs b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/FullPipelineTests.cs similarity index 70% rename from src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/FullPipelineTests.cs rename to src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/FullPipelineTests.cs index 3caf2aecf84..b3f540b04f7 100644 --- a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/FullPipelineTests.cs +++ b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/FullPipelineTests.cs @@ -1,13 +1,5 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using CookieCrumble; using HotChocolate.Language; -using HotChocolate.Stitching.Types.Pipeline.ApplyExtensions; -using HotChocolate.Stitching.Types.Pipeline.ApplyMissingBindings; -using HotChocolate.Stitching.Types.Pipeline.ApplyRenaming; -using HotChocolate.Stitching.Types.Pipeline.PrepareDocuments; -using Snapshooter.Xunit; -using Xunit; namespace HotChocolate.Stitching.Types.Pipeline; @@ -17,7 +9,7 @@ public class FullPipelineTests public async Task Apply_Local_Remove() { // arrange - MergeSchema pipeline = CreatePipeline(); + var pipeline = CreatePipeline(); var serviceA = new ServiceConfiguration( "ServiceA", @@ -50,7 +42,7 @@ public async Task Apply_Local_Remove() await pipeline(context); // assert - context.Documents.Single().SyntaxTree.ToString().MatchSnapshot(); + context.Documents.Single().SyntaxTree.MatchSnapshot(); } private MergeSchema CreatePipeline() diff --git a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Local_Remove.snap b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Local_Remove.graphql similarity index 50% rename from src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Local_Remove.snap rename to src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Local_Remove.graphql index 857c83616e8..2da2ddea366 100644 --- a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Local_Remove.snap +++ b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Local_Remove.graphql @@ -1,7 +1,7 @@ -schema @_hc_service(name: "abc") { +schema @_hc_service(name: "abc") { } type Foo @a { abc: String @remove -} +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Is_Preserved.snap b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Is_Preserved.graphql similarity index 77% rename from src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Is_Preserved.snap rename to src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Is_Preserved.graphql index eaa1f7d224e..06ab36f5daf 100644 --- a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Is_Preserved.snap +++ b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Is_Preserved.graphql @@ -1,4 +1,4 @@ -extend type Bar { +extend type Bar { def: String } @@ -8,4 +8,4 @@ schema @_hc_service(name: "abc") { type Foo { abc: String -} +} \ No newline at end of file diff --git a/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Merge_Directives_2_Single_Document.graphql b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Merge_Directives_2_Single_Document.graphql new file mode 100644 index 00000000000..98fd27ea2e9 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Merge_Directives_2_Single_Document.graphql @@ -0,0 +1,7 @@ +schema @_hc_service(name: "abc") { + +} + +type Foo @a @b { + abc: String +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Merge_Directives_Single_Document.snap b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Merge_Directives_Single_Document.graphql similarity index 50% rename from src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Merge_Directives_Single_Document.snap rename to src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Merge_Directives_Single_Document.graphql index 8124d3987a0..f15cde94132 100644 --- a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Merge_Directives_Single_Document.snap +++ b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Merge_Directives_Single_Document.graphql @@ -1,7 +1,7 @@ -schema @_hc_service(name: "abc") { +schema @_hc_service(name: "abc") { } type Foo @directive { abc: String -} +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Merge_Field_Directives_Single_Document.snap b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Merge_Field_Directives_Single_Document.graphql similarity index 50% rename from src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Merge_Field_Directives_Single_Document.snap rename to src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Merge_Field_Directives_Single_Document.graphql index 39f886e2274..e5f01bc14a1 100644 --- a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Merge_Field_Directives_Single_Document.snap +++ b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Merge_Field_Directives_Single_Document.graphql @@ -1,7 +1,7 @@ -schema @_hc_service(name: "abc") { +schema @_hc_service(name: "abc") { } type Foo { abc: String @directive -} +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Single_Document.snap b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Single_Document.graphql similarity index 51% rename from src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Single_Document.snap rename to src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Single_Document.graphql index 0c1976ad3e6..74a88f0eff0 100644 --- a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Single_Document.snap +++ b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Single_Document.graphql @@ -1,8 +1,8 @@ -schema @_hc_service(name: "abc") { +schema @_hc_service(name: "abc") { } type Foo { abc: String def: String -} +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename.snap b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename.graphql similarity index 74% rename from src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename.snap rename to src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename.graphql index c259e731326..cb8f12630e8 100644 --- a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename.snap +++ b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename.graphql @@ -1,4 +1,4 @@ -schema @_hc_service(name: "abc") { +schema @_hc_service(name: "abc") { } @@ -6,4 +6,4 @@ type Foo { def: String @_hc_bind(to: "abc", as: "abc") def: Int ghi: Int @_hc_bind(to: "bar", as: "baz") -} +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Interface_Field.snap b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Interface_Field.graphql similarity index 77% rename from src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Interface_Field.snap rename to src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Interface_Field.graphql index 4f52eb63d8f..60693e76b96 100644 --- a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Interface_Field.snap +++ b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Interface_Field.graphql @@ -1,4 +1,4 @@ -schema @_hc_service(name: "SchemaName") { +schema @_hc_service(name: "SchemaName") { } @@ -8,4 +8,4 @@ type Foo implements IFoo { interface IFoo { newName: String @_hc_bind(to: "SchemaName", as: "abc") -} +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Interface_Field_2.snap b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Interface_Field_2.graphql similarity index 86% rename from src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Interface_Field_2.snap rename to src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Interface_Field_2.graphql index caba703f137..ad1e0c938c9 100644 --- a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Interface_Field_2.snap +++ b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Interface_Field_2.graphql @@ -1,4 +1,4 @@ -schema @_hc_service(name: "SchemaName") { +schema @_hc_service(name: "SchemaName") { } @@ -14,4 +14,4 @@ interface IFoo { interface IFooExt implements IFoo { newName: String @_hc_bind(to: "SchemaName", as: "abc") def: String -} +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Interface_Name.snap b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Interface_Name.graphql similarity index 73% rename from src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Interface_Name.snap rename to src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Interface_Name.graphql index cf7dc8557da..092919cbee8 100644 --- a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Interface_Name.snap +++ b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Interface_Name.graphql @@ -1,4 +1,4 @@ -schema @_hc_service(name: "abc") { +schema @_hc_service(name: "abc") { } @@ -8,4 +8,4 @@ type Foo implements IDef { interface IDef @_hc_bind(to: "abc", as: "IFoo") { abc: String -} +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Object_And_Refactor_Usages.snap b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Object_And_Refactor_Usages.graphql similarity index 57% rename from src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Object_And_Refactor_Usages.snap rename to src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Object_And_Refactor_Usages.graphql index 29711cc6630..ffce7b1ca51 100644 --- a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Object_And_Refactor_Usages.snap +++ b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Object_And_Refactor_Usages.graphql @@ -1,4 +1,4 @@ -schema @_hc_service(name: "abc") { +schema @_hc_service(name: "abc") { } @@ -10,4 +10,4 @@ type Bar @_hc_bind(to: "abc", as: "Foo") { abc: String } -union FooOrBaz = Bar | Baz +union FooOrBaz = Bar | Baz \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Scalar_And_Update_Usages.snap b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Scalar_And_Update_Usages.graphql similarity index 85% rename from src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Scalar_And_Update_Usages.snap rename to src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Scalar_And_Update_Usages.graphql index f05be6ca131..8262190be9e 100644 --- a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Scalar_And_Update_Usages.snap +++ b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyLocalRenamingMiddlewareTests.Apply_Local_Rename_Scalar_And_Update_Usages.graphql @@ -1,4 +1,4 @@ -extend scalar String @rename(to: "SpecialString") +extend scalar String @rename(to: "SpecialString") schema @_hc_service(name: "abc") { @@ -17,4 +17,4 @@ type Foo { input FooInput { a: [SpecialString!]! -} +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyMissingBindingsMiddlewareTests.Apply_Missing_Bindings.snap b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyMissingBindingsMiddlewareTests.Apply_Missing_Bindings.graphql similarity index 88% rename from src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyMissingBindingsMiddlewareTests.Apply_Missing_Bindings.snap rename to src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyMissingBindingsMiddlewareTests.Apply_Missing_Bindings.graphql index e4a7db9c070..9088bbbd0e3 100644 --- a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyMissingBindingsMiddlewareTests.Apply_Missing_Bindings.snap +++ b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyMissingBindingsMiddlewareTests.Apply_Missing_Bindings.graphql @@ -1,4 +1,4 @@ -schema @_hc_service(name: "SchemaName") { +schema @_hc_service(name: "SchemaName") { } @@ -14,4 +14,4 @@ interface IFoo { interface IFooExt implements IFoo { newName: String @_hc_bind(to: "SchemaName", as: "abc") def: String @_hc_bind(to: "SchemaName") -} +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/FullPipelineTests.Apply_Local_Remove.snap b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/FullPipelineTests.Apply_Local_Remove.graphql similarity index 77% rename from src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/FullPipelineTests.Apply_Local_Remove.snap rename to src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/FullPipelineTests.Apply_Local_Remove.graphql index 67010969e52..27b7357a24a 100644 --- a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/FullPipelineTests.Apply_Local_Remove.snap +++ b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/Pipeline/__snapshots__/FullPipelineTests.Apply_Local_Remove.graphql @@ -1,4 +1,4 @@ -schema @_hc_service(name: "ServiceA") @_hc_service(name: "ServiceB") { +schema @_hc_service(name: "ServiceA") @_hc_service(name: "ServiceB") { } @@ -11,4 +11,4 @@ type Foo { xyz: String @_hc_bind(to: "ServiceA", as: "abc") zzz: Float @_hc_bind(to: "ServiceA") @_hc_bind(to: "ServiceB") abc: Int @_hc_bind(to: "ServiceB") -} +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/__snapshots__/DefaultObjectTypeDefinitionMergerTests.Merge_Simple_Type.snap b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/__snapshots__/DefaultObjectTypeDefinitionMergerTests.Merge_Simple_Type.snap similarity index 60% rename from src/HotChocolate/Stitching/test/Stitching.Types.Tests/__snapshots__/DefaultObjectTypeDefinitionMergerTests.Merge_Simple_Type.snap rename to src/HotChocolate/Fusion/test/Stitching.Types.Tests/__snapshots__/DefaultObjectTypeDefinitionMergerTests.Merge_Simple_Type.snap index 45ed27f6c39..bc4bb08e36c 100644 --- a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/__snapshots__/DefaultObjectTypeDefinitionMergerTests.Merge_Simple_Type.snap +++ b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/__snapshots__/DefaultObjectTypeDefinitionMergerTests.Merge_Simple_Type.snap @@ -1,4 +1,4 @@ -type Foo { +type Foo { b: String a: String -} +} \ No newline at end of file diff --git a/src/HotChocolate/Fusion/test/Stitching.Types.Tests/__snapshots__/DefaultObjectTypeDefinitionMergerTests.Same_Field_In_Each_Schema_Version.snap b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/__snapshots__/DefaultObjectTypeDefinitionMergerTests.Same_Field_In_Each_Schema_Version.snap new file mode 100644 index 00000000000..f6822ff4d96 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Stitching.Types.Tests/__snapshots__/DefaultObjectTypeDefinitionMergerTests.Same_Field_In_Each_Schema_Version.snap @@ -0,0 +1,3 @@ +type Foo { + a: String +} \ No newline at end of file diff --git a/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.Utilities.cs b/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.Utilities.cs index bb88969bd61..d5b9fb29981 100644 --- a/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.Utilities.cs +++ b/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.Utilities.cs @@ -8,8 +8,9 @@ namespace HotChocolate.Language; public ref partial struct Utf8GraphQLParser { + // note: this is internal for legacy stitching [MethodImpl(MethodImplOptions.AggressiveInlining)] - private NameNode ParseName() + internal NameNode ParseName() { var start = Start(); var name = ExpectName(); @@ -22,8 +23,9 @@ private NameNode ParseName() ); } + // note: this is internal for legacy stitching [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool MoveNext() => _reader.MoveNext(); + internal bool MoveNext() => _reader.MoveNext(); [MethodImpl(MethodImplOptions.AggressiveInlining)] private TokenInfo Start() @@ -69,11 +71,13 @@ private string ExpectName() throw new SyntaxException(_reader, Parser_InvalidToken, TokenKind.Name, _reader.Kind); } + // note: this is internal for legacy stitching [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ExpectColon() => Expect(TokenKind.Colon); + internal void ExpectColon() => Expect(TokenKind.Colon); + // note: this is internal for the stitching legacy layer [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ExpectDollar() => Expect(TokenKind.Dollar); + internal void ExpectDollar() => Expect(TokenKind.Dollar); [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ExpectAt() => Expect(TokenKind.At); @@ -97,8 +101,9 @@ private string ExpectString() [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ExpectSpread() => Expect(TokenKind.Spread); + // note: this is internal for legacy stitching [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ExpectRightParenthesis() => Expect(TokenKind.RightParenthesis); + internal void ExpectRightParenthesis() => Expect(TokenKind.RightParenthesis); [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ExpectRightBrace() => Expect(TokenKind.RightBrace); diff --git a/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.Values.cs b/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.Values.cs index 16a7bdcc6ea..a2a9cd66e53 100644 --- a/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.Values.cs +++ b/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.Values.cs @@ -9,6 +9,7 @@ namespace HotChocolate.Language; // Implements the parsing rules in the Values section. public ref partial struct Utf8GraphQLParser { + // note: this is internal for the stitching legacy layer /// /// Parses a value. /// : @@ -29,7 +30,7 @@ namespace HotChocolate.Language; /// Defines if only constant values are allowed; /// otherwise, variables are allowed. /// - private IValueNode ParseValueLiteral(bool isConstant) + internal IValueNode ParseValueLiteral(bool isConstant) { if (_reader.Kind == TokenKind.LeftBracket) { diff --git a/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.cs b/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.cs index 0701bdbdffd..704ecf6677e 100644 --- a/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.cs +++ b/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.cs @@ -54,6 +54,17 @@ namespace HotChocolate.Language; /// public int ParsedSyntaxNodes => _parsedNodes; + /// + /// Defines if the parser reached the end of the source text. + /// + public bool IsEndOfFile => _reader.Kind is TokenKind.EndOfFile; + + // note: we added this internal access for legacy stitching. + /// + /// Provides internal access to the underlying GraphQL reader. + /// + internal Utf8GraphQLReader Reader => _reader; + public DocumentNode Parse() { _parsedNodes = 0; diff --git a/src/HotChocolate/Language/src/Language.Visitors/SyntaxRewriter~1.cs b/src/HotChocolate/Language/src/Language.Visitors/SyntaxRewriter~1.cs index 0d7c7386b65..2441f60954c 100644 --- a/src/HotChocolate/Language/src/Language.Visitors/SyntaxRewriter~1.cs +++ b/src/HotChocolate/Language/src/Language.Visitors/SyntaxRewriter~1.cs @@ -144,9 +144,7 @@ protected virtual TContext OnEnter(ISyntaxNode node, TContext context) if (!ReferenceEquals(definitions, node.Definitions)) { - return new DocumentNode( - node.Location, - definitions); + return new DocumentNode(node.Location, definitions); } return node; diff --git a/src/HotChocolate/Stitching/HotChocolate.Stitching.sln b/src/HotChocolate/Stitching/HotChocolate.Stitching.sln index d9397e4b053..a4b8162ec84 100644 --- a/src/HotChocolate/Stitching/HotChocolate.Stitching.sln +++ b/src/HotChocolate/Stitching/HotChocolate.Stitching.sln @@ -41,9 +41,13 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Execution", ". EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.AspNetCore.Tests", "..\AspNetCore\test\AspNetCore.Tests\HotChocolate.AspNetCore.Tests.csproj", "{709EE9F8-BA48-4C5A-8BDA-96B96689A1FC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Stitching.Types.Tests", "test\Stitching.Types.Tests\HotChocolate.Stitching.Types.Tests.csproj", "{12A50382-A67F-44AB-A665-56A10DE0E8E8}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Stitching", "src\Stitching\HotChocolate.Stitching.csproj", "{C00D54A3-7AC1-4F6B-A7EF-FB865090E7D3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Stitching.Types", "src\Stitching.Types\HotChocolate.Stitching.Types.csproj", "{AD6A63BB-A445-4B44-A4A0-95B10279DB72}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Stitching.Abstractions", "src\Stitching.Abstractions\HotChocolate.Stitching.Abstractions.csproj", "{B8143A31-EC3C-4410-AC85-9C28DA022DC8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Stitching.Redis", "src\Stitching.Redis\HotChocolate.Stitching.Redis.csproj", "{EF3290BD-E6C9-452A-AAC1-22785543F4A6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Stitching.Tests", "test\Stitching.Tests\HotChocolate.Stitching.Tests.csproj", "{9F9C412E-A2C4-446F-B306-E0ADFC3286F2}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -211,30 +215,54 @@ Global {709EE9F8-BA48-4C5A-8BDA-96B96689A1FC}.Release|x64.Build.0 = Release|Any CPU {709EE9F8-BA48-4C5A-8BDA-96B96689A1FC}.Release|x86.ActiveCfg = Release|Any CPU {709EE9F8-BA48-4C5A-8BDA-96B96689A1FC}.Release|x86.Build.0 = Release|Any CPU - {12A50382-A67F-44AB-A665-56A10DE0E8E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {12A50382-A67F-44AB-A665-56A10DE0E8E8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {12A50382-A67F-44AB-A665-56A10DE0E8E8}.Debug|x64.ActiveCfg = Debug|Any CPU - {12A50382-A67F-44AB-A665-56A10DE0E8E8}.Debug|x64.Build.0 = Debug|Any CPU - {12A50382-A67F-44AB-A665-56A10DE0E8E8}.Debug|x86.ActiveCfg = Debug|Any CPU - {12A50382-A67F-44AB-A665-56A10DE0E8E8}.Debug|x86.Build.0 = Debug|Any CPU - {12A50382-A67F-44AB-A665-56A10DE0E8E8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {12A50382-A67F-44AB-A665-56A10DE0E8E8}.Release|Any CPU.Build.0 = Release|Any CPU - {12A50382-A67F-44AB-A665-56A10DE0E8E8}.Release|x64.ActiveCfg = Release|Any CPU - {12A50382-A67F-44AB-A665-56A10DE0E8E8}.Release|x64.Build.0 = Release|Any CPU - {12A50382-A67F-44AB-A665-56A10DE0E8E8}.Release|x86.ActiveCfg = Release|Any CPU - {12A50382-A67F-44AB-A665-56A10DE0E8E8}.Release|x86.Build.0 = Release|Any CPU - {AD6A63BB-A445-4B44-A4A0-95B10279DB72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AD6A63BB-A445-4B44-A4A0-95B10279DB72}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AD6A63BB-A445-4B44-A4A0-95B10279DB72}.Debug|x64.ActiveCfg = Debug|Any CPU - {AD6A63BB-A445-4B44-A4A0-95B10279DB72}.Debug|x64.Build.0 = Debug|Any CPU - {AD6A63BB-A445-4B44-A4A0-95B10279DB72}.Debug|x86.ActiveCfg = Debug|Any CPU - {AD6A63BB-A445-4B44-A4A0-95B10279DB72}.Debug|x86.Build.0 = Debug|Any CPU - {AD6A63BB-A445-4B44-A4A0-95B10279DB72}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AD6A63BB-A445-4B44-A4A0-95B10279DB72}.Release|Any CPU.Build.0 = Release|Any CPU - {AD6A63BB-A445-4B44-A4A0-95B10279DB72}.Release|x64.ActiveCfg = Release|Any CPU - {AD6A63BB-A445-4B44-A4A0-95B10279DB72}.Release|x64.Build.0 = Release|Any CPU - {AD6A63BB-A445-4B44-A4A0-95B10279DB72}.Release|x86.ActiveCfg = Release|Any CPU - {AD6A63BB-A445-4B44-A4A0-95B10279DB72}.Release|x86.Build.0 = Release|Any CPU + {C00D54A3-7AC1-4F6B-A7EF-FB865090E7D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C00D54A3-7AC1-4F6B-A7EF-FB865090E7D3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C00D54A3-7AC1-4F6B-A7EF-FB865090E7D3}.Debug|x64.ActiveCfg = Debug|Any CPU + {C00D54A3-7AC1-4F6B-A7EF-FB865090E7D3}.Debug|x64.Build.0 = Debug|Any CPU + {C00D54A3-7AC1-4F6B-A7EF-FB865090E7D3}.Debug|x86.ActiveCfg = Debug|Any CPU + {C00D54A3-7AC1-4F6B-A7EF-FB865090E7D3}.Debug|x86.Build.0 = Debug|Any CPU + {C00D54A3-7AC1-4F6B-A7EF-FB865090E7D3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C00D54A3-7AC1-4F6B-A7EF-FB865090E7D3}.Release|Any CPU.Build.0 = Release|Any CPU + {C00D54A3-7AC1-4F6B-A7EF-FB865090E7D3}.Release|x64.ActiveCfg = Release|Any CPU + {C00D54A3-7AC1-4F6B-A7EF-FB865090E7D3}.Release|x64.Build.0 = Release|Any CPU + {C00D54A3-7AC1-4F6B-A7EF-FB865090E7D3}.Release|x86.ActiveCfg = Release|Any CPU + {C00D54A3-7AC1-4F6B-A7EF-FB865090E7D3}.Release|x86.Build.0 = Release|Any CPU + {B8143A31-EC3C-4410-AC85-9C28DA022DC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B8143A31-EC3C-4410-AC85-9C28DA022DC8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B8143A31-EC3C-4410-AC85-9C28DA022DC8}.Debug|x64.ActiveCfg = Debug|Any CPU + {B8143A31-EC3C-4410-AC85-9C28DA022DC8}.Debug|x64.Build.0 = Debug|Any CPU + {B8143A31-EC3C-4410-AC85-9C28DA022DC8}.Debug|x86.ActiveCfg = Debug|Any CPU + {B8143A31-EC3C-4410-AC85-9C28DA022DC8}.Debug|x86.Build.0 = Debug|Any CPU + {B8143A31-EC3C-4410-AC85-9C28DA022DC8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B8143A31-EC3C-4410-AC85-9C28DA022DC8}.Release|Any CPU.Build.0 = Release|Any CPU + {B8143A31-EC3C-4410-AC85-9C28DA022DC8}.Release|x64.ActiveCfg = Release|Any CPU + {B8143A31-EC3C-4410-AC85-9C28DA022DC8}.Release|x64.Build.0 = Release|Any CPU + {B8143A31-EC3C-4410-AC85-9C28DA022DC8}.Release|x86.ActiveCfg = Release|Any CPU + {B8143A31-EC3C-4410-AC85-9C28DA022DC8}.Release|x86.Build.0 = Release|Any CPU + {EF3290BD-E6C9-452A-AAC1-22785543F4A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EF3290BD-E6C9-452A-AAC1-22785543F4A6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EF3290BD-E6C9-452A-AAC1-22785543F4A6}.Debug|x64.ActiveCfg = Debug|Any CPU + {EF3290BD-E6C9-452A-AAC1-22785543F4A6}.Debug|x64.Build.0 = Debug|Any CPU + {EF3290BD-E6C9-452A-AAC1-22785543F4A6}.Debug|x86.ActiveCfg = Debug|Any CPU + {EF3290BD-E6C9-452A-AAC1-22785543F4A6}.Debug|x86.Build.0 = Debug|Any CPU + {EF3290BD-E6C9-452A-AAC1-22785543F4A6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EF3290BD-E6C9-452A-AAC1-22785543F4A6}.Release|Any CPU.Build.0 = Release|Any CPU + {EF3290BD-E6C9-452A-AAC1-22785543F4A6}.Release|x64.ActiveCfg = Release|Any CPU + {EF3290BD-E6C9-452A-AAC1-22785543F4A6}.Release|x64.Build.0 = Release|Any CPU + {EF3290BD-E6C9-452A-AAC1-22785543F4A6}.Release|x86.ActiveCfg = Release|Any CPU + {EF3290BD-E6C9-452A-AAC1-22785543F4A6}.Release|x86.Build.0 = Release|Any CPU + {9F9C412E-A2C4-446F-B306-E0ADFC3286F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9F9C412E-A2C4-446F-B306-E0ADFC3286F2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9F9C412E-A2C4-446F-B306-E0ADFC3286F2}.Debug|x64.ActiveCfg = Debug|Any CPU + {9F9C412E-A2C4-446F-B306-E0ADFC3286F2}.Debug|x64.Build.0 = Debug|Any CPU + {9F9C412E-A2C4-446F-B306-E0ADFC3286F2}.Debug|x86.ActiveCfg = Debug|Any CPU + {9F9C412E-A2C4-446F-B306-E0ADFC3286F2}.Debug|x86.Build.0 = Debug|Any CPU + {9F9C412E-A2C4-446F-B306-E0ADFC3286F2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9F9C412E-A2C4-446F-B306-E0ADFC3286F2}.Release|Any CPU.Build.0 = Release|Any CPU + {9F9C412E-A2C4-446F-B306-E0ADFC3286F2}.Release|x64.ActiveCfg = Release|Any CPU + {9F9C412E-A2C4-446F-B306-E0ADFC3286F2}.Release|x64.Build.0 = Release|Any CPU + {9F9C412E-A2C4-446F-B306-E0ADFC3286F2}.Release|x86.ActiveCfg = Release|Any CPU + {9F9C412E-A2C4-446F-B306-E0ADFC3286F2}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -253,8 +281,10 @@ Global {68ADFD79-A37C-429A-90A7-584B7E27F613} = {D7A7C1D4-6239-4B4C-A80C-E953334A83F8} {C1CF3B06-4A02-46A6-98B2-448B5DD4E7D3} = {D7A7C1D4-6239-4B4C-A80C-E953334A83F8} {709EE9F8-BA48-4C5A-8BDA-96B96689A1FC} = {D7A7C1D4-6239-4B4C-A80C-E953334A83F8} - {12A50382-A67F-44AB-A665-56A10DE0E8E8} = {CA934242-6AB7-40F0-B433-A2D9BF10EF4A} - {AD6A63BB-A445-4B44-A4A0-95B10279DB72} = {D530CEBF-33A3-4BCE-9887-A50F5AE789A3} + {C00D54A3-7AC1-4F6B-A7EF-FB865090E7D3} = {D530CEBF-33A3-4BCE-9887-A50F5AE789A3} + {B8143A31-EC3C-4410-AC85-9C28DA022DC8} = {D530CEBF-33A3-4BCE-9887-A50F5AE789A3} + {EF3290BD-E6C9-452A-AAC1-22785543F4A6} = {D530CEBF-33A3-4BCE-9887-A50F5AE789A3} + {9F9C412E-A2C4-446F-B306-E0ADFC3286F2} = {CA934242-6AB7-40F0-B433-A2D9BF10EF4A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {FB1557E1-2F94-4540-93E5-B47698838B72} diff --git a/src/HotChocolate/Stitching/src/Stitching.Abstractions/HotChocolate.Stitching.Abstractions.csproj b/src/HotChocolate/Stitching/src/Stitching.Abstractions/HotChocolate.Stitching.Abstractions.csproj new file mode 100644 index 00000000000..61c5fe04539 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching.Abstractions/HotChocolate.Stitching.Abstractions.csproj @@ -0,0 +1,16 @@ + + + + HotChocolate.Stitching.Abstractions + HotChocolate.Stitching.Abstractions + HotChocolate.Stitching + Contains the Hot Chocolate GraphQL schema stitching abstractions. + enable + + + + + + + + diff --git a/src/HotChocolate/Stitching/src/Stitching.Abstractions/PublicAPI.Shipped.txt b/src/HotChocolate/Stitching/src/Stitching.Abstractions/PublicAPI.Shipped.txt new file mode 100644 index 00000000000..1aefa98e667 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching.Abstractions/PublicAPI.Shipped.txt @@ -0,0 +1,6 @@ +#nullable enable +HotChocolate.Stitching.RemoteSchemaDefinition +HotChocolate.Stitching.RemoteSchemaDefinition.Document.get -> HotChocolate.Language.DocumentNode! +HotChocolate.Stitching.RemoteSchemaDefinition.ExtensionDocuments.get -> System.Collections.Generic.IReadOnlyList! +HotChocolate.Stitching.RemoteSchemaDefinition.Name.get -> HotChocolate.NameString +HotChocolate.Stitching.RemoteSchemaDefinition.RemoteSchemaDefinition(HotChocolate.NameString name, HotChocolate.Language.DocumentNode! document, System.Collections.Generic.IEnumerable? extensionDocuments = null) -> void diff --git a/src/HotChocolate/Stitching/src/Stitching.Abstractions/PublicAPI.Unshipped.txt b/src/HotChocolate/Stitching/src/Stitching.Abstractions/PublicAPI.Unshipped.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/HotChocolate/Stitching/src/Stitching.Abstractions/RemoteSchemaDefinition.cs b/src/HotChocolate/Stitching/src/Stitching.Abstractions/RemoteSchemaDefinition.cs new file mode 100644 index 00000000000..c018077a9d6 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching.Abstractions/RemoteSchemaDefinition.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using HotChocolate.Language; + +namespace HotChocolate.Stitching +{ + /// + /// Defines a remote schema and how it shall be stitched into the Hot Chocolate gateway. + /// + public class RemoteSchemaDefinition + { + public RemoteSchemaDefinition( + string name, + DocumentNode document, + IEnumerable? extensionDocuments = null) + { + Name = name; + Document = document; + ExtensionDocuments = extensionDocuments?.ToArray() ?? Array.Empty(); + } + + /// + /// Gets the name of the schema. + /// + public string Name { get; } + + /// + /// Gets the main schema documents. + /// + public DocumentNode Document { get; } + + /// + /// Gets the documents that describes how type are being merged + /// into types from other services. + /// + public IReadOnlyList ExtensionDocuments { get; } + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching.Redis/DependencyInjection/HotChocolateStitchingRedisPublishSchemaDefinitionDescriptorExtensions.cs b/src/HotChocolate/Stitching/src/Stitching.Redis/DependencyInjection/HotChocolateStitchingRedisPublishSchemaDefinitionDescriptorExtensions.cs new file mode 100644 index 00000000000..f9b069d9fa1 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching.Redis/DependencyInjection/HotChocolateStitchingRedisPublishSchemaDefinitionDescriptorExtensions.cs @@ -0,0 +1,31 @@ +using System; +using HotChocolate; +using HotChocolate.Stitching.Redis; +using HotChocolate.Stitching.SchemaDefinitions; +using HotChocolate.Utilities; +using StackExchange.Redis; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class HotChocolateStitchingRedisPublishSchemaDefinitionDescriptorExtensions + { + public static IPublishSchemaDefinitionDescriptor PublishToRedis( + this IPublishSchemaDefinitionDescriptor descriptor, + string configurationName, + Func connectionFactory) + { + if (connectionFactory is null) + { + throw new ArgumentNullException(nameof(connectionFactory)); + } + + configurationName.EnsureGraphQLName(nameof(configurationName)); + + return descriptor.SetSchemaDefinitionPublisher(sp => + { + var connection = connectionFactory(sp); + return new RedisSchemaDefinitionPublisher(configurationName, connection); + }); + } + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching.Redis/DependencyInjection/HotChocolateStitchingRedisRequestExecutorBuilderExtensions.cs b/src/HotChocolate/Stitching/src/Stitching.Redis/DependencyInjection/HotChocolateStitchingRedisRequestExecutorBuilderExtensions.cs new file mode 100644 index 00000000000..4e0f69cc9f3 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching.Redis/DependencyInjection/HotChocolateStitchingRedisRequestExecutorBuilderExtensions.cs @@ -0,0 +1,41 @@ +using Microsoft.Extensions.DependencyInjection.Extensions; +using HotChocolate.Execution.Configuration; +using HotChocolate.Stitching.Redis; +using HotChocolate.Stitching.Requests; +using HotChocolate.Utilities; +using StackExchange.Redis; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class HotChocolateStitchingRedisRequestExecutorBuilderExtensions + { + public static IRequestExecutorBuilder AddRemoteSchemasFromRedis( + this IRequestExecutorBuilder builder, + string configurationName, + Func connectionFactory) + { + if (connectionFactory is null) + { + throw new ArgumentNullException(nameof(connectionFactory)); + } + + configurationName.EnsureGraphQLName(nameof(configurationName)); + + builder.Services.AddSingleton(sp => + { + var connection = connectionFactory(sp); + var database = connection.GetDatabase(); + var subscriber = connection.GetSubscriber(); + return new RedisExecutorOptionsProvider( + builder.Name, configurationName, database, subscriber); + }); + + // Last but not least, we will setup the stitching context which will + // provide access to the remote executors which in turn use the just configured + // request executor proxies to send requests to the downstream services. + builder.Services.TryAddScoped(); + + return builder; + } + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching.Redis/HotChocolate.Stitching.Redis.csproj b/src/HotChocolate/Stitching/src/Stitching.Redis/HotChocolate.Stitching.Redis.csproj new file mode 100644 index 00000000000..b669186bb6b --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching.Redis/HotChocolate.Stitching.Redis.csproj @@ -0,0 +1,20 @@ + + + + HotChocolate.Stitching.Redis + HotChocolate.Stitching.Redis + Contains the Hot Chocolate GraphQL schema stitching layer. + enable + + + + + + + + + + + + + diff --git a/src/HotChocolate/Stitching/src/Stitching.Redis/PublicAPI.Shipped.txt b/src/HotChocolate/Stitching/src/Stitching.Redis/PublicAPI.Shipped.txt new file mode 100644 index 00000000000..992014cdf83 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching.Redis/PublicAPI.Shipped.txt @@ -0,0 +1,8 @@ +#nullable enable +HotChocolate.Stitching.Redis.RedisSchemaDefinitionPublisher +HotChocolate.Stitching.Redis.RedisSchemaDefinitionPublisher.PublishAsync(HotChocolate.Stitching.RemoteSchemaDefinition! schemaDefinition, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +HotChocolate.Stitching.Redis.RedisSchemaDefinitionPublisher.RedisSchemaDefinitionPublisher(HotChocolate.NameString configurationName, StackExchange.Redis.IConnectionMultiplexer! connection) -> void +Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRedisPublishSchemaDefinitionDescriptorExtensions +Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRedisRequestExecutorBuilderExtensions +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRedisPublishSchemaDefinitionDescriptorExtensions.PublishToRedis(this HotChocolate.Stitching.SchemaDefinitions.IPublishSchemaDefinitionDescriptor! descriptor, HotChocolate.NameString configurationName, System.Func! connectionFactory) -> HotChocolate.Stitching.SchemaDefinitions.IPublishSchemaDefinitionDescriptor! +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRedisRequestExecutorBuilderExtensions.AddRemoteSchemasFromRedis(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, HotChocolate.NameString configurationName, System.Func! connectionFactory) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! diff --git a/src/HotChocolate/Stitching/src/Stitching.Redis/PublicAPI.Unshipped.txt b/src/HotChocolate/Stitching/src/Stitching.Redis/PublicAPI.Unshipped.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/HotChocolate/Stitching/src/Stitching.Redis/RedisExecutorOptionsProvider.cs b/src/HotChocolate/Stitching/src/Stitching.Redis/RedisExecutorOptionsProvider.cs new file mode 100644 index 00000000000..4154c9c4d84 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching.Redis/RedisExecutorOptionsProvider.cs @@ -0,0 +1,170 @@ +using System.Text.Json; +using Microsoft.Extensions.DependencyInjection; +using HotChocolate.Execution.Configuration; +using HotChocolate.Language; +using StackExchange.Redis; + +namespace HotChocolate.Stitching.Redis +{ + internal class RedisExecutorOptionsProvider : IRequestExecutorOptionsProvider + { + private readonly string _schemaName; + private readonly string _configurationName; + private readonly IDatabase _database; + private readonly List _listeners = new(); + + public RedisExecutorOptionsProvider( + string schemaName, + string configurationName, + IDatabase database, + ISubscriber subscriber) + { + _schemaName = schemaName; + _configurationName = configurationName; + _database = database; + subscriber.Subscribe(configurationName).OnMessage(OnChangeMessageAsync); + } + + public async ValueTask> GetOptionsAsync( + CancellationToken cancellationToken) + { + var schemaDefinitions = + await GetSchemaDefinitionsAsync(cancellationToken) + .ConfigureAwait(false); + + var factoryOptions = new List(); + + foreach (var schemaDefinition in schemaDefinitions) + { + await CreateFactoryOptionsAsync( + schemaDefinition, + factoryOptions, + cancellationToken) + .ConfigureAwait(false); + } + + return factoryOptions; + } + + public IDisposable OnChange(Action listener) => + new OnChangeListener(_listeners, listener); + + private async Task OnChangeMessageAsync(ChannelMessage message) + { + string schemaName = message.Message; + + var schemaDefinition = + await GetRemoteSchemaDefinitionAsync(schemaName) + .ConfigureAwait(false); + + var factoryOptions = new List(); + await CreateFactoryOptionsAsync(schemaDefinition, factoryOptions, default) + .ConfigureAwait(false); + + lock (_listeners) + { + foreach (var listener in _listeners) + { + foreach (var options in factoryOptions) + { + listener.OnChange(options); + } + } + } + } + + private async ValueTask> GetSchemaDefinitionsAsync( + CancellationToken cancellationToken) + { + var items = await _database.SetMembersAsync(_configurationName) + .ConfigureAwait(false); + + var schemaDefinitions = new List(); + + foreach (var schemaName in items.Select(t => (string)t)) + { + cancellationToken.ThrowIfCancellationRequested(); + + var schemaDefinition = + await GetRemoteSchemaDefinitionAsync(schemaName) + .ConfigureAwait(false); + + schemaDefinitions.Add(schemaDefinition); + } + + return schemaDefinitions; + } + + private async Task CreateFactoryOptionsAsync( + RemoteSchemaDefinition schemaDefinition, + IList factoryOptions, + CancellationToken cancellationToken) + { + await using var services = + new ServiceCollection() + .AddGraphQL(_schemaName) + .AddRemoteSchema( + schemaDefinition.Name, + (_, _) => new ValueTask(schemaDefinition)) + .Services + .BuildServiceProvider(); + + var optionsMonitor = + services.GetRequiredService(); + + var options = + await optionsMonitor.GetAsync(schemaDefinition.Name, cancellationToken) + .ConfigureAwait(false); + + factoryOptions.Add(new ConfigureRequestExecutorSetup(schemaDefinition.Name, options)); + + options = + await optionsMonitor.GetAsync(_schemaName, cancellationToken) + .ConfigureAwait(false); + + factoryOptions.Add(new ConfigureRequestExecutorSetup(_schemaName, options)); + } + + private async Task GetRemoteSchemaDefinitionAsync(string schemaName) + { + var key = $"{_configurationName}.{schemaName}"; + var json = (byte[])await _database.StringGetAsync(key).ConfigureAwait(false); + var dto = JsonSerializer.Deserialize(json); + + return new RemoteSchemaDefinition( + dto.Name, + Utf8GraphQLParser.Parse(dto.Document), + dto.ExtensionDocuments.Select(Utf8GraphQLParser.Parse)); + } + + private sealed class OnChangeListener : IDisposable + { + private readonly List _listeners; + private readonly Action _onChange; + + public OnChangeListener( + List listeners, + Action onChange) + { + _listeners = listeners; + _onChange = onChange; + + lock (_listeners) + { + _listeners.Add(this); + } + } + + public void OnChange(IConfigureRequestExecutorSetup options) => + _onChange(options); + + public void Dispose() + { + lock (_listeners) + { + _listeners.Remove(this); + } + } + } + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching.Redis/RedisSchemaDefinitionPublisher.cs b/src/HotChocolate/Stitching/src/Stitching.Redis/RedisSchemaDefinitionPublisher.cs new file mode 100644 index 00000000000..e17f8699170 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching.Redis/RedisSchemaDefinitionPublisher.cs @@ -0,0 +1,50 @@ +using System.Text.Json; +using HotChocolate.Stitching.SchemaDefinitions; +using StackExchange.Redis; + +namespace HotChocolate.Stitching.Redis; + +public class RedisSchemaDefinitionPublisher : ISchemaDefinitionPublisher +{ + private readonly IConnectionMultiplexer _connection; + private readonly string _configurationName; + + public RedisSchemaDefinitionPublisher( + string configurationName, + IConnectionMultiplexer connection) + { + _connection = connection; + _configurationName = configurationName; + } + + public async ValueTask PublishAsync( + RemoteSchemaDefinition schemaDefinition, + CancellationToken cancellationToken = default) + { + var key = $"{_configurationName}.{schemaDefinition.Name}"; + var json = SerializeSchemaDefinition(schemaDefinition); + + var database = _connection.GetDatabase(); + await database.StringSetAsync(key, json).ConfigureAwait(false); + await database.SetAddAsync(_configurationName, schemaDefinition.Name) + .ConfigureAwait(false); + + var subscriber = _connection.GetSubscriber(); + await subscriber.PublishAsync(_configurationName, schemaDefinition.Name) + .ConfigureAwait(false); + } + + private string SerializeSchemaDefinition(RemoteSchemaDefinition schemaDefinition) + { + var dto = new SchemaDefinitionDto + { + Name = schemaDefinition.Name, + Document = schemaDefinition.Document.ToString(false), + }; + + dto.ExtensionDocuments.AddRange( + schemaDefinition.ExtensionDocuments.Select(t => t.ToString()).ToList()); + + return JsonSerializer.Serialize(dto); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching.Redis/SchemaDefinitionDto.cs b/src/HotChocolate/Stitching/src/Stitching.Redis/SchemaDefinitionDto.cs new file mode 100644 index 00000000000..d053bcf0c06 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching.Redis/SchemaDefinitionDto.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace HotChocolate.Stitching.Redis +{ + internal sealed class SchemaDefinitionDto + { + public string? Name { get; set; } + + public string? Document { get; set; } + + public List ExtensionDocuments { get; set; } = new List(); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Delegation/CollectUsedVariableVisitor.cs b/src/HotChocolate/Stitching/src/Stitching/Delegation/CollectUsedVariableVisitor.cs new file mode 100644 index 00000000000..83fec07d52f --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Delegation/CollectUsedVariableVisitor.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using HotChocolate.Language; + +namespace HotChocolate.Stitching.Delegation; + +internal class CollectUsedVariableVisitor : QuerySyntaxWalker> +{ + public void CollectUsedVariables( + FieldNode fieldNode, + ISet usedVariables) + { + VisitField(fieldNode, usedVariables); + } + + public void CollectUsedVariables( + IEnumerable fragmentDefinitions, + ISet usedVariables) + { + foreach (var fragmentDefinition in + fragmentDefinitions) + { + VisitFragmentDefinition(fragmentDefinition, usedVariables); + } + } + + protected override void VisitVariable( + VariableNode node, ISet context) + { + context.Add(node.Name.Value); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Delegation/DelegateToRemoteSchemaMiddleware.cs b/src/HotChocolate/Stitching/src/Stitching/Delegation/DelegateToRemoteSchemaMiddleware.cs new file mode 100644 index 00000000000..a7c02fc82d6 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Delegation/DelegateToRemoteSchemaMiddleware.cs @@ -0,0 +1,512 @@ +using System.Buffers; +using System.Collections; +using System.Collections.Immutable; +using System.Globalization; +using HotChocolate.Execution; +using HotChocolate.Language; +using HotChocolate.Resolvers; +using HotChocolate.Stitching.Delegation.ScopedVariables; +using HotChocolate.Stitching.Requests; +using HotChocolate.Stitching.Utilities; +using HotChocolate.Types; +using static HotChocolate.Stitching.WellKnownContextData; +using static HotChocolate.Stitching.Properties.StitchingResources; + +namespace HotChocolate.Stitching.Delegation; + +public sealed class DelegateToRemoteSchemaMiddleware +{ + private const string _remoteErrorField = "remote"; + private const string _schemaNameErrorField = "schemaName"; + + private static readonly RootScopedVariableResolver _resolvers = new(); + + private readonly FieldDelegate _next; + + public DelegateToRemoteSchemaMiddleware(FieldDelegate next) + { + _next = next ?? throw new ArgumentNullException(nameof(next)); + } + + public async Task InvokeAsync(IMiddlewareContext context) + { + var delegateDirective = context.Selection + .Field + .Directives[DirectiveNames.Delegate] + .FirstOrDefault()?.ToObject(); + + if (delegateDirective != null) + { + IImmutableStack path; + IImmutableStack reversePath; + + if (delegateDirective.Path is null) + { + path = ImmutableStack.Empty; + reversePath = ImmutableStack.Empty; + } + else + { + path = SelectionPathParser.Parse(delegateDirective.Path); + reversePath = ImmutableStack.CreateRange(path); + } + + var request = CreateQuery(context, delegateDirective.Schema, path, reversePath); + + var result = await ExecuteQueryAsync(context, request, delegateDirective.Schema) + .ConfigureAwait(false); + context.RegisterForCleanup(result.DisposeAsync, cleanAfter: CleanAfter.Request); + + UpdateContextData(context, result, delegateDirective); + + var value = ExtractData(result.Data, reversePath, context.ResponseName); + context.Result = value is null or NullValueNode ? null : new SerializedData(value); + if (result.Errors is not null) + { + ReportErrors(delegateDirective.Schema, context, path, result.Errors); + } + } + + await _next.Invoke(context).ConfigureAwait(false); + } + + private static void UpdateContextData( + IResolverContext context, + IQueryResult result, + DelegateDirective delegateDirective) + { + if (result.ContextData is { Count: > 0 }) + { + var builder = ImmutableDictionary.CreateBuilder(); + builder.AddRange(context.ScopedContextData); + builder[SchemaName] = delegateDirective.Schema; + builder.AddRange(result.ContextData); + context.ScopedContextData = builder.ToImmutableDictionary(); + } + else + { + context.SetScopedState(SchemaName, delegateDirective.Schema); + } + } + + private static IQueryRequest CreateQuery( + IMiddlewareContext context, + string schemaName, + IImmutableStack path, + IImmutableStack reversePath) + { + var fieldRewriter = new ExtractFieldQuerySyntaxRewriter( + context.Schema, + context.Service>()); + + var operationType = + context.Schema.IsRootType(context.ObjectType) + ? context.Operation.Type + : OperationType.Query; + + var extractedField = fieldRewriter.ExtractField( + schemaName, context.Operation.Document, context.Operation.Definition, + context.Selection, context.ObjectType); + + IEnumerable scopedVariables = + ResolveScopedVariables( + context, schemaName, operationType, + reversePath, fieldRewriter); + + var variableValues = + CreateVariableValues( + context, schemaName, scopedVariables, + extractedField, fieldRewriter); + + var builder = + RemoteQueryBuilder.New() + .SetRequestField(extractedField.SyntaxNodes[0]) + .SetOperation(context.Operation.Definition.Name, operationType) + .SetSelectionPath(path) + .AddVariables(CreateVariableDefs(variableValues)) + .AddFragmentDefinitions(extractedField.Fragments); + + if (extractedField.SyntaxNodes.Count > 1) + { + for (var i = 1; i < extractedField.SyntaxNodes.Count; i++) + { + builder.AddAdditionalField(extractedField.SyntaxNodes[i]); + } + } + + var query = builder.Build(schemaName, context.Schema.GetNameLookup()); + + var requestBuilder = QueryRequestBuilder.New(); + + AddVariables(requestBuilder, query, variableValues); + + requestBuilder + .SetQuery(query) + .AddGlobalState(IsAutoGenerated, true); + + return requestBuilder.Create(); + } + + private static async Task ExecuteQueryAsync( + IResolverContext context, + IQueryRequest request, + string schemaName) + { + var stitchingContext = context.Service(); + var executor = stitchingContext.GetRemoteRequestExecutor(schemaName); + var result = await executor.ExecuteAsync(request).ConfigureAwait(false); + + if (result is IQueryResult queryResult) + { + return queryResult; + } + + throw new GraphQLException(DelegationMiddleware_OnlyQueryResults); + } + + private static object? ExtractData( + IReadOnlyDictionary? data, + IImmutableStack reversePath, + string fieldName) + { + if (data is null || data.Count == 0) + { + return null; + } + + if (reversePath.IsEmpty) + { + return data.First().Value; + } + + object? current = data; + + while (!reversePath.IsEmpty && current is not null) + { + reversePath = reversePath.Pop(out var component); + + if (current is IReadOnlyDictionary obj) + { + if (reversePath.IsEmpty) + { + current = obj.First().Value; + } + else + { + obj.TryGetValue(component.Name.Value, out current); + } + } + else if (current is IList list) + { + var aggregated = new List(); + for (var i = 0; i < list.Count; i++) + { + var key = reversePath.IsEmpty + ? fieldName + : component.Name.Value; + + if (list[i] is IReadOnlyDictionary lobj && + lobj.TryGetValue(key, out var item)) + { + aggregated.Add(item); + } + else + { + aggregated.Add(null); + } + } + + current = aggregated; + } + else + { + current = null; + } + } + + return current; + } + + private static void ReportErrors( + string schemaName, + IResolverContext context, + IImmutableStack fetchPath, + IEnumerable errors) + { + foreach (var error in errors) + { + var builder = ErrorBuilder + .FromError(error) + .SetExtension(_remoteErrorField, error.RemoveException()) + .SetExtension(_schemaNameErrorField, schemaName); + + if (error.Path is not null) + { + builder + .SetPath(RewriteErrorPath(error.Path, context.Path, fetchPath)) + .ClearLocations() + .AddLocation(context.Selection.SyntaxNode); + } + else if (IsHttpError(error)) + { + builder + .SetPath(context.Path) + .ClearLocations() + .AddLocation(context.Selection.SyntaxNode); + } + + context.ReportError(builder.Build()); + } + } + + private static Path RewriteErrorPath( + Path errorPath, + Path fieldPath, + IImmutableStack fetchPath) + { + var depth = errorPath.Length + 1; + var buffer = ArrayPool.Shared.Rent(depth); + var paths = buffer.AsSpan().Slice(0, depth); + + try + { + var current = errorPath; + + do + { + paths[--depth] = current; + current = current.Parent; + } while (!current .IsRoot); + + depth = 0; + while (!fetchPath.IsEmpty) + { + fetchPath = fetchPath.Pop(out var fp); + if (paths[depth] is NamePathSegment np && np.Name.Equals(fp.Name.Value)) + { + depth++; + } + else + { + return fieldPath; + } + } + + paths = depth == 0 ? paths.Slice(1) : paths.Slice(depth); + + if (paths.Length == 0) + { + return fieldPath; + } + + current = fieldPath; + + for (var i = 0; i < paths.Length; i++) + { + if (paths[i] is IndexerPathSegment index) + { + current = PathFactory.Instance.Append(current, index.Index); + } + else if (paths[i] is NamePathSegment name) + { + current = PathFactory.Instance.Append(current, name.Name); + } + } + + return current; + } + finally + { + ArrayPool.Shared.Return(buffer); + } + } + + private static bool IsHttpError(IError error) => + error.Code == ErrorCodes.Stitching.HttpRequestException; + + private static IReadOnlyCollection CreateVariableValues( + IMiddlewareContext context, + string schemaName, + IEnumerable scopedVariables, + ExtractedField extractedField, + ExtractFieldQuerySyntaxRewriter rewriter) + { + var values = new Dictionary(); + + foreach (var value in scopedVariables) + { + values[value.Name] = value; + } + + foreach (var value in ResolveUsedRequestVariables( + context.Schema, schemaName, extractedField, + context.Variables, rewriter)) + { + values[value.Name] = value; + } + + return values.Values; + } + + private static IReadOnlyList ResolveScopedVariables( + IResolverContext context, + string schemaName, + OperationType operationType, + IImmutableStack reversePath, + ExtractFieldQuerySyntaxRewriter rewriter) + { + var variables = new List(); + + var stitchingContext = context.Service(); + var remoteSchema = stitchingContext.GetRemoteSchema(schemaName); + IComplexOutputType type = remoteSchema.GetOperationType(operationType)!; + var path = reversePath; + + while (!path.IsEmpty) + { + path = path.Pop(out var component); + var field = ResolveFieldFromComponent(type, component); + ResolveScopedVariableArguments( + context, schemaName, component, + field, variables, rewriter); + + if (!path.IsEmpty) + { + if (!(field.Type.NamedType() is IComplexOutputType complexOutputType)) + { + throw new GraphQLException( + new Error(DelegationMiddleware_PathElementTypeUnexpected)); + } + + type = complexOutputType; + } + } + + return variables; + } + + private static IOutputField ResolveFieldFromComponent( + IComplexOutputType type, + SelectionPathComponent component) + { + if (!type.Fields.TryGetField(component.Name.Value, out var field)) + { + // throw helper + throw new GraphQLException(new Error + ( + string.Format( + CultureInfo.InvariantCulture, + DelegationMiddleware_PathElementInvalid, + component.Name.Value, + type.Name) + )); + } + + return field; + } + + private static void ResolveScopedVariableArguments( + IResolverContext context, + string schemaName, + SelectionPathComponent component, + IOutputField field, + ICollection variables, + ExtractFieldQuerySyntaxRewriter rewriter) + { + foreach (var argument in component.Arguments) + { + if (!field.Arguments.TryGetField(argument.Name.Value, out var arg)) + { + throw new QueryException( + ErrorBuilder.New() + .SetMessage( + DelegationMiddleware_ArgumentNotFound, + argument.Name.Value) + .SetExtension("argument", argument.Name.Value) + .SetCode(ErrorCodes.Stitching.ArgumentNotFound) + .Build()); + } + + if (argument.Value is ScopedVariableNode sv) + { + var variable = _resolvers.Resolve(context, sv, arg.Type); + var value = rewriter.RewriteValueNode( + schemaName, arg.Type, variable.Value!); + variables.Add(variable.WithValue(value)); + } + } + } + + private static IEnumerable ResolveUsedRequestVariables( + ISchema schema, + string schemaName, + ExtractedField extractedField, + IVariableValueCollection requestVariables, + ExtractFieldQuerySyntaxRewriter rewriter) + { + foreach (var variable in extractedField.Variables) + { + var name = variable.Variable.Name.Value; + var namedType = schema.GetType( + variable.Type.NamedType().Name.Value); + + if (!requestVariables.TryGetVariable(name, out IValueNode? value)) + { + value = NullValueNode.Default; + } + + value = rewriter.RewriteValueNode( + schemaName, + (IInputType)variable.Type.ToType(namedType), + value); + + yield return new ScopedVariableValue + ( + name, + variable.Type, + value, + variable.DefaultValue + ); + } + } + + private static void AddVariables( + IQueryRequestBuilder builder, + DocumentNode query, + IEnumerable variableValues) + { + var operation = + query.Definitions.OfType().First(); + + var usedVariables = new HashSet( + operation.VariableDefinitions.Select(t => + t.Variable.Name.Value)); + + foreach (var variableValue in variableValues) + { + if (usedVariables.Contains(variableValue.Name)) + { + builder.AddVariableValue(variableValue.Name, variableValue.Value); + } + } + } + + private static IReadOnlyList CreateVariableDefs( + IReadOnlyCollection variableValues) + { + var definitions = new List(); + + foreach (var variableValue in variableValues) + { + definitions.Add(new VariableDefinitionNode( + null, + new VariableNode(new NameNode(variableValue.Name)), + variableValue.Type, + variableValue.DefaultValue, + Array.Empty() + )); + } + + return definitions; + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Delegation/DictionaryDeserializer.cs b/src/HotChocolate/Stitching/src/Stitching/Delegation/DictionaryDeserializer.cs new file mode 100644 index 00000000000..1c8df2b9cef --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Delegation/DictionaryDeserializer.cs @@ -0,0 +1,159 @@ +using System.Collections; +using HotChocolate.Language; +using HotChocolate.Types; +using static HotChocolate.Execution.PathFactory; + +namespace HotChocolate.Stitching.Delegation; + +internal static class DictionaryDeserializer +{ + public static object? DeserializeResult( + IType fieldType, + object? obj, + InputParser parser, + Path path) + { + var namedType = fieldType.NamedType(); + + if (namedType is IInputType && fieldType is IInputType inputType) + { + if (namedType.Kind == TypeKind.Enum) + { + return DeserializeEnumResult(inputType, obj, parser, path); + } + + if (namedType.Kind == TypeKind.Scalar) + { + return DeserializeScalarResult(inputType, obj, parser, path); + } + } + + return obj is NullValueNode + ? null + : obj; + } + + private static object? DeserializeEnumResult( + IInputType inputType, + object? value, + InputParser parser, + Path path) + { + switch (value) + { + case IReadOnlyList list: + { + var elementType = (IInputType)inputType.ElementType(); + var deserializedList = (IList)Activator.CreateInstance(inputType.RuntimeType)!; + + var i = 0; + + foreach (var item in list) + { + deserializedList.Add( + DeserializeEnumResult( + elementType, + item, + parser, + Instance.Append(path, i++))); + } + + return deserializedList; + } + + case ListValueNode listLiteral: + { + var elementType = (IInputType)inputType.ElementType(); + var list = new List(); + + var i = 0; + + foreach (var item in listLiteral.Items) + { + list.Add( + DeserializeEnumResult( + elementType, + item, + parser, + Instance.Append(path, i++))); + } + + return list; + } + + case StringValueNode stringLiteral: + return parser.ParseResult(stringLiteral.Value, inputType, path); + + case IValueNode literal: + return parser.ParseLiteral(literal, inputType, path); + + default: + return parser.ParseResult(value, inputType, path); + } + } + + private static object? DeserializeScalarResult( + IInputType inputType, + object? value, + InputParser parser, + Path path) + { + switch (value) + { + case IReadOnlyList list: + { + var elementType = inputType; + var runtimeType = typeof(List); + + if (inputType.IsListType()) + { + elementType = (IInputType)inputType.ElementType(); + runtimeType = inputType.RuntimeType; + } + + var deserializedList = + (IList)Activator.CreateInstance(runtimeType)!; + + var i = 0; + + foreach (var item in list) + { + deserializedList.Add( + DeserializeScalarResult( + elementType, + item, + parser, + Instance.Append(path, i++))); + } + + return deserializedList; + } + + case ListValueNode listLiteral: + { + var elementType = (IInputType)inputType.ElementType(); + var list = new List(); + + var i = 0; + + foreach (var item in listLiteral.Items) + { + list.Add( + DeserializeScalarResult( + elementType, + item, + parser, + Instance.Append(path, i++))); + } + + return list; + } + + case IValueNode literal: + return parser.ParseLiteral(literal, inputType, path); + + default: + return parser.ParseResult(value, inputType, path); + } + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Delegation/DictionaryResultMiddleware.cs b/src/HotChocolate/Stitching/src/Stitching/Delegation/DictionaryResultMiddleware.cs new file mode 100644 index 00000000000..18ec4d38b14 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Delegation/DictionaryResultMiddleware.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using HotChocolate.Resolvers; +using HotChocolate.Types; + +namespace HotChocolate.Stitching.Delegation; + +public class DictionaryResultMiddleware +{ + private readonly FieldDelegate _next; + + public DictionaryResultMiddleware(FieldDelegate next) + { + _next = next ?? throw new ArgumentNullException(nameof(next)); + } + + public ValueTask InvokeAsync(IMiddlewareContext context) + { + if (context.Result is SerializedData s) + { + context.Result = s.Data is IReadOnlyDictionary d + ? d + : DictionaryDeserializer.DeserializeResult( + context.Selection.Type, + s.Data, + context.Service(), + context.Path); + } + else if (context.Result is null && + !context.Selection.Field.Directives.Contains(DirectiveNames.Computed) && + context.Parent() is IReadOnlyDictionary dict) + { + var responseName = context.Selection.SyntaxNode.Alias == null + ? context.Selection.SyntaxNode.Name.Value + : context.Selection.SyntaxNode.Alias.Value; + + dict.TryGetValue(responseName, out var obj); + context.Result = DictionaryDeserializer.DeserializeResult( + context.Selection.Type, + obj, + context.Service(), + context.Path); + } + + return _next.Invoke(context); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Delegation/ExtractFieldQuerySyntaxRewriter.Context.cs b/src/HotChocolate/Stitching/src/Stitching/Delegation/ExtractFieldQuerySyntaxRewriter.Context.cs new file mode 100644 index 00000000000..bf9869a70ef --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Delegation/ExtractFieldQuerySyntaxRewriter.Context.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using HotChocolate.Language; +using HotChocolate.Language.Visitors; +using HotChocolate.Types; + +namespace HotChocolate.Stitching.Delegation; + +public partial class ExtractFieldQuerySyntaxRewriter +{ + public class Context : ISyntaxVisitorContext + { + public Context( + string schema, + INamedOutputType? typeContext, + DocumentNode? document, + OperationDefinitionNode? operation) + { + Schema = schema; + Variables = new Dictionary(); + Document = document; + Operation = operation; + TypeContext = typeContext; + Fragments = new Dictionary(); + FragmentPath = ImmutableHashSet.Empty; + } + + public string Schema { get; } + + public DocumentNode? Document { get; } + + public OperationDefinitionNode? Operation { get; } + + public IDictionary Variables { get; } + + public INamedOutputType? TypeContext { get; set; } + + public IOutputField? OutputField { get; set; } + + public IInputField? InputField { get; set; } + + public IInputType? InputType { get; set; } + + public ImmutableHashSet FragmentPath { get; set; } + + public IDictionary Fragments { get; } + + public Context Clone() + { + return (Context)MemberwiseClone(); + } + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Delegation/ExtractFieldQuerySyntaxRewriter.cs b/src/HotChocolate/Stitching/src/Stitching/Delegation/ExtractFieldQuerySyntaxRewriter.cs new file mode 100644 index 00000000000..ca220efaad5 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Delegation/ExtractFieldQuerySyntaxRewriter.cs @@ -0,0 +1,454 @@ +using HotChocolate.Execution.Processing; +using HotChocolate.Language; +using HotChocolate.Language.Visitors; +using HotChocolate.Stitching.Utilities; +using HotChocolate.Types; +using HotChocolate.Utilities; +using HotChocolate.Utilities.Introspection; + +namespace HotChocolate.Stitching.Delegation; + +public partial class ExtractFieldQuerySyntaxRewriter + : SyntaxRewriter +{ + private readonly ISchema _schema; + private readonly FieldDependencyResolver _dependencyResolver; + private readonly IQueryDelegationRewriter[] _rewriters; + + public ExtractFieldQuerySyntaxRewriter( + ISchema schema, + IEnumerable rewriters) + { + _schema = schema ?? throw new ArgumentNullException(nameof(schema)); + _dependencyResolver = new FieldDependencyResolver(schema); + _rewriters = rewriters.ToArray(); + } + + public ExtractedField ExtractField( + string sourceSchema, + DocumentNode document, + OperationDefinitionNode operation, + ISelection selection, + INamedOutputType declaringType) + { + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + + if (operation == null) + { + throw new ArgumentNullException(nameof(operation)); + } + + if (selection == null) + { + throw new ArgumentNullException(nameof(selection)); + } + + if (declaringType == null) + { + throw new ArgumentNullException(nameof(declaringType)); + } + + if (string.IsNullOrEmpty(sourceSchema)) + { + throw new ArgumentNullException(nameof(sourceSchema)); + } + + var context = new Context( + sourceSchema, + declaringType, + document, + operation); + + var syntaxNodes = new List(); + + var syntaxNode = selection.SyntaxNode; + + var field = RewriteField(syntaxNode, context); + + if (selection.Field.Type.NamedType().IsLeafType() || + (field.SelectionSet is not null && + field.SelectionSet.Selections.Count > 0)) + { + syntaxNodes.Add(field); + } + + return new ExtractedField( + syntaxNodes, + context.Variables.Values.ToList(), + context.Fragments.Values.ToList()); + } + + public IValueNode RewriteValueNode( + string sourceSchema, + IInputType inputType, + IValueNode value) + { + sourceSchema.EnsureGraphQLName(nameof(sourceSchema)); + + var context = new Context(sourceSchema, null, null, null) { InputType = inputType }; + return (IValueNode)Rewrite(value, context)!; + } + + protected override FieldNode RewriteField( + FieldNode node, + Context context) + { + var directives = node.Directives; + + if (directives.Count > 0) + { + List? temp = null; + + foreach (var directive in directives) + { + if (BuiltInTypes.IsBuiltInType(directive.Name.Value)) + { + temp ??= new List(directives); + temp.Remove(directive); + } + } + + if (temp is not null) + { + directives = temp; + } + } + + if (context.TypeContext is IComplexOutputType type && + type.Fields.TryGetField(node.Name.Value, out var field)) + { + var cloned = context.Clone(); + cloned.OutputField = field; + + var name = node.Name; + var alias = node.Alias; + + if (field.TryGetSourceDirective(cloned.Schema, out var sourceDirective)) + { + alias ??= name; + name = new NameNode(sourceDirective.Name); + } + + var required = RewriteNodeOrDefault(node.Required, cloned); + directives = RewriteList(directives, cloned); + var arguments = RewriteList(node.Arguments, cloned); + var selectionSet = node.SelectionSet; + + if (node.SelectionSet is not null && field.Type.NamedType() is INamedOutputType n) + { + var selectionSetContext = cloned.Clone(); + selectionSetContext.TypeContext = n; + selectionSet = RewriteNodeOrDefault(node.SelectionSet, selectionSetContext); + } + + if (!ReferenceEquals(name, node.Name) || + !ReferenceEquals(alias, node.Alias) || + !ReferenceEquals(required, node.Required) || + !ReferenceEquals(directives, node.Directives) || + !ReferenceEquals(arguments, node.Arguments) || + !ReferenceEquals(selectionSet, node.SelectionSet)) + { + node = new FieldNode( + node.Location, + name, + alias, + required, + directives, + arguments, + selectionSet); + } + + return OnRewriteField(node, cloned); + } + + if (!ReferenceEquals(directives, node.Directives)) + { + return node.WithDirectives(directives); + } + + return node; + } + + private FieldNode OnRewriteField( + FieldNode node, + Context context) + { + if (_rewriters.Length == 0) + { + return node; + } + + var current = node; + + for (var i = 0; i < _rewriters.Length; i++) + { + current = _rewriters[i].OnRewriteField( + context.Schema, + context.TypeContext!, + context.OutputField!, + current); + } + + return current; + } + + protected override SelectionSetNode RewriteSelectionSet( + SelectionSetNode node, + Context context) + { + var current = node; + + var selections = new List(current.Selections); + + var dependencies = + _dependencyResolver.GetFieldDependencies( + context.Document!, + current, + context.TypeContext!); + + RemoveDelegationFields(current, context, selections); + AddDependencies(context.TypeContext!, selections, dependencies); + + if (selections.OfType().All(n => n.Name.Value != WellKnownFieldNames.TypeName)) + { + selections.Add(CreateField(WellKnownFieldNames.TypeName)); + } + + current = current.WithSelections(selections); + current = base.RewriteSelectionSet(current, context); + current = OnRewriteSelectionSet(current!, context); + + return current; + } + + private SelectionSetNode OnRewriteSelectionSet( + SelectionSetNode node, + Context context) + { + if (_rewriters.Length == 0) + { + return node; + } + + var current = node; + + for (var i = 0; i < _rewriters.Length; i++) + { + current = _rewriters[i].OnRewriteSelectionSet( + context.Schema, + context.TypeContext!, + context.OutputField!, + current); + } + + return current; + } + + protected override ArgumentNode? RewriteArgument( + ArgumentNode node, + Context context) + { + var current = node; + + if (context.OutputField != null && + context.OutputField.Arguments.TryGetField( + current.Name.Value, + out var inputField)) + { + var cloned = context.Clone(); + cloned.InputField = inputField; + cloned.InputType = inputField.Type; + + if (inputField.TryGetSourceDirective( + context.Schema, + out var sourceDirective) && + !sourceDirective.Name.Equals(current.Name.Value)) + { + current = current.WithName(new NameNode(sourceDirective.Name)); + } + + return base.RewriteArgument(current, cloned); + } + + return base.RewriteArgument(current, context); + } + + protected override ObjectFieldNode? RewriteObjectField( + ObjectFieldNode node, + Context context) + { + var current = node; + + if (context.InputType?.NamedType() is InputObjectType inputType && + inputType.Fields.TryGetField(current.Name.Value, out var inputField)) + { + var cloned = context.Clone(); + cloned.InputField = inputField; + cloned.InputType = inputField.Type; + + if (inputField.TryGetSourceDirective(context.Schema, out var sourceDirective) && + !sourceDirective.Name.Equals(current.Name.Value)) + { + current = current.WithName( + new NameNode(sourceDirective.Name)); + } + + Rewrite(node.Value, context); + + return base.RewriteObjectField(current, cloned); + } + + return base.RewriteObjectField(current, context); + } + + private static void RemoveDelegationFields( + SelectionSetNode node, + Context context, + ICollection selections) + { + if (context.TypeContext is IComplexOutputType type) + { + foreach (var selection in node.Selections.OfType()) + { + if (type.Fields.TryGetField(selection.Name.Value, out var field) && + IsDelegationField(field.Directives)) + { + selections.Remove(selection); + } + } + } + } + + private static bool IsDelegationField(IDirectiveCollection directives) + => directives.Contains(DirectiveNames.Delegate) || + directives.Contains(DirectiveNames.Computed); + + private static void AddDependencies( + Types.IHasName typeContext, + List selections, + IEnumerable dependencies) + { + foreach (var typeGroup in dependencies.GroupBy(t => t.TypeName)) + { + var fields = new List(); + + foreach (var fieldName in typeGroup.Select(t => t.FieldName)) + { + fields.Add(CreateField(fieldName)); + } + + if (typeGroup.Key.Equals(typeContext.Name)) + { + selections.AddRange(fields); + } + else + { + selections.Add( + new InlineFragmentNode( + null, + new NamedTypeNode(null, new NameNode(typeGroup.Key)), + Array.Empty(), + new SelectionSetNode(null, fields))); + } + } + } + + private static FieldNode CreateField(string fieldName) + => new(null, + new NameNode(fieldName), + null, + null, + Array.Empty(), + Array.Empty(), + null); + + protected override VariableNode? RewriteVariable( + VariableNode node, + Context context) + { + if (!context.Variables.ContainsKey(node.Name.Value)) + { + var variableDefinition = + context.Operation!.VariableDefinitions + .First(t => t.Variable.Name.Value.EqualsOrdinal(node.Name.Value)); + context.Variables[node.Name.Value] = variableDefinition!; + } + + return base.RewriteVariable(node, context); + } + + protected override FragmentSpreadNode? RewriteFragmentSpread( + FragmentSpreadNode node, + Context context) + { + var name = node.Name.Value; + + if (!context.Fragments.TryGetValue(name, out var fragment)) + { + fragment = context.Document!.Definitions + .OfType() + .First(t => t.Name.Value.EqualsOrdinal(name)); + fragment = RewriteFragmentDefinition(fragment, context); + context.Fragments[name] = fragment!; + } + + return base.RewriteFragmentSpread(node, context); + } + + protected override FragmentDefinitionNode? RewriteFragmentDefinition( + FragmentDefinitionNode node, + Context context) + { + var currentContext = context; + var current = node; + + if (currentContext.FragmentPath.Contains(current.Name.Value)) + { + return node; + } + + if (_schema.TryGetType(current.TypeCondition.Name.Value, out var type)) + { + currentContext = currentContext.Clone(); + currentContext.TypeContext = type; + currentContext.FragmentPath = currentContext.FragmentPath.Add(current.Name.Value); + + if (type.TryGetSourceDirective(context.Schema, out var sourceDirective)) + { + current = current.WithTypeCondition( + current.TypeCondition.WithName( + new NameNode(sourceDirective.Name))); + } + } + + return base.RewriteFragmentDefinition(current, currentContext); + } + + protected override InlineFragmentNode? RewriteInlineFragment( + InlineFragmentNode node, + Context context) + { + var currentContext = context; + var current = node; + + if (_schema.TryGetType(current.TypeCondition!.Name.Value, out var type)) + { + currentContext = currentContext.Clone(); + currentContext.TypeContext = type; + + if (type.TryGetSourceDirective( + context.Schema, + out var sourceDirective)) + { + current = current.WithTypeCondition( + current.TypeCondition.WithName( + new NameNode(sourceDirective.Name))); + } + } + + return base.RewriteInlineFragment(current, currentContext); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Delegation/ExtractedField.cs b/src/HotChocolate/Stitching/src/Stitching/Delegation/ExtractedField.cs new file mode 100644 index 00000000000..63ee53bcbc5 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Delegation/ExtractedField.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using HotChocolate.Language; + +namespace HotChocolate.Stitching.Delegation; + +public class ExtractedField +{ + public ExtractedField( + IReadOnlyList syntaxNodes, + IReadOnlyList variables, + IReadOnlyList fragments) + { + SyntaxNodes = syntaxNodes ?? throw new ArgumentNullException(nameof(syntaxNodes)); + Variables = variables ?? throw new ArgumentNullException(nameof(variables)); + Fragments = fragments ?? throw new ArgumentNullException(nameof(fragments)); + } + + public IReadOnlyList SyntaxNodes { get; } + + public IReadOnlyList Variables { get; } + + public IReadOnlyList Fragments { get; } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Delegation/RemoteFieldHelper.cs b/src/HotChocolate/Stitching/src/Stitching/Delegation/RemoteFieldHelper.cs new file mode 100644 index 00000000000..d06fe6d9c03 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Delegation/RemoteFieldHelper.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using HotChocolate.Resolvers; +using HotChocolate.Types; + +namespace HotChocolate.Stitching.Delegation; + +internal static class RemoteFieldHelper +{ + public static object? RemoteFieldResolver(IPureResolverContext context) + { + if (!context.Selection.Field.Directives.Contains(DirectiveNames.Computed) && + context.Parent() is IReadOnlyDictionary dict) + { + var responseName = context.Selection.SyntaxNode.Alias == null + ? context.Selection.SyntaxNode.Name.Value + : context.Selection.SyntaxNode.Alias.Value; + + dict.TryGetValue(responseName, out var obj); + return DictionaryDeserializer.DeserializeResult( + context.Selection.Type, + obj, + context.Service(), + context.Path); + } + + return null; + } +} + diff --git a/src/HotChocolate/Stitching/src/Stitching/Delegation/RemoteQueryBuilder.cs b/src/HotChocolate/Stitching/src/Stitching/Delegation/RemoteQueryBuilder.cs new file mode 100644 index 00000000000..b660eecfe52 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Delegation/RemoteQueryBuilder.cs @@ -0,0 +1,326 @@ +using System.Collections.Immutable; +using HotChocolate.Language; +using HotChocolate.Utilities; + +namespace HotChocolate.Stitching.Delegation; + +public class RemoteQueryBuilder +{ + private static readonly CollectUsedVariableVisitor _usedVariables = new(); + private readonly List _additionalFields = new(); + private readonly List _variables = new(); + private readonly List _fragments = new(); + private NameNode _operationName = new("fetch"); + private OperationType _operation = OperationType.Query; + private IImmutableStack _path = + ImmutableStack.Empty; + private FieldNode? _requestField; + + public RemoteQueryBuilder SetOperation( + NameNode? name, + OperationType operation) + { + if (name != null) + { + _operationName = name; + } + + _operation = operation; + return this; + } + + public RemoteQueryBuilder SetSelectionPath( + IImmutableStack selectionPath) + { + _path = selectionPath ?? throw new ArgumentNullException(nameof(selectionPath)); + return this; + } + + public RemoteQueryBuilder SetRequestField(FieldNode field) + { + _requestField = field ?? throw new ArgumentNullException(nameof(field)); + return this; + } + + public RemoteQueryBuilder AddAdditionalField( + FieldNode field) + { + if (field == null) + { + throw new ArgumentNullException(nameof(field)); + } + _additionalFields.Add(field); + return this; + } + + public RemoteQueryBuilder AddVariable( + string name, ITypeNode type) => + AddVariable(name, type, null); + + public RemoteQueryBuilder AddVariable( + string name, + ITypeNode type, + IValueNode? defaultValue) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + name.EnsureGraphQLName(); + + AddVariable(new VariableDefinitionNode + ( + null, + new VariableNode(new NameNode(name)), + type, + defaultValue, + Array.Empty() + )); + + return this; + } + + public RemoteQueryBuilder AddVariable( + VariableDefinitionNode variable) + { + if (variable == null) + { + throw new ArgumentNullException(nameof(variable)); + } + + _variables.Add(variable); + + return this; + } + + public RemoteQueryBuilder AddVariables( + IEnumerable variables) + { + if (variables == null) + { + throw new ArgumentNullException(nameof(variables)); + } + + _variables.AddRange(variables); + + return this; + } + + public RemoteQueryBuilder AddFragmentDefinitions( + IEnumerable fragments) + { + if (fragments == null) + { + throw new ArgumentNullException(nameof(fragments)); + } + + _fragments.AddRange(fragments); + + return this; + } + + public DocumentNode Build( + string targetSchema, + IReadOnlyDictionary<(string Type, string Schema), string> nameLookup) + { + if (_requestField == null || _path == null) + { + throw new InvalidOperationException(); + } + + var requestField = _requestField; + + if (_additionalFields.Count == 0) + { + return CreateDelegationQuery( + targetSchema, + nameLookup, + _operation, + _path, + new[] { requestField }); + } + + var fields = new List { _requestField }; + fields.AddRange(_additionalFields); + + return CreateDelegationQuery( + targetSchema, + nameLookup, + _operation, + _path, + fields); + } + + private DocumentNode CreateDelegationQuery( + string targetSchema, + IReadOnlyDictionary<(string Type, string Schema), string> nameLookup, + OperationType operation, + IImmutableStack path, + IEnumerable requestedFields) + { + var usedVariables = new HashSet(); + var fields = new List(); + + foreach (var requestedField in requestedFields) + { + var currentPath = path; + + if (currentPath.IsEmpty) + { + currentPath = currentPath.Push( + new SelectionPathComponent( + requestedField.Name, + Array.Empty())); + } + + var current = CreateRequestedField(requestedField, ref currentPath); + + while (!currentPath.IsEmpty) + { + currentPath = currentPath.Pop(out var component); + current = CreateSelection(current, component); + } + + _usedVariables.CollectUsedVariables(current, usedVariables); + _usedVariables.CollectUsedVariables(_fragments, usedVariables); + + fields.Add(current); + } + + var variables = _variables + .Where(t => usedVariables.Contains(t.Variable.Name.Value)) + .ToList(); + + for (var i = 0; i < variables.Count; i++) + { + var variable = variables[i]; + var typeName = variable.Type.NamedType().Name.Value; + + if (nameLookup.TryGetValue((typeName, targetSchema), out var targetName)) + { + variable = variable.WithType(RewriteType(variable.Type, targetName)); + variables[i] = variable; + } + } + + var operationDefinition = + CreateOperation(_operationName, operation, fields, variables); + + var definitions = new List { operationDefinition }; + definitions.AddRange(_fragments); + + return new DocumentNode(null, definitions); + } + + private static ITypeNode RewriteType(ITypeNode type, string typeName) + { + if (type is NonNullTypeNode nonNullType) + { + return new NonNullTypeNode( + (INullableTypeNode)RewriteType(nonNullType.Type, typeName)); + } + + if (type is ListTypeNode listTypeNode) + { + return new ListTypeNode(RewriteType(listTypeNode.Type, typeName)); + } + + return new NamedTypeNode(typeName); + } + + private static FieldNode CreateRequestedField( + FieldNode requestedField, + ref IImmutableStack path) + { + path = path.Pop(out var component); + + var responseName = requestedField.Alias == null + ? requestedField.Name.Value + : requestedField.Alias.Value; + + var alias = component.Name.Value.EqualsOrdinal(responseName) + ? null + : new NameNode(responseName); + + var arguments = + component.Arguments.Count == 0 + ? requestedField.Arguments + : RewriteVariableNames(component.Arguments).ToList(); + + return new FieldNode + ( + null, + component.Name, + alias, + null, + requestedField.Directives, + arguments, + requestedField.SelectionSet + ); + } + + private static FieldNode CreateSelection( + FieldNode previous, + SelectionPathComponent next) + { + var selectionSet = new SelectionSetNode( + null, + new List { previous }); + + return CreateSelection(selectionSet, next, null); + } + + private static FieldNode CreateSelection( + SelectionSetNode selectionSet, + SelectionPathComponent next, + string? alias) + { + var aliasNode = string.IsNullOrEmpty(alias) + ? null : new NameNode(alias); + + return new FieldNode + ( + null, + next.Name, + aliasNode, + null, + Array.Empty(), + RewriteVariableNames(next.Arguments).ToList(), + selectionSet + ); + } + + private static OperationDefinitionNode CreateOperation( + NameNode name, + OperationType operation, + IReadOnlyList fields, + IReadOnlyList variables) + { + return new OperationDefinitionNode( + null, + name, + operation, + variables, + Array.Empty(), + new SelectionSetNode(null, fields)); + } + + private static IEnumerable RewriteVariableNames( + IEnumerable arguments) + { + foreach (var argument in arguments) + { + if (argument.Value is ScopedVariableNode v) + { + yield return argument.WithValue(v.ToVariableNode()); + } + else + { + yield return argument; + } + } + } + + public static RemoteQueryBuilder New() => new RemoteQueryBuilder(); +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Delegation/ScopedVariables/ArgumentScopedVariableResolver.cs b/src/HotChocolate/Stitching/src/Stitching/Delegation/ScopedVariables/ArgumentScopedVariableResolver.cs new file mode 100644 index 00000000000..ee10e63c080 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Delegation/ScopedVariables/ArgumentScopedVariableResolver.cs @@ -0,0 +1,54 @@ +using System; +using HotChocolate.Language; +using HotChocolate.Resolvers; +using HotChocolate.Types; +using static HotChocolate.Stitching.ThrowHelper; +using static HotChocolate.Stitching.Properties.StitchingResources; + +namespace HotChocolate.Stitching.Delegation.ScopedVariables; + +internal class ArgumentScopedVariableResolver : IScopedVariableResolver +{ + public ScopedVariableValue Resolve( + IResolverContext context, + ScopedVariableNode variable, + IInputType targetType) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (variable is null) + { + throw new ArgumentNullException(nameof(variable)); + } + + if (!ScopeNames.Arguments.Equals(variable.Scope.Value)) + { + throw new ArgumentException( + ArgumentScopedVariableResolver_CannotHandleVariable, + nameof(variable)); + } + + if (!context.Selection.Field.Arguments.TryGetField( + variable.Name.Value, + out var argument)) + { + throw ArgumentScopedVariableResolver_InvalidArgumentName( + variable.Name.Value, + context.Selection.SyntaxNode, + context.Path); + } + + return new ScopedVariableValue + ( + variable.ToVariableName(), + argument.Type.ToTypeNode(), + context.ArgumentLiteral(variable.Name.Value), + argument.Type.IsNonNullType() && argument.DefaultValue.IsNull() + ? null + : argument.DefaultValue + ); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Delegation/ScopedVariables/ContextDataScopedVariableResolver.cs b/src/HotChocolate/Stitching/src/Stitching/Delegation/ScopedVariables/ContextDataScopedVariableResolver.cs new file mode 100644 index 00000000000..5abcca6606e --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Delegation/ScopedVariables/ContextDataScopedVariableResolver.cs @@ -0,0 +1,59 @@ +using System; +using HotChocolate.Execution; +using HotChocolate.Language; +using HotChocolate.Resolvers; +using HotChocolate.Stitching.Properties; +using HotChocolate.Types; +using static HotChocolate.Execution.PathFactory; +using static HotChocolate.Stitching.Properties.StitchingResources; + +namespace HotChocolate.Stitching.Delegation.ScopedVariables; + +internal class ContextDataScopedVariableResolver : IScopedVariableResolver +{ + public ScopedVariableValue Resolve( + IResolverContext context, + ScopedVariableNode variable, + IInputType targetType) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (variable == null) + { + throw new ArgumentNullException(nameof(variable)); + } + + if (targetType == null) + { + throw new ArgumentNullException(nameof(targetType)); + } + + if (!ScopeNames.ContextData.Equals(variable.Scope.Value)) + { + throw new ArgumentException( + ContextDataScopedVariableResolver_CannotHandleVariable, + nameof(variable)); + } + + context.ContextData.TryGetValue(variable.Name.Value, out var data); + var formatter = context.Service(); + + var literal = data switch + { + IValueNode l => l, + null => NullValueNode.Default, + _ => formatter.FormatValue(data, targetType, Instance.New(variable.Name.Value)) + }; + + return new ScopedVariableValue + ( + variable.ToVariableName(), + targetType.ToTypeNode(), + literal, + null + ); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Delegation/ScopedVariables/FieldScopedVariableResolver.cs b/src/HotChocolate/Stitching/src/Stitching/Delegation/ScopedVariables/FieldScopedVariableResolver.cs new file mode 100644 index 00000000000..2fe7b508e1e --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Delegation/ScopedVariables/FieldScopedVariableResolver.cs @@ -0,0 +1,70 @@ +using HotChocolate.Execution; +using HotChocolate.Language; +using HotChocolate.Resolvers; +using HotChocolate.Types; +using static HotChocolate.Execution.PathFactory; +using static HotChocolate.Stitching.Properties.StitchingResources; + +namespace HotChocolate.Stitching.Delegation.ScopedVariables; + +internal class FieldScopedVariableResolver + : IScopedVariableResolver +{ + public ScopedVariableValue Resolve( + IResolverContext context, + ScopedVariableNode variable, + IInputType targetType) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (variable == null) + { + throw new ArgumentNullException(nameof(variable)); + } + + if (!ScopeNames.Fields.Equals(variable.Scope.Value)) + { + throw new ArgumentException( + FieldScopedVariableResolver_CannotHandleVariable, + nameof(variable)); + } + + if (context.ObjectType.Fields.TryGetField(variable.Name.Value, out var field)) + { + var parent = context.Parent(); + + IValueNode? valueLiteral = null; + + if (parent is IReadOnlyDictionary dict && + dict.TryGetValue(field.Name, out var value)) + { + var formatter = context.Service(); + + if (value is IValueNode v) + { + valueLiteral = v; + } + else if(field.Type.IsInputType() && field.Type is IInputType type) + { + valueLiteral = formatter.FormatValue(value, type, Instance.New(field.Name)); + } + } + + return new ScopedVariableValue + ( + variable.ToVariableName(), + targetType.ToTypeNode(), + valueLiteral ?? NullValueNode.Default, + null + ); + } + + throw ThrowHelper.FieldScopedVariableResolver_InvalidFieldName( + variable.Name.Value, + context.Selection.SyntaxNode, + context.Path); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Delegation/ScopedVariables/IScopedVariableResolver.cs b/src/HotChocolate/Stitching/src/Stitching/Delegation/ScopedVariables/IScopedVariableResolver.cs new file mode 100644 index 00000000000..1004045cfaa --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Delegation/ScopedVariables/IScopedVariableResolver.cs @@ -0,0 +1,12 @@ +using HotChocolate.Resolvers; +using HotChocolate.Types; + +namespace HotChocolate.Stitching.Delegation.ScopedVariables; + +internal interface IScopedVariableResolver +{ + ScopedVariableValue Resolve( + IResolverContext context, + ScopedVariableNode variable, + IInputType targetType); +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Delegation/ScopedVariables/RootScopedVariableResolver.cs b/src/HotChocolate/Stitching/src/Stitching/Delegation/ScopedVariables/RootScopedVariableResolver.cs new file mode 100644 index 00000000000..4b8b3be77f4 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Delegation/ScopedVariables/RootScopedVariableResolver.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using HotChocolate.Execution; +using HotChocolate.Resolvers; +using HotChocolate.Stitching.Properties; +using HotChocolate.Types; + +namespace HotChocolate.Stitching.Delegation.ScopedVariables; + +internal class RootScopedVariableResolver + : IScopedVariableResolver +{ + private readonly Dictionary _resolvers = + new Dictionary + { + { ScopeNames.Arguments, new ArgumentScopedVariableResolver() }, + { ScopeNames.Fields, new FieldScopedVariableResolver() }, + { ScopeNames.ContextData, new ContextDataScopedVariableResolver() }, + { ScopeNames.ScopedContextData, new ScopedContextDataScopedVariableResolver() } + }; + + public ScopedVariableValue Resolve( + IResolverContext context, + ScopedVariableNode variable, + IInputType targetType) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (variable is null) + { + throw new ArgumentNullException(nameof(variable)); + } + + if (_resolvers.TryGetValue(variable.Scope.Value, out var resolver)) + { + return resolver.Resolve(context, variable, targetType); + } + + throw ThrowHelper.RootScopedVariableResolver_ScopeNotSupported( + variable.Scope.Value, + context.Selection.SyntaxNode, + context.Path); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Delegation/ScopedVariables/ScopeNames.cs b/src/HotChocolate/Stitching/src/Stitching/Delegation/ScopedVariables/ScopeNames.cs new file mode 100644 index 00000000000..2cdfd156804 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Delegation/ScopedVariables/ScopeNames.cs @@ -0,0 +1,9 @@ +namespace HotChocolate.Stitching.Delegation.ScopedVariables; + +public static class ScopeNames +{ + public static string Arguments { get; } = "arguments"; + public static string Fields { get; } = "fields"; + public static string ContextData { get; } = "contextData"; + public static string ScopedContextData { get; } = "scopedContextData"; +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Delegation/ScopedVariables/ScopedContextDataScopedVariableResolver.cs b/src/HotChocolate/Stitching/src/Stitching/Delegation/ScopedVariables/ScopedContextDataScopedVariableResolver.cs new file mode 100644 index 00000000000..670386cc74a --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Delegation/ScopedVariables/ScopedContextDataScopedVariableResolver.cs @@ -0,0 +1,58 @@ +using HotChocolate.Execution; +using HotChocolate.Language; +using HotChocolate.Resolvers; +using HotChocolate.Types; +using static HotChocolate.Execution.PathFactory; +using static HotChocolate.Stitching.Properties.StitchingResources; + +namespace HotChocolate.Stitching.Delegation.ScopedVariables; + +internal class ScopedContextDataScopedVariableResolver + : IScopedVariableResolver +{ + public ScopedVariableValue Resolve( + IResolverContext context, + ScopedVariableNode variable, + IInputType targetType) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (variable is null) + { + throw new ArgumentNullException(nameof(variable)); + } + + if (targetType is null) + { + throw new ArgumentNullException(nameof(targetType)); + } + + if (!ScopeNames.ScopedContextData.Equals(variable.Scope.Value)) + { + throw new ArgumentException( + ScopedCtxDataScopedVariableResolver_CannotHandleVariable, + nameof(variable)); + } + + context.ScopedContextData.TryGetValue(variable.Name.Value, out var data); + var formatter = context.Service(); + + var literal = data switch + { + IValueNode l => l, + null => NullValueNode.Default, + _ => formatter.FormatValue(data, targetType, Instance.New(variable.Name.Value)) + }; + + return new ScopedVariableValue + ( + variable.ToVariableName(), + targetType.ToTypeNode(), + literal, + null + ); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Delegation/ScopedVariables/ScopedVariableValue.cs b/src/HotChocolate/Stitching/src/Stitching/Delegation/ScopedVariables/ScopedVariableValue.cs new file mode 100644 index 00000000000..2d9358636f8 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Delegation/ScopedVariables/ScopedVariableValue.cs @@ -0,0 +1,30 @@ +using System; +using HotChocolate.Language; + +namespace HotChocolate.Stitching.Delegation.ScopedVariables; + +internal readonly struct ScopedVariableValue +{ + internal ScopedVariableValue( + string name, + ITypeNode type, + IValueNode? value, + IValueNode? defaultValue) + { + Name = name ?? throw new ArgumentNullException(nameof(name)); + Type = type ?? throw new ArgumentNullException(nameof(type)); + Value = value; + DefaultValue = defaultValue; + } + + public string Name { get; } + + public ITypeNode Type { get; } + + public IValueNode? Value { get; } + + public IValueNode? DefaultValue { get; } + + public ScopedVariableValue WithValue(IValueNode value) => + new ScopedVariableValue(Name, Type, value, DefaultValue); +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Delegation/SerializedData.cs b/src/HotChocolate/Stitching/src/Stitching/Delegation/SerializedData.cs new file mode 100644 index 00000000000..e9731546429 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Delegation/SerializedData.cs @@ -0,0 +1,11 @@ +namespace HotChocolate.Stitching.Delegation; + +public sealed class SerializedData +{ + public SerializedData(object? data) + { + Data = data; + } + + public object? Data { get; } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/DependencyInjection/HotChocolateStitchingRequestExecutorExtensions.DomainServices.cs b/src/HotChocolate/Stitching/src/Stitching/DependencyInjection/HotChocolateStitchingRequestExecutorExtensions.DomainServices.cs new file mode 100644 index 00000000000..8be29fbf524 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/DependencyInjection/HotChocolateStitchingRequestExecutorExtensions.DomainServices.cs @@ -0,0 +1,30 @@ +using System; +using HotChocolate.Execution.Configuration; +using HotChocolate.Stitching.SchemaDefinitions; + +namespace Microsoft.Extensions.DependencyInjection; + +public static partial class HotChocolateStitchingRequestExecutorExtensions +{ + public static IRequestExecutorBuilder PublishSchemaDefinition( + this IRequestExecutorBuilder builder, + Action configure) + { + var descriptor = new PublishSchemaDefinitionDescriptor(builder); + configure(descriptor); + + var typeInterceptor = new SchemaDefinitionTypeInterceptor(!descriptor.HasPublisher); + var schemaInterceptor = new SchemaDefinitionSchemaInterceptor(descriptor); + + builder + .AddType() + .TryAddTypeInterceptor(typeInterceptor) + .TryAddSchemaInterceptor(schemaInterceptor) + .ConfigureOnRequestExecutorCreatedAsync( + async (sp, executor, ct) => await descriptor + .PublishAsync(sp, ct) + .ConfigureAwait(false)); + + return builder; + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/DependencyInjection/HotChocolateStitchingRequestExecutorExtensions.cs b/src/HotChocolate/Stitching/src/Stitching/DependencyInjection/HotChocolateStitchingRequestExecutorExtensions.cs new file mode 100644 index 00000000000..ba99725d8a6 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/DependencyInjection/HotChocolateStitchingRequestExecutorExtensions.cs @@ -0,0 +1,1049 @@ +using System.Reflection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using HotChocolate; +using HotChocolate.Execution; +using HotChocolate.Execution.Configuration; +using HotChocolate.Execution.Internal; +using HotChocolate.Language; +using HotChocolate.Resolvers; +using HotChocolate.Stitching; +using HotChocolate.Stitching.Merge; +using HotChocolate.Stitching.Merge.Rewriters; +using HotChocolate.Stitching.Pipeline; +using HotChocolate.Stitching.Requests; +using HotChocolate.Stitching.SchemaDefinitions; +using HotChocolate.Stitching.Utilities; +using HotChocolate.Utilities; +using HotChocolate.Utilities.Introspection; +using static HotChocolate.Stitching.ThrowHelper; + +namespace Microsoft.Extensions.DependencyInjection; + +public static partial class HotChocolateStitchingRequestExecutorExtensions +{ + /// + /// This middleware delegates GraphQL requests to a different GraphQL server using + /// GraphQL HTTP requests. + /// + /// + /// The . + /// + /// + /// Returns the . + /// + /// + /// The is null. + /// + public static IRequestExecutorBuilder UseHttpRequests( + this IRequestExecutorBuilder builder) + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return builder.UseRequest(); + } + + public static IRequestExecutorBuilder UseHttpRequestPipeline( + this IRequestExecutorBuilder builder) + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return builder + .UseInstrumentation() + .UseExceptions() + .UseDocumentCache() + .UseDocumentParser() + .UseDocumentValidation() + .UseOperationCache() + .UseOperationResolver() + .UseOperationVariableCoercion() + .UseHttpRequests(); + } + + public static IRequestExecutorBuilder AddRemoteSchema( + this IRequestExecutorBuilder builder, + string schemaName, + bool ignoreRootTypes = false) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + schemaName.EnsureGraphQLName(nameof(schemaName)); + + return AddRemoteSchema( + builder, + schemaName, + async (services, cancellationToken) => + { + // The schema will be fetched via HTTP from the downstream service. + // We will use the schema name to get a the HttpClient, which + // we expect is correctly configured. + var httpClient = services + .GetRequiredService() + .CreateClient(schemaName); + + // Next we will fetch the schema definition which contains the + // schema document and other configuration + return await new IntrospectionHelper(httpClient, schemaName) + .GetSchemaDefinitionAsync(cancellationToken) + .ConfigureAwait(false); + }, + ignoreRootTypes); + } + + public static IRequestExecutorBuilder AddRemoteSchemaFromString( + this IRequestExecutorBuilder builder, + string schemaName, + string schemaSdl, + bool ignoreRootTypes = false) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + schemaName.EnsureGraphQLName(nameof(schemaName)); + + return AddRemoteSchema( + builder, + schemaName, + (_, __) => + new ValueTask( + new RemoteSchemaDefinition( + schemaName, + Utf8GraphQLParser.Parse(schemaSdl))), + ignoreRootTypes); + } + + public static IRequestExecutorBuilder AddRemoteSchemaFromFile( + this IRequestExecutorBuilder builder, + string schemaName, + string fileName, + bool ignoreRootTypes = false) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + schemaName.EnsureGraphQLName(nameof(schemaName)); + + return AddRemoteSchema( + builder, + schemaName, + async (_, cancellationToken) => + { + var schemaSdl = await File + .ReadAllBytesAsync(fileName, cancellationToken) + .ConfigureAwait(false); + + return new RemoteSchemaDefinition( + schemaName, + Utf8GraphQLParser.Parse(schemaSdl)); + }, + ignoreRootTypes); + } + + public static IRequestExecutorBuilder AddRemoteSchema( + this IRequestExecutorBuilder builder, + string schemaName, + Func> loadSchema, + bool ignoreRootTypes = false) + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (loadSchema is null) + { + throw new ArgumentNullException(nameof(loadSchema)); + } + + schemaName.EnsureGraphQLName(nameof(schemaName)); + + // first we add a full GraphQL schema and executor that represents the remote schema. + // This remote schema will be used by the stitching engine to execute queries against + // this schema and also to lookup types in order correctly convert between scalars. + builder + .AddGraphQL(schemaName) + .ConfigureSchemaServices(services => + { + services.TryAddSingleton( + sp => new HttpRequestClient( + sp.GetCombinedServices().GetRequiredService(), + sp.GetRequiredService(), + sp.GetCombinedServices() + .GetRequiredService())); + + services.TryAddSingleton< + IHttpStitchingRequestInterceptor, + HttpStitchingRequestInterceptor>(); + }) + .ConfigureSchemaAsync( + async (services, schemaBuilder, cancellationToken) => + { + // No we need to load the schema document. + var schemaDef = + await loadSchema(services, cancellationToken) + .ConfigureAwait(false); + + var document = schemaDef.Document.RemoveBuiltInTypes(); + + // We store the schema definition on the schema building context + // and copy it to the schema once that is built. + schemaBuilder + .SetContextData(typeof(RemoteSchemaDefinition).FullName!, schemaDef) + .TryAddTypeInterceptor(); + + // The document is used to create a SDL-first schema ... + schemaBuilder.AddDocument(document); + + // ... which will fail if any resolver is actually used ... + schemaBuilder.Use(_ => _ => throw new NotSupportedException()); + }) + // ... instead we are using a special request pipeline that does everything like + // the standard pipeline except the last middleware will not start the execution + // algorithms but delegate the request via HTTP to the downstream schema. + .UseHttpRequestPipeline(); + + // Next, we will register a request executor proxy with the stitched schema, + // that the stitching runtime will use to send requests to the schema representing + // the downstream service. + builder + .ConfigureSchemaAsync(async (services, schemaBuilder, cancellationToken) => + { + var noLockExecutorResolver = + services.GetRequiredService(); + + var executor = await noLockExecutorResolver + .GetRequestExecutorNoLockAsync(schemaName, cancellationToken) + .ConfigureAwait(false); + + var autoProxy = AutoUpdateRequestExecutorProxy.Create( + new RequestExecutorProxy( + services.GetRequiredService(), + schemaName), + executor); + + schemaBuilder + .AddRemoteExecutor(schemaName, autoProxy) + .TryAddSchemaInterceptor() + .TryAddTypeInterceptor(); + + var schemaDefinition = + (RemoteSchemaDefinition)autoProxy.Schema + .ContextData[typeof(RemoteSchemaDefinition).FullName!]!; + + var extensionsRewriter = new SchemaExtensionsRewriter(); + + foreach (var extensionDocument in schemaDefinition.ExtensionDocuments) + { + var doc = (DocumentNode)extensionsRewriter.Rewrite( + extensionDocument, new(schemaName))!; + + var schemaExtension = + doc.Definitions.OfType().FirstOrDefault(); + + if (schemaExtension is not null && + schemaExtension.Directives.Count == 0 && + schemaExtension.OperationTypes.Count == 0) + { + var definitions = doc.Definitions.ToList(); + definitions.Remove(schemaExtension); + doc = doc.WithDefinitions(definitions); + } + + schemaBuilder.AddTypeExtensions(doc); + } + + schemaBuilder.AddTypeRewriter( + new RemoveFieldRewriter( + new FieldReference( + autoProxy.Schema.QueryType.Name, + SchemaDefinitionFieldNames.SchemaDefinitionField), + schemaName)); + + schemaBuilder.AddDocumentRewriter( + new RemoveTypeRewriter( + SchemaDefinitionType.Names.SchemaDefinition, + schemaName)); + + foreach (var schemaAction in extensionsRewriter.SchemaActions) + { + switch (schemaAction.Name.Value) + { + case DirectiveNames.RemoveRootTypes: + schemaBuilder.AddDocumentRewriter( + new RemoveRootTypeRewriter(schemaName)); + break; + + case DirectiveNames.RemoveType: + schemaBuilder.AddDocumentRewriter( + new RemoveTypeRewriter( + GetArgumentValue( + schemaAction, + DirectiveFieldNames.RemoveType_TypeName), + schemaName)); + break; + + case DirectiveNames.RenameType: + schemaBuilder.AddTypeRewriter( + new RenameTypeRewriter( + GetArgumentValue( + schemaAction, + DirectiveFieldNames.RenameType_TypeName), + GetArgumentValue( + schemaAction, + DirectiveFieldNames.RenameType_NewTypeName), + schemaName)); + break; + + case DirectiveNames.RenameField: + schemaBuilder.AddTypeRewriter( + new RenameFieldRewriter( + new FieldReference( + GetArgumentValue( + schemaAction, + DirectiveFieldNames.RenameField_TypeName), + GetArgumentValue( + schemaAction, + DirectiveFieldNames.RenameField_FieldName)), + GetArgumentValue( + schemaAction, + DirectiveFieldNames.RenameField_NewFieldName), + schemaName)); + break; + } + } + }); + + // Last but not least, we will setup the stitching context which will + // provide access to the remote executors which in turn use the just configured + // request executor proxies to send requests to the downstream services. + builder.Services.TryAddScoped(); + + if (ignoreRootTypes) + { + builder.AddDocumentRewriter(new RemoveRootTypeRewriter(schemaName)); + } + + return builder; + } + + public static IRequestExecutorBuilder AddLocalSchema( + this IRequestExecutorBuilder builder, + string schemaName, + bool ignoreRootTypes = false) + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } + + schemaName.EnsureGraphQLName(nameof(schemaName)); + + // Next, we will register a request executor proxy with the stitched schema, + // that the stitching runtime will use to send requests to the schema representing + // the downstream service. + builder + .ConfigureSchemaAsync(async (services, schemaBuilder, cancellationToken) => + { + var noLockExecutorResolver = + services.GetRequiredService(); + + var executor = await noLockExecutorResolver + .GetRequestExecutorNoLockAsync(schemaName, cancellationToken) + .ConfigureAwait(false); + + var autoProxy = AutoUpdateRequestExecutorProxy.Create( + new RequestExecutorProxy( + services.GetRequiredService(), + schemaName), + executor); + + schemaBuilder + .AddRemoteExecutor(schemaName, autoProxy) + .TryAddSchemaInterceptor() + .TryAddTypeInterceptor(); + + schemaBuilder.AddTypeRewriter( + new RemoveFieldRewriter( + new FieldReference( + autoProxy.Schema.QueryType.Name, + SchemaDefinitionFieldNames.SchemaDefinitionField), + schemaName)); + + schemaBuilder.AddDocumentRewriter( + new RemoveTypeRewriter( + SchemaDefinitionType.Names.SchemaDefinition, + schemaName)); + }); + + // Last but not least, we will setup the stitching context which will + // provide access to the remote executors which in turn use the just configured + // request executor proxies to send requests to the downstream services. + builder.Services.TryAddScoped(); + + if (ignoreRootTypes) + { + builder.AddDocumentRewriter(new RemoveRootTypeRewriter(schemaName)); + } + + return builder; + } + + /// + /// Add a type merge rule in order to define how a type is merged. + /// + /// + /// The . + /// + /// + /// A factory that create the type merging rule. + /// + /// + /// Returns the . + /// + /// + /// is null, or + /// is null. + /// + public static IRequestExecutorBuilder AddTypeMergeRule( + this IRequestExecutorBuilder builder, + MergeTypeRuleFactory mergeRuleFactory) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (mergeRuleFactory == null) + { + throw new ArgumentNullException(nameof(mergeRuleFactory)); + } + + return builder.ConfigureSchema( + s => s.AddTypeMergeRules(mergeRuleFactory)); + } + + /// + /// Add a directive merge rule in order to define + /// how a directive is merged. + /// + /// + /// The . + /// + /// + /// A factory that create the directive merging rule. + /// + /// + /// Returns the . + /// + /// + /// is null, or + /// is null. + /// + public static IRequestExecutorBuilder AddDirectiveMergeRule( + this IRequestExecutorBuilder builder, + MergeDirectiveRuleFactory mergeRuleFactory) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (mergeRuleFactory == null) + { + throw new ArgumentNullException(nameof(mergeRuleFactory)); + } + + return builder.ConfigureSchema( + s => s.AddDirectiveMergeRules(mergeRuleFactory)); + } + + /// + /// Add a type definition rewriter in order to rewrite a + /// type definition on a remote schema document before + /// it is being merged. + /// + /// + /// The . + /// + /// + /// The type definition rewriter. + /// + /// + /// Returns the . + /// + /// + /// is null, or + /// is null. + /// + public static IRequestExecutorBuilder AddTypeRewriter( + this IRequestExecutorBuilder builder, + ITypeRewriter rewriter) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (rewriter == null) + { + throw new ArgumentNullException(nameof(rewriter)); + } + + return builder.ConfigureSchema(s => s.AddTypeRewriter(rewriter)); + } + + /// + /// Add a document rewriter in order to rewrite + /// a remote schema document before it is being merged. + /// + /// + /// The . + /// + /// + /// The document rewriter. + /// + /// + /// Returns the . + /// + /// + /// is null, or + /// is null. + /// + public static IRequestExecutorBuilder AddDocumentRewriter( + this IRequestExecutorBuilder builder, + IDocumentRewriter rewriter) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (rewriter == null) + { + throw new ArgumentNullException(nameof(rewriter)); + } + + return builder.ConfigureSchema( + s => s.AddDocumentRewriter(rewriter)); + } + + /// + /// Adds a schema SDL that contains type extensions. + /// Extension documents can be used to extend merged types + /// or even replace them. + /// + /// + /// The . + /// + /// + /// The GraphQL schema SDL. + /// + /// + /// Returns the . + /// + /// + /// is null, or + /// is null. + /// + public static IRequestExecutorBuilder AddTypeExtensionsFromString( + this IRequestExecutorBuilder builder, + string schemaSdl) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (string.IsNullOrEmpty(schemaSdl)) + { + throw new ArgumentNullException(nameof(schemaSdl)); + } + + return builder.ConfigureSchema( + s => s.AddTypeExtensions(Utf8GraphQLParser.Parse(schemaSdl))); + } + + /// + /// Adds a schema SDL that contains type extensions. + /// Extension documents can be used to extend merged types + /// or even replace them. + /// + /// + /// The . + /// + /// + /// The file name of the type extension document. + /// + /// + /// Returns the . + /// + /// + /// is null, or + /// is null. + /// + public static IRequestExecutorBuilder AddTypeExtensionsFromFile( + this IRequestExecutorBuilder builder, + string fileName) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (string.IsNullOrEmpty(fileName)) + { + throw new ArgumentNullException(nameof(fileName)); + } + + return builder.ConfigureSchemaAsync( + async (s, ct) => + { +#if NETSTANDARD2_0 + byte[] content = await Task + .Run(() => File.ReadAllBytes(fileName), ct) + .ConfigureAwait(false); +#else + var content = await File + .ReadAllBytesAsync(fileName, ct) + .ConfigureAwait(false); +#endif + + s.AddTypeExtensions(Utf8GraphQLParser.Parse(content)); + }); + } + + /// + /// Adds a schema SDL that contains type extensions. + /// Extension documents can be used to extend merged types + /// or even replace them. + /// + /// + /// The . + /// + /// + /// The assembly from which the type extension file shall be resolved. + /// + /// + /// The resource key of the type extension file + /// + /// + /// Returns the . + /// + /// + /// is null, or + /// is null. + /// is null. + /// + public static IRequestExecutorBuilder AddTypeExtensionsFromResource( + this IRequestExecutorBuilder builder, + Assembly assembly, + string key) + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (assembly is null) + { + throw new ArgumentNullException(nameof(assembly)); + } + + if (key is null) + { + throw new ArgumentNullException(nameof(key)); + } + + return builder.ConfigureSchemaAsync( + async (s, ct) => + { + var stream = assembly.GetManifestResourceStream(key); + + if (stream is null) + { + throw RequestExecutorBuilder_ResourceNotFound(key); + } + +#if NET5_0 || NET6_0 + await using (stream) +#else + using (stream) +#endif + { + var buffer = new byte[stream.Length]; + await stream.ReadAsync(buffer, 0, buffer.Length, ct).ConfigureAwait(false); + s.AddTypeExtensions(Utf8GraphQLParser.Parse(buffer)); + } + }); + } + + /// + /// Add a document rewriter that is executed on + /// the merged schema document. + /// + /// + /// The . + /// + /// + /// A delegate that is called to execute the + /// rewrite document logic. + /// + /// + /// Returns the . + /// + /// + /// is null, or + /// is null. + /// + public static IRequestExecutorBuilder AddMergedDocumentRewriter( + this IRequestExecutorBuilder builder, + Func rewrite) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (rewrite == null) + { + throw new ArgumentNullException(nameof(rewrite)); + } + + return builder.ConfigureSchema( + s => s.AddMergedDocRewriter(rewrite)); + } + + /// + /// Adds a schema visitor that is executed on + /// the merged schema document. + /// + /// + /// The . + /// + /// + /// A delegate that is called to execute the + /// document visitation logic. + /// + /// + /// Returns the . + /// + /// + /// is null, or + /// is null. + /// + public static IRequestExecutorBuilder AddMergedDocVisitor( + this IRequestExecutorBuilder builder, + Action visit) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (visit == null) + { + throw new ArgumentNullException(nameof(visit)); + } + + return builder.ConfigureSchema( + s => s.AddMergedDocVisitor(visit)); + } + + /// + /// Removes the root types of remote schemas before merging them into the main schema. + /// + /// + /// The . + /// + /// + /// The schema to which this rewriter applies to. + /// + /// + /// Returns the . + /// + /// + /// is null. + /// + public static IRequestExecutorBuilder IgnoreRootTypes( + this IRequestExecutorBuilder builder, + string? schemaName = null) + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } + + schemaName?.EnsureGraphQLName(nameof(schemaName)); + + return builder.AddDocumentRewriter( + new RemoveRootTypeRewriter(schemaName)); + } + + /// + /// Removes a file from the schema document that is being imported. + /// + /// + /// The . + /// + /// + /// The type that shall be removed from the schema document. + /// + /// + /// The schema to which this rewriter applies to. + /// + /// + /// Returns the . + /// + /// + /// is null. + /// + public static IRequestExecutorBuilder IgnoreType( + this IRequestExecutorBuilder builder, + string typeName, + string? schemaName = null) + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } + + typeName.EnsureGraphQLName(nameof(typeName)); + schemaName?.EnsureGraphQLName(nameof(schemaName)); + + return builder.AddDocumentRewriter( + new RemoveTypeRewriter(typeName, schemaName)); + } + + public static IRequestExecutorBuilder IgnoreField( + this IRequestExecutorBuilder builder, + string typeName, + string fieldName, + string? schemaName = null) => + IgnoreField(builder, new FieldReference(typeName, fieldName), schemaName); + + public static IRequestExecutorBuilder IgnoreField( + this IRequestExecutorBuilder builder, + FieldReference field, + string? schemaName = null) + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (field is null) + { + throw new ArgumentNullException(nameof(field)); + } + + schemaName?.EnsureGraphQLName(nameof(schemaName)); + + return builder.AddTypeRewriter(new RemoveFieldRewriter(field, schemaName)); + } + + public static IRequestExecutorBuilder RenameType( + this IRequestExecutorBuilder builder, + string originalTypeName, + string newTypeName, + string? schemaName = null) + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } + + originalTypeName.EnsureGraphQLName(nameof(originalTypeName)); + newTypeName.EnsureGraphQLName(nameof(newTypeName)); + schemaName?.EnsureGraphQLName(nameof(schemaName)); + + return builder.AddTypeRewriter( + new RenameTypeRewriter(originalTypeName, newTypeName, schemaName)); + } + + public static IRequestExecutorBuilder RewriteType( + this IRequestExecutorBuilder builder, + string originalTypeName, + string newTypeName, + string schemaName) + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } + + originalTypeName.EnsureGraphQLName(nameof(originalTypeName)); + newTypeName.EnsureGraphQLName(nameof(newTypeName)); + schemaName.EnsureGraphQLName(nameof(schemaName)); + + return builder.ConfigureSchema( + s => s.AddNameLookup(originalTypeName, newTypeName, schemaName)); + } + + public static IRequestExecutorBuilder RenameField( + this IRequestExecutorBuilder builder, + string typeName, + string fieldName, + string newFieldName, + string? schemaName = null) => + RenameField( + builder, + new FieldReference(typeName, fieldName), + newFieldName, + schemaName); + + public static IRequestExecutorBuilder RenameField( + this IRequestExecutorBuilder builder, + FieldReference field, + string newFieldName, + string? schemaName = null) + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (field is null) + { + throw new ArgumentNullException(nameof(field)); + } + + schemaName?.EnsureGraphQLName(nameof(schemaName)); + newFieldName.EnsureGraphQLName(nameof(newFieldName)); + + return builder.AddTypeRewriter( + new RenameFieldRewriter(field, newFieldName, schemaName)); + } + + public static IRequestExecutorBuilder RenameField( + this IRequestExecutorBuilder builder, + string typeName, + string fieldName, + string argumentName, + string newArgumentName, + string? schemaName = null) => + RenameField(builder, + new FieldReference(typeName, fieldName), + argumentName, + newArgumentName, + schemaName); + + public static IRequestExecutorBuilder RenameField( + this IRequestExecutorBuilder builder, + FieldReference field, + string argumentName, + string newArgumentName, + string? schemaName = null) + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (field is null) + { + throw new ArgumentNullException(nameof(field)); + } + + argumentName.EnsureGraphQLName(nameof(argumentName)); + newArgumentName.EnsureGraphQLName(nameof(newArgumentName)); + schemaName?.EnsureGraphQLName(nameof(schemaName)); + + return builder.AddTypeRewriter( + new RenameFieldArgumentRewriter( + field, + argumentName, + newArgumentName, + schemaName)); + } + + public static IRequestExecutorBuilder AddTypeRewriter( + this IRequestExecutorBuilder builder, + RewriteTypeDefinitionDelegate rewrite) + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (rewrite is null) + { + throw new ArgumentNullException(nameof(rewrite)); + } + + return builder.AddTypeRewriter(new DelegateTypeRewriter(rewrite)); + } + + public static IRequestExecutorBuilder AddDocumentRewriter( + this IRequestExecutorBuilder builder, + RewriteDocumentDelegate rewrite) + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (rewrite is null) + { + throw new ArgumentNullException(nameof(rewrite)); + } + + return builder.AddDocumentRewriter( + new DelegateDocumentRewriter(rewrite)); + } + + public static IRequestExecutorBuilder AddTypeMergeHandler( + this IRequestExecutorBuilder builder) + where T : class, ITypeMergeHandler + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return builder.AddTypeMergeRule( + SchemaMergerExtensions.CreateTypeMergeRule()); + } + + public static IRequestExecutorBuilder AddDirectiveMergeHandler( + this IRequestExecutorBuilder builder) + where T : class, IDirectiveMergeHandler + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return builder.AddDirectiveMergeRule( + SchemaMergerExtensions.CreateDirectiveMergeRule()); + } + + private static string GetArgumentValue(DirectiveNode directive, string argumentName) + { + var argument = directive.Arguments + .FirstOrDefault(a => a.Name.Value.EqualsOrdinal(argumentName)); + + if (argument is null) + { + throw RequestExecutorBuilder_ArgumentWithNameWasNotFound(argumentName); + } + + if (argument.Value is StringValueNode sv) + { + return sv.Value; + } + + throw RequestExecutorBuilder_ArgumentValueWasNotAStringValue(argumentName); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Directives/ComputedDirective.cs b/src/HotChocolate/Stitching/src/Stitching/Directives/ComputedDirective.cs new file mode 100644 index 00000000000..295ba473f00 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Directives/ComputedDirective.cs @@ -0,0 +1,6 @@ +namespace HotChocolate.Stitching; + +public class ComputedDirective +{ + public string[] DependantOn { get; set; } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Directives/ComputedDirectiveType.cs b/src/HotChocolate/Stitching/src/Stitching/Directives/ComputedDirectiveType.cs new file mode 100644 index 00000000000..a9bbb156693 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Directives/ComputedDirectiveType.cs @@ -0,0 +1,21 @@ +using HotChocolate.Stitching.Properties; +using HotChocolate.Types; + +namespace HotChocolate.Stitching; + +public class ComputedDirectiveType + : DirectiveType +{ + protected override void Configure( + IDirectiveTypeDescriptor descriptor) + { + descriptor.Name(DirectiveNames.Computed); + + descriptor.Location(DirectiveLocation.FieldDefinition); + + descriptor.Argument(t => t.DependantOn) + .Name(DirectiveFieldNames.Computed_DependantOn) + .Type>>() + .Description(StitchingResources.ComputedDirectiveType_Description); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Directives/DelegateDirective.cs b/src/HotChocolate/Stitching/src/Stitching/Directives/DelegateDirective.cs new file mode 100644 index 00000000000..7381fad1db4 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Directives/DelegateDirective.cs @@ -0,0 +1,8 @@ +namespace HotChocolate.Stitching; + +public sealed class DelegateDirective +{ + public string? Path { get; set; } + + public string Schema { get; set; } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Directives/DelegateDirectiveType.cs b/src/HotChocolate/Stitching/src/Stitching/Directives/DelegateDirectiveType.cs new file mode 100644 index 00000000000..a009a3a0412 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Directives/DelegateDirectiveType.cs @@ -0,0 +1,28 @@ +using HotChocolate.Stitching.Properties; +using HotChocolate.Types; + +namespace HotChocolate.Stitching; + +public sealed class DelegateDirectiveType : DirectiveType +{ + protected override void Configure( + IDirectiveTypeDescriptor descriptor) + { + descriptor + .Name(DirectiveNames.Delegate) + .Description(StitchingResources.DelegateDirectiveType_Description) + .Location(DirectiveLocation.FieldDefinition); + + descriptor + .Argument(t => t.Path) + .Name(DirectiveFieldNames.Delegate_Path) + .Type() + .Description(StitchingResources.DelegateDirectiveType_Path_FieldDescription); + + descriptor + .Argument(t => t.Schema) + .Name(DirectiveFieldNames.Delegate_Schema) + .Type>() + .Description(StitchingResources.DelegateDirectiveType_Schema_FieldDescription); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Directives/DirectiveFieldNames.cs b/src/HotChocolate/Stitching/src/Stitching/Directives/DirectiveFieldNames.cs new file mode 100644 index 00000000000..1e917ea29ee --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Directives/DirectiveFieldNames.cs @@ -0,0 +1,26 @@ +namespace HotChocolate.Stitching; + +internal static class DirectiveFieldNames +{ + public static string Source_Schema { get; } = "schema"; + + public static string Source_Name { get; } = "name"; + + public static string Delegate_Schema { get; } = "schema"; + + public static string Delegate_Path { get; } = "path"; + + public static string Computed_DependantOn { get; } = "dependantOn"; + + public static string RemoveType_TypeName { get; } = "typeName"; + + public static string RenameType_TypeName { get; } = "typeName"; + + public static string RenameType_NewTypeName { get; } = "newTypeName"; + + public static string RenameField_TypeName { get; } = "typeName"; + + public static string RenameField_FieldName { get; } = "fieldName"; + + public static string RenameField_NewFieldName { get; } = "newFieldName"; +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Directives/DirectiveNames.cs b/src/HotChocolate/Stitching/src/Stitching/Directives/DirectiveNames.cs new file mode 100644 index 00000000000..57d0106d4c6 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Directives/DirectiveNames.cs @@ -0,0 +1,18 @@ +namespace HotChocolate.Stitching; + +internal static class DirectiveNames +{ + public static string Delegate { get; } = "delegate"; + + public static string Computed { get; } = "computed"; + + public static string Source { get; } = "source"; + + public const string RemoveRootTypes = "_removeRootTypes"; + + public const string RemoveType = "_removeType"; + + public const string RenameType = "_renameType"; + + public const string RenameField = "_renameField"; +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Directives/ScopedVariableNode.cs b/src/HotChocolate/Stitching/src/Stitching/Directives/ScopedVariableNode.cs new file mode 100644 index 00000000000..3b96030fe6c --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Directives/ScopedVariableNode.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using HotChocolate.Language; + +namespace HotChocolate.Stitching; + +public sealed class ScopedVariableNode + : IValueNode + , IEquatable +{ + public ScopedVariableNode( + string scope, + string name) + : this(new NameNode(scope), new NameNode(name)) + { + } + + public ScopedVariableNode( + NameNode scope, + NameNode name) + : this(null, scope, name) + { + } + + public ScopedVariableNode( + Language.Location? location, + NameNode scope, + NameNode name) + { + Location = location; + Scope = scope ?? throw new ArgumentNullException(nameof(scope)); + Name = name ?? throw new ArgumentNullException(nameof(name)); + } + + public SyntaxKind Kind { get; } = SyntaxKind.Variable; + + public Language.Location? Location { get; } + + public NameNode Scope { get; } + + public NameNode Name { get; } + + public string Value => Scope.Value + ":" + Name.Value; + + object IValueNode.Value => Value; + + public IEnumerable GetNodes() => Enumerable.Empty(); + + /// + /// Determines whether the specified + /// is equal to the current . + /// + /// + /// The to compare with the current + /// . + /// + /// + /// true if the specified + /// is equal to the current ; + /// otherwise, false. + /// + public bool Equals(ScopedVariableNode? other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(other, this)) + { + return true; + } + + return other.Value.Equals(Value, StringComparison.Ordinal); + } + + /// + /// Determines whether the specified is equal + /// to the current . + /// + /// + /// The to compare with the current + /// . + /// + /// + /// true if the specified is equal + /// to the current ; + /// otherwise, false. + /// + public bool Equals(IValueNode? other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(other, this)) + { + return true; + } + + if (other is ScopedVariableNode v) + { + return Equals(v); + } + + return false; + } + + /// + /// Determines whether the specified is equal to + /// the current . + /// + /// + /// The to compare with the current + /// . + /// + /// + /// true if the specified is equal to the + /// current ; otherwise, false. + /// + public override bool Equals(object? obj) + { + if (obj is null) + { + return false; + } + + if (ReferenceEquals(obj, this)) + { + return true; + } + + return Equals(obj as ScopedVariableNode); + } + + /// + /// Serves as a hash function for a + /// object. + /// + /// + /// A hash code for this instance that is suitable for use in + /// hashing algorithms and data structures such as a hash table. + /// + public override int GetHashCode() + { + unchecked + { + return (Kind.GetHashCode() * 397) + ^ (Value.GetHashCode() * 97); + } + } + + /// + /// Returns a that represents the current + /// . + /// + /// + /// A that represents the current + /// . + /// + public override string ToString() + { + return Value; + } + + public string ToString(bool indented) + { + throw new NotSupportedException(); + } + + public VariableNode ToVariableNode() + { + return new VariableNode(new NameNode(ToVariableName())); + } + + public string ToVariableName() + { + return "__" + Scope.Value + "_" + Name.Value; + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Directives/SelectionPathComponent.cs b/src/HotChocolate/Stitching/src/Stitching/Directives/SelectionPathComponent.cs new file mode 100644 index 00000000000..289e2ae8c02 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Directives/SelectionPathComponent.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Text; +using HotChocolate.Language; +using HotChocolate.Language.Utilities; + +namespace HotChocolate.Stitching; + +public class SelectionPathComponent +{ + public SelectionPathComponent( + NameNode name, + IReadOnlyList arguments) + { + Name = name + ?? throw new ArgumentNullException(nameof(name)); + Arguments = arguments + ?? throw new ArgumentNullException(nameof(arguments)); + } + + public NameNode Name { get; } + + public IReadOnlyList Arguments { get; } + + public override string ToString() + { + if (Arguments.Count == 0) + { + return Name.Value; + } + + var sb = new StringBuilder(); + sb.Append(Name.Value); + sb.Append('('); + sb.Append(SerializeArgument(Arguments[0])); + + for (var i = 1; i < Arguments.Count; i++) + { + sb.Append(','); + sb.Append(' '); + sb.Append(SerializeArgument(Arguments[i])); + } + + sb.Append(')'); + return sb.ToString(); + } + + private static string SerializeArgument(ArgumentNode argument) + { + return $"{argument.Name.Value}: {SerializeValue(argument.Value)}"; + } + + private static string SerializeValue(IValueNode value) + { + if (value is ScopedVariableNode variable) + { + return $"${variable.Scope.Value}:{variable.Name.Value}"; + } + return value.Print(); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Directives/SelectionPathParser.cs b/src/HotChocolate/Stitching/src/Stitching/Directives/SelectionPathParser.cs new file mode 100644 index 00000000000..d20d08d6f82 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Directives/SelectionPathParser.cs @@ -0,0 +1,134 @@ +using System.Buffers; +using System.Collections.Immutable; +using HotChocolate.Language; + +namespace HotChocolate.Stitching; + +internal static class SelectionPathParser +{ + private const int _maxStackSize = 256; + + public static IImmutableStack Parse(string path) + { + if (path is null) + { + throw new ArgumentNullException(nameof(path)); + } + + byte[]? rented = null; + var buffer = path.Length < _maxStackSize + ? stackalloc byte[path.Length] + : rented = ArrayPool.Shared.Rent(path.Length); + + try + { + buffer = buffer.Slice(0, path.Length); + Prepare(path, buffer); + var reader = new Utf8GraphQLReader(buffer); + var parser = new Utf8GraphQLParser(reader, ParserOptions.Default); + return ParseSelectionPath(ref parser); + } + finally + { + if (rented is not null) + { + buffer.Clear(); + ArrayPool.Shared.Return(rented); + } + } + } + + private static void Prepare(string path, Span sourceText) + { + for (var i = 0; i < path.Length; i++) + { + var current = path[i]; + sourceText[i] = current == GraphQLConstants.Dot ? (byte)' ' : (byte)current; + } + } + + private static ImmutableStack ParseSelectionPath( + ref Utf8GraphQLParser parser) + { + var path = ImmutableStack.Empty; + + parser.MoveNext(); + + while (!parser.IsEndOfFile) + { + path = path.Push(ParseSelectionPathComponent(ref parser)); + } + + return path; + } + + private static SelectionPathComponent ParseSelectionPathComponent( + ref Utf8GraphQLParser parser) + { + var name = parser.ParseName(); + var arguments = ParseArguments(ref parser); + return new SelectionPathComponent(name, arguments); + } + + private static List ParseArguments( + ref Utf8GraphQLParser parser) + { + var list = new List(); + + if (parser.Reader.Kind == TokenKind.LeftParenthesis) + { + // skip opening token + parser.MoveNext(); + + while (parser.Reader.Kind != TokenKind.RightParenthesis) + { + list.Add(ParseArgument(ref parser)); + } + + // skip closing token + parser.ExpectRightParenthesis(); + + } + return list; + } + + private static ArgumentNode ParseArgument(ref Utf8GraphQLParser parser) + { + var name = parser.ParseName(); + + parser.ExpectColon(); + + var value = ParseValueLiteral(ref parser); + + return new ArgumentNode + ( + null, + name, + value + ); + } + private static IValueNode ParseValueLiteral( + ref Utf8GraphQLParser parser) + { + if (parser.Reader.Kind == TokenKind.Dollar) + { + return ParseVariable(ref parser); + } + return parser.ParseValueLiteral(true); + } + + private static ScopedVariableNode ParseVariable(ref Utf8GraphQLParser parser) + { + parser.ExpectDollar(); + var scope = parser.ParseName(); + parser.ExpectColon(); + var name = parser.ParseName(); + + return new ScopedVariableNode + ( + null, + scope, + name + ); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Directives/SourceDirective.cs b/src/HotChocolate/Stitching/src/Stitching/Directives/SourceDirective.cs new file mode 100644 index 00000000000..a4a6152e783 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Directives/SourceDirective.cs @@ -0,0 +1,8 @@ +namespace HotChocolate.Stitching; + +public class SourceDirective +{ + public string Name { get; set; } + + public string Schema { get; set; } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Directives/SourceDirectiveType.cs b/src/HotChocolate/Stitching/src/Stitching/Directives/SourceDirectiveType.cs new file mode 100644 index 00000000000..54f19d75f63 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Directives/SourceDirectiveType.cs @@ -0,0 +1,42 @@ +using HotChocolate.Stitching.Properties; +using HotChocolate.Types; + +namespace HotChocolate.Stitching; + +public class SourceDirectiveType + : DirectiveType +{ + protected override void Configure( + IDirectiveTypeDescriptor descriptor) + { + descriptor + .Name(DirectiveNames.Source) + .Description(StitchingResources.SourceDirectiveType_Description) + .Repeatable(); + + descriptor + .Location(DirectiveLocation.Enum) + .Location(DirectiveLocation.Object) + .Location(DirectiveLocation.Interface) + .Location(DirectiveLocation.Union) + .Location(DirectiveLocation.InputObject) + .Location(DirectiveLocation.FieldDefinition) + .Location(DirectiveLocation.InputFieldDefinition) + .Location(DirectiveLocation.ArgumentDefinition) + .Location(DirectiveLocation.EnumValue); + + descriptor + .Argument(t => t.Name) + .Name(DirectiveFieldNames.Source_Name) + .Type>() + .Description(StitchingResources + .SourceDirectiveType_Name_Description); + + descriptor + .Argument(t => t.Schema) + .Name(DirectiveFieldNames.Source_Schema) + .Type>() + .Description(StitchingResources + .SourceDirectiveType_Schema_Description); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/ErrorHelper.cs b/src/HotChocolate/Stitching/src/Stitching/ErrorHelper.cs new file mode 100644 index 00000000000..d6712478ffa --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/ErrorHelper.cs @@ -0,0 +1,17 @@ +using System.Net; + +namespace HotChocolate.Stitching; + +public static class ErrorHelper +{ + public static IError HttpRequestClient_HttpError( + HttpStatusCode statusCode, + string? responseBody) => + ErrorBuilder.New() + .SetMessage( + "HTTP error {0} while fetching from downstream service.", + statusCode) + .SetCode(ErrorCodes.Stitching.HttpRequestException) + .SetExtension("response", responseBody) + .Build(); +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Extensions/ContextDataExtensions.cs b/src/HotChocolate/Stitching/src/Stitching/Extensions/ContextDataExtensions.cs new file mode 100644 index 00000000000..698280e9f66 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Extensions/ContextDataExtensions.cs @@ -0,0 +1,375 @@ +using HotChocolate.Execution; +using HotChocolate.Language; +using HotChocolate.Stitching.Merge; +using HotChocolate.Stitching.Merge.Rewriters; +using HotChocolate.Types.Descriptors; +using static HotChocolate.Stitching.ThrowHelper; +using static HotChocolate.Stitching.WellKnownContextData; + +namespace HotChocolate.Stitching; + +internal static class ContextDataExtensions +{ + public static IReadOnlyDictionary GetRemoteExecutors( + this IHasContextData hasContextData) + { + if (hasContextData.ContextData.TryGetValue(RemoteExecutors, out var o) && + o is IReadOnlyDictionary executors) + { + return executors; + } + + throw RequestExecutorBuilder_RemoteExecutorNotFound(); + } + + public static IReadOnlyDictionary GetRemoteExecutors( + this ISchema schema) + { + if (schema.ContextData.TryGetValue(RemoteExecutors, out var o) && + o is IReadOnlyDictionary executors) + { + return executors; + } + + throw RequestExecutorBuilder_RemoteExecutorNotFound(); + } + + public static ISchemaBuilder AddRemoteExecutor( + this ISchemaBuilder schemaBuilder, + string schemaName, + IRequestExecutor executor) + { + return schemaBuilder + .SetContextData( + RemoteExecutors, + current => + { + if (current is not OrderedDictionary dict) + { + dict = new OrderedDictionary(); + } + + dict[schemaName] = executor; + return dict; + }); + } + + public static IReadOnlyList GetTypeMergeRules( + this IDescriptorContext hasContextData) + { + if (hasContextData.ContextData.TryGetValue(TypeMergeRules, out var o) && + o is IReadOnlyList rules) + { + return rules; + } + + return Array.Empty(); + } + + public static ISchemaBuilder AddTypeMergeRules( + this ISchemaBuilder schemaBuilder, + MergeTypeRuleFactory mergeRuleFactory) => + schemaBuilder.SetContextData( + TypeMergeRules, + current => + { + if (current is not List list) + { + list = new List(); + } + + list.Add(mergeRuleFactory); + return list; + }); + + public static IReadOnlyList GetDirectiveMergeRules( + this IDescriptorContext hasContextData) + { + if (hasContextData.ContextData.TryGetValue(DirectiveMergeRules, out var o) && + o is IReadOnlyList rules) + { + return rules; + } + + return Array.Empty(); + } + + public static ISchemaBuilder AddDirectiveMergeRules( + this ISchemaBuilder schemaBuilder, + MergeDirectiveRuleFactory mergeRuleFactory) => + schemaBuilder.SetContextData( + DirectiveMergeRules, + current => + { + if (!(current is List list)) + { + list = new List(); + } + + list.Add(mergeRuleFactory); + return list; + }); + + public static IReadOnlyList GetTypeRewriter( + this IDescriptorContext hasContextData) + { + if (hasContextData.ContextData.TryGetValue(TypeRewriter, out var o) && + o is IReadOnlyList rules) + { + return rules; + } + + return Array.Empty(); + } + + public static ISchemaBuilder AddTypeRewriter( + this ISchemaBuilder schemaBuilder, + ITypeRewriter rewriter) => + schemaBuilder.SetContextData( + TypeRewriter, + current => + { + if (!(current is List list)) + { + list = new List(); + } + + list.Add(rewriter); + return list; + }); + + public static IReadOnlyList GetDocumentRewriter( + this IDescriptorContext hasContextData) + { + if (hasContextData.ContextData.TryGetValue(DocumentRewriter, out var o) && + o is IReadOnlyList rules) + { + return rules; + } + + return Array.Empty(); + } + + public static ISchemaBuilder AddDocumentRewriter( + this ISchemaBuilder schemaBuilder, + IDocumentRewriter rewriter) => + schemaBuilder.SetContextData( + DocumentRewriter, + current => + { + if (!(current is List list)) + { + list = new List(); + } + + list.Add(rewriter); + return list; + }); + + public static IReadOnlyList GetTypeExtensions( + this IDescriptorContext hasContextData, + string? name = null) + { + var key = name is not null ? $"{TypeExtensions}.{name}" : TypeExtensions; + + if (hasContextData.ContextData.TryGetValue(key, out var o) && + o is IReadOnlyList rules) + { + return rules; + } + + return Array.Empty(); + } + + public static ISchemaBuilder AddTypeExtensions( + this ISchemaBuilder schemaBuilder, + DocumentNode document, + string? name = null) + { + var key = name is not null ? $"{TypeExtensions}.{name}" : TypeExtensions; + + return schemaBuilder.SetContextData( + key, + current => + { + if (!(current is List list)) + { + list = new List(); + } + + list.Add(document); + return list; + }); + } + + public static IReadOnlyList> GetMergedDocRewriter( + this IDescriptorContext hasContextData) + { + if (hasContextData.ContextData.TryGetValue(MergedDocRewriter, out var o) && + o is IReadOnlyList> rules) + { + return rules; + } + + return Array.Empty>(); + } + + public static ISchemaBuilder AddMergedDocRewriter( + this ISchemaBuilder schemaBuilder, + Func rewrite) => + schemaBuilder.SetContextData( + MergedDocRewriter, + current => + { + if (!(current is List> list)) + { + list = new List>(); + } + + list.Add(rewrite); + return list; + }); + + public static IReadOnlyList> GetMergedDocVisitors( + this IDescriptorContext hasContextData) + { + if (hasContextData.ContextData.TryGetValue(MergedDocVisitors, out var o) && + o is IReadOnlyList> rules) + { + return rules; + } + + return Array.Empty>(); + } + + public static ISchemaBuilder AddMergedDocVisitor( + this ISchemaBuilder schemaBuilder, + Action visit) => + schemaBuilder.SetContextData( + MergedDocVisitors, + current => + { + if (!(current is List> list)) + { + list = new List>(); + } + + list.Add(visit); + return list; + }); + + public static IReadOnlyDictionary> GetExternalFieldLookup( + this IHasContextData hasContextData) + { + if (hasContextData.ContextData.TryGetValue(ExternalFieldLookup, out var value) && + value is IReadOnlyDictionary> dict) + { + return dict; + } + + return new Dictionary>(); + } + + public static ISchemaBuilder AddExternalFieldLookup( + this ISchemaBuilder schemaBuilder, + IReadOnlyDictionary> externalFieldLookup) + { + return schemaBuilder.SetContextData(ExternalFieldLookup, externalFieldLookup); + } + + public static IReadOnlyDictionary<(string, string), string> GetNameLookup( + this ISchema schema) + { + if (schema.ContextData.TryGetValue(NameLookup, out var value) && + value is IReadOnlyDictionary<(string, string), string> dict) + { + return dict; + } + + throw RequestExecutorBuilder_NameLookupNotFound(); + } + + public static IReadOnlyDictionary<(string, string), string> GetNameLookup( + this IHasContextData hasContextData) + { + if (hasContextData.ContextData.TryGetValue(NameLookup, out var value) && + value is IReadOnlyDictionary<(string, string), string> dict) + { + return dict; + } + + return new Dictionary<(string, string), string>(); + } + + public static ISchemaBuilder AddNameLookup( + this ISchemaBuilder schemaBuilder, + IReadOnlyDictionary<(string, string), string> nameLookup) + { + return schemaBuilder.SetContextData(NameLookup, current => + { + if (current is IDictionary<(string, string), string> dict) + { + foreach (var item in nameLookup) + { + dict[item.Key] = item.Value; + } + + return dict; + } + + return nameLookup.ToDictionary(t => t.Key, t => t.Value); + }); + } + + public static ISchemaBuilder AddNameLookup( + this ISchemaBuilder schemaBuilder, + string originalTypeName, + string newTypeName, + string schemaName) + { + return schemaBuilder.SetContextData(NameLookup, current => + { + if (current is IDictionary<(string, string), string> dict) + { + dict[(newTypeName, schemaName)] = originalTypeName; + + return dict; + } + + return new Dictionary<(string, string), string> + { + { (newTypeName, schemaName), originalTypeName } + }; + }); + } + + public static IReadOnlyList GetSchemaDefinitions( + this IReadOnlyDictionary contextData) + { + if (contextData.TryGetValue(WellKnownContextData.SchemaDefinitions, out var o) && + o is IReadOnlyList schemaDefinitions) + { + return schemaDefinitions; + } + + return Array.Empty(); + } + + public static List GetOrAddSchemaDefinitions( + this IDescriptorContext descriptorContext) + { + if (descriptorContext.ContextData.TryGetValue( + WellKnownContextData.SchemaDefinitions, + out var o) && + o is List schemaDefinitions) + { + return schemaDefinitions; + } + + schemaDefinitions = new List(); + descriptorContext.ContextData.Add( + WellKnownContextData.SchemaDefinitions, + schemaDefinitions); + return schemaDefinitions; + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Extensions/HasDirectiveExtensions.cs b/src/HotChocolate/Stitching/src/Stitching/Extensions/HasDirectiveExtensions.cs new file mode 100644 index 00000000000..8c285922f45 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Extensions/HasDirectiveExtensions.cs @@ -0,0 +1,33 @@ +using System.Diagnostics.CodeAnalysis; +using HotChocolate.Types; + +namespace HotChocolate.Stitching; + +internal static class HasDirectiveExtensions +{ + public static bool TryGetSourceDirective( + this IHasDirectives hasDirectives, + string schemaName, + [NotNullWhen(true)] out SourceDirective? sourceDirective) + { + sourceDirective = hasDirectives.Directives[DirectiveNames.Source] + .Select(t => t.ToObject()) + .FirstOrDefault(t => schemaName.Equals(t.Schema)); + return sourceDirective != null; + } + + public static bool TryGetSourceName( + this IHasDirectives hasDirectives, + string schemaName, + [NotNullWhen(true)] out string? sourceName) + { + if (TryGetSourceDirective(hasDirectives, schemaName, out var sd)) + { + sourceName = sd.Name; + return true; + } + + sourceName = null; + return false; + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/HotChocolate.Stitching.csproj b/src/HotChocolate/Stitching/src/Stitching/HotChocolate.Stitching.csproj new file mode 100644 index 00000000000..228b4e1ef38 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/HotChocolate.Stitching.csproj @@ -0,0 +1,35 @@ + + + + HotChocolate.Stitching + HotChocolate.Stitching + HotChocolate.Stitching + Contains the Hot Chocolate GraphQL schema stitching layer. + enable + + + + + + + + + + + + + + True + True + StitchingResources.resx + + + + + + ResXFileCodeGenerator + StitchingResources.Designer.cs + + + + diff --git a/src/HotChocolate/Stitching/src/Stitching/InternalsVisibleTo.cs b/src/HotChocolate/Stitching/src/Stitching/InternalsVisibleTo.cs new file mode 100644 index 00000000000..bf409df5f20 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/InternalsVisibleTo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("HotChocolate.Stitching.Tests")] diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/AddSchemaExtensionRewriter.MergeContext.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/AddSchemaExtensionRewriter.MergeContext.cs new file mode 100644 index 00000000000..84af45f19b3 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/AddSchemaExtensionRewriter.MergeContext.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.Linq; +using HotChocolate.Language; +using HotChocolate.Language.Visitors; + +namespace HotChocolate.Stitching.Merge; + +public partial class AddSchemaExtensionRewriter +{ + public class MergeContext : ISyntaxVisitorContext + { + public MergeContext(DocumentNode schema, DocumentNode extensions) + { + Extensions = extensions.Definitions + .OfType() + .ToDictionary(t => t.Name.Value); + + Directives = schema.Definitions + .OfType() + .ToDictionary(t => t.Name.Value); + } + + public IDictionary Extensions { get; } + + public IDictionary Directives { get; } + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/AddSchemaExtensionRewriter.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/AddSchemaExtensionRewriter.cs new file mode 100644 index 00000000000..5632bad8381 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/AddSchemaExtensionRewriter.cs @@ -0,0 +1,549 @@ +using System.Globalization; +using HotChocolate.Execution; +using HotChocolate.Language; +using HotChocolate.Language.Visitors; +using HotChocolate.Stitching.Properties; +using static HotChocolate.Stitching.Merge.AddSchemaExtensionRewriter; + +namespace HotChocolate.Stitching.Merge; + +public partial class AddSchemaExtensionRewriter : SyntaxRewriter +{ + private readonly Dictionary _globalDirectives; + + public AddSchemaExtensionRewriter() + { + _globalDirectives = new Dictionary(); + } + + public AddSchemaExtensionRewriter(IEnumerable globalDirectives) + { + if (globalDirectives is null) + { + throw new ArgumentNullException(nameof(globalDirectives)); + } + + _globalDirectives = globalDirectives.ToDictionary(t => t.Name.Value); + } + + public DocumentNode AddExtensions( + DocumentNode schema, + DocumentNode extensions) + { + if (schema == null) + { + throw new ArgumentNullException(nameof(schema)); + } + + if (extensions == null) + { + throw new ArgumentNullException(nameof(extensions)); + } + + var newTypes = extensions.Definitions.OfType().ToList(); + var newDirectives = extensions.Definitions.OfType().ToList(); + + var current = schema; + + if (newTypes.Count > 0 || newDirectives.Count > 0) + { + current = RemoveDirectives(current, newDirectives.Select(t => t.Name.Value)); + current = RemoveTypes(current, newTypes.Select(t => t.Name.Value)); + + var definitions = schema.Definitions.ToList(); + definitions.AddRange(newTypes); + definitions.AddRange(newDirectives); + current = current.WithDefinitions(definitions); + } + + var context = new MergeContext(current, extensions); + current = RewriteDocument(current, context); + + if (current is null) + { + throw new InvalidOperationException("The current node was removed."); + } + + if (context.Extensions.Count > 0) + { + var definitions = current.Definitions.ToList(); + + foreach (var notProcessed in context.Extensions.Keys.Except( + current.Definitions.OfType().Select(t => t.Name.Value))) + { + definitions.Add(context.Extensions[notProcessed]); + } + + return current.WithDefinitions(definitions); + } + + return current; + } + + private static DocumentNode RemoveDirectives( + DocumentNode document, + IEnumerable directiveNames) + { + return RemoveDefinitions( + document, + d => d.Definitions.OfType() + .ToDictionary(t => t.Name.Value, t => (IDefinitionNode)t), + directiveNames); + } + + private static DocumentNode RemoveTypes( + DocumentNode document, + IEnumerable directiveNames) + { + return RemoveDefinitions( + document, + d => d.Definitions.OfType() + .ToDictionary(t => t.Name.Value, t => (IDefinitionNode)t), + directiveNames); + } + + private static DocumentNode RemoveDefinitions( + DocumentNode document, + Func> toDict, + IEnumerable names) + { + var definitions = document.Definitions.ToList(); + var directives = toDict(document); + + foreach (var name in names) + { + if (directives.TryGetValue(name, out var directive)) + { + definitions.Remove(directive); + } + } + + return document.WithDefinitions(definitions); + } + + protected override UnionTypeDefinitionNode? RewriteUnionTypeDefinition( + UnionTypeDefinitionNode node, + MergeContext context) + { + var current = node; + + if (context.Extensions.TryGetValue(current.Name.Value, out var extension)) + { + if (extension is UnionTypeExtensionNode unionTypeExtension) + { + current = AddTypes(current, unionTypeExtension); + + var captured = current; + current = AddDirectives( + current, + unionTypeExtension, + d => captured.WithDirectives(d), + context); + } + else + { + throw new SchemaMergeException( + current, + extension, + string.Format( + CultureInfo.InvariantCulture, + StitchingResources.AddSchemaExtensionRewriter_TypeMismatch, + node.Name.Value, + node.Kind, + extension.Kind)); + } + } + + return base.RewriteUnionTypeDefinition(current, context); + } + + private static UnionTypeDefinitionNode AddTypes( + UnionTypeDefinitionNode typeDefinition, + UnionTypeExtensionNode typeExtension) + { + if (typeExtension.Types.Count == 0) + { + return typeDefinition; + } + + var types = new OrderedDictionary(); + + foreach (var type in typeDefinition.Types) + { + types[type.Name.Value] = type; + } + + foreach (var type in typeExtension.Types) + { + types[type.Name.Value] = type; + } + + return typeDefinition.WithTypes(types.Values.ToList()); + } + + protected override ObjectTypeDefinitionNode? RewriteObjectTypeDefinition( + ObjectTypeDefinitionNode node, + MergeContext context) + { + var current = node; + + if (context.Extensions.TryGetValue( + current.Name.Value, + out var extension)) + { + if (extension is ObjectTypeExtensionNode objectTypeExtension) + { + + current = AddInterfaces(current, objectTypeExtension); + current = AddFields(current, objectTypeExtension); + + var captured = current; + current = AddDirectives( + current, + objectTypeExtension, + d => captured.WithDirectives(d), + context); + } + else + { + throw new SchemaMergeException( + current, + extension, + string.Format( + CultureInfo.InvariantCulture, + StitchingResources + .AddSchemaExtensionRewriter_TypeMismatch, + node.Name.Value, + node.Kind, + extension.Kind)); + } + } + + return base.RewriteObjectTypeDefinition(current, context); + } + + private static ObjectTypeDefinitionNode AddFields( + ObjectTypeDefinitionNode typeDefinition, + ObjectTypeExtensionNode typeExtension) + { + var fields = AddFields(typeDefinition.Fields, typeExtension.Fields); + + return Equals(fields, typeDefinition.Fields) + ? typeDefinition + : typeDefinition.WithFields(fields); + } + + private static ObjectTypeDefinitionNode AddInterfaces( + ObjectTypeDefinitionNode typeDefinition, + ObjectTypeExtensionNode typeExtension) + { + if (typeExtension.Interfaces.Count == 0) + { + return typeDefinition; + } + + var interfaces = new HashSet( + typeDefinition.Interfaces.Select(t => t.Name.Value)); + + foreach (var interfaceName in + typeExtension.Interfaces.Select(t => t.Name.Value)) + { + interfaces.Add(interfaceName); + } + + if (interfaces.Count == typeDefinition.Interfaces.Count) + { + return typeDefinition; + } + + return typeDefinition.WithInterfaces( + interfaces.Select(n => new NamedTypeNode(new NameNode(n))) + .ToList()); + } + + protected override InterfaceTypeDefinitionNode? RewriteInterfaceTypeDefinition( + InterfaceTypeDefinitionNode node, + MergeContext context) + { + var current = node; + + if (context.Extensions.TryGetValue( + current.Name.Value, + out var extension)) + { + if (extension is InterfaceTypeExtensionNode ite) + { + current = AddFields(current, ite); + + var captured = current; + current = AddDirectives( + current, + ite, + d => captured.WithDirectives(d), + context); + } + else + { + throw new SchemaMergeException( + current, + extension, + string.Format( + CultureInfo.InvariantCulture, + StitchingResources.AddSchemaExtensionRewriter_TypeMismatch, + node.Name.Value, + node.Kind, + extension.Kind)); + } + } + + return base.RewriteInterfaceTypeDefinition(current, context); + } + + private static InterfaceTypeDefinitionNode AddFields( + InterfaceTypeDefinitionNode typeDefinition, + InterfaceTypeExtensionNode typeExtension) + { + var fields = AddFields(typeDefinition.Fields, typeExtension.Fields); + + return Equals(fields, typeDefinition.Fields) + ? typeDefinition + : typeDefinition.WithFields(fields); + } + + private static IReadOnlyList AddFields( + IReadOnlyList typeDefinitionFields, + IReadOnlyList typeExtensionFields) + { + if (typeExtensionFields.Count == 0) + { + return typeDefinitionFields; + } + + var fields = new OrderedDictionary(); + + foreach (var field in typeDefinitionFields) + { + fields[field.Name.Value] = field; + } + + foreach (var field in typeExtensionFields) + { + // we allow an extension to override fields. + fields[field.Name.Value] = field; + } + + return fields.Values.ToList(); + } + + protected override InputObjectTypeDefinitionNode? RewriteInputObjectTypeDefinition( + InputObjectTypeDefinitionNode node, + MergeContext context) + { + var current = node; + + if (context.Extensions.TryGetValue( + current.Name.Value, + out var extension)) + { + if (extension is InputObjectTypeExtensionNode typeExtension) + { + current = AddInputFields(current, typeExtension); + + var captured = current; + current = AddDirectives( + current, + typeExtension, + d => captured.WithDirectives(d), + context); + } + else + { + throw new SchemaMergeException( + current, + extension, + string.Format( + CultureInfo.InvariantCulture, + StitchingResources.AddSchemaExtensionRewriter_TypeMismatch, + node.Name.Value, + node.Kind, + extension.Kind)); + } + } + + return base.RewriteInputObjectTypeDefinition(current, context); + } + + private static InputObjectTypeDefinitionNode AddInputFields( + InputObjectTypeDefinitionNode typeDefinition, + InputObjectTypeExtensionNode typeExtension) + { + if (typeExtension.Fields.Count == 0) + { + return typeDefinition; + } + + var fields = new OrderedDictionary(); + + foreach (var field in typeDefinition.Fields) + { + fields[field.Name.Value] = field; + } + + foreach (var field in typeExtension.Fields) + { + // we allow an extension to override fields. + fields[field.Name.Value] = field; + } + + return typeDefinition.WithFields(fields.Values.ToList()); + } + + protected override EnumTypeDefinitionNode? RewriteEnumTypeDefinition( + EnumTypeDefinitionNode node, + MergeContext context) + { + var current = node; + + if (context.Extensions.TryGetValue( + current.Name.Value, + out var extension)) + { + if (extension is EnumTypeExtensionNode ete) + { + current = AddEnumValues(current, ete); + + var captured = current; + current = AddDirectives( + current, + ete, + d => captured.WithDirectives(d), + context); + } + else + { + throw new SchemaMergeException( + current, + extension, + string.Format( + CultureInfo.InvariantCulture, + StitchingResources.AddSchemaExtensionRewriter_TypeMismatch, + node.Name.Value, + node.Kind, + extension.Kind)); + } + } + + return base.RewriteEnumTypeDefinition(current, context); + } + + private static EnumTypeDefinitionNode AddEnumValues( + EnumTypeDefinitionNode typeDefinition, + EnumTypeExtensionNode typeExtension) + { + if (typeExtension.Values.Count == 0) + { + return typeDefinition; + } + + var values = + new OrderedDictionary(); + + foreach (var value in typeDefinition.Values) + { + values[value.Name.Value] = value; + } + + foreach (var value in typeExtension.Values) + { + // we allow an extension to override values. + values[value.Name.Value] = value; + } + + return typeDefinition.WithValues(values.Values.ToList()); + } + + protected override ScalarTypeDefinitionNode? RewriteScalarTypeDefinition( + ScalarTypeDefinitionNode node, + MergeContext context) + { + var current = node; + + if (context.Extensions.TryGetValue( + current.Name.Value, + out var extension)) + { + if (extension is ScalarTypeExtensionNode ste) + { + var captured = current; + current = AddDirectives( + current, + ste, + d => captured.WithDirectives(d), + context); + } + else + { + throw new SchemaMergeException( + current, + extension, + string.Format( + CultureInfo.InvariantCulture, + StitchingResources.AddSchemaExtensionRewriter_TypeMismatch, + node.Name.Value, + node.Kind, + extension.Kind)); + } + } + + return base.RewriteScalarTypeDefinition(current, context); + } + + private TDefinition AddDirectives( + TDefinition typeDefinition, + TExtension typeExtension, + Func, TDefinition> withDirectives, + MergeContext context) + where TDefinition : NamedSyntaxNode, ITypeDefinitionNode + where TExtension : NamedSyntaxNode, ITypeExtensionNode + { + if (typeExtension.Directives.Count == 0) + { + return typeDefinition; + } + + var alreadyDeclared = new HashSet( + typeDefinition.Directives.Select(t => t.Name.Value)); + var directives = new List(); + + foreach (var directive in typeExtension.Directives) + { + if (!_globalDirectives.TryGetValue(directive.Name.Value, + out var directiveDefinition) + && !context.Directives.TryGetValue(directive.Name.Value, + out directiveDefinition)) + { + throw new SchemaMergeException( + typeDefinition, typeExtension, + string.Format( + CultureInfo.InvariantCulture, StitchingResources + .AddSchemaExtensionRewriter_DirectiveDoesNotExist, + directive.Name.Value)); + } + + if (!alreadyDeclared.Add(directive.Name.Value) + && !directiveDefinition.IsRepeatable) + { + throw new SchemaMergeException( + typeDefinition, typeExtension, + string.Format( + CultureInfo.InvariantCulture, StitchingResources + .AddSchemaExtensionRewriter_DirectiveIsUnique, + directive.Name.Value)); + } + + directives.Add(directive); + } + + return withDirectives.Invoke(directives); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/DirectiveTypeInfo.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/DirectiveTypeInfo.cs new file mode 100644 index 00000000000..8389c5552fc --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/DirectiveTypeInfo.cs @@ -0,0 +1,18 @@ +using HotChocolate.Language; + +namespace HotChocolate.Stitching.Merge; + +public class DirectiveTypeInfo : IDirectiveTypeInfo +{ + public DirectiveTypeInfo( + DirectiveDefinitionNode definition, + ISchemaInfo schema) + { + Definition = definition; + Schema = schema; + } + + public DirectiveDefinitionNode Definition { get; } + + public ISchemaInfo Schema { get; } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/EnumTypeInfo.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/EnumTypeInfo.cs new file mode 100644 index 00000000000..88164ff2531 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/EnumTypeInfo.cs @@ -0,0 +1,14 @@ +using HotChocolate.Language; + +namespace HotChocolate.Stitching.Merge; + +internal class EnumTypeInfo + : TypeInfo +{ + public EnumTypeInfo( + EnumTypeDefinitionNode typeDefinition, + ISchemaInfo schema) + : base(typeDefinition, schema) + { + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/Extensions/EnumerableExtensions.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/Extensions/EnumerableExtensions.cs new file mode 100644 index 00000000000..0d6dce6ac1b --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/Extensions/EnumerableExtensions.cs @@ -0,0 +1,25 @@ +namespace HotChocolate.Stitching.Merge; + +internal static class EnumerableExtensions +{ + public static IReadOnlyList NotOfType( + this IEnumerable types) + { + if (types is null) + { + throw new ArgumentNullException(nameof(types)); + } + + var list = new List(); + + foreach (var type in types) + { + if (type is not T) + { + list.Add(type); + } + } + + return list; + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/Extensions/MergeSyntaxNodeExtensions.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/Extensions/MergeSyntaxNodeExtensions.cs new file mode 100644 index 00000000000..0f3d8f5eda2 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/Extensions/MergeSyntaxNodeExtensions.cs @@ -0,0 +1,489 @@ +using System.Text; +using HotChocolate.Language; +using HotChocolate.Stitching.Properties; +using HotChocolate.Utilities; + +namespace HotChocolate.Stitching.Merge; + +public static class MergeSyntaxNodeExtensions +{ + public static T Rename( + this T enumTypeDefinition, + string newName, + params string[] schemaNames) + where T : ITypeDefinitionNode + => Rename(enumTypeDefinition, newName, (IEnumerable)schemaNames); + + public static T Rename( + this T typeDefinitionNode, + string newName, + IEnumerable schemaNames) + where T : ITypeDefinitionNode + { + ITypeDefinitionNode node = typeDefinitionNode; + + node = node switch + { + ObjectTypeDefinitionNode otd => Rename(otd, newName, schemaNames), + InterfaceTypeDefinitionNode itd => Rename(itd, newName, schemaNames), + UnionTypeDefinitionNode utd => Rename(utd, newName, schemaNames), + InputObjectTypeDefinitionNode iotd => Rename(iotd, newName, schemaNames), + EnumTypeDefinitionNode etd => Rename(etd, newName, schemaNames), + ScalarTypeDefinitionNode std => Rename(std, newName, schemaNames), + _ => throw new NotSupportedException(), + }; + + return (T)node; + } + + public static FieldDefinitionNode Rename( + this FieldDefinitionNode enumTypeDefinition, + string newName, + params string[] schemaNames) + => Rename( + enumTypeDefinition, + newName, + (IEnumerable)schemaNames); + + public static FieldDefinitionNode Rename( + this FieldDefinitionNode enumTypeDefinition, + string newName, + IEnumerable schemaNames) + => AddSource( + enumTypeDefinition, + newName, + schemaNames, + (n, d) => enumTypeDefinition.WithName(n).WithDirectives(d)); + + public static InputValueDefinitionNode Rename( + this InputValueDefinitionNode enumTypeDefinition, + string newName, + params string[] schemaNames) + { + return Rename( + enumTypeDefinition, + newName, + (IEnumerable)schemaNames); + } + + public static InputValueDefinitionNode Rename( + this InputValueDefinitionNode enumTypeDefinition, + string newName, + IEnumerable schemaNames) + { + return AddSource(enumTypeDefinition, newName, schemaNames, + (n, d) => enumTypeDefinition + .WithName(n).WithDirectives(d)); + } + + public static ScalarTypeDefinitionNode Rename( + this ScalarTypeDefinitionNode enumTypeDefinition, + string newName, + params string[] schemaNames) + { + return Rename( + enumTypeDefinition, + newName, + (IEnumerable)schemaNames); + } + + public static ScalarTypeDefinitionNode Rename( + this ScalarTypeDefinitionNode enumTypeDefinition, + string newName, + IEnumerable schemaNames) + { + return AddSource(enumTypeDefinition, newName, schemaNames, + (n, d) => enumTypeDefinition + .WithName(n).WithDirectives(d)); + } + + public static DirectiveDefinitionNode Rename( + this DirectiveDefinitionNode directiveDefinition, + string newName, + params string[] schemaNames) + { + return Rename( + directiveDefinition, + newName, + (IEnumerable)schemaNames); + } + + public static DirectiveDefinitionNode Rename( + this DirectiveDefinitionNode directiveDefinition, + string newName, + IEnumerable schemaNames) + { + return directiveDefinition.WithName(new NameNode(newName)); + } + + public static EnumTypeDefinitionNode Rename( + this EnumTypeDefinitionNode enumTypeDefinition, + string newName, + params string[] schemaNames) + { + return Rename( + enumTypeDefinition, + newName, + (IEnumerable)schemaNames); + } + + public static EnumTypeDefinitionNode Rename( + this EnumTypeDefinitionNode enumTypeDefinition, + string newName, + IEnumerable schemaNames) + { + return AddSource(enumTypeDefinition, newName, schemaNames, + (n, d) => enumTypeDefinition + .WithName(n).WithDirectives(d)); + } + + public static InputObjectTypeDefinitionNode Rename( + this InputObjectTypeDefinitionNode enumTypeDefinition, + string newName, + params string[] schemaNames) + { + return Rename( + enumTypeDefinition, + newName, + (IEnumerable)schemaNames); + } + + public static InputObjectTypeDefinitionNode Rename( + this InputObjectTypeDefinitionNode enumTypeDefinition, + string newName, + IEnumerable schemaNames) + { + return AddSource(enumTypeDefinition, newName, schemaNames, + (n, d) => enumTypeDefinition + .WithName(n).WithDirectives(d)); + } + + public static UnionTypeDefinitionNode Rename( + this UnionTypeDefinitionNode unionTypeDefinition, + string newName, + params string[] schemaNames) + { + return Rename( + unionTypeDefinition, + newName, + (IEnumerable)schemaNames); + } + + public static UnionTypeDefinitionNode Rename( + this UnionTypeDefinitionNode unionTypeDefinition, + string newName, + IEnumerable schemaNames) + { + return AddSource(unionTypeDefinition, newName, schemaNames, + (n, d) => unionTypeDefinition + .WithName(n).WithDirectives(d)); + } + + public static ObjectTypeDefinitionNode Rename( + this ObjectTypeDefinitionNode objectTypeDefinition, + string newName, + params string[] schemaNames) + { + return Rename( + objectTypeDefinition, + newName, + (IEnumerable)schemaNames); + } + + public static ObjectTypeDefinitionNode Rename( + this ObjectTypeDefinitionNode objectTypeDefinition, + string newName, + IEnumerable schemaNames) + { + return AddSource(objectTypeDefinition, newName, schemaNames, + (n, d) => objectTypeDefinition + .WithName(n).WithDirectives(d)); + } + + public static InterfaceTypeDefinitionNode Rename( + this InterfaceTypeDefinitionNode interfaceTypeDefinition, + string newName, + params string[] schemaNames) + { + return Rename( + interfaceTypeDefinition, + newName, + (IEnumerable)schemaNames); + } + + public static InterfaceTypeDefinitionNode Rename( + this InterfaceTypeDefinitionNode interfaceTypeDefinition, + string newName, + IEnumerable schemaNames) + { + return AddSource(interfaceTypeDefinition, newName, schemaNames, + (n, d) => interfaceTypeDefinition + .WithName(n).WithDirectives(d)); + } + + private static T AddSource( + T interfaceTypeDefinition, + string newName, + IEnumerable schemaNames, + Func, T> rewrite) + where T : NamedSyntaxNode + { + if (interfaceTypeDefinition == null) + { + throw new ArgumentNullException( + nameof(interfaceTypeDefinition)); + } + + if (schemaNames == null) + { + throw new ArgumentNullException(nameof(schemaNames)); + } + + newName.EnsureGraphQLName(nameof(newName)); + + var originalName = interfaceTypeDefinition.Name.Value; + + var directives = + AddRenamedDirective( + interfaceTypeDefinition.Directives, + originalName, + schemaNames); + + return rewrite(new NameNode(newName), directives); + } + + private static IReadOnlyList AddRenamedDirective( + IEnumerable directives, + string originalName, + IEnumerable schemaNames) + { + var list = new List(directives); + var hasSchemas = false; + + foreach (var schemaName in schemaNames) + { + hasSchemas = true; + if (!list.Any(t => HasSourceDirective(t, schemaName))) + { + list.Add(new DirectiveNode + ( + DirectiveNames.Source, + new ArgumentNode( + DirectiveFieldNames.Source_Name, + originalName), + new ArgumentNode( + DirectiveFieldNames.Source_Schema, + schemaName) + )); + } + } + + if (!hasSchemas) + { + throw new ArgumentException( + StitchingResources.MergeSyntaxNodeExtensions_NoSchema, + nameof(schemaNames)); + } + + return list; + } + + private static bool HasSourceDirective( + DirectiveNode directive, + string schemaName) + { + if (DirectiveNames.Source.Equals(directive.Name.Value)) + { + var argument = directive.Arguments.FirstOrDefault(t => + DirectiveFieldNames.Source_Schema.Equals(t.Name.Value)); + return argument?.Value is StringValueNode sv + && schemaName.Equals(sv.Value); + } + return false; + } + + public static FieldDefinitionNode AddDelegationPath( + this FieldDefinitionNode field, + string schemaName) + => AddDelegationPath(field, schemaName, false); + + public static FieldDefinitionNode AddDelegationPath( + this FieldDefinitionNode field, + string schemaName, + bool overwrite) + => AddDelegationPath(field, schemaName, (string?)null, overwrite); + + public static FieldDefinitionNode AddDelegationPath( + this FieldDefinitionNode field, + string schemaName, + SelectionPathComponent selectionPath) => + AddDelegationPath(field, schemaName, selectionPath, false); + + public static FieldDefinitionNode AddDelegationPath( + this FieldDefinitionNode field, + string schemaName, + SelectionPathComponent selectionPath, + bool overwrite) + { + if (field == null) + { + throw new ArgumentNullException(nameof(field)); + } + + if (selectionPath == null) + { + throw new ArgumentNullException(nameof(selectionPath)); + } + + schemaName.EnsureGraphQLName(nameof(schemaName)); + + return AddDelegationPath( + field, + schemaName, + selectionPath.ToString(), + overwrite); + } + + public static FieldDefinitionNode AddDelegationPath( + this FieldDefinitionNode field, + string schemaName, + IReadOnlyCollection selectionPath) => + AddDelegationPath(field, schemaName, selectionPath, false); + + public static FieldDefinitionNode AddDelegationPath( + this FieldDefinitionNode field, + string schemaName, + IReadOnlyCollection selectionPath, + bool overwrite) + { + if (field == null) + { + throw new ArgumentNullException(nameof(field)); + } + + if (selectionPath == null) + { + throw new ArgumentNullException(nameof(selectionPath)); + } + + schemaName.EnsureGraphQLName(nameof(schemaName)); + + if (selectionPath.Count == 0) + { + return AddDelegationPath(field, schemaName); + } + + if (selectionPath.Count == 1) + { + return AddDelegationPath( + field, + schemaName, + selectionPath.Single()); + } + + var path = new StringBuilder(); + path.Append(selectionPath.First()); + + foreach (var component in selectionPath.Skip(1)) + { + path.Append('.'); + path.Append(component); + } + + return AddDelegationPath(field, schemaName, path.ToString(), overwrite); + } + + public static FieldDefinitionNode AddDelegationPath( + this FieldDefinitionNode field, + string schemaName, + string? delegationPath) + => AddDelegationPath(field, schemaName, delegationPath, false); + + public static FieldDefinitionNode AddDelegationPath( + this FieldDefinitionNode field, + string schemaName, + string? delegationPath, + bool overwrite) + { + if (field is null) + { + throw new ArgumentNullException(nameof(field)); + } + + schemaName.EnsureGraphQLName(nameof(schemaName)); + + if (!overwrite && field.Directives.Any(t => + DirectiveNames.Delegate.Equals(t.Name.Value))) + { + return field; + } + + var list = new List(field.Directives); + + list.RemoveAll(t => DirectiveNames.Delegate.Equals(t.Name.Value)); + + var arguments = new List + { + new ArgumentNode( + DirectiveFieldNames.Delegate_Schema, + schemaName) + }; + + if (!string.IsNullOrEmpty(delegationPath)) + { + arguments.Add(new ArgumentNode( + DirectiveFieldNames.Delegate_Path, + delegationPath)); + } + + list.Add(new DirectiveNode + ( + DirectiveNames.Delegate, + arguments + )); + + return field.WithDirectives(list); + } + + public static string GetOriginalName( + this INamedSyntaxNode typeDefinition, + string schemaName) + { + if (typeDefinition == null) + { + throw new ArgumentNullException(nameof(typeDefinition)); + } + + schemaName.EnsureGraphQLName(nameof(schemaName)); + + var sourceDirective = typeDefinition.Directives + .FirstOrDefault(t => HasSourceDirective(t, schemaName)); + + if (sourceDirective is not null) + { + var argument = sourceDirective.Arguments.First(t => + DirectiveFieldNames.Source_Name.Equals(t.Name.Value)); + if (argument.Value is StringValueNode value) + { + return value.Value; + } + } + + return typeDefinition.Name.Value; + } + + public static bool IsFromSchema( + this INamedSyntaxNode typeDefinition, + string schemaName) + { + if (typeDefinition == null) + { + throw new ArgumentNullException(nameof(typeDefinition)); + } + + schemaName.EnsureGraphQLName(nameof(schemaName)); + + return typeDefinition.Directives.Any(t => + HasSourceDirective(t, schemaName)); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/Extensions/SchemaInfoExtensions.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/Extensions/SchemaInfoExtensions.cs new file mode 100644 index 00000000000..3436830f603 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/Extensions/SchemaInfoExtensions.cs @@ -0,0 +1,17 @@ +using HotChocolate.Language; + +namespace HotChocolate.Stitching.Merge; + +internal static class SchemaInfoExtensions +{ + public static ObjectTypeDefinitionNode? GetRootType( + this ISchemaInfo schema, + OperationType operation) + => operation switch + { + OperationType.Query => schema.QueryType, + OperationType.Mutation => schema.MutationType, + OperationType.Subscription => schema.SubscriptionType, + _ => throw new NotSupportedException(), + }; +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/Extensions/SchemaMergerExtensions.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/Extensions/SchemaMergerExtensions.cs new file mode 100644 index 00000000000..a57684b9f7f --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/Extensions/SchemaMergerExtensions.cs @@ -0,0 +1,188 @@ +using System.Reflection; +using HotChocolate.Resolvers; +using HotChocolate.Stitching.Merge.Rewriters; +using HotChocolate.Stitching.Properties; +using HotChocolate.Utilities; + +namespace HotChocolate.Stitching.Merge; + +public static class SchemaMergerExtensions +{ + public static ISchemaMerger IgnoreRootTypes( + this ISchemaMerger schemaMerger, + string? schemaName = null) + { + if (schemaMerger == null) + { + throw new ArgumentNullException(nameof(schemaMerger)); + } + + schemaName?.EnsureGraphQLName(nameof(schemaName)); + + return schemaMerger.AddDocumentRewriter( + new RemoveRootTypeRewriter(schemaName)); + } + + public static ISchemaMerger IgnoreType( + this ISchemaMerger schemaMerger, + string typeName, + string? schemaName = null) + { + if (schemaMerger == null) + { + throw new ArgumentNullException(nameof(schemaMerger)); + } + + typeName.EnsureGraphQLName(nameof(typeName)); + schemaName?.EnsureGraphQLName(nameof(schemaName)); + + return schemaMerger.AddDocumentRewriter( + new RemoveTypeRewriter(typeName, schemaName)); + } + + public static ISchemaMerger IgnoreField( + this ISchemaMerger schemaMerger, + FieldReference field, + string? schemaName = null) + { + if (schemaMerger is null) + { + throw new ArgumentNullException(nameof(schemaMerger)); + } + + if (field is null) + { + throw new ArgumentNullException(nameof(field)); + } + + schemaName?.EnsureGraphQLName(nameof(schemaName)); + + return schemaMerger.AddTypeRewriter( + new RemoveFieldRewriter(field, schemaName)); + } + + public static ISchemaMerger RenameType( + this ISchemaMerger schemaMerger, + string originalTypeName, + string newTypeName, + string? schemaName = null) + { + if (schemaMerger is null) + { + throw new ArgumentNullException(nameof(schemaMerger)); + } + + originalTypeName.EnsureGraphQLName(nameof(originalTypeName)); + newTypeName.EnsureGraphQLName(nameof(newTypeName)); + schemaName?.EnsureGraphQLName(nameof(schemaName)); + + return schemaMerger.AddTypeRewriter( + new RenameTypeRewriter(originalTypeName, newTypeName, schemaName)); + } + + public static ISchemaMerger RenameField( + this ISchemaMerger schemaMerger, + FieldReference field, + string newFieldName, + string? schemaName = null) + { + if (schemaMerger is null) + { + throw new ArgumentNullException(nameof(schemaMerger)); + } + + if (field is null) + { + throw new ArgumentNullException(nameof(field)); + } + + newFieldName.EnsureGraphQLName(nameof(newFieldName)); + schemaName?.EnsureGraphQLName(nameof(schemaName)); + + return schemaMerger.AddTypeRewriter( + new RenameFieldRewriter(field, newFieldName, schemaName)); + } + + [Obsolete("Use AddTypeMergeHandler")] + public static ISchemaMerger AddMergeHandler( + this ISchemaMerger merger) + where T : class, ITypeMergeHandler => + AddTypeMergeHandler(merger); + + public static ISchemaMerger AddTypeMergeHandler( + this ISchemaMerger merger) + where T : class, ITypeMergeHandler + { + if (merger == null) + { + throw new ArgumentNullException(nameof(merger)); + } + + merger.AddTypeMergeRule(CreateTypeMergeRule()); + + return merger; + } + + public static ISchemaMerger AddDirectiveMergeHandler( + this ISchemaMerger merger) + where T : class, IDirectiveMergeHandler + { + if (merger == null) + { + throw new ArgumentNullException(nameof(merger)); + } + + merger.AddDirectiveMergeRule(CreateDirectiveMergeRule()); + + return merger; + } + + internal static MergeTypeRuleFactory CreateTypeMergeRule() + where T : class, ITypeMergeHandler + { + var constructor = + CreateHandlerInternal(); + + return next => + { + var handler = (ITypeMergeHandler)constructor + .Invoke(new object[] { next }); + return handler.Merge; + }; + } + + internal static MergeDirectiveRuleFactory CreateDirectiveMergeRule() + where T : class, IDirectiveMergeHandler + { + var constructor = + CreateHandlerInternal(); + + return next => + { + var handler = (IDirectiveMergeHandler)constructor + .Invoke(new object[] { next }); + return handler.Merge; + }; + } + + private static ConstructorInfo CreateHandlerInternal() + where THandler : class + { + var constructor = typeof(THandler).GetTypeInfo() + .DeclaredConstructors.SingleOrDefault(c => + { + var parameters = c.GetParameters(); + return parameters.Length == 1 + && parameters[0].ParameterType == + typeof(TRule); + }); + + if (constructor == null) + { + throw new ArgumentException(StitchingResources + .SchemaMergerExtensions_NoValidConstructor); + } + + return constructor; + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/Extensions/TypeInfoExtensions.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/Extensions/TypeInfoExtensions.cs new file mode 100644 index 00000000000..cbaef27bcdf --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/Extensions/TypeInfoExtensions.cs @@ -0,0 +1,37 @@ +namespace HotChocolate.Stitching.Merge; + +public static class TypeInfoExtensions +{ + public static bool IsQueryType(this ITypeInfo typeInfo) + { + if (typeInfo == null) + { + throw new ArgumentNullException(nameof(typeInfo)); + } + + return typeInfo.IsRootType + && typeInfo.Definition == typeInfo.Schema.QueryType; + } + + public static bool IsMutationType(this ITypeInfo typeInfo) + { + if (typeInfo == null) + { + throw new ArgumentNullException(nameof(typeInfo)); + } + + return typeInfo.IsRootType + && typeInfo.Definition == typeInfo.Schema.MutationType; + } + + public static bool IsSubscriptionType(this ITypeInfo typeInfo) + { + if (typeInfo == null) + { + throw new ArgumentNullException(nameof(typeInfo)); + } + + return typeInfo.IsRootType + && typeInfo.Definition == typeInfo.Schema.SubscriptionType; + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/ComplexTypeMergeHelpers.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/ComplexTypeMergeHelpers.cs new file mode 100644 index 00000000000..b43f38d6ce8 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/ComplexTypeMergeHelpers.cs @@ -0,0 +1,96 @@ +using HotChocolate.Language; +using HotChocolate.Utilities; + +namespace HotChocolate.Stitching.Merge.Handlers; + +internal static class ComplexTypeMergeHelpers +{ + public static bool CanBeMergedWith( + this InterfaceTypeInfo left, + InterfaceTypeInfo right) + => CanBeMerged(left.Definition, right.Definition); + + public static bool CanBeMergedWith( + this ObjectTypeInfo left, + ObjectTypeInfo right) + => CanBeMerged(left.Definition, right.Definition); + + private static bool CanBeMerged( + ComplexTypeDefinitionNodeBase left, + ComplexTypeDefinitionNodeBase right) + { + if (left.Name.Value.EqualsOrdinal(right.Name.Value) + && left.Fields.Count == right.Fields.Count) + { + var leftFields = left.Fields.ToDictionary(t => t.Name.Value); + var rightFields = right.Fields.ToDictionary(t => t.Name.Value); + + foreach (var name in leftFields.Keys) + { + var leftField = leftFields[name]; + if (!rightFields.TryGetValue(name, out var rightField) + || !HasSameShape(leftField, rightField)) + { + return false; + } + } + return true; + } + return false; + } + + private static bool HasSameShape( + FieldDefinitionNode left, + FieldDefinitionNode right) + { + if (left.Name.Value.EqualsOrdinal(right.Name.Value) + && HasSameType(left.Type, right.Type) + && left.Arguments.Count == right.Arguments.Count) + { + return HasSameArguments(left.Arguments, right.Arguments); + } + return false; + } + + public static bool HasSameArguments( + IReadOnlyList left, + IReadOnlyList right) + { + var leftArgs = left.ToDictionary(t => t.Name.Value); + var rightArgs = right.ToDictionary(t => t.Name.Value); + + foreach (var name in leftArgs.Keys) + { + var leftArgument = leftArgs[name]; + if (!rightArgs.TryGetValue(name, out var rightArgument) + || !HasSameType(leftArgument.Type, rightArgument.Type)) + { + return false; + } + } + return true; + } + + private static bool HasSameType(ITypeNode left, ITypeNode right) + { + if (left is NonNullTypeNode lnntn + && right is NonNullTypeNode rnntn) + { + return HasSameType(lnntn.Type, rnntn.Type); + } + + if (left is ListTypeNode lltn + && right is ListTypeNode rltn) + { + return HasSameType(lltn.Type, rltn.Type); + } + + if (left is NamedTypeNode lntn + && right is NamedTypeNode rntn) + { + return lntn.Name.Value.EqualsOrdinal(rntn.Name.Value); + } + + return false; + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/DirectiveTypeMergeHandler.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/DirectiveTypeMergeHandler.cs new file mode 100644 index 00000000000..9e0a16a9af7 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/DirectiveTypeMergeHandler.cs @@ -0,0 +1,103 @@ +using HotChocolate.Language; + +namespace HotChocolate.Stitching.Merge.Handlers; + +internal class DirectiveTypeMergeHandler +{ + private readonly MergeDirectiveRuleDelegate _next; + + public DirectiveTypeMergeHandler(MergeDirectiveRuleDelegate next) + { + _next = next ?? throw new ArgumentNullException(nameof(next)); + } + + public void Merge( + ISchemaMergeContext context, + IReadOnlyList types) + { + var notMerged = types.ToList(); + + while (notMerged.Count > 0) + { + MergeNextType(context, notMerged); + } + } + + private void MergeNextType( + ISchemaMergeContext context, + List notMerged) + { + var left = notMerged[0]; + + var readyToMerge = new List(); + readyToMerge.Add(left); + + for (var i = 1; i < notMerged.Count; i++) + { + if (CanBeMerged(left.Definition, notMerged[i].Definition)) + { + readyToMerge.Add(notMerged[i]); + } + } + + var name = readyToMerge[0].Definition.Name.Value; + + if (context.ContainsDirective(name)) + { + throw new InvalidOperationException($"Unable to merge {name}, directive with this name already exists."); + } + + MergeTypes(context, readyToMerge, name); + notMerged.RemoveAll(readyToMerge.Contains); + } + + protected void MergeTypes( + ISchemaMergeContext context, + IReadOnlyList types, + string newTypeName) + { + var definitions = types + .Select(t => t.Definition) + .ToList(); + + var definition = + definitions[0].Rename( + newTypeName, + types.Select(t => t.Schema.Name)); + + context.AddDirective(definition); + } + + private static bool CanBeMerged(DirectiveDefinitionNode left, DirectiveDefinitionNode right) + { + if (!left.Name.Value.Equals(right.Name.Value, StringComparison.Ordinal)) + { + return false; + } + + if (left.Locations.Count != right.Locations.Count) + { + return false; + } + + var leftLocations = left.Locations.Select(l => l.Value).OrderBy(l => l).ToList(); + var rightLocations = right.Locations.Select(l => l.Value).OrderBy(l => l).ToList(); + + if (!leftLocations.SequenceEqual(rightLocations)) + { + return false; + } + + if (left.IsRepeatable != right.IsRepeatable) + { + return false; + } + + if (left.Arguments.Count != right.Arguments.Count) + { + return false; + } + + return ComplexTypeMergeHelpers.HasSameArguments(left.Arguments, right.Arguments); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/EnumTypeMergeHandler.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/EnumTypeMergeHandler.cs new file mode 100644 index 00000000000..36411e5e9b1 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/EnumTypeMergeHandler.cs @@ -0,0 +1,121 @@ +using HotChocolate.Language; + +namespace HotChocolate.Stitching.Merge.Handlers; + +internal class EnumTypeMergeHandler + : ITypeMergeHandler +{ + private readonly MergeTypeRuleDelegate _next; + + public EnumTypeMergeHandler(MergeTypeRuleDelegate next) + { + _next = next ?? throw new ArgumentNullException(nameof(next)); + } + + public void Merge( + ISchemaMergeContext context, + IReadOnlyList types) + { + if (types.OfType().Any()) + { + var notMerged = types.OfType().ToList(); + var hasLeftovers = types.Count > notMerged.Count; + + while (notMerged.Count > 0) + { + MergeNextType(context, notMerged); + } + + if (hasLeftovers) + { + _next.Invoke(context, types.NotOfType()); + } + } + else + { + _next.Invoke(context, types); + } + } + + private static void MergeNextType( + ISchemaMergeContext context, + List notMerged) + { + var left = notMerged[0]; + + var leftValueSet = new HashSet( + left.Definition.Values.Select(t => t.Name.Value)); + + var readyToMerge = new List(); + readyToMerge.Add(left); + + for (var i = 1; i < notMerged.Count; i++) + { + if (CanBeMerged(leftValueSet, notMerged[i].Definition)) + { + readyToMerge.Add(notMerged[i]); + } + } + + MergeType(context, readyToMerge); + notMerged.RemoveAll(readyToMerge.Contains); + } + + private static void MergeType( + ISchemaMergeContext context, + IReadOnlyList types) + { + var definition = types[0].Definition; + + var descriptionDef = + types.Select(t => t.Definition) + .FirstOrDefault(t => t.Description != null); + + if (descriptionDef != null) + { + definition = definition.WithDescription( + descriptionDef.Description); + } + + context.AddType(definition.Rename( + TypeMergeHelpers.CreateName(context, types), + types.Select(t => t.Schema.Name))); + } + + internal static bool CanBeMerged( + EnumTypeDefinitionNode left, + EnumTypeDefinitionNode right) + { + if (left == null) + { + throw new ArgumentNullException(nameof(left)); + } + + if (right == null) + { + throw new ArgumentNullException(nameof(right)); + } + + var leftValueSet = new HashSet( + left.Values.Select(t => t.Name.Value)); + return CanBeMerged(leftValueSet, right); + } + + private static bool CanBeMerged( + ICollection left, + EnumTypeDefinitionNodeBase right) + { + if (left.Count == right.Values.Count) + { + for (var i = 0; i < right.Values.Count; i++) + { + if (!left.Contains(right.Values[i].Name.Value)) + { + return false; + } + } + return true; + } + return false; + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/InputObjectTypeMergeHandler.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/InputObjectTypeMergeHandler.cs new file mode 100644 index 00000000000..0adcd3e7e41 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/InputObjectTypeMergeHandler.cs @@ -0,0 +1,242 @@ +using System.ComponentModel; +using System; +using System.Collections.Generic; +using System.Linq; +using HotChocolate.Language; + +namespace HotChocolate.Stitching.Merge.Handlers; + +internal class InputObjectTypeMergeHandler + : TypeMergeHandlerBase +{ + public InputObjectTypeMergeHandler(MergeTypeRuleDelegate next) + : base(next) + { + } + + protected override void MergeTypes( + ISchemaMergeContext context, + IReadOnlyList types, + string newTypeName) + { + var definitions = types + .Select(t => t.Definition) + .ToList(); + + var definition = + definitions[0].Rename( + newTypeName, + types.Select(t => t.Schema.Name)); + + context.AddType(definition); + } + + protected override bool CanBeMerged( + InputObjectTypeInfo left, + InputObjectTypeInfo right) + { + var processed = new HashSet(); + var queue = new Queue(); + var fieldTypes = new List(); + + queue.Enqueue(new TypePair(left, right)); + + while (queue.Count > 0) + { + var pair = queue.Dequeue(); + processed.Add(pair.Left.Definition.Name.Value); + fieldTypes.Clear(); + + if (pair.Left.Definition is InputObjectTypeDefinitionNode ld + && pair.Right.Definition is InputObjectTypeDefinitionNode rd + && CanBeMerged(ld, rd, fieldTypes) + && CanHandleFieldTypes(pair, fieldTypes, processed, queue)) + { + processed.Add(ld.Name.Value); + } + else + { + return false; + } + } + + return true; + } + + private static bool CanHandleFieldTypes( + TypePair typePair, + ICollection fieldTypes, + ISet processed, + Queue queue) + { + if (fieldTypes.Count > 0) + { + foreach (var typeName in fieldTypes) + { + if (processed.Add(typeName) + && !TryEnqueueFieldType(typePair, typeName, queue)) + { + return false; + } + } + } + + return true; + } + + private static bool TryEnqueueFieldType( + TypePair typePair, + string typeName, + Queue queue) + { + if (typePair.Left.Schema.Types.TryGetValue(typeName, + out var lt) + && typePair.Right.Schema.Types.TryGetValue(typeName, + out var rt)) + { + return TryEnqueueForAnalysis( + TypeInfo.Create(lt, typePair.Left.Schema), + TypeInfo.Create(rt, typePair.Right.Schema), + queue); + } + else if (!typePair.Left.Schema.Types.ContainsKey(typeName) + && !typePair.Right.Schema.Types.ContainsKey(typeName)) + { + // if the type does not exist in either schema then we expect + // it to be a scalar type. + return true; + } + + return false; + } + + private static bool TryEnqueueForAnalysis( + ITypeInfo left, ITypeInfo right, + Queue queue) + { + switch (GetMergeStatus(left.Definition, left.Definition)) + { + case MergeStatus.Analyze: + queue.Enqueue(new TypePair(right, left)); + return true; + case MergeStatus.Merge: + return true; + default: + return false; + } + } + + private static MergeStatus GetMergeStatus( + ITypeDefinitionNode leftType, + ITypeDefinitionNode rightType) + { + if (leftType is InputObjectTypeDefinitionNode + && rightType is InputObjectTypeDefinitionNode) + { + return MergeStatus.Analyze; + } + else if (leftType is ScalarTypeDefinitionNode + && rightType is ScalarTypeDefinitionNode) + { + return MergeStatus.Merge; + } + else if (leftType is EnumTypeDefinitionNode let + && rightType is EnumTypeDefinitionNode ret) + { + return EnumTypeMergeHandler.CanBeMerged(let, ret) + ? MergeStatus.Merge + : MergeStatus.Invalid; + } + + return MergeStatus.Analyze; + } + + private static bool CanBeMerged( + InputObjectTypeDefinitionNode left, + InputObjectTypeDefinitionNode right, + ICollection typesToAnalyze) + { + if (left.Name.Value.Equals( + right.Name.Value, + StringComparison.Ordinal) + && left.Fields.Count == right.Fields.Count) + { + var leftArgs = + left.Fields.ToDictionary(t => t.Name.Value); + var rightArgs = + left.Fields.ToDictionary(t => t.Name.Value); + + foreach (var name in leftArgs.Keys) + { + var leftArgument = leftArgs[name]; + if (!rightArgs.TryGetValue(name, + out var rightArgument) + || !HasSameType( + leftArgument.Type, + rightArgument.Type, + typesToAnalyze)) + { + return false; + } + } + return true; + } + return false; + } + + private static bool HasSameType( + ITypeNode left, + ITypeNode right, + ICollection typesToAnalyze) + { + if (left is NonNullTypeNode lnntn + && right is NonNullTypeNode rnntn) + { + return HasSameType(lnntn.Type, rnntn.Type, typesToAnalyze); + } + + if (left is ListTypeNode lltn + && right is ListTypeNode rltn) + { + return HasSameType(lltn.Type, rltn.Type, typesToAnalyze); + } + + if (left is NamedTypeNode lntn + && right is NamedTypeNode rntn) + { + + if (lntn.Name.Value.Equals( + rntn.Name.Value, + StringComparison.Ordinal)) + { + typesToAnalyze.Add(rntn.Name.Value); + return true; + } + return false; + } + + throw new NotSupportedException(); + } + + private class TypePair + { + public TypePair( + ITypeInfo left, + ITypeInfo right) + { + Left = left; + Right = right; + } + + public ITypeInfo Left { get; } + + public ITypeInfo Right { get; } + } + + private enum MergeStatus + { + Merge, + Invalid, + Analyze + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/InterfaceTypeMergeHandler.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/InterfaceTypeMergeHandler.cs new file mode 100644 index 00000000000..549c1f7d4ca --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/InterfaceTypeMergeHandler.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using HotChocolate.Language; + +namespace HotChocolate.Stitching.Merge.Handlers; + +internal class InterfaceTypeMergeHandler + : TypeMergeHandlerBase +{ + public InterfaceTypeMergeHandler(MergeTypeRuleDelegate next) + : base(next) + { + } + + protected override void MergeTypes( + ISchemaMergeContext context, + IReadOnlyList types, + string newTypeName) + { + var definitions = types + .Select(t => t.Definition) + .ToList(); + + var definition = + definitions[0].Rename( + newTypeName, + types.Select(t => t.Schema.Name)); + + context.AddType(definition); + } + + protected override bool CanBeMerged( + InterfaceTypeInfo left, InterfaceTypeInfo right) => + left.CanBeMergedWith(right); +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/ObjectTypeMergeHandler.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/ObjectTypeMergeHandler.cs new file mode 100644 index 00000000000..f35ad7168fa --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/ObjectTypeMergeHandler.cs @@ -0,0 +1,37 @@ +using HotChocolate.Language; + +namespace HotChocolate.Stitching.Merge.Handlers; + +internal class ObjectTypeMergeHandler : TypeMergeHandlerBase +{ + public ObjectTypeMergeHandler(MergeTypeRuleDelegate next) + : base(next) + { + } + + protected override void MergeTypes( + ISchemaMergeContext context, + IReadOnlyList types, + string newTypeName) + { + var definitions = types + .Select(t => t.Definition) + .ToList(); + + // ? : how do we handle the interfaces correctly + var interfaces = new HashSet( + definitions.SelectMany(d => + d.Interfaces.Select(t => t.Name.Value))); + + var definition = definitions[0] + .WithInterfaces(interfaces.Select(t => + new NamedTypeNode(new NameNode(t))).ToList()) + .Rename(newTypeName, types.Select(t => t.Schema.Name)); + + context.AddType(definition); + } + + protected override bool CanBeMerged( + ObjectTypeInfo left, ObjectTypeInfo right) => + left.CanBeMergedWith(right); +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/RootTypeMergeHandler.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/RootTypeMergeHandler.cs new file mode 100644 index 00000000000..ab7ec0e8e16 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/RootTypeMergeHandler.cs @@ -0,0 +1,91 @@ +using HotChocolate.Language; +using HotChocolate.Stitching.Delegation.ScopedVariables; + +namespace HotChocolate.Stitching.Merge.Handlers; + +internal class RootTypeMergeHandler : ITypeMergeHandler +{ + private readonly MergeTypeRuleDelegate _next; + + public RootTypeMergeHandler(MergeTypeRuleDelegate next) + { + _next = next ?? throw new ArgumentNullException(nameof(next)); + } + + public void Merge( + ISchemaMergeContext context, + IReadOnlyList types) + { + if (types.Count > 0) + { + if (types[0].IsRootType) + { + var names = new HashSet(); + var fields = new List(); + + foreach (var type in types.OfType()) + { + IntegrateFields(type.Definition, type, names, fields); + } + + if (types[0].Schema.TryGetOperationType( + (ObjectTypeDefinitionNode)types[0].Definition, + out var operationType)) + { + var mergedRootType = new ObjectTypeDefinitionNode + ( + null, + new NameNode(operationType.ToString()), + null, + Array.Empty(), + Array.Empty(), + fields + ); + context.AddType(mergedRootType); + } + } + else + { + _next.Invoke(context, types); + } + } + } + + private static void IntegrateFields( + ObjectTypeDefinitionNode rootType, + ITypeInfo typeInfo, + ISet names, + ICollection fields) + { + var schemaName = typeInfo.Schema.Name; + + foreach (var field in rootType.Fields) + { + var current = field; + + if (names.Add(current.Name.Value)) + { + current = current.AddDelegationPath(schemaName); + } + else + { + var path = new SelectionPathComponent( + field.Name, + field.Arguments + .Select( + t => new ArgumentNode( + t.Name, + new ScopedVariableNode( + null, + new NameNode(ScopeNames.Arguments), + t.Name))) + .ToList()); + + var newName = new NameNode(typeInfo.CreateUniqueName(current)); + current = current.WithName(newName).AddDelegationPath(schemaName, path); + } + + fields.Add(current); + } + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/ScalarTypeMergeHandler.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/ScalarTypeMergeHandler.cs new file mode 100644 index 00000000000..838c49fa9d5 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/ScalarTypeMergeHandler.cs @@ -0,0 +1,23 @@ +namespace HotChocolate.Stitching.Merge.Handlers; + +internal class ScalarTypeMergeHandler : ITypeMergeHandler +{ + private readonly MergeTypeRuleDelegate _next; + + public ScalarTypeMergeHandler(MergeTypeRuleDelegate next) + { + _next = next ?? throw new ArgumentNullException(nameof(next)); + } + + public void Merge( + ISchemaMergeContext context, + IReadOnlyList types) + { + var unhandled = + types.OfType().Any() + ? types.NotOfType() + : types; + + _next.Invoke(context, unhandled); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/TypeMergeHandlerBase.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/TypeMergeHandlerBase.cs new file mode 100644 index 00000000000..d0469c28440 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/TypeMergeHandlerBase.cs @@ -0,0 +1,68 @@ +namespace HotChocolate.Stitching.Merge.Handlers; + +public abstract class TypeMergeHandlerBase: ITypeMergeHandler where T : ITypeInfo +{ + private readonly MergeTypeRuleDelegate _next; + + protected TypeMergeHandlerBase(MergeTypeRuleDelegate next) + { + _next = next ?? throw new ArgumentNullException(nameof(next)); + } + + public void Merge( + ISchemaMergeContext context, + IReadOnlyList types) + { + if (types.OfType().Any()) + { + var notMerged = types.OfType().ToList(); + var hasLeftovers = types.Count > notMerged.Count; + + while (notMerged.Count > 0) + { + MergeNextType(context, notMerged); + } + + if (hasLeftovers) + { + _next.Invoke(context, types.NotOfType()); + } + } + else + { + _next.Invoke(context, types); + } + } + + private void MergeNextType( + ISchemaMergeContext context, + List notMerged) + { + var left = notMerged[0]; + + var readyToMerge = new List(); + readyToMerge.Add(left); + + for (var i = 1; i < notMerged.Count; i++) + { + if (CanBeMerged(left, notMerged[i])) + { + readyToMerge.Add(notMerged[i]); + } + } + + var newTypeName = + TypeMergeHelpers.CreateName( + context, readyToMerge); + + MergeTypes(context, readyToMerge, newTypeName); + notMerged.RemoveAll(readyToMerge.Contains); + } + + protected abstract bool CanBeMerged(T left, T right); + + protected abstract void MergeTypes( + ISchemaMergeContext context, + IReadOnlyList types, + string newTypeName); +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/TypeMergeHelpers.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/TypeMergeHelpers.cs new file mode 100644 index 00000000000..d2916c1ff91 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/TypeMergeHelpers.cs @@ -0,0 +1,80 @@ +using HotChocolate.Language; + +namespace HotChocolate.Stitching.Merge.Handlers; + +internal static class TypeMergeHelpers +{ + private const int _maxRetries = 10000; + + public static string CreateName( + ISchemaMergeContext context, + params T[] types) + where T : ITypeInfo => + CreateName(context, (IReadOnlyList)types); + + + public static string CreateName( + ISchemaMergeContext context, + IReadOnlyList types) + where T : ITypeInfo + { + var name = types[0].Definition.Name.Value; + + if (context.ContainsType(name)) + { + for (var i = 0; i < types.Count; i++) + { + name = types[i].CreateUniqueName(); + if (!context.ContainsType(name)) + { + break; + } + } + + if (context.ContainsType(name)) + { + name = types[0].Definition.Name.Value; + + for (var i = 0; i < _maxRetries; i++) + { + var n = name + $"_{i}"; + if (!context.ContainsType(name)) + { + name = n; + break; + } + } + } + } + + return name; + } + + public static string CreateUniqueName( + this ITypeInfo typeInfo) + { + if (typeInfo == null) + { + throw new ArgumentNullException(nameof(typeInfo)); + } + + return $"{typeInfo.Schema.Name}_{typeInfo.Definition.Name.Value}"; + } + + public static string CreateUniqueName( + this ITypeInfo typeInfo, + NamedSyntaxNode namedSyntaxNode) + { + if (typeInfo == null) + { + throw new ArgumentNullException(nameof(typeInfo)); + } + + if (namedSyntaxNode == null) + { + throw new ArgumentNullException(nameof(namedSyntaxNode)); + } + + return $"{typeInfo.Schema.Name}_{namedSyntaxNode.Name.Value}"; + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/UnionTypeMergeHandler.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/UnionTypeMergeHandler.cs new file mode 100644 index 00000000000..f42daa5f75d --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/Handlers/UnionTypeMergeHandler.cs @@ -0,0 +1,38 @@ +namespace HotChocolate.Stitching.Merge.Handlers; + +internal class UnionTypeMergeHandler : ITypeMergeHandler +{ + private readonly MergeTypeRuleDelegate _next; + + public UnionTypeMergeHandler(MergeTypeRuleDelegate next) + { + _next = next ?? throw new ArgumentNullException(nameof(next)); + } + + public void Merge( + ISchemaMergeContext context, + IReadOnlyList types) + { + if (types.OfType().Any()) + { + var notMerged = types.OfType().ToList(); + var hasLeftovers = types.Count > notMerged.Count; + + for (var i = 0; i < notMerged.Count; i++) + { + context.AddType(notMerged[i].Definition.Rename( + TypeMergeHelpers.CreateName(context, notMerged[i]), + notMerged[i].Schema.Name)); + } + + if (hasLeftovers) + { + _next.Invoke(context, types.NotOfType()); + } + } + else + { + _next.Invoke(context, types); + } + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/IDirectiveMergeHandler.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/IDirectiveMergeHandler.cs new file mode 100644 index 00000000000..f3f154bf3ae --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/IDirectiveMergeHandler.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace HotChocolate.Stitching.Merge; + +public interface IDirectiveMergeHandler +{ + void Merge( + ISchemaMergeContext context, + IReadOnlyList directives); +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/IDirectiveTypeInfo.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/IDirectiveTypeInfo.cs new file mode 100644 index 00000000000..0940d1f1375 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/IDirectiveTypeInfo.cs @@ -0,0 +1,9 @@ +using HotChocolate.Language; + +namespace HotChocolate.Stitching.Merge; + +public interface IDirectiveTypeInfo +{ + DirectiveDefinitionNode Definition { get; } + ISchemaInfo Schema { get; } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/ISchemaInfo.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/ISchemaInfo.cs new file mode 100644 index 00000000000..25bd5bfc219 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/ISchemaInfo.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using HotChocolate.Language; + +namespace HotChocolate.Stitching.Merge; + +public interface ISchemaInfo +{ + string Name { get; } + + DocumentNode Document { get; } + + IReadOnlyDictionary Types { get; } + + IReadOnlyDictionary Directives { get; } + + ObjectTypeDefinitionNode QueryType { get; } + + ObjectTypeDefinitionNode? MutationType { get; } + + ObjectTypeDefinitionNode? SubscriptionType { get; } + + bool IsRootType(ITypeDefinitionNode typeDefinition); + + bool TryGetOperationType( + ObjectTypeDefinitionNode rootType, out + OperationType operationType); +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/ISchemaMergeContext.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/ISchemaMergeContext.cs new file mode 100644 index 00000000000..a9f67845548 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/ISchemaMergeContext.cs @@ -0,0 +1,14 @@ +using HotChocolate.Language; + +namespace HotChocolate.Stitching.Merge; + +public interface ISchemaMergeContext +{ + void AddType(ITypeDefinitionNode type); + + void AddDirective(DirectiveDefinitionNode directive); + + bool ContainsType(string typeName); + + bool ContainsDirective(string directiveName); +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/ISchemaMerger.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/ISchemaMerger.cs new file mode 100644 index 00000000000..7a9886217e9 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/ISchemaMerger.cs @@ -0,0 +1,23 @@ +using System; +using HotChocolate.Language; +using HotChocolate.Stitching.Merge.Rewriters; + +namespace HotChocolate.Stitching.Merge; + +public interface ISchemaMerger +{ + ISchemaMerger AddSchema(string name, DocumentNode schema); + + [Obsolete("Use AddTypeMergeRule")] + ISchemaMerger AddMergeRule(MergeTypeRuleFactory factory); + + ISchemaMerger AddTypeMergeRule(MergeTypeRuleFactory factory); + + ISchemaMerger AddDirectiveMergeRule(MergeDirectiveRuleFactory factory); + + ISchemaMerger AddTypeRewriter(ITypeRewriter rewriter); + + ISchemaMerger AddDocumentRewriter(IDocumentRewriter rewriter); + + DocumentNode Merge(); +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/ITypeInfo.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/ITypeInfo.cs new file mode 100644 index 00000000000..983541a3424 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/ITypeInfo.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using HotChocolate.Language; + +namespace HotChocolate.Stitching.Merge; + +public interface ITypeInfo +{ + ITypeDefinitionNode Definition { get; } + + ISchemaInfo Schema { get; } + + bool IsRootType { get; } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/ITypeInfo~1.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/ITypeInfo~1.cs new file mode 100644 index 00000000000..783cca71a4c --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/ITypeInfo~1.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using HotChocolate.Language; + +namespace HotChocolate.Stitching.Merge; + +public interface ITypeInfo + : ITypeInfo + where T : ITypeDefinitionNode +{ + new T Definition { get; } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/ITypeMergeHandler.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/ITypeMergeHandler.cs new file mode 100644 index 00000000000..38acc34f806 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/ITypeMergeHandler.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace HotChocolate.Stitching.Merge; + +public interface ITypeMergeHandler +{ + void Merge( + ISchemaMergeContext context, + IReadOnlyList types); +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/InputObjectTypeInfo.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/InputObjectTypeInfo.cs new file mode 100644 index 00000000000..5576af20e21 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/InputObjectTypeInfo.cs @@ -0,0 +1,14 @@ +using HotChocolate.Language; + +namespace HotChocolate.Stitching.Merge; + +internal class InputObjectTypeInfo + : TypeInfo +{ + public InputObjectTypeInfo( + InputObjectTypeDefinitionNode typeDefinition, + ISchemaInfo schema) + : base(typeDefinition, schema) + { + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/InterfaceTypeInfo.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/InterfaceTypeInfo.cs new file mode 100644 index 00000000000..e8f704a096a --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/InterfaceTypeInfo.cs @@ -0,0 +1,14 @@ +using HotChocolate.Language; + +namespace HotChocolate.Stitching.Merge; + +internal class InterfaceTypeInfo + : TypeInfo +{ + public InterfaceTypeInfo( + InterfaceTypeDefinitionNode typeDefinition, + ISchemaInfo schema) + : base(typeDefinition, schema) + { + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/MergeDirectiveRuleDelegate.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/MergeDirectiveRuleDelegate.cs new file mode 100644 index 00000000000..49fed5eb55f --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/MergeDirectiveRuleDelegate.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace HotChocolate.Stitching.Merge; + +public delegate MergeDirectiveRuleDelegate MergeDirectiveRuleFactory( + MergeDirectiveRuleDelegate next); + +public delegate void MergeDirectiveRuleDelegate( + ISchemaMergeContext context, + IReadOnlyList types); diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/MergeTypeDelegate.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/MergeTypeDelegate.cs new file mode 100644 index 00000000000..13559549e17 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/MergeTypeDelegate.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace HotChocolate.Stitching.Merge; + +public delegate MergeTypeRuleDelegate MergeTypeRuleFactory( + MergeTypeRuleDelegate next); + +public delegate void MergeTypeRuleDelegate( + ISchemaMergeContext context, + IReadOnlyList types); diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/ObjectTypeInfo.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/ObjectTypeInfo.cs new file mode 100644 index 00000000000..eaec9c593e6 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/ObjectTypeInfo.cs @@ -0,0 +1,14 @@ +using HotChocolate.Language; + +namespace HotChocolate.Stitching.Merge; + +internal class ObjectTypeInfo + : TypeInfo +{ + public ObjectTypeInfo( + ObjectTypeDefinitionNode typeDefinition, + ISchemaInfo schema) + : base(typeDefinition, schema) + { + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/DelegateDocumentRewriter.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/DelegateDocumentRewriter.cs new file mode 100644 index 00000000000..d59c4d5634e --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/DelegateDocumentRewriter.cs @@ -0,0 +1,22 @@ +using System; +using HotChocolate.Language; + +namespace HotChocolate.Stitching.Merge.Rewriters; + +public delegate DocumentNode RewriteDocumentDelegate(ISchemaInfo schema, DocumentNode document); + +internal class DelegateDocumentRewriter : IDocumentRewriter +{ + private readonly RewriteDocumentDelegate _rewrite; + + public DelegateDocumentRewriter(RewriteDocumentDelegate rewrite) + { + _rewrite = rewrite + ?? throw new ArgumentNullException(nameof(rewrite)); + } + + public DocumentNode Rewrite(ISchemaInfo schema, DocumentNode document) + { + return _rewrite.Invoke(schema, document); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/DelegateTypeRewriter.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/DelegateTypeRewriter.cs new file mode 100644 index 00000000000..1689233eb78 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/DelegateTypeRewriter.cs @@ -0,0 +1,22 @@ +using HotChocolate.Language; + +namespace HotChocolate.Stitching.Merge.Rewriters; + +public delegate ITypeDefinitionNode RewriteTypeDefinitionDelegate( + ISchemaInfo schema, + ITypeDefinitionNode typeDefinition); + +internal class DelegateTypeRewriter : ITypeRewriter +{ + private readonly RewriteTypeDefinitionDelegate _rewrite; + + public DelegateTypeRewriter(RewriteTypeDefinitionDelegate rewrite) + { + _rewrite = rewrite ?? throw new ArgumentNullException(nameof(rewrite)); + } + + public ITypeDefinitionNode Rewrite( + ISchemaInfo schema, + ITypeDefinitionNode typeDefinition) + => _rewrite.Invoke(schema, typeDefinition); +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/IDocumentRewriter.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/IDocumentRewriter.cs new file mode 100644 index 00000000000..53d754eafa6 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/IDocumentRewriter.cs @@ -0,0 +1,8 @@ +using HotChocolate.Language; + +namespace HotChocolate.Stitching.Merge.Rewriters; + +public interface IDocumentRewriter +{ + DocumentNode Rewrite(ISchemaInfo schema, DocumentNode document); +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/ITypeRewriter.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/ITypeRewriter.cs new file mode 100644 index 00000000000..7a66e7aaa36 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/ITypeRewriter.cs @@ -0,0 +1,8 @@ +using HotChocolate.Language; + +namespace HotChocolate.Stitching.Merge.Rewriters; + +public interface ITypeRewriter +{ + ITypeDefinitionNode Rewrite(ISchemaInfo schema, ITypeDefinitionNode typeDefinition); +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/RemoveFieldRewriter.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/RemoveFieldRewriter.cs new file mode 100644 index 00000000000..c162fe459d4 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/RemoveFieldRewriter.cs @@ -0,0 +1,84 @@ +using HotChocolate.Language; +using HotChocolate.Resolvers; +using HotChocolate.Utilities; + +namespace HotChocolate.Stitching.Merge.Rewriters; + +internal class RemoveFieldRewriter + : ITypeRewriter +{ + public RemoveFieldRewriter(FieldReference field, string? schemaName = null) + { + Field = field ?? throw new ArgumentNullException(nameof(field)); + SchemaName = schemaName?.EnsureGraphQLName(nameof(schemaName)); + } + + public FieldReference Field { get; } + + public string? SchemaName { get; } + + public ITypeDefinitionNode Rewrite( + ISchemaInfo schema, + ITypeDefinitionNode typeDefinition) + { + if (!string.IsNullOrEmpty(SchemaName) && !SchemaName.Equals(schema.Name)) + { + return typeDefinition; + } + + var typeName = typeDefinition.GetOriginalName(schema.Name); + if (!Field.TypeName.Equals(typeName)) + { + return typeDefinition; + } + + switch (typeDefinition) + { + case InputObjectTypeDefinitionNode iotd: + return RemoveFields(iotd); + + case ObjectTypeDefinitionNode otd: + return RemoveFields(otd, f => otd.WithFields(f)); + + case InterfaceTypeDefinitionNode itd: + return RemoveFields(itd, f => itd.WithFields(f)); + + default: + return typeDefinition; + } + } + + private T RemoveFields( + T typeDefinition, + RewriteFieldsDelegate rewrite) + where T : ComplexTypeDefinitionNodeBase, ITypeDefinitionNode + { + var renamedFields = new List(); + + foreach (var field in typeDefinition.Fields) + { + if (!Field.FieldName.Equals(field.Name.Value)) + { + renamedFields.Add(field); + } + } + + return rewrite(renamedFields); + } + + private InputObjectTypeDefinitionNode RemoveFields( + InputObjectTypeDefinitionNode typeDefinition) + { + var renamedFields = new List(); + + foreach (var field in typeDefinition.Fields) + { + if (!Field.FieldName.Equals(field.Name.Value)) + { + renamedFields.Add(field); + } + } + + return typeDefinition.WithFields(renamedFields); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/RemoveRootTypeRewriter.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/RemoveRootTypeRewriter.cs new file mode 100644 index 00000000000..e4807cede7f --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/RemoveRootTypeRewriter.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using HotChocolate.Language; +using HotChocolate.Utilities; + +namespace HotChocolate.Stitching.Merge.Rewriters; + +internal class RemoveRootTypeRewriter : IDocumentRewriter +{ + public RemoveRootTypeRewriter(string? schemaName = null) => + SchemaName = schemaName?.EnsureGraphQLName(nameof(schemaName)); + + public string? SchemaName { get; } + + public DocumentNode Rewrite(ISchemaInfo schema, DocumentNode document) + { + if (!string.IsNullOrEmpty(SchemaName) && !SchemaName.Equals(schema.Name)) + { + return document; + } + + var definitions = new List(document.Definitions); + + RemoveType(definitions, schema.QueryType?.Name.Value); + RemoveType(definitions, schema.MutationType?.Name.Value); + RemoveType(definitions, schema.SubscriptionType?.Name.Value); + + return document.WithDefinitions(definitions); + } + + private static void RemoveType(ICollection definitions, string? typeName) + { + if (typeName is not null) + { + var rootType = definitions + .OfType() + .FirstOrDefault(t => t.Name.Value.Equals(typeName)); + + if (rootType is not null) + { + definitions.Remove(rootType); + } + } + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/RemoveTypeRewriter.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/RemoveTypeRewriter.cs new file mode 100644 index 00000000000..86dcb940777 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/RemoveTypeRewriter.cs @@ -0,0 +1,38 @@ +using HotChocolate.Language; +using HotChocolate.Utilities; + +namespace HotChocolate.Stitching.Merge.Rewriters; + +internal class RemoveTypeRewriter : IDocumentRewriter +{ + public RemoveTypeRewriter(string typeName, string? schemaName = null) + { + TypeName = typeName.EnsureGraphQLName(nameof(typeName)); + SchemaName = schemaName?.EnsureGraphQLName(nameof(schemaName)); + } + + public string TypeName { get; } + + public string? SchemaName { get; } + + public DocumentNode Rewrite(ISchemaInfo schema, DocumentNode document) + { + if (!string.IsNullOrEmpty(SchemaName) && !SchemaName.Equals(schema.Name)) + { + return document; + } + + var typeDefinition = document.Definitions + .OfType() + .FirstOrDefault(t => TypeName.Equals(t.GetOriginalName(schema.Name))); + + if (typeDefinition is null) + { + return document; + } + + var definitions = new List(document.Definitions); + definitions.Remove(typeDefinition); + return document.WithDefinitions(definitions); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/RenameFieldArgumentRewriter.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/RenameFieldArgumentRewriter.cs new file mode 100644 index 00000000000..c068878b7f1 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/RenameFieldArgumentRewriter.cs @@ -0,0 +1,104 @@ +using HotChocolate.Language; +using HotChocolate.Resolvers; +using HotChocolate.Utilities; + +namespace HotChocolate.Stitching.Merge.Rewriters; + +internal class RenameFieldArgumentRewriter + : ITypeRewriter +{ + public RenameFieldArgumentRewriter( + FieldReference field, + string argumentName, + string newArgumentName, + string? schemaName = null) + { + Field = field ?? throw new ArgumentNullException(nameof(field)); + ArgumentName = argumentName.EnsureGraphQLName(nameof(argumentName)); + NewArgumentName = newArgumentName.EnsureGraphQLName(nameof(newArgumentName)); + SchemaName = schemaName?.EnsureGraphQLName(nameof(schemaName)); + } + + public FieldReference Field { get; } + + public string ArgumentName { get; } + + public string NewArgumentName { get; } + + public string? SchemaName { get; } + + public ITypeDefinitionNode Rewrite( + ISchemaInfo schema, + ITypeDefinitionNode typeDefinition) + { + if (!string.IsNullOrEmpty(SchemaName) && !SchemaName.Equals(schema.Name)) + { + return typeDefinition; + } + + var typeName = typeDefinition.GetOriginalName(schema.Name); + if (!Field.TypeName.Equals(typeName)) + { + return typeDefinition; + } + + switch (typeDefinition) + { + case ObjectTypeDefinitionNode otd: + return SelectField(otd, schema.Name, + f => otd.WithFields(f)); + + case InterfaceTypeDefinitionNode itd: + return SelectField(itd, schema.Name, + f => itd.WithFields(f)); + + default: + return typeDefinition; + } + } + + private T SelectField( + T typeDefinition, + string schemaName, + RewriteFieldsDelegate rewrite) + where T : ComplexTypeDefinitionNodeBase, ITypeDefinitionNode + { + var renamedFields = new List(); + + foreach (var field in typeDefinition.Fields) + { + if (Field.FieldName.Equals(field.Name.Value)) + { + renamedFields.Add(RewriteArgument(schemaName, field)); + } + else + { + renamedFields.Add(field); + } + } + + return rewrite(renamedFields); + } + + private FieldDefinitionNode RewriteArgument( + string schemaName, + FieldDefinitionNode field) + { + var renamedArguments = new List(); + + foreach (var argument in field.Arguments) + { + if (ArgumentName.Equals(argument.Name.Value)) + { + renamedArguments.Add(argument.Rename( + NewArgumentName, schemaName)); + } + else + { + renamedArguments.Add(argument); + } + } + + return field.WithArguments(renamedArguments); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/RenameFieldRewriter.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/RenameFieldRewriter.cs new file mode 100644 index 00000000000..fe3b9894cbc --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/RenameFieldRewriter.cs @@ -0,0 +1,93 @@ +using HotChocolate.Language; +using HotChocolate.Resolvers; +using HotChocolate.Utilities; + +namespace HotChocolate.Stitching.Merge.Rewriters; + +internal class RenameFieldRewriter : ITypeRewriter +{ + public RenameFieldRewriter( + FieldReference field, + string newFieldName, + string? schemaName = null) + { + Field = field ?? throw new ArgumentNullException(nameof(field)); + NewFieldName = newFieldName.EnsureGraphQLName(nameof(newFieldName)); + SchemaName = schemaName?.EnsureGraphQLName(nameof(schemaName)); + } + + public FieldReference Field { get; } + + public string NewFieldName { get; } + + public string? SchemaName { get; } + + public ITypeDefinitionNode Rewrite( + ISchemaInfo schema, + ITypeDefinitionNode typeDefinition) + { + if (!string.IsNullOrEmpty(SchemaName) && !SchemaName.Equals(schema.Name)) + { + return typeDefinition; + } + + var typeName = typeDefinition.GetOriginalName(schema.Name); + if (!Field.TypeName.Equals(typeName)) + { + return typeDefinition; + } + + switch (typeDefinition) + { + case InputObjectTypeDefinitionNode iotd: + return RenameFields(iotd, schema.Name); + + case ObjectTypeDefinitionNode otd: + return RenameFields(otd, schema.Name, + f => otd.WithFields(f)); + + case InterfaceTypeDefinitionNode itd: + return RenameFields(itd, schema.Name, + f => itd.WithFields(f)); + + default: + return typeDefinition; + } + } + + private T RenameFields( + T typeDefinition, + string schemaName, + RewriteFieldsDelegate rewrite) + where T : ComplexTypeDefinitionNodeBase, ITypeDefinitionNode + { + var renamedFields = new List(); + + foreach (var field in typeDefinition.Fields) + { + renamedFields.Add( + Field.FieldName.Equals(field.Name.Value) + ? field.Rename(NewFieldName, schemaName) + : field); + } + + return rewrite(renamedFields); + } + + private InputObjectTypeDefinitionNode RenameFields( + InputObjectTypeDefinitionNode typeDefinition, + string schemaName) + { + var renamedFields = new List(); + + foreach (var field in typeDefinition.Fields) + { + renamedFields.Add( + Field.FieldName.Equals(field.Name.Value) + ? field.Rename(NewFieldName, schemaName) + : field); + } + + return typeDefinition.WithFields(renamedFields); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/RenameTypeRewriter.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/RenameTypeRewriter.cs new file mode 100644 index 00000000000..2228d95d642 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/RenameTypeRewriter.cs @@ -0,0 +1,41 @@ +using HotChocolate.Language; +using HotChocolate.Utilities; + +namespace HotChocolate.Stitching.Merge.Rewriters; + +internal class RenameTypeRewriter + : ITypeRewriter +{ + public RenameTypeRewriter( + string originalTypeName, + string newTypeName, + string? schemaName = null) + { + OriginalTypeName = originalTypeName.EnsureGraphQLName(nameof(originalTypeName)); + NewTypeName = newTypeName.EnsureGraphQLName(nameof(newTypeName)); + SchemaName = schemaName?.EnsureGraphQLName(nameof(schemaName)); + } + + public string OriginalTypeName { get; } + + public string NewTypeName { get; } + + public string? SchemaName { get; } + + public ITypeDefinitionNode Rewrite( + ISchemaInfo schema, + ITypeDefinitionNode typeDefinition) + { + if (!string.IsNullOrEmpty(SchemaName) && !SchemaName.Equals(schema.Name)) + { + return typeDefinition; + } + + if (!OriginalTypeName.Equals(typeDefinition.Name.Value)) + { + return typeDefinition; + } + + return typeDefinition.Rename(NewTypeName, schema.Name); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/RewriteFieldsDelegate.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/RewriteFieldsDelegate.cs new file mode 100644 index 00000000000..5e677f18d3d --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/Rewriters/RewriteFieldsDelegate.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; +using HotChocolate.Language; + +namespace HotChocolate.Stitching.Merge.Rewriters; + +internal delegate T RewriteFieldsDelegate( + IReadOnlyList fields) + where T : ComplexTypeDefinitionNodeBase, ITypeDefinitionNode; diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/ScalarTypeInfo.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/ScalarTypeInfo.cs new file mode 100644 index 00000000000..551a4704ccf --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/ScalarTypeInfo.cs @@ -0,0 +1,14 @@ +using HotChocolate.Language; + +namespace HotChocolate.Stitching.Merge; + +internal class ScalarTypeInfo + : TypeInfo +{ + public ScalarTypeInfo( + ScalarTypeDefinitionNode typeDefinition, + ISchemaInfo schema) + : base(typeDefinition, schema) + { + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/SchemaInfo.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/SchemaInfo.cs new file mode 100644 index 00000000000..af70a374ec9 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/SchemaInfo.cs @@ -0,0 +1,186 @@ +using System.Xml.Linq; +using System.Linq; +using System.Collections.Generic; +using HotChocolate.Language; +using System; +using HotChocolate.Stitching.Properties; + +namespace HotChocolate.Stitching.Merge; + +internal class SchemaInfo + : ISchemaInfo +{ + private static readonly Dictionary _names = + new Dictionary + { + { + OperationType.Query, + OperationType.Query.ToString() + }, + { + OperationType.Mutation, + OperationType.Mutation.ToString() + }, + { + OperationType.Subscription, + OperationType.Subscription.ToString() + } + }; + private ObjectTypeDefinitionNode _queryType; + private ObjectTypeDefinitionNode _mutationType; + private ObjectTypeDefinitionNode _subscriptionType; + + public SchemaInfo(string name, DocumentNode document) + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException( + StitchingResources.SchemaName_EmptyOrNull, + nameof(name)); + } + + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + + Name = name; + Document = document; + + var types = + document.Definitions + .OfType() + .ToDictionary(t => t.Name.Value); + Types = types; + + Directives = document.Definitions + .OfType() + .ToDictionary(t => t.Name.Value); + + var schemaDefinition = document.Definitions + .OfType().FirstOrDefault(); + + RootTypes = GetRootTypeMapppings( + GetRootTypeNameMapppings(schemaDefinition), + types); + } + + protected Dictionary RootTypes + { get; } + + public string Name { get; } + + public DocumentNode Document { get; } + + public IReadOnlyDictionary Types + { get; } + + public IReadOnlyDictionary Directives + { get; } + + public ObjectTypeDefinitionNode QueryType + { + get + { + if (_queryType == null + && RootTypes.TryGetValue(OperationType.Query, + out var type)) + { + _queryType = type; + } + return _queryType; + } + } + + public ObjectTypeDefinitionNode MutationType + { + get + { + if (_mutationType == null + && RootTypes.TryGetValue(OperationType.Mutation, + out var type)) + { + _mutationType = type; + } + return _mutationType; + } + } + + public ObjectTypeDefinitionNode SubscriptionType + { + get + { + if (_subscriptionType == null + && RootTypes.TryGetValue(OperationType.Subscription, + out var type)) + { + _subscriptionType = type; + } + return _subscriptionType; + } + } + + public bool IsRootType(ITypeDefinitionNode typeDefinition) + { + if (typeDefinition == null) + { + throw new ArgumentNullException(nameof(typeDefinition)); + } + + if (typeDefinition is ObjectTypeDefinitionNode ot) + { + return RootTypes.ContainsValue(ot); + } + + return false; + } + + public bool TryGetOperationType( + ObjectTypeDefinitionNode rootType, + out OperationType operationType) + { + if (RootTypes.ContainsValue(rootType)) + { + operationType = RootTypes.First(t => t.Value == rootType).Key; + return true; + } + + operationType = default; + return false; + } + + private static Dictionary + GetRootTypeMapppings( + IDictionary nameMappings, + IDictionary types) + { + var map = new Dictionary(); + + foreach (var nameMapping in + nameMappings) + { + if (types.TryGetValue(nameMapping.Value, out + var definition) + && definition is ObjectTypeDefinitionNode objectType) + { + types.Remove(nameMapping.Value); + map.Add(nameMapping.Key, objectType); + } + } + + return map; + } + + private static IDictionary + GetRootTypeNameMapppings(SchemaDefinitionNodeBase schemaDefinition) + { + if (schemaDefinition == null) + { + return _names; + } + + return schemaDefinition.OperationTypes.ToDictionary( + t => t.Operation, + t => t.Type.Name.Value); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/SchemaMergeContext.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/SchemaMergeContext.cs new file mode 100644 index 00000000000..3b4452db82e --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/SchemaMergeContext.cs @@ -0,0 +1,68 @@ +using System.Diagnostics.Contracts; +using System.Collections.Specialized; +using System; +using System.Collections.Generic; +using System.Linq; +using HotChocolate.Language; +using HotChocolate.Utilities; + +namespace HotChocolate.Stitching.Merge; + +public class SchemaMergeContext + : ISchemaMergeContext +{ + private readonly Dictionary _types = new(); + private readonly Dictionary _dirs = new(); + + public void AddType(ITypeDefinitionNode type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (_types.ContainsKey(type.Name.Value)) + { + throw new ArgumentException( + "A type with that name was already added."); + } + + _types.Add(type.Name.Value, type); + } + + public void AddDirective(DirectiveDefinitionNode directive) + { + if (directive == null) + { + throw new ArgumentNullException(nameof(directive)); + } + + if (_dirs.ContainsKey(directive.Name.Value)) + { + throw new ArgumentException( + "A type with that name was already added."); + } + + _dirs.Add(directive.Name.Value, directive); + } + + public bool ContainsType(string typeName) + { + typeName.EnsureGraphQLName(nameof(typeName)); + return _types.ContainsKey(typeName); + } + + public bool ContainsDirective(string directiveName) + { + directiveName.EnsureGraphQLName(nameof(directiveName)); + return _dirs.ContainsKey(directiveName); + } + + public DocumentNode CreateSchema() + { + var definitions = new List(); + definitions.AddRange(_types.Values); + definitions.AddRange(_dirs.Values); + return new DocumentNode(definitions); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/SchemaMergeException.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/SchemaMergeException.cs new file mode 100644 index 00000000000..e4d9f7426a5 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/SchemaMergeException.cs @@ -0,0 +1,32 @@ +using System; +using System.Runtime.Serialization; +using HotChocolate.Language; + +namespace HotChocolate.Stitching.Merge; + +[Serializable] +public class SchemaMergeException + : Exception +{ + public SchemaMergeException( + ITypeDefinitionNode typeDefinition, + ITypeExtensionNode typeExtension, + string message) + : base(message) + { + TypeDefinition = typeDefinition + ?? throw new ArgumentNullException(nameof(typeDefinition)); + TypeExtension = typeExtension + ?? throw new ArgumentNullException(nameof(typeExtension)); + } + + + protected SchemaMergeException( + SerializationInfo info, + StreamingContext context) + : base(info, context) { } + + public ITypeDefinitionNode TypeDefinition { get; } + + public ITypeExtensionNode TypeExtension { get; } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/SchemaMerger.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/SchemaMerger.cs new file mode 100644 index 00000000000..dc9f775b263 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/SchemaMerger.cs @@ -0,0 +1,405 @@ +using HotChocolate.Language; +using HotChocolate.Execution; +using HotChocolate.Stitching.Merge.Handlers; +using HotChocolate.Stitching.Merge.Rewriters; +using HotChocolate.Utilities; + +namespace HotChocolate.Stitching.Merge; + +public class SchemaMerger + : ISchemaMerger +{ + private static readonly List _defaultMergeRules = + new() + { + SchemaMergerExtensions.CreateTypeMergeRule(), + SchemaMergerExtensions.CreateTypeMergeRule(), + SchemaMergerExtensions.CreateTypeMergeRule(), + SchemaMergerExtensions.CreateTypeMergeRule(), + SchemaMergerExtensions.CreateTypeMergeRule(), + SchemaMergerExtensions.CreateTypeMergeRule(), + SchemaMergerExtensions.CreateTypeMergeRule(), + }; + private readonly List _mergeRules = new(); + private readonly List _directiveMergeRules = new(); + private readonly List _typeRewriters = new(); + private readonly List _docRewriters = new(); + private readonly OrderedDictionary _schemas = new(); + + [Obsolete("Use AddTypeMergeRule")] + public ISchemaMerger AddMergeRule(MergeTypeRuleFactory factory) => + AddTypeMergeRule(factory); + + public ISchemaMerger AddTypeMergeRule(MergeTypeRuleFactory factory) + { + if (factory == null) + { + throw new ArgumentNullException(nameof(factory)); + } + + _mergeRules.Add(factory); + return this; + } + + public ISchemaMerger AddDirectiveMergeRule( + MergeDirectiveRuleFactory factory) + { + if (factory == null) + { + throw new ArgumentNullException(nameof(factory)); + } + + _directiveMergeRules.Add(factory); + return this; + } + + public ISchemaMerger AddSchema(string name, DocumentNode schema) + { + if (schema == null) + { + throw new ArgumentNullException(nameof(schema)); + } + + name.EnsureGraphQLName(); + + _schemas.Add(name, schema); + + return this; + } + + public ISchemaMerger AddTypeRewriter(ITypeRewriter rewriter) + { + if (rewriter == null) + { + throw new ArgumentNullException(nameof(rewriter)); + } + + _typeRewriters.Add(rewriter); + return this; + } + + public ISchemaMerger AddDocumentRewriter(IDocumentRewriter rewriter) + { + if (rewriter == null) + { + throw new ArgumentNullException(nameof(rewriter)); + } + + _docRewriters.Add(rewriter); + return this; + } + + public DocumentNode Merge() + { + var mergeTypes = CompileMergeTypeDelegate(); + var mergeDirectives = CompileMergeDirectiveDelegate(); + var schemas = CreateSchemaInfos(); + + var context = new SchemaMergeContext(); + + // merge root types + MergeRootType(context, OperationType.Query, schemas, mergeTypes); + MergeRootType(context, OperationType.Mutation, schemas, mergeTypes); + MergeRootType(context, OperationType.Subscription, schemas, mergeTypes); + + // merge all other types + MergeTypes(context, CreateTypesNameSet(schemas), schemas, mergeTypes); + MergeDirectives(context, CreateDirectivesNameSet(schemas), schemas, mergeDirectives); + + return RewriteTypeReferences(schemas, context.CreateSchema()); + } + + private IReadOnlyList CreateSchemaInfos() + { + var original = _schemas + .Select(t => new SchemaInfo(t.Key, PrepareSchemaDocument(t.Value, t.Key))) + .ToList(); + + if (_docRewriters.Count == 0 && _typeRewriters.Count == 0) + { + return original; + } + + var rewritten = new List(); + var referenceRewriter = new TypeReferenceRewriter(); + + foreach (var schemaInfo in original) + { + var current = schemaInfo.Document; + current = RewriteDocument(schemaInfo, current); + current = RewriteTypes(schemaInfo, current); + + if (current == schemaInfo.Document) + { + rewritten.Add(schemaInfo); + } + else + { + current = referenceRewriter.RewriteSchema( + current, schemaInfo.Name); + + rewritten.Add(new SchemaInfo( + schemaInfo.Name, + current)); + } + } + + return rewritten; + } + + private static DocumentNode PrepareSchemaDocument( + DocumentNode document, + string schemaName) + { + var definitions = new List(); + foreach (var definition in document.Definitions) + { + if (definition is ITypeDefinitionNode typeDefinition) + { + if (!IsIntrospectionType(typeDefinition)) + { + definitions.Add(typeDefinition.Rename( + typeDefinition.Name.Value, schemaName)); + } + } + else + { + definitions.Add(definition); + } + } + return document.WithDefinitions(definitions); + } + + private static bool IsIntrospectionType(ITypeDefinitionNode typeDefinition) + { + // we should check this against the actual known list of intro types. + return typeDefinition.Name.Value.StartsWith("__", StringComparison.Ordinal); + } + + private DocumentNode RewriteDocument( + ISchemaInfo schema, + DocumentNode document) + { + var current = document; + + foreach (var rewriter in _docRewriters) + { + current = rewriter.Rewrite(schema, current); + } + + return current; + } + + private DocumentNode RewriteTypes( + ISchemaInfo schema, + DocumentNode document) + { + if (_typeRewriters.Count == 0) + { + return document; + } + + var definitions = new List(); + + foreach (var definition in document.Definitions) + { + if (definition is ITypeDefinitionNode typeDefinition) + { + foreach (var rewriter in _typeRewriters) + { + typeDefinition = rewriter.Rewrite(schema, typeDefinition); + } + definitions.Add(typeDefinition); + } + else + { + definitions.Add(definition); + } + } + + return document.WithDefinitions(definitions); + } + + private static DocumentNode RewriteTypeReferences( + IReadOnlyList schemas, + DocumentNode document) + { + var current = document; + var referenceRewriter = new TypeReferenceRewriter(); + + foreach (var schema in schemas) + { + current = referenceRewriter.RewriteSchema(current, schema.Name); + } + + return current; + } + + private static void MergeRootType( + ISchemaMergeContext context, + OperationType operation, + IEnumerable schemas, + MergeTypeRuleDelegate merge) + { + var types = new List(); + + foreach (var schema in schemas) + { + var rootType = schema.GetRootType(operation); + if (rootType is not null) + { + types.Add(new ObjectTypeInfo(rootType, schema)); + } + } + + if (types.Count > 0) + { + merge(context, types); + } + } + + private void MergeTypes( + ISchemaMergeContext context, + ISet typeNames, + IReadOnlyCollection schemas, + MergeTypeRuleDelegate merge) + { + var types = new List(); + + foreach (var typeName in typeNames) + { + SetTypes(typeName, schemas, types); + merge(context, types); + } + } + + private static ISet CreateTypesNameSet( + IReadOnlyCollection schemas) + { + var names = new HashSet(); + + foreach (var schema in schemas) + { + foreach (var name in schema.Types.Keys) + { + names.Add(name); + } + } + + return names; + } + + private static ISet CreateDirectivesNameSet( + IReadOnlyCollection schemas) + { + var names = new HashSet(); + + foreach (var schema in schemas) + { + foreach (var name in schema.Directives.Keys) + { + names.Add(name); + } + } + + return names; + } + + private void MergeDirectives( + ISchemaMergeContext context, + ISet typeNames, + IReadOnlyCollection schemas, + MergeDirectiveRuleDelegate merge) + { + var directives = new List(); + + foreach (var typeName in typeNames) + { + SetDirectives(typeName, schemas, directives); + merge(context, directives); + } + } + + private static void SetTypes( + string name, + IReadOnlyCollection schemas, + ICollection types) + { + types.Clear(); + + foreach (var schema in schemas) + { + if (schema.Types.TryGetValue(name, + out var typeDefinition)) + { + types.Add(TypeInfo.Create(typeDefinition, schema)); + } + } + } + + private static void SetDirectives( + string name, + IReadOnlyCollection schemas, + ICollection directives) + { + directives.Clear(); + + foreach (var schema in schemas) + { + if (schema.Directives.TryGetValue(name, + out var directiveDefinition)) + { + directives.Add(new DirectiveTypeInfo( + directiveDefinition, schema)); + } + } + } + + private MergeTypeRuleDelegate CompileMergeTypeDelegate() + { + MergeTypeRuleDelegate current = (c, t) => + { + if (t.Count > 0) + { + throw new NotSupportedException( + "The type definitions could not be handled."); + } + }; + + var handlers = new List(); + handlers.AddRange(_mergeRules); + handlers.AddRange(_defaultMergeRules); + + for (var i = handlers.Count - 1; i >= 0; i--) + { + current = handlers[i].Invoke(current); + } + + return current; + } + + private MergeDirectiveRuleDelegate CompileMergeDirectiveDelegate() + { + MergeDirectiveRuleDelegate current = (c, t) => + { + if (t.Count > 0) + { + throw new NotSupportedException( + "The type definitions could not be handled."); + } + }; + + var handlers = new List(); + handlers.AddRange(_directiveMergeRules); + handlers.Add(c => new DirectiveTypeMergeHandler(c).Merge); + + for (var i = handlers.Count - 1; i >= 0; i--) + { + current = handlers[i].Invoke(current); + } + + return current; + } + + public static SchemaMerger New() => new(); +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/TypeInfo.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/TypeInfo.cs new file mode 100644 index 00000000000..c9eea6e51de --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/TypeInfo.cs @@ -0,0 +1,48 @@ +using System; +using HotChocolate.Language; + +namespace HotChocolate.Stitching.Merge; + +internal class TypeInfo + : ITypeInfo +{ + protected TypeInfo( + ITypeDefinitionNode typeDefinition, + ISchemaInfo schema) + { + Definition = typeDefinition + ?? throw new ArgumentNullException(nameof(typeDefinition)); + Schema = schema + ?? throw new ArgumentNullException(nameof(schema)); + IsRootType = schema.IsRootType(typeDefinition); + } + + public ITypeDefinitionNode Definition { get; } + + public ISchemaInfo Schema { get; } + + public bool IsRootType { get; } + + public static ITypeInfo Create( + ITypeDefinitionNode typeDefinition, + ISchemaInfo schema) + { + switch (typeDefinition) + { + case ObjectTypeDefinitionNode otd: + return new ObjectTypeInfo(otd, schema); + case InterfaceTypeDefinitionNode itd: + return new InterfaceTypeInfo(itd, schema); + case UnionTypeDefinitionNode utd: + return new UnionTypeInfo(utd, schema); + case InputObjectTypeDefinitionNode iotd: + return new InputObjectTypeInfo(iotd, schema); + case EnumTypeDefinitionNode etd: + return new EnumTypeInfo(etd, schema); + case ScalarTypeDefinitionNode std: + return new ScalarTypeInfo(std, schema); + default: + throw new NotSupportedException(); + } + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/TypeInfo~1.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/TypeInfo~1.cs new file mode 100644 index 00000000000..75c372b1979 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/TypeInfo~1.cs @@ -0,0 +1,18 @@ +using System; +using HotChocolate.Language; + +namespace HotChocolate.Stitching.Merge; + +internal class TypeInfo + : TypeInfo + , ITypeInfo + where T : ITypeDefinitionNode +{ + protected TypeInfo(T typeDefinition, ISchemaInfo schema) + : base(typeDefinition, schema) + { + Definition = typeDefinition; + } + + public new T Definition { get; } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/TypeReferenceRewriter.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/TypeReferenceRewriter.cs new file mode 100644 index 00000000000..0b56268ea2c --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/TypeReferenceRewriter.cs @@ -0,0 +1,337 @@ +using HotChocolate.Language; +using HotChocolate.Language.Visitors; +using HotChocolate.Utilities; + +namespace HotChocolate.Stitching.Merge; + +internal sealed class TypeReferenceRewriter : SyntaxRewriter +{ + public DocumentNode? RewriteSchema( + DocumentNode document, + string schemaName) + { + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + + schemaName.EnsureGraphQLName(nameof(schemaName)); + + IReadOnlyDictionary renamedTypes = + GetRenamedTypes(document, schemaName); + + var fieldsToRename = + GetFieldsToRename(document, schemaName); + + var context = new Context( + schemaName, renamedTypes, fieldsToRename); + + return RewriteDocument(document, context); + } + + private static Dictionary GetRenamedTypes( + DocumentNode document, + string schemaName) + { + var names = new Dictionary(); + + foreach (var type in document.Definitions + .OfType()) + { + var originalName = type.GetOriginalName(schemaName); + if (!originalName.Equals(type.Name.Value)) + { + names[originalName] = type.Name.Value; + } + } + + return names; + } + + private static Dictionary GetFieldsToRename( + DocumentNode document, + string schemaName) + { + var fieldsToRename = + new Dictionary(); + + var types = document.Definitions + .OfType() + .Where(t => t.IsFromSchema(schemaName)) + .ToDictionary(t => t.GetOriginalName(schemaName)); + + var queue = new Queue(types.Keys); + + var context = new RenameFieldsContext( + types, fieldsToRename, schemaName); + + while (queue.Count > 0) + { + var name = queue.Dequeue(); + + switch (types[name]) + { + case ObjectTypeDefinitionNode objectType: + RenameObjectField(objectType, context); + break; + case InterfaceTypeDefinitionNode interfaceType: + RenameInterfaceField(interfaceType, context); + break; + default: + throw new NotSupportedException(); + } + } + + return fieldsToRename; + } + + private static void RenameObjectField( + ObjectTypeDefinitionNode objectType, + RenameFieldsContext renameContext) + { + var interfaceTypes = + GetInterfaceTypes(objectType, renameContext.Types); + + foreach (var fieldDefinition in + objectType.Fields) + { + var originalName = + fieldDefinition.GetOriginalName(renameContext.SchemaName); + if (!originalName.Equals(fieldDefinition.Name.Value)) + { + foreach (var interfaceType in + GetInterfacesThatProvideFieldDefinition( + originalName, interfaceTypes)) + { + RenameInterfaceField(interfaceType, + renameContext, originalName, + fieldDefinition.Name.Value); + } + } + } + } + + private static IReadOnlyCollection GetInterfaceTypes( + ObjectTypeDefinitionNode objectType, + IDictionary types) + { + var interfaceTypes = new List(); + + foreach (var namedType in objectType.Interfaces) + { + if (types.TryGetValue(namedType.Name.Value, + out var ct) + && ct is InterfaceTypeDefinitionNode it) + { + interfaceTypes.Add(it); + } + } + + return interfaceTypes; + } + + private static IReadOnlyCollection + GetInterfacesThatProvideFieldDefinition( + string originalFieldName, + IEnumerable interfaceTypes) + { + var items = new List(); + + foreach (var interfaceType in + interfaceTypes) + { + if (interfaceType.Fields.Any(t => + originalFieldName.Equals(t.Name.Value))) + { + items.Add(interfaceType); + } + } + + return items; + } + + private static void RenameInterfaceField( + InterfaceTypeDefinitionNode interfaceType, + RenameFieldsContext renameContext) + { + foreach (var fieldDefinition in + interfaceType.Fields) + { + var originalName = fieldDefinition.GetOriginalName(renameContext.SchemaName); + if (!originalName.Equals(fieldDefinition.Name.Value)) + { + RenameInterfaceField( + interfaceType, renameContext, + originalName, fieldDefinition.Name.Value); + } + } + } + + private static void RenameInterfaceField( + InterfaceTypeDefinitionNode interfaceType, + RenameFieldsContext renameContext, + string originalFieldName, + string newFieldName) + { + var objectTypes = + renameContext.Types.Values + .OfType() + .Where(t => t.Interfaces.Select(i => i.Name.Value) + .Any(n => string.Equals(n, + interfaceType.Name.Value, + StringComparison.Ordinal))) + .ToList(); + + AddNewFieldName(interfaceType, renameContext, + originalFieldName, newFieldName); + + foreach (var objectType in objectTypes) + { + AddNewFieldName(objectType, renameContext, + originalFieldName, newFieldName); + } + } + + private static void AddNewFieldName( + ComplexTypeDefinitionNodeBase type, + RenameFieldsContext renameContext, + string originalFieldName, + string newFieldName) + { + var fieldDefinition = type.Fields.FirstOrDefault( + t => originalFieldName.Equals(t.GetOriginalName( + renameContext.SchemaName))); + if (fieldDefinition != null) + { + renameContext.RenamedFields[fieldDefinition] = newFieldName; + } + } + + protected override ObjectTypeDefinitionNode? RewriteObjectTypeDefinition( + ObjectTypeDefinitionNode node, + Context context) + { + if (IsRelevant(node, context)) + { + return base.RewriteObjectTypeDefinition(node, context); + } + + return node; + } + + + protected override InterfaceTypeDefinitionNode? + RewriteInterfaceTypeDefinition( + InterfaceTypeDefinitionNode node, + Context context) + { + if (IsRelevant(node, context)) + { + return base.RewriteInterfaceTypeDefinition(node, context); + } + + return node; + } + + protected override UnionTypeDefinitionNode RewriteUnionTypeDefinition( + UnionTypeDefinitionNode node, + Context context) + { + if (IsRelevant(node, context)) + { + return base.RewriteUnionTypeDefinition(node, context); + } + + return node; + } + + protected override InputObjectTypeDefinitionNode + RewriteInputObjectTypeDefinition( + InputObjectTypeDefinitionNode node, + Context context) + { + if (IsRelevant(node, context)) + { + return base.RewriteInputObjectTypeDefinition(node, context); + } + + return node; + } + + protected override NamedTypeNode RewriteNamedType( + NamedTypeNode node, + Context context) + { + if (context.Names.TryGetValue(node.Name.Value, + out var newName)) + { + return node.WithName(node.Name.WithValue(newName)); + } + return node; + } + + protected override FieldDefinitionNode? RewriteFieldDefinition( + FieldDefinitionNode node, + Context context) + { + var current = node; + + if (context.FieldNames.TryGetValue(current, out var newName)) + { + current = current.Rename(newName, context.SourceSchema); + } + + return base.RewriteFieldDefinition(current, context); + } + + private static bool IsRelevant( + NamedSyntaxNode typeDefinition, + Context context) + { + return string.IsNullOrEmpty(context.SourceSchema) + || typeDefinition.IsFromSchema(context.SourceSchema); + } + + public sealed class Context : ISyntaxVisitorContext + { + public Context( + string? sourceSchema, + IReadOnlyDictionary names, + IReadOnlyDictionary fieldNames) + { + SourceSchema = sourceSchema + ?? throw new ArgumentNullException(nameof(sourceSchema)); + Names = names + ?? throw new ArgumentNullException(nameof(names)); + FieldNames = fieldNames + ?? throw new ArgumentNullException(nameof(fieldNames)); + } + + public string? SourceSchema { get; } + + public IReadOnlyDictionary Names { get; } + + public IReadOnlyDictionary + FieldNames + { get; } + } + + private class RenameFieldsContext + { + public RenameFieldsContext( + IDictionary types, + IDictionary renamedFields, + string schemaName) + { + Types = types; + RenamedFields = renamedFields; + SchemaName = schemaName; + } + + public IDictionary Types { get; } + + public IDictionary RenamedFields { get; } + + public string SchemaName { get; } + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/UnionTypeInfo.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/UnionTypeInfo.cs new file mode 100644 index 00000000000..014d510bc3a --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/UnionTypeInfo.cs @@ -0,0 +1,14 @@ +using HotChocolate.Language; + +namespace HotChocolate.Stitching.Merge; + +internal class UnionTypeInfo + : TypeInfo +{ + public UnionTypeInfo( + UnionTypeDefinitionNode typeDefinition, + ISchemaInfo schema) + : base(typeDefinition, schema) + { + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Pipeline/HttpRequestClient.cs b/src/HotChocolate/Stitching/src/Stitching/Pipeline/HttpRequestClient.cs new file mode 100644 index 00000000000..3122804a68a --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Pipeline/HttpRequestClient.cs @@ -0,0 +1,332 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using HotChocolate.Execution; +using HotChocolate.Language; +using HotChocolate.Stitching.Properties; +using HotChocolate.Utilities; + +#nullable enable + +namespace HotChocolate.Stitching.Pipeline; + +internal class HttpRequestClient +{ + private static readonly (string Key, string Value) _contentType = + ("Content-Type", "application/json; charset=utf-8"); + + private static readonly JsonWriterOptions _jsonWriterOptions = + new JsonWriterOptions + { + SkipValidation = true, + Indented = false + }; + + private readonly IHttpClientFactory _clientFactory; + private readonly IErrorHandler _errorHandler; + private readonly IHttpStitchingRequestInterceptor _requestInterceptor; + + public HttpRequestClient( + IHttpClientFactory clientFactory, + IErrorHandler errorHandler, + IHttpStitchingRequestInterceptor requestInterceptor) + { + _clientFactory = clientFactory; + _errorHandler = errorHandler; + _requestInterceptor = requestInterceptor; + } + + public async Task FetchAsync( + IQueryRequest request, + string targetSchema, + CancellationToken cancellationToken = default) + { + using var writer = new ArrayWriter(); + + using var requestMessage = + await CreateRequestAsync(writer, request, targetSchema, cancellationToken) + .ConfigureAwait(false); + + return await FetchAsync( + request, + requestMessage, + targetSchema, + cancellationToken) + .ConfigureAwait(false); + } + + private async Task FetchAsync( + IQueryRequest request, + HttpRequestMessage requestMessage, + string targetSchema, + CancellationToken cancellationToken) + { + try + { + using var httpClient = _clientFactory.CreateClient(targetSchema); + + using var responseMessage = await httpClient + .SendAsync(requestMessage, cancellationToken) + .ConfigureAwait(false); + + var result = + responseMessage.IsSuccessStatusCode + ? await ParseResponseMessageAsync(responseMessage, cancellationToken) + .ConfigureAwait(false) + : await ParseErrorResponseMessageAsync(responseMessage, cancellationToken) + .ConfigureAwait(false); + + return await _requestInterceptor.OnReceivedResultAsync( + targetSchema, + request, + result, + responseMessage, + cancellationToken) + .ConfigureAwait(false); + } + catch (Exception ex) + { + var error = _errorHandler.CreateUnexpectedError(ex) + .SetCode(ErrorCodes.Stitching.UnknownRequestException) + .Build(); + + return QueryResultBuilder.CreateError(error); + } + } + + internal static async ValueTask CreateRequestMessageAsync( + ArrayWriter writer, + IQueryRequest request, + CancellationToken cancellationToken) + { + await using var jsonWriter = new Utf8JsonWriter(writer, _jsonWriterOptions); + + WriteJsonRequest(writer, jsonWriter, request); + await jsonWriter.FlushAsync(cancellationToken).ConfigureAwait(false); + + var requestMessage = new HttpRequestMessage + { + Method = HttpMethod.Post, + Content = new ByteArrayContent(writer.GetInternalBuffer(), 0, writer.Length) + { + Headers = { { _contentType.Key, _contentType.Value } } + } + }; + + return requestMessage; + } + + private static async ValueTask ParseErrorResponseMessageAsync( + HttpResponseMessage responseMessage, + CancellationToken cancellationToken) + { +#if NET5_0 || NET6_0 + await using var stream = await responseMessage.Content + .ReadAsStreamAsync(cancellationToken) + .ConfigureAwait(false); +#else + using var stream = await responseMessage.Content + .ReadAsStreamAsync() + .ConfigureAwait(false); +#endif + + try + { + var response = + await BufferHelper.ReadAsync( + stream, + ParseResponse, + cancellationToken) + .ConfigureAwait(false); + + return HttpResponseDeserializer.Deserialize(response); + } + catch + { + string? responseBody = null; + + if (stream.Length > 0) + { + var buffer = new byte[stream.Length]; + stream.Seek(0, SeekOrigin.Begin); + await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken) + .ConfigureAwait(false); + responseBody = Encoding.UTF8.GetString(buffer, 0, buffer.Length); + } + + return QueryResultBuilder.CreateError( + ErrorHelper.HttpRequestClient_HttpError( + responseMessage.StatusCode, + responseBody)); + } + } + + internal static async ValueTask ParseResponseMessageAsync( + HttpResponseMessage responseMessage, + CancellationToken cancellationToken) + { +#if NET5_0 || NET6_0 + await using var stream = await responseMessage.Content + .ReadAsStreamAsync(cancellationToken) + .ConfigureAwait(false); +#else + using var stream = await responseMessage.Content + .ReadAsStreamAsync() + .ConfigureAwait(false); +#endif + + var response = + await BufferHelper.ReadAsync( + stream, + ParseResponse, + cancellationToken) + .ConfigureAwait(false); + + return HttpResponseDeserializer.Deserialize(response); + } + + private async ValueTask CreateRequestAsync( + ArrayWriter writer, + IQueryRequest request, + string targetSchema, + CancellationToken cancellationToken = default) + { + var requestMessage = + await CreateRequestMessageAsync(writer, request, cancellationToken) + .ConfigureAwait(false); + + await _requestInterceptor + .OnCreateRequestAsync(targetSchema, request, requestMessage, cancellationToken) + .ConfigureAwait(false); + + return requestMessage; + } + + private static IReadOnlyDictionary ParseResponse( + byte[] buffer, int bytesBuffered) => + Utf8GraphQLRequestParser.ParseResponse(buffer.AsSpan(0, bytesBuffered))!; + + private static void WriteJsonRequest( + ArrayWriter writer, + Utf8JsonWriter jsonWriter, + IQueryRequest request) + { + jsonWriter.WriteStartObject(); + jsonWriter.WriteString("query", request.Query!.AsSpan()); + + if (request.OperationName is not null) + { + jsonWriter.WriteString("operationName", request.OperationName); + } + + WriteJsonRequestVariables(writer, jsonWriter, request.VariableValues); + jsonWriter.WriteEndObject(); + } + + private static void WriteJsonRequestVariables( + ArrayWriter writer, + Utf8JsonWriter jsonWriter, + IReadOnlyDictionary? variables) + { + if (variables is not null && variables.Count > 0) + { + jsonWriter.WritePropertyName("variables"); + + jsonWriter.WriteStartObject(); + + foreach (var variable in variables) + { + jsonWriter.WritePropertyName(variable.Key); + WriteValue(writer, jsonWriter, variable.Value); + } + + jsonWriter.WriteEndObject(); + } + } + + private static void WriteValue( + ArrayWriter writer, + Utf8JsonWriter jsonWriter, + object? value) + { + if (value is null || value is NullValueNode) + { + jsonWriter.WriteNullValue(); + } + else + { + switch (value) + { + case ObjectValueNode obj: + jsonWriter.WriteStartObject(); + + foreach (var field in obj.Fields) + { + jsonWriter.WritePropertyName(field.Name.Value); + WriteValue(writer, jsonWriter, field.Value); + } + + jsonWriter.WriteEndObject(); + break; + + case ListValueNode list: + jsonWriter.WriteStartArray(); + + foreach (var item in list.Items) + { + WriteValue(writer, jsonWriter, item); + } + + jsonWriter.WriteEndArray(); + break; + + case StringValueNode s: + jsonWriter.WriteStringValue(s.Value); + break; + + case EnumValueNode e: + jsonWriter.WriteStringValue(e.Value); + break; + + case IntValueNode i: + WriterNumber(i.AsSpan(), jsonWriter, writer); + break; + + case FloatValueNode f: + WriterNumber(f.AsSpan(), jsonWriter, writer); + break; + + case BooleanValueNode b: + jsonWriter.WriteBooleanValue(b.Value); + break; + + default: + throw new NotSupportedException( + StitchingResources.HttpRequestClient_UnknownVariableValueKind); + } + } + } + + private static void WriterNumber( + ReadOnlySpan number, + Utf8JsonWriter jsonWriter, + ArrayWriter arrayWriter) + { + jsonWriter.WriteNumberValue(0); + jsonWriter.Flush(); + arrayWriter.GetInternalBuffer()[arrayWriter.Length - 1] = number[0]; + + if (number.Length > 1) + { + number = number.Slice(1); + var span = arrayWriter.GetSpan(number.Length); + number.CopyTo(span); + arrayWriter.Advance(number.Length); + } + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Pipeline/HttpRequestMiddleware.cs b/src/HotChocolate/Stitching/src/Stitching/Pipeline/HttpRequestMiddleware.cs new file mode 100644 index 00000000000..20f40f39ff6 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Pipeline/HttpRequestMiddleware.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading.Tasks; +using HotChocolate.Execution; + +namespace HotChocolate.Stitching.Pipeline; + +internal class HttpRequestMiddleware +{ + private readonly RequestDelegate _next; + private readonly HttpRequestClient _httpRequestClient; + + public HttpRequestMiddleware( + RequestDelegate next, + [SchemaService]HttpRequestClient httpRequestClient) + { + _next = next ?? throw new ArgumentNullException(nameof(next)); + _httpRequestClient = httpRequestClient; + } + + public async ValueTask InvokeAsync(IRequestContext context) + { + context.Result = + await _httpRequestClient.FetchAsync( + context.Request, + context.Schema.Name, + context.RequestAborted) + .ConfigureAwait(false); + + await _next.Invoke(context).ConfigureAwait(false); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Pipeline/HttpResponseDeserializer.cs b/src/HotChocolate/Stitching/src/Stitching/Pipeline/HttpResponseDeserializer.cs new file mode 100644 index 00000000000..8d9993293d0 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Pipeline/HttpResponseDeserializer.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using HotChocolate.Execution; +using HotChocolate.Language; +using HotChocolate.Utilities; + +namespace HotChocolate.Stitching.Pipeline; + +internal static class HttpResponseDeserializer +{ + private const string _data = "data"; + private const string _extensions = "extensions"; + private const string _errors = "errors"; + + private static readonly ObjectValueToDictionaryConverter _converter = + new ObjectValueToDictionaryConverter(); + + public static IQueryResult Deserialize( + IReadOnlyDictionary serializedResult) + { + var result = new QueryResultBuilder(); + + if (serializedResult.TryGetValue(_data, out var data)) + { + result.SetData(data as IReadOnlyDictionary); + } + + if (serializedResult.TryGetValue(_extensions, out var extensionData)) + { + result.SetExtensions(extensionData as IReadOnlyDictionary); + } + + DeserializeErrors(result, serializedResult); + + return result.Create(); + } + + private static void DeserializeErrors( + IQueryResultBuilder result, + IReadOnlyDictionary serializedResult) + { + if (serializedResult.TryGetValue(_errors, out var o) + && o is IReadOnlyList errors) + { + foreach (var obj in errors) + { + var error = ErrorBuilder + .FromDictionary(DeserializeErrorObject(obj)) + .Build(); + + result.AddError(error); + } + } + } + + private static object? DeserializeErrorValue(object? value) + { + switch (value) + { + case IReadOnlyDictionary obj: + return DeserializeErrorObject(obj); + + case IReadOnlyList list: + return DeserializeErrorList(list); + + case StringValueNode sv: + return sv.Value; + + case EnumValueNode ev: + return ev.Value; + + case IntValueNode iv: + return iv.ToInt32(); + + case FloatValueNode fv: + return fv.ToDouble(); + + case BooleanValueNode bv: + return bv.Value; + + case NullValueNode: + case null: + return null; + + default: + throw new NotSupportedException(); + } + } + + private static Dictionary DeserializeErrorObject( + object obj) + { + if (obj is IReadOnlyDictionary dict) + { + return DeserializeErrorObject(dict); + } + + throw new NotSupportedException("An error object must be a dictionary."); + } + + private static Dictionary DeserializeErrorObject( + IReadOnlyDictionary obj) + { + var deserialized = new Dictionary(); + + foreach (var item in obj) + { + deserialized.Add(item.Key, DeserializeErrorValue(item.Value)); + } + + return deserialized; + } + + private static List DeserializeErrorList( + IReadOnlyList list) + { + var deserialized = new List(); + + foreach (var item in list) + { + deserialized.Add(DeserializeErrorValue(item)); + } + + return deserialized; + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Pipeline/HttpStitchingRequestInterceptor.cs b/src/HotChocolate/Stitching/src/Stitching/Pipeline/HttpStitchingRequestInterceptor.cs new file mode 100644 index 00000000000..a0d83e05cfa --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Pipeline/HttpStitchingRequestInterceptor.cs @@ -0,0 +1,28 @@ +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using HotChocolate.Execution; + +namespace HotChocolate.Stitching.Pipeline; + +public class HttpStitchingRequestInterceptor : IHttpStitchingRequestInterceptor +{ + public virtual ValueTask OnCreateRequestAsync( + string targetSchema, + IQueryRequest request, + HttpRequestMessage requestMessage, + CancellationToken cancellationToken = default) + { + return default; + } + + public ValueTask OnReceivedResultAsync( + string targetSchema, + IQueryRequest request, + IQueryResult result, + HttpResponseMessage responseMessage, + CancellationToken cancellationToken = default) + { + return new ValueTask(result); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Pipeline/IHttpStitchingRequestInterceptor.cs b/src/HotChocolate/Stitching/src/Stitching/Pipeline/IHttpStitchingRequestInterceptor.cs new file mode 100644 index 00000000000..bfc5eff2c9e --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Pipeline/IHttpStitchingRequestInterceptor.cs @@ -0,0 +1,22 @@ +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using HotChocolate.Execution; + +namespace HotChocolate.Stitching.Pipeline; + +public interface IHttpStitchingRequestInterceptor +{ + ValueTask OnCreateRequestAsync( + string targetSchema, + IQueryRequest request, + HttpRequestMessage requestMessage, + CancellationToken cancellationToken = default); + + ValueTask OnReceivedResultAsync( + string targetSchema, + IQueryRequest request, + IQueryResult result, + HttpResponseMessage responseMessage, + CancellationToken cancellationToken = default); +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Properties/StitchingResources.Designer.cs b/src/HotChocolate/Stitching/src/Stitching/Properties/StitchingResources.Designer.cs new file mode 100644 index 00000000000..955e21612f3 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Properties/StitchingResources.Designer.cs @@ -0,0 +1,300 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace HotChocolate.Stitching.Properties { + using System; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class StitchingResources { + + private static System.Resources.ResourceManager resourceMan; + + private static System.Globalization.CultureInfo resourceCulture; + + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal StitchingResources() { + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Resources.ResourceManager ResourceManager { + get { + if (object.Equals(null, resourceMan)) { + System.Resources.ResourceManager temp = new System.Resources.ResourceManager("HotChocolate.Stitching.Properties.StitchingResources", typeof(StitchingResources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + internal static string AddSchemaExtensionRewriter_DirectiveDoesNotExist { + get { + return ResourceManager.GetString("AddSchemaExtensionRewriter_DirectiveDoesNotExist", resourceCulture); + } + } + + internal static string AddSchemaExtensionRewriter_DirectiveIsUnique { + get { + return ResourceManager.GetString("AddSchemaExtensionRewriter_DirectiveIsUnique", resourceCulture); + } + } + + internal static string AddSchemaExtensionRewriter_TypeMismatch { + get { + return ResourceManager.GetString("AddSchemaExtensionRewriter_TypeMismatch", resourceCulture); + } + } + + internal static string ArgumentScopedVariableResolver_CannotHandleVariable { + get { + return ResourceManager.GetString("ArgumentScopedVariableResolver_CannotHandleVariable", resourceCulture); + } + } + + internal static string ArgumentScopedVariableResolver_InvalidArgumentName { + get { + return ResourceManager.GetString("ArgumentScopedVariableResolver_InvalidArgumentName", resourceCulture); + } + } + + internal static string ComputedDirectiveType_Description { + get { + return ResourceManager.GetString("ComputedDirectiveType_Description", resourceCulture); + } + } + + internal static string ContextDataScopedVariableResolver_CannotHandleVariable { + get { + return ResourceManager.GetString("ContextDataScopedVariableResolver_CannotHandleVariable", resourceCulture); + } + } + + internal static string DelegateDirectiveType_Description { + get { + return ResourceManager.GetString("DelegateDirectiveType_Description", resourceCulture); + } + } + + internal static string DelegateDirectiveType_Schema_FieldDescription { + get { + return ResourceManager.GetString("DelegateDirectiveType_Schema_FieldDescription", resourceCulture); + } + } + + internal static string DelegateDirectiveType_Path_FieldDescription { + get { + return ResourceManager.GetString("DelegateDirectiveType_Path_FieldDescription", resourceCulture); + } + } + + internal static string DelegationMiddleware_ArgumentNotFound { + get { + return ResourceManager.GetString("DelegationMiddleware_ArgumentNotFound", resourceCulture); + } + } + + internal static string DelegationMiddleware_OnlyQueryResults { + get { + return ResourceManager.GetString("DelegationMiddleware_OnlyQueryResults", resourceCulture); + } + } + + internal static string DelegationMiddleware_PathElementInvalid { + get { + return ResourceManager.GetString("DelegationMiddleware_PathElementInvalid", resourceCulture); + } + } + + internal static string DelegationMiddleware_PathElementTypeUnexpected { + get { + return ResourceManager.GetString("DelegationMiddleware_PathElementTypeUnexpected", resourceCulture); + } + } + + internal static string ExtensionsFilePath_EmptyOrNull { + get { + return ResourceManager.GetString("ExtensionsFilePath_EmptyOrNull", resourceCulture); + } + } + + internal static string Extensions_EmptyOrNull { + get { + return ResourceManager.GetString("Extensions_EmptyOrNull", resourceCulture); + } + } + + internal static string FieldScopedVariableResolver_CannotHandleVariable { + get { + return ResourceManager.GetString("FieldScopedVariableResolver_CannotHandleVariable", resourceCulture); + } + } + + internal static string FieldScopedVariableResolver_InvalidFieldName { + get { + return ResourceManager.GetString("FieldScopedVariableResolver_InvalidFieldName", resourceCulture); + } + } + + internal static string IntrospectionDeserializer_Json_NullOrEmpty { + get { + return ResourceManager.GetString("IntrospectionDeserializer_Json_NullOrEmpty", resourceCulture); + } + } + + internal static string MergeSyntaxNodeExtensions_NoSchema { + get { + return ResourceManager.GetString("MergeSyntaxNodeExtensions_NoSchema", resourceCulture); + } + } + + internal static string QueryRequestBuilder_OperationNameInvalid { + get { + return ResourceManager.GetString("QueryRequestBuilder_OperationNameInvalid", resourceCulture); + } + } + + internal static string QueryRequestBuilder_OperationNameMissing { + get { + return ResourceManager.GetString("QueryRequestBuilder_OperationNameMissing", resourceCulture); + } + } + + internal static string QueryRequestBuilder_QueryIsNull { + get { + return ResourceManager.GetString("QueryRequestBuilder_QueryIsNull", resourceCulture); + } + } + + internal static string RemoteExecutorBuilder_NoSchema { + get { + return ResourceManager.GetString("RemoteExecutorBuilder_NoSchema", resourceCulture); + } + } + + internal static string RemoteExecutorBuilder_NoSchemaName { + get { + return ResourceManager.GetString("RemoteExecutorBuilder_NoSchemaName", resourceCulture); + } + } + + internal static string RootScopedVariableResolver_ScopeNotSupported { + get { + return ResourceManager.GetString("RootScopedVariableResolver_ScopeNotSupported", resourceCulture); + } + } + + internal static string ScalarType_InvalidBaseType { + get { + return ResourceManager.GetString("ScalarType_InvalidBaseType", resourceCulture); + } + } + + internal static string SchemaFilePath_EmptyOrNull { + get { + return ResourceManager.GetString("SchemaFilePath_EmptyOrNull", resourceCulture); + } + } + + internal static string SchemaMergerExtensions_NoValidConstructor { + get { + return ResourceManager.GetString("SchemaMergerExtensions_NoValidConstructor", resourceCulture); + } + } + + internal static string SchemaName_EmptyOrNull { + get { + return ResourceManager.GetString("SchemaName_EmptyOrNull", resourceCulture); + } + } + + internal static string SchemaName_NotFound { + get { + return ResourceManager.GetString("SchemaName_NotFound", resourceCulture); + } + } + + internal static string Schema_EmptyOrNull { + get { + return ResourceManager.GetString("Schema_EmptyOrNull", resourceCulture); + } + } + + internal static string ScopedCtxDataScopedVariableResolver_CannotHandleVariable { + get { + return ResourceManager.GetString("ScopedCtxDataScopedVariableResolver_CannotHandleVariable", resourceCulture); + } + } + + internal static string SourceDirectiveType_Description { + get { + return ResourceManager.GetString("SourceDirectiveType_Description", resourceCulture); + } + } + + internal static string SourceDirectiveType_Name_Description { + get { + return ResourceManager.GetString("SourceDirectiveType_Name_Description", resourceCulture); + } + } + + internal static string SourceDirectiveType_Schema_Description { + get { + return ResourceManager.GetString("SourceDirectiveType_Schema_Description", resourceCulture); + } + } + + internal static string StitchingBuilder_SchemaNameInUse { + get { + return ResourceManager.GetString("StitchingBuilder_SchemaNameInUse", resourceCulture); + } + } + + internal static string Type_NotSupported { + get { + return ResourceManager.GetString("Type_NotSupported", resourceCulture); + } + } + + internal static string BufferedRequest_Create_QueryCannotBeNull { + get { + return ResourceManager.GetString("BufferedRequest_Create_QueryCannotBeNull", resourceCulture); + } + } + + internal static string ThrowHelper_BufferedRequest_VariableDoesNotExist { + get { + return ResourceManager.GetString("ThrowHelper_BufferedRequest_VariableDoesNotExist", resourceCulture); + } + } + + internal static string ThrowHelper_BufferedRequest_OperationNotFound { + get { + return ResourceManager.GetString("ThrowHelper_BufferedRequest_OperationNotFound", resourceCulture); + } + } + + internal static string HttpRequestClient_UnknownVariableValueKind { + get { + return ResourceManager.GetString("HttpRequestClient_UnknownVariableValueKind", resourceCulture); + } + } + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Properties/StitchingResources.resx b/src/HotChocolate/Stitching/src/Stitching/Properties/StitchingResources.resx new file mode 100644 index 00000000000..66bbee6adc8 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Properties/StitchingResources.resx @@ -0,0 +1,246 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The directive `{0}` was not specified in this schema and cannot be used. + + + The directive `{0}` is not marked as repeatable and can only be declared once. + + + `{0}` is of type `{1}` and cannot be extended with `{2}`. + + + This resolver can only handle argument scopes. + + + An argument with the name `{0}` does not exist. + + + Specifies the fields on which a computed field is dependent on. + + + This resolver can only handle contextData scopes. + + + Delegates a resolver to a remote schema. + + + The name of the schema to which this field shall be delegated to. + + + The path to the field on the remote schema. + + + The path element argument `{0}` was not found. + + + Only query results are supported in the delegation middleware. + + + The path element `{0}` has no corresponding field on type `{1}`. + + + The scalar path elements are only allowed at the end. + + + The schema extensions file path mustn't be null or empty. + + + The extensions document mustn't be null or empty. + + + This resolver can only handle field scopes. + + + A field with the name `{0}` does not exist. + + + json mustn't be null or empty. + + + No schema was specified. + + + The specified operation `{0}` does not exist. + + + Specify an operation name in order to create a query request that contains multiple operations. + + + Specify a query in order to create a query request. + + + Cannot build a remote executor without a schema. + + + Cannot build a remote executor without a schema name. + + + The specified scope `{0}` is not supported. + + + The provided type must extend `HotChocolate.Types.ScalarType` in order to be recognised as valid scalar type. + + + The schema file path mustn't be null or empty. + + + A type merge handler has to have one constructor that has only one parameter of the type MergeTypeDelegate. + + + The schema name mustn't be null or empty. + + + There is no schema with the given name `{0}`. + + + The schema mustn't be null or empty. + + + This resolver can only handle scopedContextData scopes. + + + Annotates the original name of a type. + + + The original name of the annotated type. + + + The name of the schema to which this type belongs to. + + + The specified schema name was already beeing used. + + + The type definition is not yet supported. + + + The request must contain a query document. + + + The specified variable `{0}` does not exist or is of an invalid type. + + + The provided remote query does not contain the specified operation.\r\n\r\n`{0}` + + + Unknown variable value kind. + + diff --git a/src/HotChocolate/Stitching/src/Stitching/PublicAPI.Shipped.txt b/src/HotChocolate/Stitching/src/Stitching/PublicAPI.Shipped.txt new file mode 100644 index 00000000000..8fc4fa7b332 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/PublicAPI.Shipped.txt @@ -0,0 +1,377 @@ +#nullable enable +abstract HotChocolate.Stitching.Merge.Handlers.TypeMergeHanlderBase.CanBeMerged(T left, T right) -> bool +abstract HotChocolate.Stitching.Merge.Handlers.TypeMergeHanlderBase.MergeTypes(HotChocolate.Stitching.Merge.ISchemaMergeContext! context, System.Collections.Generic.IReadOnlyList! types, HotChocolate.NameString newTypeName) -> void +HotChocolate.Stitching.ComputedDirective +HotChocolate.Stitching.ComputedDirective.ComputedDirective() -> void +HotChocolate.Stitching.ComputedDirective.DependantOn.get -> HotChocolate.NameString[]! +HotChocolate.Stitching.ComputedDirective.DependantOn.set -> void +HotChocolate.Stitching.ComputedDirectiveType +HotChocolate.Stitching.ComputedDirectiveType.ComputedDirectiveType() -> void +HotChocolate.Stitching.DelegateDirective +HotChocolate.Stitching.DelegateDirective.DelegateDirective() -> void +HotChocolate.Stitching.DelegateDirective.Path.get -> string? +HotChocolate.Stitching.DelegateDirective.Path.set -> void +HotChocolate.Stitching.DelegateDirective.Schema.get -> HotChocolate.NameString +HotChocolate.Stitching.DelegateDirective.Schema.set -> void +HotChocolate.Stitching.DelegateDirectiveType +HotChocolate.Stitching.DelegateDirectiveType.DelegateDirectiveType() -> void +HotChocolate.Stitching.Delegation.DelegateToRemoteSchemaMiddleware +HotChocolate.Stitching.Delegation.DelegateToRemoteSchemaMiddleware.DelegateToRemoteSchemaMiddleware(HotChocolate.Resolvers.FieldDelegate! next) -> void +HotChocolate.Stitching.Delegation.DelegateToRemoteSchemaMiddleware.InvokeAsync(HotChocolate.Resolvers.IMiddlewareContext! context) -> System.Threading.Tasks.Task! +HotChocolate.Stitching.Delegation.DictionaryResultMiddleware +HotChocolate.Stitching.Delegation.DictionaryResultMiddleware.DictionaryResultMiddleware(HotChocolate.Resolvers.FieldDelegate! next) -> void +HotChocolate.Stitching.Delegation.DictionaryResultMiddleware.InvokeAsync(HotChocolate.Resolvers.IMiddlewareContext! context) -> System.Threading.Tasks.ValueTask +HotChocolate.Stitching.Delegation.ExtractedField +HotChocolate.Stitching.Delegation.ExtractedField.ExtractedField(System.Collections.Generic.IReadOnlyList! syntaxNodes, System.Collections.Generic.IReadOnlyList! variables, System.Collections.Generic.IReadOnlyList! fragments) -> void +HotChocolate.Stitching.Delegation.ExtractedField.Fragments.get -> System.Collections.Generic.IReadOnlyList! +HotChocolate.Stitching.Delegation.ExtractedField.SyntaxNodes.get -> System.Collections.Generic.IReadOnlyList! +HotChocolate.Stitching.Delegation.ExtractedField.Variables.get -> System.Collections.Generic.IReadOnlyList! +HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter +HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.Context +HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.Context.Clone() -> HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.Context! +HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.Context.Context(HotChocolate.NameString schema, HotChocolate.Types.INamedOutputType? typeContext, HotChocolate.Language.DocumentNode? document, HotChocolate.Language.OperationDefinitionNode? operation) -> void +HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.Context.Document.get -> HotChocolate.Language.DocumentNode? +HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.Context.FragmentPath.get -> System.Collections.Immutable.ImmutableHashSet! +HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.Context.FragmentPath.set -> void +HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.Context.Fragments.get -> System.Collections.Generic.IDictionary! +HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.Context.InputField.get -> HotChocolate.Types.IInputField? +HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.Context.InputField.set -> void +HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.Context.InputType.get -> HotChocolate.Types.IInputType? +HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.Context.InputType.set -> void +HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.Context.Operation.get -> HotChocolate.Language.OperationDefinitionNode? +HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.Context.OutputField.get -> HotChocolate.Types.IOutputField? +HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.Context.OutputField.set -> void +HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.Context.Schema.get -> HotChocolate.NameString +HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.Context.TypeContext.get -> HotChocolate.Types.INamedOutputType? +HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.Context.TypeContext.set -> void +HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.Context.Variables.get -> System.Collections.Generic.IDictionary! +HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.ExtractField(HotChocolate.NameString sourceSchema, HotChocolate.Language.DocumentNode! document, HotChocolate.Language.OperationDefinitionNode! operation, HotChocolate.Resolvers.IFieldSelection! selection, HotChocolate.Types.INamedOutputType! declaringType) -> HotChocolate.Stitching.Delegation.ExtractedField! +HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.ExtractFieldQuerySyntaxRewriter(HotChocolate.ISchema! schema, System.Collections.Generic.IEnumerable! rewriters) -> void +HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.RewriteValueNode(HotChocolate.NameString sourceSchema, HotChocolate.Types.IInputType! inputType, HotChocolate.Language.IValueNode! value) -> HotChocolate.Language.IValueNode! +HotChocolate.Stitching.Delegation.RemoteQueryBuilder +HotChocolate.Stitching.Delegation.RemoteQueryBuilder.AddAdditionalField(HotChocolate.Language.FieldNode! field) -> HotChocolate.Stitching.Delegation.RemoteQueryBuilder! +HotChocolate.Stitching.Delegation.RemoteQueryBuilder.AddFragmentDefinitions(System.Collections.Generic.IEnumerable! fragments) -> HotChocolate.Stitching.Delegation.RemoteQueryBuilder! +HotChocolate.Stitching.Delegation.RemoteQueryBuilder.AddVariable(HotChocolate.Language.VariableDefinitionNode! variable) -> HotChocolate.Stitching.Delegation.RemoteQueryBuilder! +HotChocolate.Stitching.Delegation.RemoteQueryBuilder.AddVariable(HotChocolate.NameString name, HotChocolate.Language.ITypeNode! type, HotChocolate.Language.IValueNode? defaultValue) -> HotChocolate.Stitching.Delegation.RemoteQueryBuilder! +HotChocolate.Stitching.Delegation.RemoteQueryBuilder.AddVariable(HotChocolate.NameString name, HotChocolate.Language.ITypeNode! type) -> HotChocolate.Stitching.Delegation.RemoteQueryBuilder! +HotChocolate.Stitching.Delegation.RemoteQueryBuilder.AddVariables(System.Collections.Generic.IEnumerable! variables) -> HotChocolate.Stitching.Delegation.RemoteQueryBuilder! +HotChocolate.Stitching.Delegation.RemoteQueryBuilder.Build(HotChocolate.NameString targetSchema, System.Collections.Generic.IReadOnlyDictionary<(HotChocolate.NameString Type, HotChocolate.NameString Schema), HotChocolate.NameString>! nameLookup) -> HotChocolate.Language.DocumentNode! +HotChocolate.Stitching.Delegation.RemoteQueryBuilder.RemoteQueryBuilder() -> void +HotChocolate.Stitching.Delegation.RemoteQueryBuilder.SetOperation(HotChocolate.Language.NameNode? name, HotChocolate.Language.OperationType operation) -> HotChocolate.Stitching.Delegation.RemoteQueryBuilder! +HotChocolate.Stitching.Delegation.RemoteQueryBuilder.SetRequestField(HotChocolate.Language.FieldNode! field) -> HotChocolate.Stitching.Delegation.RemoteQueryBuilder! +HotChocolate.Stitching.Delegation.RemoteQueryBuilder.SetSelectionPath(System.Collections.Immutable.IImmutableStack! selectionPath) -> HotChocolate.Stitching.Delegation.RemoteQueryBuilder! +HotChocolate.Stitching.Delegation.ScopedVariables.ScopeNames +HotChocolate.Stitching.Delegation.SerializedData +HotChocolate.Stitching.Delegation.SerializedData.Data.get -> object? +HotChocolate.Stitching.Delegation.SerializedData.SerializedData(object? data) -> void +HotChocolate.Stitching.ErrorHelper +HotChocolate.Stitching.Merge.AddSchemaExtensionRewriter +HotChocolate.Stitching.Merge.AddSchemaExtensionRewriter.AddExtensions(HotChocolate.Language.DocumentNode! schema, HotChocolate.Language.DocumentNode! extensions) -> HotChocolate.Language.DocumentNode! +HotChocolate.Stitching.Merge.AddSchemaExtensionRewriter.AddSchemaExtensionRewriter() -> void +HotChocolate.Stitching.Merge.AddSchemaExtensionRewriter.AddSchemaExtensionRewriter(System.Collections.Generic.IEnumerable! globalDirectives) -> void +HotChocolate.Stitching.Merge.AddSchemaExtensionRewriter.MergeContext +HotChocolate.Stitching.Merge.AddSchemaExtensionRewriter.MergeContext.Directives.get -> System.Collections.Generic.IDictionary! +HotChocolate.Stitching.Merge.AddSchemaExtensionRewriter.MergeContext.Extensions.get -> System.Collections.Generic.IDictionary! +HotChocolate.Stitching.Merge.AddSchemaExtensionRewriter.MergeContext.MergeContext(HotChocolate.Language.DocumentNode! schema, HotChocolate.Language.DocumentNode! extensions) -> void +HotChocolate.Stitching.Merge.DirectiveTypeInfo +HotChocolate.Stitching.Merge.DirectiveTypeInfo.Definition.get -> HotChocolate.Language.DirectiveDefinitionNode! +HotChocolate.Stitching.Merge.DirectiveTypeInfo.DirectiveTypeInfo(HotChocolate.Language.DirectiveDefinitionNode! definition, HotChocolate.Stitching.Merge.ISchemaInfo! schema) -> void +HotChocolate.Stitching.Merge.DirectiveTypeInfo.Schema.get -> HotChocolate.Stitching.Merge.ISchemaInfo! +HotChocolate.Stitching.Merge.Handlers.TypeMergeHanlderBase +HotChocolate.Stitching.Merge.Handlers.TypeMergeHanlderBase.Merge(HotChocolate.Stitching.Merge.ISchemaMergeContext! context, System.Collections.Generic.IReadOnlyList! types) -> void +HotChocolate.Stitching.Merge.Handlers.TypeMergeHanlderBase.TypeMergeHanlderBase(HotChocolate.Stitching.Merge.MergeTypeRuleDelegate! next) -> void +HotChocolate.Stitching.Merge.IDirectiveMergeHandler +HotChocolate.Stitching.Merge.IDirectiveMergeHandler.Merge(HotChocolate.Stitching.Merge.ISchemaMergeContext! context, System.Collections.Generic.IReadOnlyList! directives) -> void +HotChocolate.Stitching.Merge.IDirectiveTypeInfo +HotChocolate.Stitching.Merge.IDirectiveTypeInfo.Definition.get -> HotChocolate.Language.DirectiveDefinitionNode! +HotChocolate.Stitching.Merge.IDirectiveTypeInfo.Schema.get -> HotChocolate.Stitching.Merge.ISchemaInfo! +HotChocolate.Stitching.Merge.ISchemaInfo +HotChocolate.Stitching.Merge.ISchemaInfo.Directives.get -> System.Collections.Generic.IReadOnlyDictionary! +HotChocolate.Stitching.Merge.ISchemaInfo.Document.get -> HotChocolate.Language.DocumentNode! +HotChocolate.Stitching.Merge.ISchemaInfo.IsRootType(HotChocolate.Language.ITypeDefinitionNode! typeDefinition) -> bool +HotChocolate.Stitching.Merge.ISchemaInfo.MutationType.get -> HotChocolate.Language.ObjectTypeDefinitionNode? +HotChocolate.Stitching.Merge.ISchemaInfo.Name.get -> HotChocolate.NameString +HotChocolate.Stitching.Merge.ISchemaInfo.QueryType.get -> HotChocolate.Language.ObjectTypeDefinitionNode! +HotChocolate.Stitching.Merge.ISchemaInfo.SubscriptionType.get -> HotChocolate.Language.ObjectTypeDefinitionNode? +HotChocolate.Stitching.Merge.ISchemaInfo.TryGetOperationType(HotChocolate.Language.ObjectTypeDefinitionNode! rootType, out HotChocolate.Language.OperationType operationType) -> bool +HotChocolate.Stitching.Merge.ISchemaInfo.Types.get -> System.Collections.Generic.IReadOnlyDictionary! +HotChocolate.Stitching.Merge.ISchemaMergeContext +HotChocolate.Stitching.Merge.ISchemaMergeContext.AddDirective(HotChocolate.Language.DirectiveDefinitionNode! directive) -> void +HotChocolate.Stitching.Merge.ISchemaMergeContext.AddType(HotChocolate.Language.ITypeDefinitionNode! type) -> void +HotChocolate.Stitching.Merge.ISchemaMergeContext.ContainsDirective(HotChocolate.NameString directiveName) -> bool +HotChocolate.Stitching.Merge.ISchemaMergeContext.ContainsType(HotChocolate.NameString typeName) -> bool +HotChocolate.Stitching.Merge.ISchemaMerger +HotChocolate.Stitching.Merge.ISchemaMerger.AddDirectiveMergeRule(HotChocolate.Stitching.Merge.MergeDirectiveRuleFactory! factory) -> HotChocolate.Stitching.Merge.ISchemaMerger! +HotChocolate.Stitching.Merge.ISchemaMerger.AddDocumentRewriter(HotChocolate.Stitching.Merge.Rewriters.IDocumentRewriter! rewriter) -> HotChocolate.Stitching.Merge.ISchemaMerger! +HotChocolate.Stitching.Merge.ISchemaMerger.AddMergeRule(HotChocolate.Stitching.Merge.MergeTypeRuleFactory! factory) -> HotChocolate.Stitching.Merge.ISchemaMerger! +HotChocolate.Stitching.Merge.ISchemaMerger.AddSchema(HotChocolate.NameString name, HotChocolate.Language.DocumentNode! schema) -> HotChocolate.Stitching.Merge.ISchemaMerger! +HotChocolate.Stitching.Merge.ISchemaMerger.AddTypeMergeRule(HotChocolate.Stitching.Merge.MergeTypeRuleFactory! factory) -> HotChocolate.Stitching.Merge.ISchemaMerger! +HotChocolate.Stitching.Merge.ISchemaMerger.AddTypeRewriter(HotChocolate.Stitching.Merge.Rewriters.ITypeRewriter! rewriter) -> HotChocolate.Stitching.Merge.ISchemaMerger! +HotChocolate.Stitching.Merge.ISchemaMerger.Merge() -> HotChocolate.Language.DocumentNode! +HotChocolate.Stitching.Merge.ITypeInfo +HotChocolate.Stitching.Merge.ITypeInfo.Definition.get -> HotChocolate.Language.ITypeDefinitionNode! +HotChocolate.Stitching.Merge.ITypeInfo.IsRootType.get -> bool +HotChocolate.Stitching.Merge.ITypeInfo.Schema.get -> HotChocolate.Stitching.Merge.ISchemaInfo! +HotChocolate.Stitching.Merge.ITypeInfo +HotChocolate.Stitching.Merge.ITypeInfo.Definition.get -> T +HotChocolate.Stitching.Merge.ITypeMergeHandler +HotChocolate.Stitching.Merge.ITypeMergeHandler.Merge(HotChocolate.Stitching.Merge.ISchemaMergeContext! context, System.Collections.Generic.IReadOnlyList! types) -> void +HotChocolate.Stitching.Merge.MergeDirectiveRuleDelegate +HotChocolate.Stitching.Merge.MergeDirectiveRuleFactory +HotChocolate.Stitching.Merge.MergeSyntaxNodeExtensions +HotChocolate.Stitching.Merge.MergeTypeRuleDelegate +HotChocolate.Stitching.Merge.MergeTypeRuleFactory +HotChocolate.Stitching.Merge.Rewriters.IDocumentRewriter +HotChocolate.Stitching.Merge.Rewriters.IDocumentRewriter.Rewrite(HotChocolate.Stitching.Merge.ISchemaInfo! schema, HotChocolate.Language.DocumentNode! document) -> HotChocolate.Language.DocumentNode! +HotChocolate.Stitching.Merge.Rewriters.ITypeRewriter +HotChocolate.Stitching.Merge.Rewriters.ITypeRewriter.Rewrite(HotChocolate.Stitching.Merge.ISchemaInfo! schema, HotChocolate.Language.ITypeDefinitionNode! typeDefinition) -> HotChocolate.Language.ITypeDefinitionNode! +HotChocolate.Stitching.Merge.Rewriters.RewriteDocumentDelegate +HotChocolate.Stitching.Merge.Rewriters.RewriteTypeDefinitionDelegate +HotChocolate.Stitching.Merge.SchemaMergeContext +HotChocolate.Stitching.Merge.SchemaMergeContext.AddDirective(HotChocolate.Language.DirectiveDefinitionNode! directive) -> void +HotChocolate.Stitching.Merge.SchemaMergeContext.AddType(HotChocolate.Language.ITypeDefinitionNode! type) -> void +HotChocolate.Stitching.Merge.SchemaMergeContext.ContainsDirective(HotChocolate.NameString directiveName) -> bool +HotChocolate.Stitching.Merge.SchemaMergeContext.ContainsType(HotChocolate.NameString typeName) -> bool +HotChocolate.Stitching.Merge.SchemaMergeContext.CreateSchema() -> HotChocolate.Language.DocumentNode! +HotChocolate.Stitching.Merge.SchemaMergeContext.SchemaMergeContext() -> void +HotChocolate.Stitching.Merge.SchemaMergeException +HotChocolate.Stitching.Merge.SchemaMergeException.SchemaMergeException(HotChocolate.Language.ITypeDefinitionNode! typeDefinition, HotChocolate.Language.ITypeExtensionNode! typeExtension, string! message) -> void +HotChocolate.Stitching.Merge.SchemaMergeException.SchemaMergeException(System.Runtime.Serialization.SerializationInfo! info, System.Runtime.Serialization.StreamingContext context) -> void +HotChocolate.Stitching.Merge.SchemaMergeException.TypeDefinition.get -> HotChocolate.Language.ITypeDefinitionNode! +HotChocolate.Stitching.Merge.SchemaMergeException.TypeExtension.get -> HotChocolate.Language.ITypeExtensionNode! +HotChocolate.Stitching.Merge.SchemaMerger +HotChocolate.Stitching.Merge.SchemaMerger.AddDirectiveMergeRule(HotChocolate.Stitching.Merge.MergeDirectiveRuleFactory! factory) -> HotChocolate.Stitching.Merge.ISchemaMerger! +HotChocolate.Stitching.Merge.SchemaMerger.AddDocumentRewriter(HotChocolate.Stitching.Merge.Rewriters.IDocumentRewriter! rewriter) -> HotChocolate.Stitching.Merge.ISchemaMerger! +HotChocolate.Stitching.Merge.SchemaMerger.AddMergeRule(HotChocolate.Stitching.Merge.MergeTypeRuleFactory! factory) -> HotChocolate.Stitching.Merge.ISchemaMerger! +HotChocolate.Stitching.Merge.SchemaMerger.AddSchema(HotChocolate.NameString name, HotChocolate.Language.DocumentNode! schema) -> HotChocolate.Stitching.Merge.ISchemaMerger! +HotChocolate.Stitching.Merge.SchemaMerger.AddTypeMergeRule(HotChocolate.Stitching.Merge.MergeTypeRuleFactory! factory) -> HotChocolate.Stitching.Merge.ISchemaMerger! +HotChocolate.Stitching.Merge.SchemaMerger.AddTypeRewriter(HotChocolate.Stitching.Merge.Rewriters.ITypeRewriter! rewriter) -> HotChocolate.Stitching.Merge.ISchemaMerger! +HotChocolate.Stitching.Merge.SchemaMerger.Merge() -> HotChocolate.Language.DocumentNode! +HotChocolate.Stitching.Merge.SchemaMerger.SchemaMerger() -> void +HotChocolate.Stitching.Merge.SchemaMergerExtensions +HotChocolate.Stitching.Merge.TypeInfoExtensions +HotChocolate.Stitching.Pipeline.HttpStitchingRequestInterceptor +HotChocolate.Stitching.Pipeline.HttpStitchingRequestInterceptor.HttpStitchingRequestInterceptor() -> void +HotChocolate.Stitching.Pipeline.HttpStitchingRequestInterceptor.OnReceivedResultAsync(HotChocolate.NameString targetSchema, HotChocolate.Execution.IQueryRequest! request, HotChocolate.Execution.IQueryResult! result, System.Net.Http.HttpResponseMessage! responseMessage, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +HotChocolate.Stitching.Pipeline.IHttpStitchingRequestInterceptor +HotChocolate.Stitching.Pipeline.IHttpStitchingRequestInterceptor.OnCreateRequestAsync(HotChocolate.NameString targetSchema, HotChocolate.Execution.IQueryRequest! request, System.Net.Http.HttpRequestMessage! requestMessage, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +HotChocolate.Stitching.Pipeline.IHttpStitchingRequestInterceptor.OnReceivedResultAsync(HotChocolate.NameString targetSchema, HotChocolate.Execution.IQueryRequest! request, HotChocolate.Execution.IQueryResult! result, System.Net.Http.HttpResponseMessage! responseMessage, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +HotChocolate.Stitching.Requests.IRemoteRequestExecutor +HotChocolate.Stitching.Requests.IRemoteRequestExecutor.ExecuteAsync(HotChocolate.Execution.IQueryRequest! request, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +HotChocolate.Stitching.Requests.IRemoteRequestExecutor.Schema.get -> HotChocolate.ISchema! +HotChocolate.Stitching.Requests.IRemoteRequestExecutor.Services.get -> System.IServiceProvider! +HotChocolate.Stitching.Requests.IStitchingContext +HotChocolate.Stitching.Requests.IStitchingContext.GetRemoteRequestExecutor(HotChocolate.NameString schemaName) -> HotChocolate.Stitching.Requests.IRemoteRequestExecutor! +HotChocolate.Stitching.Requests.IStitchingContext.GetRemoteSchema(HotChocolate.NameString schemaName) -> HotChocolate.ISchema! +HotChocolate.Stitching.Requests.StitchingContext +HotChocolate.Stitching.Requests.StitchingContext.GetRemoteRequestExecutor(HotChocolate.NameString schemaName) -> HotChocolate.Stitching.Requests.IRemoteRequestExecutor! +HotChocolate.Stitching.Requests.StitchingContext.GetRemoteSchema(HotChocolate.NameString schemaName) -> HotChocolate.ISchema! +HotChocolate.Stitching.Requests.StitchingContext.StitchingContext(GreenDonut.IBatchScheduler! batchScheduler, HotChocolate.Execution.IRequestContextAccessor! requestContextAccessor) -> void +HotChocolate.Stitching.SchemaDefinitions.IPublishSchemaDefinitionDescriptor +HotChocolate.Stitching.SchemaDefinitions.IPublishSchemaDefinitionDescriptor.AddTypeExtensionsFromFile(string! fileName) -> HotChocolate.Stitching.SchemaDefinitions.IPublishSchemaDefinitionDescriptor! +HotChocolate.Stitching.SchemaDefinitions.IPublishSchemaDefinitionDescriptor.AddTypeExtensionsFromResource(System.Reflection.Assembly! assembly, string! key) -> HotChocolate.Stitching.SchemaDefinitions.IPublishSchemaDefinitionDescriptor! +HotChocolate.Stitching.SchemaDefinitions.IPublishSchemaDefinitionDescriptor.AddTypeExtensionsFromString(string! schemaSdl) -> HotChocolate.Stitching.SchemaDefinitions.IPublishSchemaDefinitionDescriptor! +HotChocolate.Stitching.SchemaDefinitions.IPublishSchemaDefinitionDescriptor.IgnoreRootTypes() -> HotChocolate.Stitching.SchemaDefinitions.IPublishSchemaDefinitionDescriptor! +HotChocolate.Stitching.SchemaDefinitions.IPublishSchemaDefinitionDescriptor.IgnoreType(HotChocolate.NameString typeName) -> HotChocolate.Stitching.SchemaDefinitions.IPublishSchemaDefinitionDescriptor! +HotChocolate.Stitching.SchemaDefinitions.IPublishSchemaDefinitionDescriptor.RenameField(HotChocolate.NameString typeName, HotChocolate.NameString fieldName, HotChocolate.NameString newFieldName) -> HotChocolate.Stitching.SchemaDefinitions.IPublishSchemaDefinitionDescriptor! +HotChocolate.Stitching.SchemaDefinitions.IPublishSchemaDefinitionDescriptor.RenameType(HotChocolate.NameString typeName, HotChocolate.NameString newTypeName) -> HotChocolate.Stitching.SchemaDefinitions.IPublishSchemaDefinitionDescriptor! +HotChocolate.Stitching.SchemaDefinitions.IPublishSchemaDefinitionDescriptor.SetName(HotChocolate.NameString name) -> HotChocolate.Stitching.SchemaDefinitions.IPublishSchemaDefinitionDescriptor! +HotChocolate.Stitching.SchemaDefinitions.IPublishSchemaDefinitionDescriptor.SetSchemaDefinitionPublisher(System.Func! publisherFactory) -> HotChocolate.Stitching.SchemaDefinitions.IPublishSchemaDefinitionDescriptor! +HotChocolate.Stitching.SchemaDefinitions.ISchemaDefinitionPublisher +HotChocolate.Stitching.SchemaDefinitions.ISchemaDefinitionPublisher.PublishAsync(HotChocolate.Stitching.RemoteSchemaDefinition! schemaDefinition, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +HotChocolate.Stitching.SchemaDefinitions.PublishSchemaDefinitionDescriptor +HotChocolate.Stitching.SchemaDefinitions.PublishSchemaDefinitionDescriptor.AddTypeExtensionsFromFile(string! fileName) -> HotChocolate.Stitching.SchemaDefinitions.IPublishSchemaDefinitionDescriptor! +HotChocolate.Stitching.SchemaDefinitions.PublishSchemaDefinitionDescriptor.AddTypeExtensionsFromResource(System.Reflection.Assembly! assembly, string! key) -> HotChocolate.Stitching.SchemaDefinitions.IPublishSchemaDefinitionDescriptor! +HotChocolate.Stitching.SchemaDefinitions.PublishSchemaDefinitionDescriptor.AddTypeExtensionsFromString(string! schemaSdl) -> HotChocolate.Stitching.SchemaDefinitions.IPublishSchemaDefinitionDescriptor! +HotChocolate.Stitching.SchemaDefinitions.PublishSchemaDefinitionDescriptor.Build(HotChocolate.Types.Descriptors.IDescriptorContext! context, HotChocolate.ISchema! schema) -> HotChocolate.Stitching.RemoteSchemaDefinition! +HotChocolate.Stitching.SchemaDefinitions.PublishSchemaDefinitionDescriptor.HasPublisher.get -> bool +HotChocolate.Stitching.SchemaDefinitions.PublishSchemaDefinitionDescriptor.IgnoreRootTypes() -> HotChocolate.Stitching.SchemaDefinitions.IPublishSchemaDefinitionDescriptor! +HotChocolate.Stitching.SchemaDefinitions.PublishSchemaDefinitionDescriptor.IgnoreType(HotChocolate.NameString typeName) -> HotChocolate.Stitching.SchemaDefinitions.IPublishSchemaDefinitionDescriptor! +HotChocolate.Stitching.SchemaDefinitions.PublishSchemaDefinitionDescriptor.PublishAsync(System.IServiceProvider! applicationServices, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +HotChocolate.Stitching.SchemaDefinitions.PublishSchemaDefinitionDescriptor.PublishSchemaDefinitionDescriptor(HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder) -> void +HotChocolate.Stitching.SchemaDefinitions.PublishSchemaDefinitionDescriptor.RenameField(HotChocolate.NameString typeName, HotChocolate.NameString fieldName, HotChocolate.NameString newFieldName) -> HotChocolate.Stitching.SchemaDefinitions.IPublishSchemaDefinitionDescriptor! +HotChocolate.Stitching.SchemaDefinitions.PublishSchemaDefinitionDescriptor.RenameType(HotChocolate.NameString typeName, HotChocolate.NameString newTypeName) -> HotChocolate.Stitching.SchemaDefinitions.IPublishSchemaDefinitionDescriptor! +HotChocolate.Stitching.SchemaDefinitions.PublishSchemaDefinitionDescriptor.SetName(HotChocolate.NameString name) -> HotChocolate.Stitching.SchemaDefinitions.IPublishSchemaDefinitionDescriptor! +HotChocolate.Stitching.SchemaDefinitions.PublishSchemaDefinitionDescriptor.SetSchemaDefinitionPublisher(System.Func! publisherFactory) -> HotChocolate.Stitching.SchemaDefinitions.IPublishSchemaDefinitionDescriptor! +HotChocolate.Stitching.SchemaDefinitions.SchemaDefinitionType +HotChocolate.Stitching.SchemaDefinitions.SchemaDefinitionType.Names +HotChocolate.Stitching.SchemaDefinitions.SchemaDefinitionType.SchemaDefinitionType() -> void +HotChocolate.Stitching.ScopedVariableNode +HotChocolate.Stitching.ScopedVariableNode.Equals(HotChocolate.Language.IValueNode? other) -> bool +HotChocolate.Stitching.ScopedVariableNode.Equals(HotChocolate.Stitching.ScopedVariableNode? other) -> bool +HotChocolate.Stitching.ScopedVariableNode.GetNodes() -> System.Collections.Generic.IEnumerable! +HotChocolate.Stitching.ScopedVariableNode.Kind.get -> HotChocolate.Language.SyntaxKind +HotChocolate.Stitching.ScopedVariableNode.Location.get -> HotChocolate.Language.Location? +HotChocolate.Stitching.ScopedVariableNode.Name.get -> HotChocolate.Language.NameNode! +HotChocolate.Stitching.ScopedVariableNode.Scope.get -> HotChocolate.Language.NameNode! +HotChocolate.Stitching.ScopedVariableNode.ScopedVariableNode(HotChocolate.Language.Location? location, HotChocolate.Language.NameNode! scope, HotChocolate.Language.NameNode! name) -> void +HotChocolate.Stitching.ScopedVariableNode.ScopedVariableNode(HotChocolate.Language.NameNode! scope, HotChocolate.Language.NameNode! name) -> void +HotChocolate.Stitching.ScopedVariableNode.ScopedVariableNode(string! scope, string! name) -> void +HotChocolate.Stitching.ScopedVariableNode.ToString(bool indented) -> string! +HotChocolate.Stitching.ScopedVariableNode.ToVariableName() -> string! +HotChocolate.Stitching.ScopedVariableNode.ToVariableNode() -> HotChocolate.Language.VariableNode! +HotChocolate.Stitching.ScopedVariableNode.Value.get -> string! +HotChocolate.Stitching.SelectionPathComponent +HotChocolate.Stitching.SelectionPathComponent.Arguments.get -> System.Collections.Generic.IReadOnlyList! +HotChocolate.Stitching.SelectionPathComponent.Name.get -> HotChocolate.Language.NameNode! +HotChocolate.Stitching.SelectionPathComponent.SelectionPathComponent(HotChocolate.Language.NameNode! name, System.Collections.Generic.IReadOnlyList! arguments) -> void +HotChocolate.Stitching.SourceDirective +HotChocolate.Stitching.SourceDirective.Name.get -> HotChocolate.NameString +HotChocolate.Stitching.SourceDirective.Name.set -> void +HotChocolate.Stitching.SourceDirective.Schema.get -> HotChocolate.NameString +HotChocolate.Stitching.SourceDirective.Schema.set -> void +HotChocolate.Stitching.SourceDirective.SourceDirective() -> void +HotChocolate.Stitching.SourceDirectiveType +HotChocolate.Stitching.SourceDirectiveType.SourceDirectiveType() -> void +HotChocolate.Stitching.Utilities.CopySchemaDefinitionTypeInterceptor +HotChocolate.Stitching.Utilities.CopySchemaDefinitionTypeInterceptor.CopySchemaDefinitionTypeInterceptor() -> void +HotChocolate.Stitching.Utilities.FieldDependency +HotChocolate.Stitching.Utilities.FieldDependency.Equals(HotChocolate.Stitching.Utilities.FieldDependency other) -> bool +HotChocolate.Stitching.Utilities.FieldDependency.FieldDependency() -> void +HotChocolate.Stitching.Utilities.FieldDependency.FieldDependency(HotChocolate.NameString typeName, HotChocolate.NameString fieldName) -> void +HotChocolate.Stitching.Utilities.FieldDependency.FieldName.get -> HotChocolate.NameString +HotChocolate.Stitching.Utilities.FieldDependency.TypeName.get -> HotChocolate.NameString +HotChocolate.Stitching.Utilities.FieldDependencyResolver +HotChocolate.Stitching.Utilities.FieldDependencyResolver.Context +HotChocolate.Stitching.Utilities.FieldDependencyResolver.Context.AddFragment(string! fragmentName) -> HotChocolate.Stitching.Utilities.FieldDependencyResolver.Context! +HotChocolate.Stitching.Utilities.FieldDependencyResolver.Context.Dependencies.get -> System.Collections.Generic.ISet! +HotChocolate.Stitching.Utilities.FieldDependencyResolver.Context.FragmentPath.get -> System.Collections.Immutable.ImmutableHashSet! +HotChocolate.Stitching.Utilities.FieldDependencyResolver.Context.Fragments.get -> System.Collections.Generic.IDictionary! +HotChocolate.Stitching.Utilities.FieldDependencyResolver.Context.SetTypeContext(HotChocolate.Types.INamedOutputType! type) -> HotChocolate.Stitching.Utilities.FieldDependencyResolver.Context! +HotChocolate.Stitching.Utilities.FieldDependencyResolver.Context.TypeContext.get -> HotChocolate.Types.INamedOutputType! +HotChocolate.Stitching.Utilities.FieldDependencyResolver.FieldDependencyResolver(HotChocolate.ISchema! schema) -> void +HotChocolate.Stitching.Utilities.FieldDependencyResolver.GetFieldDependencies(HotChocolate.Language.DocumentNode! document, HotChocolate.Language.FieldNode! field, HotChocolate.Types.INamedOutputType! declaringType) -> System.Collections.Generic.ISet! +HotChocolate.Stitching.Utilities.FieldDependencyResolver.GetFieldDependencies(HotChocolate.Language.DocumentNode! document, HotChocolate.Language.SelectionSetNode! selectionSet, HotChocolate.Types.INamedOutputType! declaringType) -> System.Collections.Generic.ISet! +HotChocolate.Stitching.Utilities.IntrospectionHelper +HotChocolate.Stitching.Utilities.IntrospectionHelper.GetSchemaDefinitionAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! +HotChocolate.Stitching.Utilities.IntrospectionHelper.IntrospectionHelper(System.Net.Http.HttpClient! httpClient, HotChocolate.NameString configuration) -> void +HotChocolate.Stitching.Utilities.IQueryDelegationRewriter +HotChocolate.Stitching.Utilities.IQueryDelegationRewriter.OnRewriteField(HotChocolate.NameString targetSchemaName, HotChocolate.Types.IOutputType! outputType, HotChocolate.Types.IOutputField! outputField, HotChocolate.Language.FieldNode! field) -> HotChocolate.Language.FieldNode! +HotChocolate.Stitching.Utilities.IQueryDelegationRewriter.OnRewriteSelectionSet(HotChocolate.NameString targetSchemaName, HotChocolate.Types.IOutputType! outputType, HotChocolate.Types.IOutputField! outputField, HotChocolate.Language.SelectionSetNode! selectionSet) -> HotChocolate.Language.SelectionSetNode! +HotChocolate.Stitching.Utilities.QueryDelegationRewriterBase +HotChocolate.Stitching.Utilities.QueryDelegationRewriterBase.QueryDelegationRewriterBase() -> void +HotChocolate.Stitching.Utilities.SchemaExtensionsRewriter +HotChocolate.Stitching.Utilities.SchemaExtensionsRewriter.SchemaActions.get -> System.Collections.Generic.IReadOnlyList! +HotChocolate.Stitching.Utilities.SchemaExtensionsRewriter.SchemaExtensionsRewriter() -> void +Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRequestExecutorExtensions +override HotChocolate.Stitching.ComputedDirectiveType.Configure(HotChocolate.Types.IDirectiveTypeDescriptor! descriptor) -> void +override HotChocolate.Stitching.DelegateDirectiveType.Configure(HotChocolate.Types.IDirectiveTypeDescriptor! descriptor) -> void +override HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.RewriteArgument(HotChocolate.Language.ArgumentNode! node, HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.Context! context) -> HotChocolate.Language.ArgumentNode! +override HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.RewriteDirective(HotChocolate.Language.DirectiveNode! node, HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.Context! context) -> HotChocolate.Language.DirectiveNode! +override HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.RewriteField(HotChocolate.Language.FieldNode! node, HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.Context! context) -> HotChocolate.Language.FieldNode! +override HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.RewriteFragmentDefinition(HotChocolate.Language.FragmentDefinitionNode! node, HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.Context! context) -> HotChocolate.Language.FragmentDefinitionNode! +override HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.RewriteFragmentSpread(HotChocolate.Language.FragmentSpreadNode! node, HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.Context! context) -> HotChocolate.Language.FragmentSpreadNode! +override HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.RewriteInlineFragment(HotChocolate.Language.InlineFragmentNode! node, HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.Context! context) -> HotChocolate.Language.InlineFragmentNode! +override HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.RewriteObjectField(HotChocolate.Language.ObjectFieldNode! node, HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.Context! context) -> HotChocolate.Language.ObjectFieldNode! +override HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.RewriteSelectionSet(HotChocolate.Language.SelectionSetNode! node, HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.Context! context) -> HotChocolate.Language.SelectionSetNode! +override HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.RewriteVariable(HotChocolate.Language.VariableNode! node, HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.Context! context) -> HotChocolate.Language.VariableNode! +override HotChocolate.Stitching.Delegation.ExtractFieldQuerySyntaxRewriter.VisitFragmentDefinitions.get -> bool +override HotChocolate.Stitching.Merge.AddSchemaExtensionRewriter.RewriteEnumTypeDefinition(HotChocolate.Language.EnumTypeDefinitionNode! node, HotChocolate.Stitching.Merge.AddSchemaExtensionRewriter.MergeContext! context) -> HotChocolate.Language.EnumTypeDefinitionNode! +override HotChocolate.Stitching.Merge.AddSchemaExtensionRewriter.RewriteInputObjectTypeDefinition(HotChocolate.Language.InputObjectTypeDefinitionNode! node, HotChocolate.Stitching.Merge.AddSchemaExtensionRewriter.MergeContext! context) -> HotChocolate.Language.InputObjectTypeDefinitionNode! +override HotChocolate.Stitching.Merge.AddSchemaExtensionRewriter.RewriteInterfaceTypeDefinition(HotChocolate.Language.InterfaceTypeDefinitionNode! node, HotChocolate.Stitching.Merge.AddSchemaExtensionRewriter.MergeContext! context) -> HotChocolate.Language.InterfaceTypeDefinitionNode! +override HotChocolate.Stitching.Merge.AddSchemaExtensionRewriter.RewriteObjectTypeDefinition(HotChocolate.Language.ObjectTypeDefinitionNode! node, HotChocolate.Stitching.Merge.AddSchemaExtensionRewriter.MergeContext! context) -> HotChocolate.Language.ObjectTypeDefinitionNode! +override HotChocolate.Stitching.Merge.AddSchemaExtensionRewriter.RewriteScalarTypeDefinition(HotChocolate.Language.ScalarTypeDefinitionNode! node, HotChocolate.Stitching.Merge.AddSchemaExtensionRewriter.MergeContext! context) -> HotChocolate.Language.ScalarTypeDefinitionNode! +override HotChocolate.Stitching.Merge.AddSchemaExtensionRewriter.RewriteUnionTypeDefinition(HotChocolate.Language.UnionTypeDefinitionNode! node, HotChocolate.Stitching.Merge.AddSchemaExtensionRewriter.MergeContext! context) -> HotChocolate.Language.UnionTypeDefinitionNode! +override HotChocolate.Stitching.SchemaDefinitions.SchemaDefinitionType.Configure(HotChocolate.Types.IObjectTypeDescriptor! descriptor) -> void +override HotChocolate.Stitching.ScopedVariableNode.Equals(object? obj) -> bool +override HotChocolate.Stitching.ScopedVariableNode.GetHashCode() -> int +override HotChocolate.Stitching.ScopedVariableNode.ToString() -> string! +override HotChocolate.Stitching.SelectionPathComponent.ToString() -> string! +override HotChocolate.Stitching.SourceDirectiveType.Configure(HotChocolate.Types.IDirectiveTypeDescriptor! descriptor) -> void +override HotChocolate.Stitching.Utilities.CopySchemaDefinitionTypeInterceptor.OnAfterCompleteType(HotChocolate.Configuration.ITypeCompletionContext! completionContext, HotChocolate.Types.Descriptors.Definitions.DefinitionBase? definition, System.Collections.Generic.IDictionary! contextData) -> void +override HotChocolate.Stitching.Utilities.FieldDependency.Equals(object? obj) -> bool +override HotChocolate.Stitching.Utilities.FieldDependency.GetHashCode() -> int +override HotChocolate.Stitching.Utilities.FieldDependencyResolver.VisitField(HotChocolate.Language.FieldNode! node, HotChocolate.Stitching.Utilities.FieldDependencyResolver.Context! context) -> void +override HotChocolate.Stitching.Utilities.FieldDependencyResolver.VisitFragmentDefinition(HotChocolate.Language.FragmentDefinitionNode! node, HotChocolate.Stitching.Utilities.FieldDependencyResolver.Context! context) -> void +override HotChocolate.Stitching.Utilities.FieldDependencyResolver.VisitFragmentDefinitions.get -> bool +override HotChocolate.Stitching.Utilities.FieldDependencyResolver.VisitFragmentSpread(HotChocolate.Language.FragmentSpreadNode! node, HotChocolate.Stitching.Utilities.FieldDependencyResolver.Context! context) -> void +override HotChocolate.Stitching.Utilities.FieldDependencyResolver.VisitInlineFragment(HotChocolate.Language.InlineFragmentNode! node, HotChocolate.Stitching.Utilities.FieldDependencyResolver.Context! context) -> void +override HotChocolate.Stitching.Utilities.SchemaExtensionsRewriter.RewriteDirective(HotChocolate.Language.DirectiveNode! node, string! context) -> HotChocolate.Language.DirectiveNode! +override HotChocolate.Stitching.Utilities.SchemaExtensionsRewriter.RewriteSchemaExtension(HotChocolate.Language.SchemaExtensionNode! node, string! context) -> HotChocolate.Language.SchemaExtensionNode! +static HotChocolate.Stitching.Delegation.RemoteQueryBuilder.New() -> HotChocolate.Stitching.Delegation.RemoteQueryBuilder! +static HotChocolate.Stitching.Delegation.ScopedVariables.ScopeNames.Arguments.get -> HotChocolate.NameString +static HotChocolate.Stitching.Delegation.ScopedVariables.ScopeNames.ContextData.get -> HotChocolate.NameString +static HotChocolate.Stitching.Delegation.ScopedVariables.ScopeNames.Fields.get -> HotChocolate.NameString +static HotChocolate.Stitching.Delegation.ScopedVariables.ScopeNames.ScopedContextData.get -> HotChocolate.NameString +static HotChocolate.Stitching.ErrorHelper.HttpRequestClient_HttpError(System.Net.HttpStatusCode statusCode, string? responseBody) -> HotChocolate.IError! +static HotChocolate.Stitching.Merge.MergeSyntaxNodeExtensions.AddDelegationPath(this HotChocolate.Language.FieldDefinitionNode! field, HotChocolate.NameString schemaName, bool overwrite) -> HotChocolate.Language.FieldDefinitionNode! +static HotChocolate.Stitching.Merge.MergeSyntaxNodeExtensions.AddDelegationPath(this HotChocolate.Language.FieldDefinitionNode! field, HotChocolate.NameString schemaName, HotChocolate.Stitching.SelectionPathComponent! selectionPath, bool overwrite) -> HotChocolate.Language.FieldDefinitionNode! +static HotChocolate.Stitching.Merge.MergeSyntaxNodeExtensions.AddDelegationPath(this HotChocolate.Language.FieldDefinitionNode! field, HotChocolate.NameString schemaName, HotChocolate.Stitching.SelectionPathComponent! selectionPath) -> HotChocolate.Language.FieldDefinitionNode! +static HotChocolate.Stitching.Merge.MergeSyntaxNodeExtensions.AddDelegationPath(this HotChocolate.Language.FieldDefinitionNode! field, HotChocolate.NameString schemaName, string! delegationPath, bool overwrite) -> HotChocolate.Language.FieldDefinitionNode! +static HotChocolate.Stitching.Merge.MergeSyntaxNodeExtensions.AddDelegationPath(this HotChocolate.Language.FieldDefinitionNode! field, HotChocolate.NameString schemaName, string! delegationPath) -> HotChocolate.Language.FieldDefinitionNode! +static HotChocolate.Stitching.Merge.MergeSyntaxNodeExtensions.AddDelegationPath(this HotChocolate.Language.FieldDefinitionNode! field, HotChocolate.NameString schemaName, System.Collections.Generic.IReadOnlyCollection! selectionPath, bool overwrite) -> HotChocolate.Language.FieldDefinitionNode! +static HotChocolate.Stitching.Merge.MergeSyntaxNodeExtensions.AddDelegationPath(this HotChocolate.Language.FieldDefinitionNode! field, HotChocolate.NameString schemaName, System.Collections.Generic.IReadOnlyCollection! selectionPath) -> HotChocolate.Language.FieldDefinitionNode! +static HotChocolate.Stitching.Merge.MergeSyntaxNodeExtensions.AddDelegationPath(this HotChocolate.Language.FieldDefinitionNode! field, HotChocolate.NameString schemaName) -> HotChocolate.Language.FieldDefinitionNode! +static HotChocolate.Stitching.Merge.MergeSyntaxNodeExtensions.GetOriginalName(this HotChocolate.Language.INamedSyntaxNode! typeDefinition, HotChocolate.NameString schemaName) -> HotChocolate.NameString +static HotChocolate.Stitching.Merge.MergeSyntaxNodeExtensions.IsFromSchema(this HotChocolate.Language.INamedSyntaxNode! typeDefinition, HotChocolate.NameString schemaName) -> bool +static HotChocolate.Stitching.Merge.MergeSyntaxNodeExtensions.Rename(this HotChocolate.Language.DirectiveDefinitionNode! directiveDefinition, HotChocolate.NameString newName, params HotChocolate.NameString[]! schemaNames) -> HotChocolate.Language.DirectiveDefinitionNode! +static HotChocolate.Stitching.Merge.MergeSyntaxNodeExtensions.Rename(this HotChocolate.Language.DirectiveDefinitionNode! directiveDefinition, HotChocolate.NameString newName, System.Collections.Generic.IEnumerable! schemaNames) -> HotChocolate.Language.DirectiveDefinitionNode! +static HotChocolate.Stitching.Merge.MergeSyntaxNodeExtensions.Rename(this HotChocolate.Language.EnumTypeDefinitionNode! enumTypeDefinition, HotChocolate.NameString newName, params HotChocolate.NameString[]! schemaNames) -> HotChocolate.Language.EnumTypeDefinitionNode! +static HotChocolate.Stitching.Merge.MergeSyntaxNodeExtensions.Rename(this HotChocolate.Language.EnumTypeDefinitionNode! enumTypeDefinition, HotChocolate.NameString newName, System.Collections.Generic.IEnumerable! schemaNames) -> HotChocolate.Language.EnumTypeDefinitionNode! +static HotChocolate.Stitching.Merge.MergeSyntaxNodeExtensions.Rename(this HotChocolate.Language.FieldDefinitionNode! enumTypeDefinition, HotChocolate.NameString newName, params HotChocolate.NameString[]! schemaNames) -> HotChocolate.Language.FieldDefinitionNode! +static HotChocolate.Stitching.Merge.MergeSyntaxNodeExtensions.Rename(this HotChocolate.Language.FieldDefinitionNode! enumTypeDefinition, HotChocolate.NameString newName, System.Collections.Generic.IEnumerable! schemaNames) -> HotChocolate.Language.FieldDefinitionNode! +static HotChocolate.Stitching.Merge.MergeSyntaxNodeExtensions.Rename(this HotChocolate.Language.InputObjectTypeDefinitionNode! enumTypeDefinition, HotChocolate.NameString newName, params HotChocolate.NameString[]! schemaNames) -> HotChocolate.Language.InputObjectTypeDefinitionNode! +static HotChocolate.Stitching.Merge.MergeSyntaxNodeExtensions.Rename(this HotChocolate.Language.InputObjectTypeDefinitionNode! enumTypeDefinition, HotChocolate.NameString newName, System.Collections.Generic.IEnumerable! schemaNames) -> HotChocolate.Language.InputObjectTypeDefinitionNode! +static HotChocolate.Stitching.Merge.MergeSyntaxNodeExtensions.Rename(this HotChocolate.Language.InputValueDefinitionNode! enumTypeDefinition, HotChocolate.NameString newName, params HotChocolate.NameString[]! schemaNames) -> HotChocolate.Language.InputValueDefinitionNode! +static HotChocolate.Stitching.Merge.MergeSyntaxNodeExtensions.Rename(this HotChocolate.Language.InputValueDefinitionNode! enumTypeDefinition, HotChocolate.NameString newName, System.Collections.Generic.IEnumerable! schemaNames) -> HotChocolate.Language.InputValueDefinitionNode! +static HotChocolate.Stitching.Merge.MergeSyntaxNodeExtensions.Rename(this HotChocolate.Language.InterfaceTypeDefinitionNode! interfaceTypeDefinition, HotChocolate.NameString newName, params HotChocolate.NameString[]! schemaNames) -> HotChocolate.Language.InterfaceTypeDefinitionNode! +static HotChocolate.Stitching.Merge.MergeSyntaxNodeExtensions.Rename(this HotChocolate.Language.InterfaceTypeDefinitionNode! interfaceTypeDefinition, HotChocolate.NameString newName, System.Collections.Generic.IEnumerable! schemaNames) -> HotChocolate.Language.InterfaceTypeDefinitionNode! +static HotChocolate.Stitching.Merge.MergeSyntaxNodeExtensions.Rename(this HotChocolate.Language.ObjectTypeDefinitionNode! objectTypeDefinition, HotChocolate.NameString newName, params HotChocolate.NameString[]! schemaNames) -> HotChocolate.Language.ObjectTypeDefinitionNode! +static HotChocolate.Stitching.Merge.MergeSyntaxNodeExtensions.Rename(this HotChocolate.Language.ObjectTypeDefinitionNode! objectTypeDefinition, HotChocolate.NameString newName, System.Collections.Generic.IEnumerable! schemaNames) -> HotChocolate.Language.ObjectTypeDefinitionNode! +static HotChocolate.Stitching.Merge.MergeSyntaxNodeExtensions.Rename(this HotChocolate.Language.ScalarTypeDefinitionNode! enumTypeDefinition, HotChocolate.NameString newName, params HotChocolate.NameString[]! schemaNames) -> HotChocolate.Language.ScalarTypeDefinitionNode! +static HotChocolate.Stitching.Merge.MergeSyntaxNodeExtensions.Rename(this HotChocolate.Language.ScalarTypeDefinitionNode! enumTypeDefinition, HotChocolate.NameString newName, System.Collections.Generic.IEnumerable! schemaNames) -> HotChocolate.Language.ScalarTypeDefinitionNode! +static HotChocolate.Stitching.Merge.MergeSyntaxNodeExtensions.Rename(this HotChocolate.Language.UnionTypeDefinitionNode! unionTypeDefinition, HotChocolate.NameString newName, params HotChocolate.NameString[]! schemaNames) -> HotChocolate.Language.UnionTypeDefinitionNode! +static HotChocolate.Stitching.Merge.MergeSyntaxNodeExtensions.Rename(this HotChocolate.Language.UnionTypeDefinitionNode! unionTypeDefinition, HotChocolate.NameString newName, System.Collections.Generic.IEnumerable! schemaNames) -> HotChocolate.Language.UnionTypeDefinitionNode! +static HotChocolate.Stitching.Merge.MergeSyntaxNodeExtensions.Rename(this T enumTypeDefinition, HotChocolate.NameString newName, params HotChocolate.NameString[]! schemaNames) -> T +static HotChocolate.Stitching.Merge.MergeSyntaxNodeExtensions.Rename(this T typeDefinitionNode, HotChocolate.NameString newName, System.Collections.Generic.IEnumerable! schemaNames) -> T +static HotChocolate.Stitching.Merge.SchemaMerger.New() -> HotChocolate.Stitching.Merge.SchemaMerger! +static HotChocolate.Stitching.Merge.SchemaMergerExtensions.AddDirectiveMergeHandler(this HotChocolate.Stitching.Merge.ISchemaMerger! merger) -> HotChocolate.Stitching.Merge.ISchemaMerger! +static HotChocolate.Stitching.Merge.SchemaMergerExtensions.AddMergeHandler(this HotChocolate.Stitching.Merge.ISchemaMerger! merger) -> HotChocolate.Stitching.Merge.ISchemaMerger! +static HotChocolate.Stitching.Merge.SchemaMergerExtensions.AddTypeMergeHandler(this HotChocolate.Stitching.Merge.ISchemaMerger! merger) -> HotChocolate.Stitching.Merge.ISchemaMerger! +static HotChocolate.Stitching.Merge.SchemaMergerExtensions.IgnoreField(this HotChocolate.Stitching.Merge.ISchemaMerger! schemaMerger, HotChocolate.Resolvers.FieldReference! field, HotChocolate.NameString? schemaName = null) -> HotChocolate.Stitching.Merge.ISchemaMerger! +static HotChocolate.Stitching.Merge.SchemaMergerExtensions.IgnoreRootTypes(this HotChocolate.Stitching.Merge.ISchemaMerger! schemaMerger, HotChocolate.NameString? schemaName = null) -> HotChocolate.Stitching.Merge.ISchemaMerger! +static HotChocolate.Stitching.Merge.SchemaMergerExtensions.IgnoreType(this HotChocolate.Stitching.Merge.ISchemaMerger! schemaMerger, HotChocolate.NameString typeName, HotChocolate.NameString? schemaName = null) -> HotChocolate.Stitching.Merge.ISchemaMerger! +static HotChocolate.Stitching.Merge.SchemaMergerExtensions.RenameField(this HotChocolate.Stitching.Merge.ISchemaMerger! schemaMerger, HotChocolate.Resolvers.FieldReference! field, HotChocolate.NameString newFieldName, HotChocolate.NameString? schemaName = null) -> HotChocolate.Stitching.Merge.ISchemaMerger! +static HotChocolate.Stitching.Merge.SchemaMergerExtensions.RenameType(this HotChocolate.Stitching.Merge.ISchemaMerger! schemaMerger, HotChocolate.NameString originalTypeName, HotChocolate.NameString newTypeName, HotChocolate.NameString? schemaName = null) -> HotChocolate.Stitching.Merge.ISchemaMerger! +static HotChocolate.Stitching.Merge.TypeInfoExtensions.IsMutationType(this HotChocolate.Stitching.Merge.ITypeInfo! typeInfo) -> bool +static HotChocolate.Stitching.Merge.TypeInfoExtensions.IsQueryType(this HotChocolate.Stitching.Merge.ITypeInfo! typeInfo) -> bool +static HotChocolate.Stitching.Merge.TypeInfoExtensions.IsSubscriptionType(this HotChocolate.Stitching.Merge.ITypeInfo! typeInfo) -> bool +static HotChocolate.Stitching.SchemaDefinitions.SchemaDefinitionType.Names.Document.get -> HotChocolate.NameString +static HotChocolate.Stitching.SchemaDefinitions.SchemaDefinitionType.Names.ExtensionDocuments.get -> HotChocolate.NameString +static HotChocolate.Stitching.SchemaDefinitions.SchemaDefinitionType.Names.Name.get -> HotChocolate.NameString +static HotChocolate.Stitching.SchemaDefinitions.SchemaDefinitionType.Names.SchemaDefinition.get -> HotChocolate.NameString +static HotChocolate.Stitching.Utilities.FieldDependencyResolver.Context.New(HotChocolate.Types.INamedOutputType! typeContext, System.Collections.Generic.IDictionary! fragments) -> HotChocolate.Stitching.Utilities.FieldDependencyResolver.Context! +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRequestExecutorExtensions.AddDirectiveMergeHandler(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRequestExecutorExtensions.AddDirectiveMergeRule(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, HotChocolate.Stitching.Merge.MergeDirectiveRuleFactory! mergeRuleFactory) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRequestExecutorExtensions.AddDocumentRewriter(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, HotChocolate.Stitching.Merge.Rewriters.IDocumentRewriter! rewriter) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRequestExecutorExtensions.AddDocumentRewriter(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, HotChocolate.Stitching.Merge.Rewriters.RewriteDocumentDelegate! rewrite) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRequestExecutorExtensions.AddLocalSchema(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, HotChocolate.NameString schemaName, bool ignoreRootTypes = false) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRequestExecutorExtensions.AddMergedDocumentRewriter(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, System.Func! rewrite) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRequestExecutorExtensions.AddMergedDocVisitor(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, System.Action! visit) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRequestExecutorExtensions.AddRemoteSchema(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, HotChocolate.NameString schemaName, bool ignoreRootTypes = false) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRequestExecutorExtensions.AddRemoteSchema(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, HotChocolate.NameString schemaName, System.Func>! loadSchema, bool ignoreRootTypes = false) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRequestExecutorExtensions.AddRemoteSchemaFromFile(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, HotChocolate.NameString schemaName, string! fileName, bool ignoreRootTypes = false) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRequestExecutorExtensions.AddRemoteSchemaFromString(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, HotChocolate.NameString schemaName, string! schemaSdl, bool ignoreRootTypes = false) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRequestExecutorExtensions.AddTypeExtensionsFromFile(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, string! fileName) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRequestExecutorExtensions.AddTypeExtensionsFromResource(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, System.Reflection.Assembly! assembly, string! key) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRequestExecutorExtensions.AddTypeExtensionsFromString(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, string! schemaSdl) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRequestExecutorExtensions.AddTypeMergeHandler(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRequestExecutorExtensions.AddTypeMergeRule(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, HotChocolate.Stitching.Merge.MergeTypeRuleFactory! mergeRuleFactory) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRequestExecutorExtensions.AddTypeRewriter(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, HotChocolate.Stitching.Merge.Rewriters.ITypeRewriter! rewriter) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRequestExecutorExtensions.AddTypeRewriter(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, HotChocolate.Stitching.Merge.Rewriters.RewriteTypeDefinitionDelegate! rewrite) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRequestExecutorExtensions.IgnoreField(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, HotChocolate.NameString typeName, HotChocolate.NameString fieldName, HotChocolate.NameString? schemaName = null) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRequestExecutorExtensions.IgnoreField(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, HotChocolate.Resolvers.FieldReference! field, HotChocolate.NameString? schemaName = null) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRequestExecutorExtensions.IgnoreRootTypes(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, HotChocolate.NameString? schemaName = null) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRequestExecutorExtensions.IgnoreType(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, HotChocolate.NameString typeName, HotChocolate.NameString? schemaName = null) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRequestExecutorExtensions.PublishSchemaDefinition(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, System.Action! configure) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRequestExecutorExtensions.RenameField(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, HotChocolate.NameString typeName, HotChocolate.NameString fieldName, HotChocolate.NameString argumentName, HotChocolate.NameString newArgumentName, HotChocolate.NameString? schemaName = null) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRequestExecutorExtensions.RenameField(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, HotChocolate.NameString typeName, HotChocolate.NameString fieldName, HotChocolate.NameString newFieldName, HotChocolate.NameString? schemaName = null) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRequestExecutorExtensions.RenameField(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, HotChocolate.Resolvers.FieldReference! field, HotChocolate.NameString argumentName, HotChocolate.NameString newArgumentName, HotChocolate.NameString? schemaName = null) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRequestExecutorExtensions.RenameField(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, HotChocolate.Resolvers.FieldReference! field, HotChocolate.NameString newFieldName, HotChocolate.NameString? schemaName = null) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRequestExecutorExtensions.RenameType(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, HotChocolate.NameString originalTypeName, HotChocolate.NameString newTypeName, HotChocolate.NameString? schemaName = null) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRequestExecutorExtensions.RewriteType(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, HotChocolate.NameString originalTypeName, HotChocolate.NameString newTypeName, HotChocolate.NameString schemaName) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRequestExecutorExtensions.UseHttpRequestPipeline(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! +static Microsoft.Extensions.DependencyInjection.HotChocolateStitchingRequestExecutorExtensions.UseHttpRequests(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! +virtual HotChocolate.Stitching.Pipeline.HttpStitchingRequestInterceptor.OnCreateRequestAsync(HotChocolate.NameString targetSchema, HotChocolate.Execution.IQueryRequest! request, System.Net.Http.HttpRequestMessage! requestMessage, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +virtual HotChocolate.Stitching.Utilities.QueryDelegationRewriterBase.OnRewriteField(HotChocolate.NameString targetSchemaName, HotChocolate.Types.IOutputType! outputType, HotChocolate.Types.IOutputField! outputField, HotChocolate.Language.FieldNode! field) -> HotChocolate.Language.FieldNode! +virtual HotChocolate.Stitching.Utilities.QueryDelegationRewriterBase.OnRewriteSelectionSet(HotChocolate.NameString targetSchemaName, HotChocolate.Types.IOutputType! outputType, HotChocolate.Types.IOutputField! outputField, HotChocolate.Language.SelectionSetNode! selectionSet) -> HotChocolate.Language.SelectionSetNode! diff --git a/src/HotChocolate/Stitching/src/Stitching/PublicAPI.Unshipped.txt b/src/HotChocolate/Stitching/src/Stitching/PublicAPI.Unshipped.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/HotChocolate/Stitching/src/Stitching/Requests/BufferedRequest.cs b/src/HotChocolate/Stitching/src/Stitching/Requests/BufferedRequest.cs new file mode 100644 index 00000000000..eb44c23bbf7 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Requests/BufferedRequest.cs @@ -0,0 +1,155 @@ +using HotChocolate.Execution; +using HotChocolate.Language; +using HotChocolate.Stitching.Properties; +using HotChocolate.Types; +using HotChocolate.Utilities; +using Microsoft.Extensions.DependencyInjection; +using static HotChocolate.Stitching.ThrowHelper; + +namespace HotChocolate.Stitching.Requests; + +internal sealed class BufferedRequest +{ + private BufferedRequest( + IQueryRequest request, + DocumentNode document, + OperationDefinitionNode operation) + { + Request = request; + Document = document; + Operation = operation; + Promise = new TaskCompletionSource( + TaskCreationOptions.RunContinuationsAsynchronously); + } + + public IQueryRequest Request { get; } + + public DocumentNode Document { get; } + + public OperationDefinitionNode Operation { get; } + + public TaskCompletionSource Promise { get; } + + public IDictionary? Aliases { get; set; } + + public static BufferedRequest Create(IQueryRequest request, ISchema schema) + { + if (request is null) + { + throw new ArgumentNullException(nameof(request)); + } + + if (schema == null) + { + throw new ArgumentNullException(nameof(schema)); + } + + if (request.Query is null) + { + throw new ArgumentException( + StitchingResources.BufferedRequest_Create_QueryCannotBeNull, + nameof(request)); + } + + var document = + request.Query is QueryDocument doc + ? doc.Document + : Utf8GraphQLParser.Parse(request.Query.AsSpan()); + + var operation = + ResolveOperation(document, request.OperationName); + + request = NormalizeRequest(request, operation, schema); + + return new BufferedRequest(request, document, operation); + } + + internal static OperationDefinitionNode ResolveOperation( + DocumentNode document, + string? operationName) + { + var operation = operationName is null + ? document.Definitions.OfType().SingleOrDefault() + : document.Definitions.OfType().SingleOrDefault( + t => operationName.EqualsOrdinal(t.Name?.Value)); + + if (operation is null) + { + throw BufferedRequest_OperationNotFound(document); + } + + return operation; + } + + private static IQueryRequest NormalizeRequest( + IQueryRequest request, + OperationDefinitionNode operation, + ISchema schema) + { + if (request.VariableValues is { Count: > 0 }) + { + var converter = schema.Services.GetTypeConverter(); + var formatter = schema.Services.GetRequiredService(); + var builder = QueryRequestBuilder.From(request); + + foreach (var variable in request.VariableValues) + { + if (variable.Value is not IValueNode) + { + builder.SetVariableValue( + variable.Key, + RewriteVariable( + operation, + variable.Key, + variable.Value, + schema, + converter, + formatter)); + } + } + + return builder.Create(); + } + + return request; + } + + private static IValueNode RewriteVariable( + OperationDefinitionNode operation, + string name, + object? value, + ISchema schema, + ITypeConverter converter, + InputFormatter inputFormatter) + { + var variableDefinition = + operation.VariableDefinitions.FirstOrDefault(t => + string.Equals(t.Variable.Name.Value, name, StringComparison.Ordinal)); + + if (variableDefinition is not null && + schema.TryGetType( + variableDefinition.Type.NamedType().Name.Value, + out var namedType)) + { + var variableType = (IInputType)variableDefinition.Type.ToType(namedType); + + if (value is not null && + !variableType.RuntimeType.IsInstanceOfType(value) && + converter.TryConvert( + value.GetType(), + variableType.RuntimeType, + value, + out var converted)) + { + value = converted; + } + + return inputFormatter.FormatValue( + value, + variableType, + PathFactory.Instance.New(variableDefinition.Variable.Name.Value)); + } + + throw BufferedRequest_VariableDoesNotExist(name); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Requests/IRemoteRequestExecutor.cs b/src/HotChocolate/Stitching/src/Stitching/Requests/IRemoteRequestExecutor.cs new file mode 100644 index 00000000000..78b7e21bb9f --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Requests/IRemoteRequestExecutor.cs @@ -0,0 +1,51 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using HotChocolate.Execution; + +namespace HotChocolate.Stitching.Requests; + +/// +/// This remote executor delegates GraphQL query, mutation and subscription requests for the +/// remote to the GraphQL server that can process them. +/// +public interface IRemoteRequestExecutor +{ + /// + /// Gets the schema to which this executor is bound to. + /// + ISchema Schema { get; } + + /// + /// Gets the services that are bound to this executor. + /// + IServiceProvider Services { get; } + + /// + /// Executes the given GraphQL . + /// + /// + /// The GraphQL request object. + /// + /// + /// The cancellation token. + /// + /// + /// Returns the execution result of the given GraphQL . + /// + /// If the request operation is a simple query or mutation the result is a + /// . + /// + /// If the request operation is a query or mutation where data is deferred, streamed or + /// includes live data the result is a where each result + /// that the yields is a . + /// + /// If the request operation is a subscription the result is a + /// where each result that the + /// yields is a + /// . + /// + Task ExecuteAsync( + IQueryRequest request, + CancellationToken cancellationToken = default); +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Requests/IStitchingContext.cs b/src/HotChocolate/Stitching/src/Stitching/Requests/IStitchingContext.cs new file mode 100644 index 00000000000..54e125f4ceb --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Requests/IStitchingContext.cs @@ -0,0 +1,8 @@ +namespace HotChocolate.Stitching.Requests; + +public interface IStitchingContext +{ + IRemoteRequestExecutor GetRemoteRequestExecutor(string schemaName); + + ISchema GetRemoteSchema(string schemaName); +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Requests/MergeRequestHelper.cs b/src/HotChocolate/Stitching/src/Stitching/Requests/MergeRequestHelper.cs new file mode 100644 index 00000000000..dc0307b86bc --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Requests/MergeRequestHelper.cs @@ -0,0 +1,306 @@ +using System.Buffers; +using System.Diagnostics.CodeAnalysis; +using HotChocolate.Execution; +using HotChocolate.Execution.Processing; +using HotChocolate.Language; +using static HotChocolate.Stitching.WellKnownContextData; + +namespace HotChocolate.Stitching.Requests; + +internal static class MergeRequestHelper +{ + public static IEnumerable<(IQueryRequest, IEnumerable)> MergeRequests( + IEnumerable requests) + { + foreach (var group in requests.GroupBy(t => t.Operation.Operation)) + { + var rewriter = new MergeRequestRewriter(); + var variableValues = new Dictionary(); + + var operationName = group + .Select(r => r.Request.OperationName) + .Where(n => n != null) + .Distinct() + .FirstOrDefault(); + + if (operationName is not null) + { + rewriter.SetOperationName(new NameNode(operationName)); + } + + var i = 0; + BufferedRequest first = null!; + foreach (var request in group) + { + first ??= request; + MergeRequest(request, rewriter, variableValues, $"__{i++}_"); + } + + var batch = + QueryRequestBuilder.New() + .SetQuery(rewriter.Merge()) + .SetOperation(operationName) + .SetVariableValues(variableValues) + .TrySetServices(first.Request.Services) + .Create(); + + yield return (batch, group); + } + } + + public static void DispatchResults( + IQueryResult mergedResult, + IEnumerable requests) + { + try + { + var handledErrors = new HashSet(); + BufferedRequest? current = null; + QueryResultBuilder? resultBuilder = null; + + foreach (var request in requests) + { + if (current is not null && resultBuilder is not null) + { + current.Promise.SetResult(resultBuilder.Create()); + } + + try + { + current = request; + resultBuilder = ExtractResult(request.Aliases!, mergedResult, handledErrors); + } + catch (Exception ex) + { + current = null; + resultBuilder = null; + request.Promise.SetException(ex); + } + } + + if (current is not null && resultBuilder is not null) + { + if (mergedResult.Errors is not null && + handledErrors.Count < mergedResult.Errors.Count) + { + foreach (var error in mergedResult.Errors.Except(handledErrors)) + { + resultBuilder.AddError(error); + } + } + + handledErrors.Clear(); + current.Promise.SetResult(resultBuilder.Create()); + } + } + catch (Exception ex) + { + foreach (var request in requests) + { + request.Promise.TrySetException(ex); + } + } + } + + private static void MergeRequest( + BufferedRequest bufferedRequest, + MergeRequestRewriter rewriter, + IDictionary variableValues, + string requestPrefix) + { + MergeVariables( + bufferedRequest.Request.VariableValues, + variableValues, + requestPrefix); + + var isAutoGenerated = + bufferedRequest.Request.ContextData?.ContainsKey(IsAutoGenerated) ?? false; + + bufferedRequest.Aliases = rewriter.AddQuery( + bufferedRequest, + requestPrefix, + isAutoGenerated); + } + + private static void MergeVariables( + IReadOnlyDictionary? original, + IDictionary merged, + string requestPrefix) + { + if (original is not null) + { + foreach (var item in original) + { + var variableName = item.Key.CreateNewName(requestPrefix); + merged.Add(variableName, item.Value); + } + } + } + + // This method extracts the relevant data from a merged result for a specific result. + private static QueryResultBuilder ExtractResult( + IDictionary aliases, + IQueryResult mergedResult, + ICollection handledErrors) + { + var result = QueryResultBuilder.New(); + + // We first try to identify and copy data segments that belong to our specific result. + ExtractData(aliases, mergedResult, result); + + // After extracting the data, we will try to find errors that can be associated with + // our specific request for which we are trying to branch out the result. + ExtractErrors(aliases, mergedResult, handledErrors, result); + + // Last but not least we will copy all extensions and contextData over + // to the specific responses. + if (mergedResult.Extensions is not null) + { + result.SetExtensions(mergedResult.Extensions); + } + + if (mergedResult.ContextData is not null) + { + foreach (var item in mergedResult.ContextData) + { + result.SetContextData(item.Key, item.Value); + } + } + + return result; + } + + private static void ExtractData( + IDictionary aliases, + IQueryResult mergedResult, + QueryResultBuilder result) + { + var data = new ObjectResult(); + data.EnsureCapacity(aliases.Count); + var i = 0; + + if (mergedResult.Data is not null) + { + foreach (var alias in aliases) + { + if (mergedResult.Data.TryGetValue(alias.Key, out var o)) + { + data.SetValueUnsafe(i++, alias.Value, o); + } + } + } + else + { + foreach (var alias in aliases) + { + data.SetValueUnsafe(i++, alias.Value, null); + } + } + + result.SetData(data); + } + + private static void ExtractErrors( + IDictionary aliases, + IQueryResult mergedResult, + ICollection handledErrors, + QueryResultBuilder result) + { + if (mergedResult.Errors is not null) + { + foreach (var error in mergedResult.Errors) + { + if (TryResolveField(error, aliases, out var responseName)) + { + handledErrors.Add(error); + result.AddError(RewriteError(error, responseName)); + } + } + } + } + + private static IError RewriteError(IError error, string responseName) + { + if (error.Path is null) + { + return error; + } + + return error.WithPath(error.Path.Length == 1 + ? PathFactory.Instance.New(responseName) + : ReplaceRoot(error.Path, responseName)); + } + + private static bool TryResolveField( + IError error, + IDictionary aliases, + [NotNullWhen(true)] out string? responseName) + { + if (GetRoot(error.Path) is NamePathSegment root && + aliases.TryGetValue(root.Name, out var s)) + { + responseName = s; + return true; + } + + responseName = null; + return false; + } + + private static Path? GetRoot(Path? path) + { + var current = path; + + if (current is null || current.IsRoot) + { + return null; + } + + while (!current.Parent.IsRoot) + { + current = current.Parent; + } + + return current; + } + + private static Path ReplaceRoot(Path path, string responseName) + { + var depth = path.Length + 1; + var buffer = ArrayPool.Shared.Rent(depth); + var paths = buffer.AsSpan().Slice(0, depth); + + try + { + var current = path; + + do + { + paths[--depth] = current; + current = current.Parent; + } while (!current.IsRoot); + + paths = paths.Slice(1); + + current = PathFactory.Instance.New(responseName); + + for (var i = 0; i < paths.Length; i++) + { + if (paths[i] is IndexerPathSegment index) + { + current = PathFactory.Instance.Append(current, index.Index); + } + else if (paths[i] is NamePathSegment name) + { + current = PathFactory.Instance.Append(current, name.Name); + } + } + + return current; + } + finally + { + ArrayPool.Shared.Return(buffer); + } + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Requests/MergeRequestRewriter.cs b/src/HotChocolate/Stitching/src/Stitching/Requests/MergeRequestRewriter.cs new file mode 100644 index 00000000000..3a7be650ba0 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Requests/MergeRequestRewriter.cs @@ -0,0 +1,147 @@ +using HotChocolate.Language; +using HotChocolate.Language.Visitors; +using static HotChocolate.Stitching.Requests.MergeRequestRewriter; + +namespace HotChocolate.Stitching.Requests; + +internal class MergeRequestRewriter : SyntaxRewriter +{ + private static readonly NameNode _defaultName = new("exec_batch"); + + private readonly List _fields = new(); + private readonly Dictionary _variables = new(); + private readonly Dictionary _fragments = new(); + + private Dictionary? _aliases; + private string _requestPrefix = default!; + private bool _rewriteFragments; + private OperationType? _operationType; + private NameNode? _operationName; + + public void SetOperationName(NameNode name) => _operationName = name; + + public IDictionary AddQuery( + BufferedRequest request, + string requestPrefix, + bool isAutoGenerated) + { + _requestPrefix = requestPrefix; + _rewriteFragments = !isAutoGenerated; + _operationType = request.Operation.Operation; + _aliases = new Dictionary(); + + var rewritten = RewriteDocument(request.Document, new Context(true))!; + + var operation = BufferedRequest.ResolveOperation(rewritten, request.Request.OperationName); + + foreach (var variable in operation.VariableDefinitions) + { + if (!_variables.ContainsKey(variable.Variable.Name.Value)) + { + _variables.Add(variable.Variable.Name.Value, variable); + } + } + + _fields.AddRange(operation.SelectionSet.Selections.OfType()); + + foreach (var fragment in rewritten.Definitions + .OfType()) + { + if (!_fragments.ContainsKey(fragment.Name.Value)) + { + _fragments.Add(fragment.Name.Value, fragment); + } + } + + return _aliases; + } + + public DocumentNode Merge() + { + var definitions = new List + { + new OperationDefinitionNode( + null, + _operationName ?? _defaultName, + _operationType ?? OperationType.Query, + new List(_variables.Values), + Array.Empty(), + new SelectionSetNode(null, new List(_fields)) + ) + }; + + definitions.AddRange(_fragments.Values); + + return new DocumentNode(null, definitions); + } + + protected override VariableDefinitionNode RewriteVariableDefinition( + VariableDefinitionNode node, + Context context) + => node.WithVariable( + node.Variable.WithName( + node.Variable.Name.CreateNewName(_requestPrefix))); + + protected override FieldNode? RewriteField(FieldNode node, Context context) + { + if (context.First) + { + var responseName = node.Alias ?? node.Name; + var prefix = responseName.CreateNewName(_requestPrefix); + _aliases![prefix.Value] = responseName.Value; + node = node.WithAlias(prefix); + } + + return base.RewriteField(node, new Context(false)); + } + + protected override FragmentSpreadNode RewriteFragmentSpread( + FragmentSpreadNode node, + Context context) => + _rewriteFragments + ? node.WithName(node.Name.CreateNewName(_requestPrefix)) + : node; + + protected override FragmentDefinitionNode? RewriteFragmentDefinition( + FragmentDefinitionNode node, + Context context) => + _rewriteFragments + ? base.RewriteFragmentDefinition( + node.WithName(node.Name.CreateNewName(_requestPrefix)), + new Context(false)) + : base.RewriteFragmentDefinition(node, new Context(false)); + + protected override DirectiveNode RewriteDirective( + DirectiveNode node, + Context context) + { + if (node.Arguments.Count == 0) + { + return node; + } + + var arguments = RewriteList(node.Arguments, context); + + if (!ReferenceEquals(node.Arguments, arguments)) + { + return node.WithArguments(arguments); + } + + return node; + } + + protected override VariableNode RewriteVariable( + VariableNode node, + Context context) => + node.WithName(node.Name.CreateNewName(_requestPrefix)); + + internal sealed class Context : ISyntaxVisitorContext + { + public Context(bool first) + { + First = first; + } + + public bool First { get; } + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Requests/MergeUtils.cs b/src/HotChocolate/Stitching/src/Stitching/Requests/MergeUtils.cs new file mode 100644 index 00000000000..68f934d2547 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Requests/MergeUtils.cs @@ -0,0 +1,12 @@ +using HotChocolate.Language; + +namespace HotChocolate.Stitching.Requests; + +internal static class MergeUtils +{ + public static NameNode CreateNewName(this NameNode name, string requestName) + => new($"{requestName}_{name.Value}"); + + public static string CreateNewName(this string name, string requestName) + => $"{requestName}_{name}"; +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Requests/RemoteRequestExecutor.cs b/src/HotChocolate/Stitching/src/Stitching/Requests/RemoteRequestExecutor.cs new file mode 100644 index 00000000000..94ff98a64c8 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Requests/RemoteRequestExecutor.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using GreenDonut; +using HotChocolate.Execution; + +namespace HotChocolate.Stitching.Requests; + +internal sealed class RemoteRequestExecutor + : IRemoteRequestExecutor + , IDisposable +{ + private readonly SemaphoreSlim _semaphore = new(1, 1); + private readonly List _bufferedRequests = new(); + private readonly IBatchScheduler _batchScheduler; + private readonly IRequestExecutor _executor; + private bool _taskRegistered; + + public RemoteRequestExecutor( + IBatchScheduler batchScheduler, + IRequestExecutor executor) + { + _batchScheduler = batchScheduler ?? + throw new ArgumentNullException(nameof(batchScheduler)); + _executor = executor ?? + throw new ArgumentNullException(nameof(executor)); + } + + /// + public ISchema Schema => _executor.Schema; + + /// + public IServiceProvider Services => _executor.Services; + + /// + public Task ExecuteAsync( + IQueryRequest request, + CancellationToken cancellationToken = default) + { + var bufferRequest = BufferedRequest.Create(request, Schema); + + _semaphore.Wait(cancellationToken); + + try + { + _bufferedRequests.Add(bufferRequest); + + if (!_taskRegistered) + { + _batchScheduler.Schedule(() => ExecuteRequestsInternal(cancellationToken)); + _taskRegistered = true; + } + } + finally + { + _semaphore.Release(); + } + + return bufferRequest.Promise.Task; + } + + private async ValueTask ExecuteRequestsInternal(CancellationToken cancellationToken) + { + await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + if (_bufferedRequests.Count == 1) + { + await ExecuteSingleRequestAsync(cancellationToken).ConfigureAwait(false); + } + + if (_bufferedRequests.Count > 1) + { + await ExecuteBufferedRequestBatchAsync(cancellationToken).ConfigureAwait(false); + } + + // reset the states so that we are ready for new requests to be buffered. + _taskRegistered = false; + _bufferedRequests.Clear(); + } + finally + { + _semaphore.Release(); + } + } + + private async ValueTask ExecuteSingleRequestAsync( + CancellationToken cancellationToken) + { + var request = _bufferedRequests[0]; + + var result = await _executor + .ExecuteAsync(request.Request, cancellationToken) + .ConfigureAwait(false); + + if (result is IQueryResult queryResult) + { + request.Promise.SetResult(queryResult); + } + else + { + // since we only support query/mutation at this point we will just fail + // in the event that something else was returned. + request.Promise.SetException(new NotSupportedException( + "Only IQueryResult is supported when batching.")); + } + } + + private async ValueTask ExecuteBufferedRequestBatchAsync( + CancellationToken cancellationToken) + { + // first we take all buffered requests and merge them into a single request. + // we however have to group requests by operation type. This means we should + // end up with one or two requests (query and mutation). + foreach ((IQueryRequest Merged, IEnumerable Requests) batch in + MergeRequestHelper.MergeRequests(_bufferedRequests)) + { + // now we take this merged request and run it against the executor. + var result = await _executor + .ExecuteAsync(batch.Merged, cancellationToken) + .ConfigureAwait(false); + + if (result is IQueryResult queryResult) + { + // last we will extract the results for the original buffered requests + // and fulfil the promises. + MergeRequestHelper.DispatchResults(queryResult, batch.Requests); + } + else + { + // since we only support query/mutation at this point we will just fail + // in the event that something else was returned. + foreach (var request in batch.Requests) + { + request.Promise.SetException(new NotSupportedException( + "Only IQueryResult is supported when batching.")); + } + } + } + } + + public void Dispose() + { + _semaphore.Dispose(); + + if (_executor is IDisposable d) + { + d.Dispose(); + } + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Requests/StitchingContext.cs b/src/HotChocolate/Stitching/src/Stitching/Requests/StitchingContext.cs new file mode 100644 index 00000000000..70286967a3a --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Requests/StitchingContext.cs @@ -0,0 +1,55 @@ +using System.Globalization; +using GreenDonut; +using HotChocolate.Execution; +using HotChocolate.Stitching.Properties; +using HotChocolate.Utilities; + +namespace HotChocolate.Stitching.Requests; + +public class StitchingContext : IStitchingContext +{ + private readonly Dictionary _executors = new(); + + public StitchingContext( + IBatchScheduler batchScheduler, + IRequestContextAccessor requestContextAccessor) + { + if (batchScheduler is null) + { + throw new ArgumentNullException(nameof(batchScheduler)); + } + + if (requestContextAccessor is null) + { + throw new ArgumentNullException(nameof(requestContextAccessor)); + } + + foreach (var executor in + requestContextAccessor.RequestContext.Schema.GetRemoteExecutors()) + { + _executors.Add( + executor.Key, + new RemoteRequestExecutor( + batchScheduler, + executor.Value)); + } + } + + public IRemoteRequestExecutor GetRemoteRequestExecutor(string schemaName) + { + schemaName.EnsureGraphQLName(nameof(schemaName)); + + if (_executors.TryGetValue(schemaName, out var executor)) + { + return executor; + } + + throw new ArgumentException(string.Format( + CultureInfo.InvariantCulture, + StitchingResources.SchemaName_NotFound, + schemaName)); + } + + public ISchema GetRemoteSchema(string schemaName) => + GetRemoteRequestExecutor(schemaName).Schema; +} diff --git a/src/HotChocolate/Stitching/src/Stitching/SchemaDefinitions/IPublishSchemaDefinitionDescriptor.cs b/src/HotChocolate/Stitching/src/Stitching/SchemaDefinitions/IPublishSchemaDefinitionDescriptor.cs new file mode 100644 index 00000000000..a4783d661d5 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/SchemaDefinitions/IPublishSchemaDefinitionDescriptor.cs @@ -0,0 +1,46 @@ +using System; +using System.Reflection; +using HotChocolate.Execution.Configuration; + +namespace HotChocolate.Stitching.SchemaDefinitions; + +public interface IPublishSchemaDefinitionDescriptor +{ + /// + /// Sets the configuration name. + /// + /// + /// The configuration name. + /// + /// + /// Returns the + /// + IPublishSchemaDefinitionDescriptor SetName(string name); + + IPublishSchemaDefinitionDescriptor AddTypeExtensionsFromFile( + string fileName); + + IPublishSchemaDefinitionDescriptor AddTypeExtensionsFromResource( + Assembly assembly, + string key); + + IPublishSchemaDefinitionDescriptor AddTypeExtensionsFromString( + string schemaSdl); + + IPublishSchemaDefinitionDescriptor SetSchemaDefinitionPublisher( + Func publisherFactory); + + IPublishSchemaDefinitionDescriptor IgnoreRootTypes(); + + IPublishSchemaDefinitionDescriptor IgnoreType( + string typeName); + + IPublishSchemaDefinitionDescriptor RenameType( + string typeName, + string newTypeName); + + IPublishSchemaDefinitionDescriptor RenameField( + string typeName, + string fieldName, + string newFieldName); +} diff --git a/src/HotChocolate/Stitching/src/Stitching/SchemaDefinitions/ISchemaDefinitionPublisher.cs b/src/HotChocolate/Stitching/src/Stitching/SchemaDefinitions/ISchemaDefinitionPublisher.cs new file mode 100644 index 00000000000..70c90c47c05 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/SchemaDefinitions/ISchemaDefinitionPublisher.cs @@ -0,0 +1,11 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace HotChocolate.Stitching.SchemaDefinitions; + +public interface ISchemaDefinitionPublisher +{ + ValueTask PublishAsync( + RemoteSchemaDefinition schemaDefinition, + CancellationToken cancellationToken = default); +} diff --git a/src/HotChocolate/Stitching/src/Stitching/SchemaDefinitions/PublishSchemaDefinitionDescriptor.cs b/src/HotChocolate/Stitching/src/Stitching/SchemaDefinitions/PublishSchemaDefinitionDescriptor.cs new file mode 100644 index 00000000000..fb33fcc86bb --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/SchemaDefinitions/PublishSchemaDefinitionDescriptor.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using HotChocolate.Execution.Configuration; +using HotChocolate.Language; +using HotChocolate.Types.Descriptors; +using Microsoft.Extensions.DependencyInjection; + +namespace HotChocolate.Stitching.SchemaDefinitions; + +public class PublishSchemaDefinitionDescriptor : IPublishSchemaDefinitionDescriptor +{ + private readonly IRequestExecutorBuilder _builder; + private readonly string _key = Guid.NewGuid().ToString(); + private readonly List _schemaDirectives = new List(); + private Func? _publisherFactory; + private string _name; + private RemoteSchemaDefinition? _schemaDefinition; + + public PublishSchemaDefinitionDescriptor(IRequestExecutorBuilder builder) + { + _builder = builder; + } + + public bool HasPublisher => _publisherFactory is not null; + + public IPublishSchemaDefinitionDescriptor SetName(string name) + { + _name = name; + return this; + } + + public IPublishSchemaDefinitionDescriptor AddTypeExtensionsFromFile(string fileName) + { + if (string.IsNullOrEmpty(fileName)) + { + throw new ArgumentNullException(nameof(fileName)); + } + + _builder.ConfigureSchemaAsync( + async (s, ct) => + { +#if NETSTANDARD2_0 + byte[] content = await Task + .Run(() => File.ReadAllBytes(fileName), ct) + .ConfigureAwait(false); +#else + var content = await File + .ReadAllBytesAsync(fileName, ct) + .ConfigureAwait(false); +#endif + s.AddTypeExtensions(Utf8GraphQLParser.Parse(content), _key); + }); + + return this; + } + + public IPublishSchemaDefinitionDescriptor AddTypeExtensionsFromResource( + Assembly assembly, + string key) + { + _builder.ConfigureSchemaAsync( + async (s, ct) => + { + var stream = assembly.GetManifestResourceStream(key); + + if (stream is null) + { + throw ThrowHelper.PublishSchemaDefinitionDescriptor_ResourceNotFound(key); + } + +#if NET5_0 || NET6_0 + await using (stream) +#else + using (stream) +#endif + { + var buffer = new byte[stream.Length]; + await stream.ReadAsync(buffer, 0, buffer.Length, ct).ConfigureAwait(false); + s.AddTypeExtensions(Utf8GraphQLParser.Parse(buffer), _key); + } + }); + + return this; + } + + public IPublishSchemaDefinitionDescriptor AddTypeExtensionsFromString(string schemaSdl) + { + _builder.ConfigureSchema( + s => + { + s.AddTypeExtensions(Utf8GraphQLParser.Parse(schemaSdl), _key); + }); + + return this; + } + + public IPublishSchemaDefinitionDescriptor SetSchemaDefinitionPublisher( + Func publisherFactory) + { + _publisherFactory = publisherFactory; + return this; + } + + public IPublishSchemaDefinitionDescriptor IgnoreRootTypes() + { + _schemaDirectives.Add(new DirectiveNode(DirectiveNames.RemoveRootTypes)); + return this; + } + + public IPublishSchemaDefinitionDescriptor IgnoreType( + string typeName) + { + _schemaDirectives.Add(new DirectiveNode( + DirectiveNames.RemoveType, + new ArgumentNode(DirectiveFieldNames.RemoveType_TypeName, typeName))); + return this; + } + + public IPublishSchemaDefinitionDescriptor RenameType( + string typeName, + string newTypeName) + { + _schemaDirectives.Add(new DirectiveNode( + DirectiveNames.RenameType, + new ArgumentNode(DirectiveFieldNames.RenameType_TypeName, typeName), + new ArgumentNode(DirectiveFieldNames.RenameType_NewTypeName, newTypeName))); + return this; + } + + public IPublishSchemaDefinitionDescriptor RenameField( + string typeName, + string fieldName, + string newFieldName) + { + _schemaDirectives.Add(new DirectiveNode( + DirectiveNames.RenameField, + new ArgumentNode(DirectiveFieldNames.RenameField_TypeName, typeName), + new ArgumentNode(DirectiveFieldNames.RenameField_FieldName, fieldName), + new ArgumentNode(DirectiveFieldNames.RenameField_NewFieldName, newFieldName))); + return this; + } + + public RemoteSchemaDefinition Build( + IDescriptorContext context, + ISchema schema) + { + var extensionDocuments = new List(context.GetTypeExtensions(_key)); + + if (_schemaDirectives.Count > 0) + { + var schemaExtension = new SchemaExtensionNode( + null, + _schemaDirectives, + Array.Empty()); + + extensionDocuments.Add(new DocumentNode(new[] { schemaExtension })); + } + + _schemaDefinition = new RemoteSchemaDefinition( + !string.IsNullOrEmpty(_name) ? _name : schema.Name, + schema.ToDocument(), + extensionDocuments); + + return _schemaDefinition; + } + + public async ValueTask PublishAsync( + IServiceProvider applicationServices, + CancellationToken cancellationToken = default) + { + if (_publisherFactory is not null && + _schemaDefinition is not null) + { + var publisher = _publisherFactory(applicationServices); + await publisher.PublishAsync(_schemaDefinition, cancellationToken) + .ConfigureAwait(false); + } + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/SchemaDefinitions/SchemaDefinitionFieldNames.cs b/src/HotChocolate/Stitching/src/Stitching/SchemaDefinitions/SchemaDefinitionFieldNames.cs new file mode 100644 index 00000000000..9d12df4078b --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/SchemaDefinitions/SchemaDefinitionFieldNames.cs @@ -0,0 +1,7 @@ +namespace HotChocolate.Stitching.SchemaDefinitions; + +internal static class SchemaDefinitionFieldNames +{ + public const string SchemaDefinitionField = "_schemaDefinition"; + public const string ConfigurationArgument = "configuration"; +} diff --git a/src/HotChocolate/Stitching/src/Stitching/SchemaDefinitions/SchemaDefinitionSchemaInterceptor.cs b/src/HotChocolate/Stitching/src/Stitching/SchemaDefinitions/SchemaDefinitionSchemaInterceptor.cs new file mode 100644 index 00000000000..3c658627199 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/SchemaDefinitions/SchemaDefinitionSchemaInterceptor.cs @@ -0,0 +1,29 @@ +using HotChocolate.Configuration; +using HotChocolate.Types.Descriptors; + +namespace HotChocolate.Stitching.SchemaDefinitions; + +internal class SchemaDefinitionSchemaInterceptor : SchemaInterceptor +{ + private readonly PublishSchemaDefinitionDescriptor _descriptor; + + public SchemaDefinitionSchemaInterceptor( + PublishSchemaDefinitionDescriptor descriptor) + { + _descriptor = descriptor; + } + + public override void OnBeforeCreate( + IDescriptorContext context, + ISchemaBuilder schemaBuilder) => + context.GetOrAddSchemaDefinitions(); + + public override void OnAfterCreate( + IDescriptorContext context, + ISchema schema) + { + context + .GetOrAddSchemaDefinitions() + .Add(_descriptor.Build(context, schema)); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/SchemaDefinitions/SchemaDefinitionType.cs b/src/HotChocolate/Stitching/src/Stitching/SchemaDefinitions/SchemaDefinitionType.cs new file mode 100644 index 00000000000..74e7d3f06ff --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/SchemaDefinitions/SchemaDefinitionType.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using System.Linq; +using HotChocolate.Types; + +namespace HotChocolate.Stitching.SchemaDefinitions; + +public class SchemaDefinitionType : ObjectType +{ + protected override void Configure(IObjectTypeDescriptor descriptor) + { + descriptor + .Name(Names.SchemaDefinition) + .BindFieldsExplicitly(); + + descriptor + .Field(t => t.Name) + .Name(Names.Name) + .Type>(); + + descriptor + .Field(t => t.Document) + .Name(Names.Document) + .ResolveWith(t => t.GetDocument(default!)) + .Type>(); + + descriptor + .Field(t => t.ExtensionDocuments) + .Name(Names.ExtensionDocuments) + .ResolveWith(t => t.GetExtensionDocuments(default!)) + .Type>>>(); + } + + private class Resolvers + { + public string GetDocument( + [Parent] RemoteSchemaDefinition schemaDefinition) => + schemaDefinition.Document.ToString(false); + + public IEnumerable GetExtensionDocuments( + [Parent] RemoteSchemaDefinition schemaDefinition) => + schemaDefinition.ExtensionDocuments.Select(t => t.ToString(false)); + } + + public static class Names + { + public static string SchemaDefinition { get; } = "_SchemaDefinition"; + + public static string Name { get; } = "name"; + + public static string Document { get; } = "document"; + + public static string ExtensionDocuments { get; } = "extensionDocuments"; + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/SchemaDefinitions/SchemaDefinitionTypeInterceptor.cs b/src/HotChocolate/Stitching/src/Stitching/SchemaDefinitions/SchemaDefinitionTypeInterceptor.cs new file mode 100644 index 00000000000..41ca947c329 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/SchemaDefinitions/SchemaDefinitionTypeInterceptor.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using System.Linq; +using HotChocolate.Configuration; +using HotChocolate.Types; +using HotChocolate.Types.Descriptors; +using HotChocolate.Types.Descriptors.Definitions; +using HotChocolate.Types.Introspection; +using static HotChocolate.Stitching.SchemaDefinitions.SchemaDefinitionFieldNames; + +namespace HotChocolate.Stitching.SchemaDefinitions; + +internal class SchemaDefinitionTypeInterceptor : TypeInterceptor +{ + private readonly bool _publishOnSchema; + + public SchemaDefinitionTypeInterceptor(bool publishOnSchema) + { + _publishOnSchema = publishOnSchema; + } + + public override void OnBeforeCompleteType( + ITypeCompletionContext completionContext, + DefinitionBase? definition, + IDictionary contextData) + { + // when we are visiting the query type we will add the schema definition field. + if (_publishOnSchema && + (completionContext.IsQueryType ?? false) && + definition is ObjectTypeDefinition objectTypeDefinition && + !objectTypeDefinition.Fields.Any(t => t.Name.Equals(SchemaDefinitionField))) + { + var typeNameField = objectTypeDefinition.Fields.First( + t => t.Name.Equals(IntrospectionFields.TypeName) && t.IsIntrospectionField); + var index = objectTypeDefinition.Fields.IndexOf(typeNameField) + 1; + + var descriptor = ObjectFieldDescriptor.New( + completionContext.DescriptorContext, + SchemaDefinitionField); + + descriptor + .Argument(ConfigurationArgument, a => a.Type>()) + .Type() + .Resolve(ctx => + { + var name = ctx.ArgumentValue(ConfigurationArgument); + + return ctx.Schema.ContextData + .GetSchemaDefinitions() + .FirstOrDefault(t => t.Name.Equals(name)); + }); + + objectTypeDefinition.Fields.Insert(index, descriptor.CreateDefinition()); + } + + // when we visit the schema definition we will copy over the schema definition list + // that sits on the schema creation context. + else if (definition is SchemaTypeDefinition && + completionContext.ContextData.TryGetValue( + WellKnownContextData.SchemaDefinitions, + out var schemaDefinitions)) + { + contextData[WellKnownContextData.SchemaDefinitions] = schemaDefinitions; + } + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/ThrowHelper.cs b/src/HotChocolate/Stitching/src/Stitching/ThrowHelper.cs new file mode 100644 index 00000000000..21ca187f4c8 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/ThrowHelper.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using HotChocolate.Language; +using HotChocolate.Stitching.Properties; +using static HotChocolate.Stitching.Properties.StitchingResources; + +namespace HotChocolate.Stitching; + +internal static class ThrowHelper +{ + public static InvalidOperationException BufferedRequest_VariableDoesNotExist( + string name) => + new(string.Format( + ThrowHelper_BufferedRequest_VariableDoesNotExist, + name)); + + public static InvalidOperationException BufferedRequest_OperationNotFound( + DocumentNode document) => + new(string.Format( + ThrowHelper_BufferedRequest_OperationNotFound, + document)); + + public static GraphQLException ArgumentScopedVariableResolver_InvalidArgumentName( + string variableName, + FieldNode fieldSelection, + Path path) => + new(ErrorBuilder.New() + .SetMessage( + StitchingResources.ArgumentScopedVariableResolver_InvalidArgumentName, + variableName) + .SetCode(ErrorCodes.Stitching.ArgumentNotDefined) + .SetPath(path) + .AddLocation(fieldSelection) + .Build()); + + public static GraphQLException FieldScopedVariableResolver_InvalidFieldName( + string variableName, + FieldNode fieldSelection, + Path path) => + new(ErrorBuilder.New() + .SetMessage( + StitchingResources.FieldScopedVariableResolver_InvalidFieldName, + variableName) + .SetCode(ErrorCodes.Stitching.FieldNotDefined) + .SetPath(path) + .AddLocation(fieldSelection) + .Build()); + + public static GraphQLException RootScopedVariableResolver_ScopeNotSupported( + string scopeName, + FieldNode fieldSelection, + Path path) => + new(ErrorBuilder.New() + .SetMessage( + StitchingResources.RootScopedVariableResolver_ScopeNotSupported, + scopeName) + .SetCode(ErrorCodes.Stitching.ScopeNotDefined) + .SetPath(path) + .AddLocation(fieldSelection) + .Build()); + + public static SchemaException PublishSchemaDefinitionDescriptor_ResourceNotFound( + string key) => + new( + SchemaErrorBuilder.New() + .SetMessage( + "The resource `{0}` was not found!", + key) + .Build()); + + public static SchemaException IntrospectionHelper_UnableToFetchSchemaDefinition( + IReadOnlyList errors) => + new( + SchemaErrorBuilder.New() + .SetMessage("Unable to fetch schema definition.") + .SetExtension("errors", errors) + .Build()); + + public static SchemaException RequestExecutorBuilder_ResourceNotFound( + string key) => + new( + SchemaErrorBuilder.New() + .SetMessage( + "The resource `{0}` was not found!", + key) + .Build()); + + public static SchemaException RequestExecutorBuilder_ArgumentWithNameWasNotFound( + string argument) => + new( + SchemaErrorBuilder.New() + .SetMessage( + "`{0}` is not specified.", + argument) + .Build()); + + public static SchemaException RequestExecutorBuilder_ArgumentValueWasNotAStringValue( + string argument) => + new( + SchemaErrorBuilder.New() + .SetMessage( + "`{0}` must have a string value.", + argument) + .Build()); + + public static InvalidOperationException RequestExecutorBuilder_RemoteExecutorNotFound() => + new("The mandatory remote executors have not been found."); + + public static InvalidOperationException RequestExecutorBuilder_NameLookupNotFound() => + new("A stitched schema must provide a name lookup"); +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Utilities/CopySchemaDefinitionTypeInterceptor.cs b/src/HotChocolate/Stitching/src/Stitching/Utilities/CopySchemaDefinitionTypeInterceptor.cs new file mode 100644 index 00000000000..e5d9ea8a34e --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Utilities/CopySchemaDefinitionTypeInterceptor.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using HotChocolate.Configuration; +using HotChocolate.Types.Descriptors.Definitions; + +namespace HotChocolate.Stitching.Utilities; + +public class CopySchemaDefinitionTypeInterceptor : TypeInterceptor +{ + public override void OnAfterCompleteType( + ITypeCompletionContext completionContext, + DefinitionBase? definition, + IDictionary contextData) + { + if (definition is SchemaTypeDefinition) + { + contextData[typeof(RemoteSchemaDefinition).FullName!] = + completionContext.ContextData[typeof(RemoteSchemaDefinition).FullName!]; + } + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Utilities/FieldDependency.cs b/src/HotChocolate/Stitching/src/Stitching/Utilities/FieldDependency.cs new file mode 100644 index 00000000000..090f96a4190 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Utilities/FieldDependency.cs @@ -0,0 +1,45 @@ +using HotChocolate.Utilities; + +namespace HotChocolate.Stitching.Utilities; + +public readonly struct FieldDependency + : IEquatable +{ + public FieldDependency(string typeName, string fieldName) + { + typeName.EnsureGraphQLName(nameof(typeName)); + fieldName.EnsureGraphQLName(nameof(typeName)); + + TypeName = typeName; + FieldName = fieldName; + } + + public string TypeName { get; } + + public string FieldName { get; } + + public bool Equals(FieldDependency other) + { + return other.TypeName.Equals(TypeName) + && other.FieldName.Equals(FieldName); + } + + public override bool Equals(object? obj) + { + if (obj is null) + { + return false; + } + return obj is FieldDependency f && Equals(f); + } + + public override int GetHashCode() + { + unchecked + { + var hash = TypeName.GetHashCode() * 397; + hash = hash ^ (FieldName.GetHashCode() * 7); + return hash; + } + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Utilities/FieldDependencyResolver.cs b/src/HotChocolate/Stitching/src/Stitching/Utilities/FieldDependencyResolver.cs new file mode 100644 index 00000000000..9330f577eed --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Utilities/FieldDependencyResolver.cs @@ -0,0 +1,267 @@ +using System.Collections.Immutable; +using HotChocolate.Language; +using HotChocolate.Stitching.Delegation.ScopedVariables; +using HotChocolate.Types; + +namespace HotChocolate.Stitching.Utilities; + +public class FieldDependencyResolver + : QuerySyntaxWalker +{ + private readonly ISchema _schema; + + public FieldDependencyResolver(ISchema schema) + { + _schema = schema ?? throw new ArgumentNullException(nameof(schema)); + } + + protected override bool VisitFragmentDefinitions => false; + + public ISet GetFieldDependencies( + DocumentNode document, + FieldNode field, + INamedOutputType declaringType) + { + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + + if (field == null) + { + throw new ArgumentNullException(nameof(field)); + } + + if (declaringType == null) + { + throw new ArgumentNullException(nameof(declaringType)); + } + + var context = Context.New(declaringType, GetFragments(document)); + + if (field.SelectionSet is not null) + { + VisitSelectionSet(field.SelectionSet, context); + } + + return context.Dependencies; + } + + public ISet GetFieldDependencies( + DocumentNode document, + SelectionSetNode selectionSet, + INamedOutputType declaringType) + { + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + + if (selectionSet == null) + { + throw new ArgumentNullException(nameof(selectionSet)); + } + + if (declaringType == null) + { + throw new ArgumentNullException(nameof(declaringType)); + } + + var context = Context.New(declaringType, GetFragments(document)); + + VisitSelectionSet(selectionSet, context); + + return context.Dependencies; + } + + private static IDictionary GetFragments( + DocumentNode document) + { + var fragments = new Dictionary(); + + foreach (var fragment in document.Definitions.OfType()) + { + if (!string.IsNullOrEmpty(fragment.Name?.Value)) + { + fragments[fragment.Name.Value] = fragment; + } + } + + return fragments; + } + + protected override void VisitField(FieldNode node, Context context) + { + if (context.TypeContext is IComplexOutputType type && + type.Fields.TryGetField(node.Name.Value, out var field)) + { + CollectDelegationDependencies(context, type, field); + CollectComputeDependencies(context, type, field); + } + } + + private static void CollectDelegationDependencies( + Context context, + Types.IHasName type, + IOutputField field) + { + var directive = field.Directives[DirectiveNames.Delegate].FirstOrDefault(); + + if (directive is not null) + { + CollectFieldNames( + directive.ToObject(), + type, + context.Dependencies); + } + } + + private static void CollectComputeDependencies( + Context context, + IComplexOutputType type, + IOutputField field) + { + var directive = field.Directives[DirectiveNames.Computed].FirstOrDefault(); + + var dependantOn = directive?.ToObject().DependantOn; + + if (dependantOn is not null) + { + foreach (var fieldName in dependantOn) + { + if (type.Fields.TryGetField(fieldName, out var dependency)) + { + context.Dependencies.Add( + new FieldDependency( + type.Name, + dependency.Name)); + } + } + } + } + + private static void CollectFieldNames( + DelegateDirective directive, + Types.IHasName type, + ISet dependencies) + { + var path = SelectionPathParser.Parse(directive.Path); + + foreach (var component in path) + { + foreach (var fieldName in component.Arguments + .Select(t => t.Value) + .OfType() + .Where(t => ScopeNames.Fields.Equals(t.Scope.Value)) + .Select(t => t.Name.Value)) + { + dependencies.Add(new FieldDependency(type.Name, fieldName)); + } + } + } + + protected override void VisitFragmentSpread( + FragmentSpreadNode node, + Context context) + { + base.VisitFragmentSpread(node, context); + + if (context.Fragments.TryGetValue(node.Name.Value, + out var fragment)) + { + VisitFragmentDefinition(fragment, context); + } + } + + protected override void VisitFragmentDefinition( + FragmentDefinitionNode node, + Context context) + { + var newContext = context; + + if (newContext.FragmentPath.Contains(node.Name.Value)) + { + return; + } + + if (_schema.TryGetType(node.TypeCondition.Name.Value, out var type)) + { + newContext = newContext + .AddFragment(node.Name.Value) + .SetTypeContext(type); + } + + base.VisitFragmentDefinition(node, newContext); + } + + protected override void VisitInlineFragment( + InlineFragmentNode node, + Context context) + { + var newContext = context; + + if (node.TypeCondition is not null && + _schema.TryGetType(node.TypeCondition.Name.Value, out var type)) + { + newContext = newContext.SetTypeContext(type); + } + + base.VisitInlineFragment(node, newContext); + } + + public sealed class Context + { + private Context( + INamedOutputType typeContext, + IDictionary fragments) + { + Dependencies = new HashSet(); + TypeContext = typeContext; + Fragments = fragments; + FragmentPath = ImmutableHashSet.Empty; + } + + private Context(Context context, INamedOutputType typeContext) + { + Dependencies = context.Dependencies; + TypeContext = typeContext; + Fragments = context.Fragments; + FragmentPath = context.FragmentPath; + } + + private Context( + Context context, + ImmutableHashSet fragmentPath) + { + Dependencies = context.Dependencies; + TypeContext = context.TypeContext; + Fragments = context.Fragments; + FragmentPath = fragmentPath; + } + + public ISet Dependencies { get; } + + public INamedOutputType TypeContext { get; } + + public ImmutableHashSet FragmentPath { get; } + + public IDictionary Fragments { get; } + + public Context SetTypeContext(INamedOutputType type) + { + return new Context(this, type); + } + + public Context AddFragment(string fragmentName) + { + return new Context(this, FragmentPath.Add(fragmentName)); + } + + public static Context New( + INamedOutputType typeContext, + IDictionary fragments) + { + return new Context(typeContext, fragments); + } + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Utilities/IQueryDelegationRewriter.cs b/src/HotChocolate/Stitching/src/Stitching/Utilities/IQueryDelegationRewriter.cs new file mode 100644 index 00000000000..955c5654c3a --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Utilities/IQueryDelegationRewriter.cs @@ -0,0 +1,57 @@ +using HotChocolate.Language; +using HotChocolate.Types; + +namespace HotChocolate.Stitching.Utilities; + +/// +/// This interface provides the query delegation rewriter hooks. +/// Implement this interface in order to customize the query +/// rewrite logic of the query delegation rewriter. +/// +public interface IQueryDelegationRewriter +{ + /// + /// This method will be called after the stitching layer + /// has rewritten a field and allows to add custom rewriter logic. + /// + /// + /// The name of the schema to which the query shall be delegated. + /// + /// + /// The current output type on which the selection set is declared. + /// + /// + /// The current output field on which this selection set is declared. + /// + /// + /// The field selection syntax node. + /// + FieldNode OnRewriteField( + string targetSchemaName, + IOutputType outputType, + IOutputField outputField, + FieldNode field); + + /// + /// This method will be called after the stitching layer + /// has rewritten a selection set and allows to add custom + /// rewriter logic. + /// + /// + /// The name of the schema to which the query shall be delegated. + /// + /// + /// The current output type on which the selection set is declared. + /// + /// + /// The current output field on which this selection set is declared. + /// + /// + /// The list of selections that shall be added to the delegation query. + /// + SelectionSetNode OnRewriteSelectionSet( + string targetSchemaName, + IOutputType outputType, + IOutputField outputField, + SelectionSetNode selectionSet); +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Utilities/IntrospectionHelper.cs b/src/HotChocolate/Stitching/src/Stitching/Utilities/IntrospectionHelper.cs new file mode 100644 index 00000000000..bb553f474fb --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Utilities/IntrospectionHelper.cs @@ -0,0 +1,142 @@ +using HotChocolate.Execution; +using HotChocolate.Language; +using HotChocolate.Stitching.Pipeline; +using HotChocolate.Stitching.SchemaDefinitions; +using HotChocolate.Utilities; +using HotChocolate.Utilities.Introspection; + +namespace HotChocolate.Stitching.Utilities; + +public class IntrospectionHelper +{ + private readonly HttpClient _httpClient; + private readonly string _configuration; + + public IntrospectionHelper(HttpClient httpClient, string configuration) + { + _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); + _configuration = configuration.EnsureGraphQLName(nameof(configuration)); + } + + public async Task GetSchemaDefinitionAsync( + CancellationToken cancellationToken) + { + // The introspection client will do all the hard work to negotiate + // the features this schema supports and convert the introspection + // result into a parsed GraphQL SDL document. + var schemaDocument = await new IntrospectionClient() + .DownloadSchemaAsync(_httpClient, cancellationToken) + .ConfigureAwait(false); + + // If the down-stream service provides a schema definition we will fetch it. + if (ProvidesSchemaDefinition(schemaDocument)) + { + // if a schema definition with the requested configuration name is + // available we will use it instead of the introspection result. + var schemaDefinition = + await FetchSchemaDefinitionAsync(cancellationToken) + .ConfigureAwait(false); + if (schemaDefinition is not null) + { + return schemaDefinition; + } + } + + // if no schema definition is available on the down-stream services that matches + // the configuration we will use the introspection result and infer one. + return new RemoteSchemaDefinition( + _configuration, + schemaDocument, + Array.Empty()); + } + + private static bool ProvidesSchemaDefinition(DocumentNode schemaDocument) + { + var schemaDefinition = schemaDocument.Definitions + .OfType().SingleOrDefault(); + + var operation = + schemaDefinition?.OperationTypes.FirstOrDefault( + t => t.Operation == OperationType.Query); + + if (operation is null) + { + return false; + } + + var queryType = schemaDocument.Definitions + .OfType() + .FirstOrDefault(t => t.Name.Value.EqualsOrdinal(operation.Type.Name.Value)); + + if (queryType is null) + { + return false; + } + + var schemaDefinitionField = queryType.Fields + .FirstOrDefault(t => t.Name.Value.EqualsOrdinal( + SchemaDefinitionFieldNames.SchemaDefinitionField)); + + return schemaDefinitionField?.Arguments + .Any(t => t.Name.Value.EqualsOrdinal( + SchemaDefinitionFieldNames.ConfigurationArgument)) ?? + false; + } + + private async ValueTask FetchSchemaDefinitionAsync( + CancellationToken cancellationToken) + { + using var writer = new ArrayWriter(); + + var request = + QueryRequestBuilder.New() + .SetQuery( + $@"query GetSchemaDefinition($c: String!) {{ + {SchemaDefinitionFieldNames.SchemaDefinitionField}(configuration: $c) {{ + name + document + extensionDocuments + }} + }}") + .SetVariableValue("c", new StringValueNode(_configuration)) + .Create(); + + var requestMessage = await HttpRequestClient + .CreateRequestMessageAsync(writer, request, cancellationToken) + .ConfigureAwait(false); + + var responseMessage = await _httpClient + .SendAsync(requestMessage, cancellationToken) + .ConfigureAwait(false); + + var result = await HttpRequestClient + .ParseResponseMessageAsync(responseMessage, cancellationToken) + .ConfigureAwait(false); + + if (result.Errors is { Count: > 1 }) + { + throw ThrowHelper.IntrospectionHelper_UnableToFetchSchemaDefinition(result.Errors); + } + + if (result.Data is not null && + result.Data[SchemaDefinitionFieldNames.SchemaDefinitionField] + is IReadOnlyDictionary o && + o.TryGetValue("name", out var n) && + n is StringValueNode name && + o.TryGetValue("document", out var d) && + d is StringValueNode document && + o.TryGetValue("extensionDocuments", out var e) && + e is IReadOnlyList extensionDocuments) + { + return new RemoteSchemaDefinition( + name.Value, + Utf8GraphQLParser.Parse(document.Value), + extensionDocuments + .OfType() + .Select(t => t.Value) + .Select(Utf8GraphQLParser.Parse)); + } + + return null; + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Utilities/QueryDelegationRewriterBase.cs b/src/HotChocolate/Stitching/src/Stitching/Utilities/QueryDelegationRewriterBase.cs new file mode 100644 index 00000000000..c6697e97422 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Utilities/QueryDelegationRewriterBase.cs @@ -0,0 +1,59 @@ +using HotChocolate.Language; +using HotChocolate.Types; + +namespace HotChocolate.Stitching.Utilities; + +public class QueryDelegationRewriterBase + : IQueryDelegationRewriter +{ + /// + /// This method will be called after the stitching layer + /// has rewritten a field and allows to add custom rewriter logic. + /// + /// + /// The name of the schema to which the query shall be delegated. + /// + /// + /// The current output type on which the selection set is declared. + /// + /// + /// The current output field on which this selection set is declared. + /// + /// + /// The field selection syntax node. + /// + public virtual FieldNode OnRewriteField( + string targetSchemaName, + IOutputType outputType, + IOutputField outputField, + FieldNode field) + { + return field; + } + + /// + /// This method will be called after the stitching layer + /// has rewritten a selection set and allows to add custom + /// rewriter logic. + /// + /// + /// The name of the schema to which the query shall be delegated. + /// + /// + /// The current output type on which the selection set is declared. + /// + /// + /// The current output field on which this selection set is declared. + /// + /// + /// The list of selections that shall be added to the delegation query. + /// + public virtual SelectionSetNode OnRewriteSelectionSet( + string targetSchemaName, + IOutputType outputType, + IOutputField outputField, + SelectionSetNode selectionSet) + { + return selectionSet; + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Utilities/SchemaExtensionsRewriter.cs b/src/HotChocolate/Stitching/src/Stitching/Utilities/SchemaExtensionsRewriter.cs new file mode 100644 index 00000000000..fd3f19cd839 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Utilities/SchemaExtensionsRewriter.cs @@ -0,0 +1,69 @@ +using HotChocolate.Language; +using HotChocolate.Language.Visitors; +using HotChocolate.Utilities; +using static HotChocolate.Stitching.Utilities.SchemaExtensionsRewriter; + +namespace HotChocolate.Stitching.Utilities; + +public class SchemaExtensionsRewriter : SyntaxRewriter +{ + private readonly List _directives = new(); + + public IReadOnlyList SchemaActions => _directives; + + protected override SchemaExtensionNode RewriteSchemaExtension( + SchemaExtensionNode node, + Context context) + { + var directives = new List(); + + foreach (var directive in node.Directives) + { + switch (directive.Name.Value) + { + case DirectiveNames.RemoveType: + case DirectiveNames.RenameField: + case DirectiveNames.RenameType: + case DirectiveNames.RemoveRootTypes: + _directives.Add(directive); + break; + + default: + directives.Add(directive); + break; + } + } + + return node.WithDirectives(directives); + } + + protected override DirectiveNode RewriteDirective( + DirectiveNode node, + Context context) + { + if (node.Name.Value.EqualsOrdinal(DirectiveNames.Delegate) && + !node.Arguments.Any(a => a.Name.Value.EqualsOrdinal( + DirectiveFieldNames.Delegate_Schema))) + { + var arguments = node.Arguments.ToList(); + + arguments.Add(new ArgumentNode( + DirectiveFieldNames.Delegate_Schema, + context.SchemaName)); + + return node.WithArguments(arguments); + } + + return node; + } + + public sealed class Context : ISyntaxVisitorContext + { + public Context(string schemaName) + { + SchemaName = schemaName; + } + + public string SchemaName { get; } + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Utilities/StitchingSchemaInterceptor.cs b/src/HotChocolate/Stitching/src/Stitching/Utilities/StitchingSchemaInterceptor.cs new file mode 100644 index 00000000000..fe043dd49a9 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Utilities/StitchingSchemaInterceptor.cs @@ -0,0 +1,281 @@ +using System.Diagnostics.CodeAnalysis; +using HotChocolate.Configuration; +using HotChocolate.Execution; +using HotChocolate.Language; +using HotChocolate.Resolvers; +using HotChocolate.Stitching.Merge; +using HotChocolate.Stitching.Merge.Rewriters; +using HotChocolate.Types; +using HotChocolate.Types.Descriptors; +using HotChocolate.Utilities; +using HotChocolate.Utilities.Introspection; +using static HotChocolate.Language.SyntaxKind; +using IHasName = HotChocolate.Types.IHasName; +using static HotChocolate.Stitching.DirectiveFieldNames; + +namespace HotChocolate.Stitching.Utilities; + +internal class StitchingSchemaInterceptor : SchemaInterceptor +{ + public override void OnBeforeCreate( + IDescriptorContext context, + ISchemaBuilder schemaBuilder) + { + var allSchemas = new OrderedDictionary(); + + foreach (var executor in + context.GetRemoteExecutors()) + { + allSchemas.Add(executor.Key, executor.Value.Schema.ToDocument(true)); + } + + var typeExtensions = context.GetTypeExtensions(); + + // merge schemas + var mergedSchema = MergeSchemas(context, allSchemas); + mergedSchema = AddExtensions(mergedSchema, typeExtensions); + mergedSchema = RewriteMerged(context, mergedSchema); + mergedSchema = mergedSchema.RemoveBuiltInTypes(); + + VisitMerged(context, mergedSchema); + MarkExternalFields(schemaBuilder, mergedSchema); + BuildNameLookup(context, schemaBuilder, mergedSchema, allSchemas.Keys); + + schemaBuilder + .AddDocument(mergedSchema) + .AddDirectiveType() + .AddDirectiveType() + .AddDirectiveType() + .SetTypeResolver(IsOfTypeFallback); + } + + private static DocumentNode MergeSchemas( + IDescriptorContext context, + IDictionary schemas) + { + var merger = new SchemaMerger(); + + foreach (var name in schemas.Keys) + { + merger.AddSchema(name, schemas[name]); + } + + foreach (var handler in context.GetTypeMergeRules()) + { + merger.AddTypeMergeRule(handler); + } + + foreach (var handler in context.GetDirectiveMergeRules()) + { + merger.AddDirectiveMergeRule(handler); + } + + foreach (var rewriter in context.GetDocumentRewriter()) + { + merger.AddDocumentRewriter(rewriter); + } + + foreach (var rewriter in context.GetTypeRewriter()) + { + merger.AddTypeRewriter(rewriter); + } + + return merger.Merge(); + } + + private static DocumentNode AddExtensions( + DocumentNode schema, + IReadOnlyCollection typeExtensions) + { + if (typeExtensions.Count == 0) + { + return schema; + } + + var rewriter = new AddSchemaExtensionRewriter(); + var currentSchema = schema; + + foreach (var extension in typeExtensions) + { + currentSchema = rewriter.AddExtensions( + currentSchema, + extension); + } + + return currentSchema; + } + + private static DocumentNode RewriteMerged(IDescriptorContext context, DocumentNode schema) + { + var mergedDocRewriter = + context.GetMergedDocRewriter(); + + if (mergedDocRewriter.Count == 0) + { + return schema; + } + + var current = schema; + + foreach (var rewriter in mergedDocRewriter) + { + current = rewriter.Invoke(current); + } + + return current; + } + + private static void VisitMerged(IDescriptorContext context, DocumentNode schema) + { + foreach (var visitor in context.GetMergedDocVisitors()) + { + visitor.Invoke(schema); + } + } + + private static void MarkExternalFields(ISchemaBuilder schemaBuilder, DocumentNode document) + { + var externalFieldLookup = + new Dictionary>(); + + foreach (var objectType in document.Definitions) + { + if (objectType.Kind is ObjectTypeDefinition or SyntaxKind.ObjectTypeExtension) + { + if (!externalFieldLookup.TryGetValue( + ((ComplexTypeDefinitionNodeBase)objectType).Name.Value, + out var externalFields)) + { + externalFields = new HashSet(); + externalFieldLookup.Add( + ((ComplexTypeDefinitionNodeBase)objectType).Name.Value, + externalFields); + } + + MarkExternalFields( + ((ComplexTypeDefinitionNodeBase)objectType).Fields, + externalFields); + } + } + + schemaBuilder.AddExternalFieldLookup(externalFieldLookup); + } + + private static void BuildNameLookup( + IDescriptorContext context, + ISchemaBuilder schemaBuilder, + DocumentNode document, + ICollection schemaNames) + { + Dictionary<(string Type, string TargetSchema), string> nameLookup = + new Dictionary<(string, string), string>(); + + foreach (var type in document.Definitions.OfType()) + { + foreach (var directive in type.Directives + .Where(t => t.Name.Value.EqualsOrdinal(DirectiveNames.Source))) + { + if (directive.Arguments.FirstOrDefault( + t => t.Name.Value.EqualsOrdinal(Source_Schema))?.Value + is StringValueNode schema && + directive.Arguments.FirstOrDefault( + t => t.Name.Value.EqualsOrdinal(Source_Name))?.Value + is StringValueNode name && + !name.Value.EqualsOrdinal(type.Name.Value)) + { + nameLookup[(type.Name.Value, schema.Value)] = name.Value; + } + } + } + + foreach (var rewriter in + context.GetTypeRewriter().OfType()) + { + if (rewriter.SchemaName is null) + { + foreach (var schemaName in schemaNames) + { + nameLookup[(rewriter.NewTypeName, schemaName)] = + rewriter.OriginalTypeName; + } + } + else + { + nameLookup[(rewriter.NewTypeName, rewriter.SchemaName)] = + rewriter.OriginalTypeName; + } + } + + schemaBuilder.AddNameLookup(nameLookup); + } + + private static void MarkExternalFields( + IReadOnlyList fields, + ISet externalFields) + { + foreach (var field in fields) + { + if (field.Directives.Count == 0 || + field.Directives.All(t => !t.Name.Value.EqualsOrdinal(DirectiveNames.Computed))) + { + externalFields.Add(field.Name.Value); + } + } + } + + private static bool IsOfTypeFallback( + ObjectType objectType, + IResolverContext context, + object resolverResult) + { + if (resolverResult is IReadOnlyDictionary dict) + { + if (dict.TryGetValue(WellKnownFieldNames.TypeName, out var value) && + TryDeserializeTypeName(value, out var typeName)) + { + if (objectType.Directives.Contains(DirectiveNames.Source) && + context.ScopedContextData.TryGetValue( + WellKnownContextData.SchemaName, + out var o) && + o is string schemaName && + objectType.TryGetSourceDirective(schemaName, out var source)) + { + return source.Name.Equals(typeName); + } + return objectType.Name.Equals(typeName); + } + } + else if (objectType.RuntimeType == typeof(object)) + { + return IsOfTypeWithName(objectType, resolverResult); + } + + return IsOfTypeWithClrType(objectType, resolverResult); + } + + private static bool TryDeserializeTypeName( + object serializedTypeName, + [NotNullWhen(true)] out string? typeName) + { + if (serializedTypeName is string s) + { + typeName = s; + return true; + } + + if (serializedTypeName is StringValueNode sv) + { + typeName = sv.Value; + return true; + } + + typeName = null; + return false; + } + + private static bool IsOfTypeWithClrType(IHasRuntimeType type, object? result) => + result is null || type.RuntimeType.IsInstanceOfType(result); + + private static bool IsOfTypeWithName(IHasName objectType, object? result) => + result == null || objectType.Name.Equals(result.GetType().Name); +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Utilities/StitchingTypeInterceptor.cs b/src/HotChocolate/Stitching/src/Stitching/Utilities/StitchingTypeInterceptor.cs new file mode 100644 index 00000000000..0164171ba07 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Utilities/StitchingTypeInterceptor.cs @@ -0,0 +1,107 @@ +using System.Collections.Generic; +using System.Linq; +using HotChocolate.Configuration; +using HotChocolate.Resolvers; +using HotChocolate.Stitching.Delegation; +using HotChocolate.Types; +using HotChocolate.Types.Descriptors; +using HotChocolate.Types.Descriptors.Definitions; +using static HotChocolate.Resolvers.FieldClassMiddlewareFactory; +using static HotChocolate.Stitching.WellKnownContextData; + +namespace HotChocolate.Stitching.Utilities; + +internal class StitchingTypeInterceptor : TypeInterceptor +{ + private readonly HashSet<(string, string)> _handledExternalFields = + new HashSet<(string, string)>(); + + public override void OnAfterInitialize( + ITypeDiscoveryContext discoveryContext, + DefinitionBase? definition, + IDictionary contextData) + { + if (definition is ObjectTypeDefinition objectTypeDef) + { + foreach (var objectField in objectTypeDef.Fields) + { + if (objectField.GetDirectives().Any(IsDelegatedField)) + { + var handleDictionary = + Create(); + var delegateToSchema = + Create(); + + objectField.MiddlewareDefinitions.Insert(0, new(handleDictionary)); + objectField.MiddlewareDefinitions.Insert(0, new(delegateToSchema)); + _handledExternalFields.Add((objectTypeDef.Name, objectField.Name)); + } + } + } + + if (definition is SchemaTypeDefinition) + { + if (discoveryContext.ContextData.TryGetValue(RemoteExecutors, out var value)) + { + // we copy the remote executors that are stored only on the + // schema builder context to the schema context so that + // the stitching context can access these at runtime. + contextData.Add(RemoteExecutors, value); + } + + contextData.Add(NameLookup, discoveryContext.GetNameLookup()); + } + } + + public override void OnBeforeCompleteType( + ITypeCompletionContext completionContext, + DefinitionBase? definition, + IDictionary contextData) + { + if (completionContext.Type is ObjectType objectType && + definition is ObjectTypeDefinition objectTypeDef) + { + var externalFieldLookup = + completionContext.GetExternalFieldLookup(); + if (externalFieldLookup.TryGetValue(objectType.Name, + out var external)) + { + foreach (var objectField in objectTypeDef.Fields) + { + if (external.Contains(objectField.Name) && + _handledExternalFields.Add((objectTypeDef.Name, objectField.Name))) + { + if (objectField.Resolvers.HasResolvers) + { + var handleDictionary = + Create(); + objectField.MiddlewareDefinitions.Insert(0, new(handleDictionary)); + } + else + { + objectField.Resolvers = new FieldResolverDelegates( + pureResolver: RemoteFieldHelper.RemoteFieldResolver); + } + } + } + } + } + } + + private static bool IsDelegatedField(DirectiveDefinition directiveDef) + { + if (directiveDef.Reference is NameDirectiveReference nameRef && + nameRef.Name.Equals(DirectiveNames.Delegate)) + { + return true; + } + + if (directiveDef.Reference is ClrTypeDirectiveReference typeRef && + typeRef.ClrType == typeof(DelegateDirective)) + { + return true; + } + + return false; + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/WellKnownContextData.cs b/src/HotChocolate/Stitching/src/Stitching/WellKnownContextData.cs new file mode 100644 index 00000000000..0ac83d969de --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/WellKnownContextData.cs @@ -0,0 +1,19 @@ +namespace HotChocolate.Stitching; + +internal static class WellKnownContextData +{ + public const string IsAutoGenerated = "HotChocolate.Stitching.IsAutoGenerated"; + public const string SchemaName = "HotChocolate.Stitching.SchemaName"; + public const string RemoteExecutors = "HotChocolate.Stitching.Executor"; + public const string TypeMergeRules = "HotChocolate.Stitching.TypeMergeRules"; + public const string DirectiveMergeRules = "HotChocolate.Stitching.DirectiveMergeRules"; + public const string DocumentRewriter = "HotChocolate.Stitching.DocumentRewriter"; + public const string TypeRewriter = "HotChocolate.Stitching.TypeRewriter"; + public const string TypeExtensions = "HotChocolate.Stitching.TypeExtensions"; + public const string MergedDocRewriter = "HotChocolate.Stitching.MergedDocRewriter"; + public const string MergedDocVisitors = "HotChocolate.Stitching.MergedDocVisitors"; + public const string RequestVarNames = "HotChocolate.Stitching.RequestVarNames"; + public const string ExternalFieldLookup = "HotChocolate.Stitching.ExternalFieldLookup"; + public const string NameLookup = "HotChocolate.Stitching.NameLookup"; + public const string SchemaDefinitions = "HotChocolate.Stitching.SchemaDefinitions"; +} diff --git a/src/HotChocolate/Stitching/src/Stitching/WellKnownFieldNames.cs b/src/HotChocolate/Stitching/src/Stitching/WellKnownFieldNames.cs new file mode 100644 index 00000000000..79ef2f0f3b8 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/WellKnownFieldNames.cs @@ -0,0 +1,6 @@ +namespace HotChocolate.Stitching; + +internal static class WellKnownFieldNames +{ + public static string TypeName { get; } = "__typename"; +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Configuration/ContextDataExtensionsTest.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Configuration/ContextDataExtensionsTest.cs new file mode 100644 index 00000000000..4455e7d37df --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Configuration/ContextDataExtensionsTest.cs @@ -0,0 +1,72 @@ +using HotChocolate.Configuration; +using HotChocolate.Types; +using HotChocolate.Types.Descriptors; +using HotChocolate.Types.Descriptors.Definitions; + +namespace HotChocolate.Stitching.Configuration; + +public class ContextDataExtensionsTest +{ + [Fact] + public void AddNameLookup_Single() + { + // arrange + var schemaBuilder = SchemaBuilder.New().AddQueryType(); + + // act + schemaBuilder.AddNameLookup("OriginalType1", "NewType1", "Schema1"); + schemaBuilder.AddNameLookup("OriginalType2", "NewType2", "Schema2"); + + // assert + var lookup = + schemaBuilder + .Create() + .GetType(nameof(CustomQueryType)) + .Context + .GetNameLookup(); + Assert.Equal("OriginalType1", lookup[("NewType1", "Schema1")]); + Assert.Equal("OriginalType2", lookup[("NewType2", "Schema2")]); + } + + [Fact] + public void AddNameLookup_Multiple() + { + // arrange + var schemaBuilder = SchemaBuilder.New().AddQueryType(); + var dict = new Dictionary<(string, string), string> + { + { ("NewType1", "Schema1"), "OriginalType1" }, + { ("NewType2", "Schema2"), "OriginalType2" } + }; + + // act + schemaBuilder.AddNameLookup(dict); + + // assert + var lookup = + schemaBuilder + .Create() + .GetType(nameof(CustomQueryType)) + .Context + .GetNameLookup(); + Assert.Equal("OriginalType1", lookup[("NewType1", "Schema1")]); + Assert.Equal("OriginalType2", lookup[("NewType2", "Schema2")]); + } + + public class CustomQueryType : ObjectType + { + public IDescriptorContext Context { get; set; } + + protected override void Configure(IObjectTypeDescriptor descriptor) + { + descriptor.Field("foo").Resolve("bar"); + } + + protected override ObjectTypeDefinition CreateDefinition(ITypeDiscoveryContext context) + { + Context = context.DescriptorContext; + + return base.CreateDefinition(context); + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Configuration/HotChocolateStitchingRequestExecutorExtensionsTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Configuration/HotChocolateStitchingRequestExecutorExtensionsTests.cs new file mode 100644 index 00000000000..536c10c4dcf --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Configuration/HotChocolateStitchingRequestExecutorExtensionsTests.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using HotChocolate.Configuration; +using HotChocolate.Execution; +using HotChocolate.Execution.Configuration; +using HotChocolate.Types; +using HotChocolate.Types.Descriptors; +using HotChocolate.Types.Descriptors.Definitions; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace HotChocolate.Stitching.Configuration; + +public class HotChocolateStitchingRequestExecutorExtensionsTests +{ + [Fact] + public async Task RewriteType() + { + // arrange + var executorBuilder = + new ServiceCollection().AddGraphQL().AddQueryType(); + + // act + executorBuilder.RewriteType("OriginalType1", "NewType1", "Schema1"); + executorBuilder.RewriteType("OriginalType2", "NewType2", "Schema2"); + + // assert + var schema = await executorBuilder.BuildSchemaAsync(); + var lookup = + schema + .GetType(nameof(CustomQueryType)) + .Context + .GetNameLookup(); + Assert.Equal("OriginalType1", lookup[("NewType1", "Schema1")]); + Assert.Equal("OriginalType2", lookup[("NewType2", "Schema2")]); + } + + [Fact] + public void AddTypeExtensionsFromResource_Builder_Is_Null() + { + // arrange + // act + void Configure() => + HotChocolateStitchingRequestExecutorExtensions + .AddTypeExtensionsFromResource(null!, GetType().Assembly, "abc"); + + // assert + Assert.Throws(Configure); + } + + [Fact] + public void AddTypeExtensionsFromResource_Assembly_Is_Null() + { + // arrange + // act + void Configure() => + new ServiceCollection().AddGraphQL() + .AddTypeExtensionsFromResource(null!, "abc"); + + // assert + Assert.Throws(Configure); + } + + [Fact] + public void AddTypeExtensionsFromResource_Key_Is_Null() + { + // arrange + // act + void Configure() => + new ServiceCollection().AddGraphQL() + .AddTypeExtensionsFromResource(GetType().Assembly, null!); + + // assert + Assert.Throws(Configure); + } +} + +public class CustomQueryType : ObjectType +{ + public IDescriptorContext Context { get; set; } + + protected override void Configure(IObjectTypeDescriptor descriptor) + { + descriptor.Field("foo").Resolve("bar"); + } + + protected override ObjectTypeDefinition CreateDefinition(ITypeDiscoveryContext context) + { + Context = context.DescriptorContext; + + return base.CreateDefinition(context); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/ArgumentScopedVariableResolverTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/ArgumentScopedVariableResolverTests.cs new file mode 100644 index 00000000000..f5bfad543da --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/ArgumentScopedVariableResolverTests.cs @@ -0,0 +1,190 @@ +using HotChocolate.Execution; +using HotChocolate.Execution.Processing; +using HotChocolate.Language; +using HotChocolate.Resolvers; +using HotChocolate.Stitching.Delegation.ScopedVariables; +using HotChocolate.Types; +using Moq; + +namespace HotChocolate.Stitching.Delegation; + +public class ArgumentScopedVariableResolverTests +{ + [Fact] + public void CreateVariableValue() + { + // arrange + var schema = SchemaBuilder.New() + .AddDocumentFromString("type Query { foo(a: String = \"bar\") : String }") + .Use(_ => _) + .ModifyOptions(o => o.StrictValidation = false) + .Create(); + + var field = schema.GetType("Query").Fields["foo"]; + + var selection = new Mock(MockBehavior.Strict); + selection.SetupGet(t => t.Field).Returns(field); + + var context = new Mock(MockBehavior.Strict); + context.SetupGet(t => t.Selection).Returns(selection.Object); + context.Setup(t => t.ArgumentLiteral("a")) + .Returns(new StringValueNode("baz")); + + var scopedVariable = new ScopedVariableNode( + null, + new NameNode("arguments"), + new NameNode("a")); + + // act + var resolver = new ArgumentScopedVariableResolver(); + var value = resolver.Resolve( + context.Object, + scopedVariable, + schema.GetType("String")); + + // assert + Assert.Equal("bar", Assert.IsType(value.DefaultValue).Value); + Assert.Equal("__arguments_a", value.Name); + Assert.Equal("String", Assert.IsType(value.Type).Name.Value); + Assert.Equal("baz", value.Value!.Value); + } + + [Fact] + public void ArgumentDoesNotExist() + { + // arrange + var schema = SchemaBuilder.New() + .AddDocumentFromString("type Query { foo(a: String = \"bar\") : String }") + .Use(_ => _) + .ModifyOptions(o => o.StrictValidation = false) + .Create(); + + var selection = new Mock(MockBehavior.Strict); + selection.SetupGet(t => t.Field).Returns( + schema.GetType("Query").Fields["foo"]); + + var context = new Mock(MockBehavior.Strict); + context.SetupGet(t => t.Selection).Returns(selection.Object); + + context.Setup(t => t.ArgumentValue(It.IsAny())) + .Returns("Baz"); + context.Setup(t => t.Selection.SyntaxNode) + .Returns(new FieldNode( + null, + new NameNode("foo"), + null, + null, + Array.Empty(), + Array.Empty(), + null)); + context.Setup(t => t.Path).Returns(PathFactory.Instance.New("foo")); + + var scopedVariable = new ScopedVariableNode( + null, + new NameNode("arguments"), + new NameNode("b")); + + // act + var resolver = new ArgumentScopedVariableResolver(); + Action a = () => resolver.Resolve( + context.Object, + scopedVariable, + schema.GetType("String")); + + // assert + Assert.Collection( + Assert.Throws(a).Errors, + t => Assert.Equal(ErrorCodes.Stitching.ArgumentNotDefined, t.Code)); + } + + [Fact] + public void ContextIsNull() + { + // arrange + var schema = SchemaBuilder.New() + .AddDocumentFromString("type Query { foo(a: String = \"bar\") : String }") + .Use(_ => _) + .ModifyOptions(o => o.StrictValidation = false) + .Create(); + + var scopedVariable = new ScopedVariableNode( + null, + new NameNode("arguments"), + new NameNode("b")); + + // act + var resolver = new ArgumentScopedVariableResolver(); + Action a = () => resolver.Resolve( + null!, + scopedVariable, + schema.GetType("String")); + + // assert + Assert.Equal("context", Assert.Throws(a).ParamName); + } + + [Fact] + public void ScopedVariableIsNull() + { + // arrange + var schema = SchemaBuilder.New() + .AddDocumentFromString("type Query { foo(a: String = \"bar\") : String }") + .Use(_ => _) + .ModifyOptions(o => o.StrictValidation = false) + .Create(); + + var selection = new Mock(MockBehavior.Strict); + selection.SetupGet(t => t.Field).Returns( + schema.GetType("Query").Fields["foo"]); + + var context = new Mock(MockBehavior.Strict); + context.SetupGet(t => t.Selection).Returns(selection.Object); + context.Setup(t => t.ArgumentValue(It.IsAny())) + .Returns("Baz"); + + // act + var resolver = new ArgumentScopedVariableResolver(); + Action a = () => resolver.Resolve( + context.Object, + null!, + schema.GetType("String")); + + // assert + Assert.Equal("variable", Assert.Throws(a).ParamName); + } + + [Fact] + public void InvalidScope() + { + // arrange + var schema = SchemaBuilder.New() + .AddDocumentFromString("type Query { foo(a: String = \"bar\") : String }") + .Use(_ => _) + .ModifyOptions(o => o.StrictValidation = false) + .Create(); + + var field = schema.GetType("Query").Fields["foo"]; + + var selection = new Mock(MockBehavior.Strict); + selection.SetupGet(t => t.Field).Returns(field); + + var context = new Mock(MockBehavior.Strict); + context.SetupGet(t => t.Selection).Returns(selection.Object); + context.Setup(t => t.ArgumentValue(It.IsAny())).Returns("Baz"); + + var scopedVariable = new ScopedVariableNode( + null, + new NameNode("foo"), + new NameNode("b")); + + // act + var resolver = new ArgumentScopedVariableResolver(); + Action a = () => resolver.Resolve( + context.Object, + scopedVariable, + schema.GetType("String")); + + // assert + Assert.Equal("variable", Assert.Throws(a).ParamName); + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/ContextDataScopedVariableResolverTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/ContextDataScopedVariableResolverTests.cs new file mode 100644 index 00000000000..6359bb90d74 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/ContextDataScopedVariableResolverTests.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using HotChocolate.Language; +using HotChocolate.Resolvers; +using HotChocolate.Stitching.Delegation.ScopedVariables; +using HotChocolate.Types; +using Moq; +using Xunit; + +namespace HotChocolate.Stitching.Delegation; + +public class ContextDataScopedVariableResolverTests +{ + [Fact] + public void CreateVariableValue() + { + // arrange + var inputFormatter = new InputFormatter(); + + var schema = SchemaBuilder.New() + .AddDocumentFromString("type Query { foo(a: String = \"bar\") : String }") + .Use(_ => _) + .ModifyOptions(o => o.StrictValidation = false) + .Create(); + + var contextData = new Dictionary { ["a"] = "AbcDef" }; + + var context = new Mock(MockBehavior.Strict); + context.SetupGet(t => t.ContextData).Returns(contextData); + context.Setup(t => t.Service()).Returns(inputFormatter); + + var scopedVariable = new ScopedVariableNode( + null, + new NameNode("contextData"), + new NameNode("a")); + + // act + var resolver = new ContextDataScopedVariableResolver(); + var value = resolver.Resolve( + context.Object, + scopedVariable, + schema.GetType("String")); + + // assert + Assert.Null(value.DefaultValue); + Assert.Equal("__contextData_a", value.Name); + Assert.Equal("String", Assert.IsType(value.Type).Name.Value); + Assert.Equal("AbcDef", value.Value?.Value); + } + + [Fact] + public void ContextDataEntryDoesNotExist() + { + // arrange + var inputFormatter = new InputFormatter(); + + var schema = SchemaBuilder.New() + .AddDocumentFromString("type Query { foo(a: String = \"bar\") : String }") + .Use(_ => _) + .ModifyOptions(o => o.StrictValidation = false) + .Create(); + + var contextData = new Dictionary(); + + var context = new Mock(MockBehavior.Strict); + context.SetupGet(t => t.ContextData).Returns(contextData); + context.Setup(t => t.Service()).Returns(inputFormatter); + + var scopedVariable = new ScopedVariableNode( + null, + new NameNode("contextData"), + new NameNode("a")); + + // act + var resolver = new ContextDataScopedVariableResolver(); + var value = resolver.Resolve( + context.Object, + scopedVariable, + schema.GetType("String")); + + // assert + Assert.Null(value.DefaultValue); + Assert.Equal("__contextData_a", value.Name); + Assert.Equal("String", Assert.IsType(value.Type).Name.Value); + Assert.Equal(NullValueNode.Default, value.Value); + } + + + [Fact] + public void ContextIsNull() + { + // arrange + var schema = SchemaBuilder.New() + .AddDocumentFromString("type Query { foo(a: String = \"bar\") : String }") + .Use(_ => _) + .ModifyOptions(o => o.StrictValidation = false) + .Create(); + + var scopedVariable = new ScopedVariableNode( + null, + new NameNode("contextData"), + new NameNode("b")); + + // act + var resolver = new ContextDataScopedVariableResolver(); + Action a = () => resolver.Resolve( + null!, + scopedVariable, + schema.GetType("String")); + + // assert + Assert.Equal("context", Assert.Throws(a).ParamName); + } + + [Fact] + public void ScopedVariableIsNull() + { + // arrange + var schema = SchemaBuilder.New() + .AddDocumentFromString("type Query { foo(a: String = \"bar\") : String }") + .Use(_ => _) + .ModifyOptions(o => o.StrictValidation = false) + .Create(); + + var context = new Mock(); + + // act + var resolver = new ContextDataScopedVariableResolver(); + Action a = () => resolver.Resolve( + context.Object, + null!, + schema.GetType("String")); + + // assert + Assert.Equal("variable", Assert.Throws(a).ParamName); + } + + [Fact] + public void TargetTypeIsNull() + { + // arrange + var context = new Mock(); + + var scopedVariable = new ScopedVariableNode( + null, + new NameNode("contextData"), + new NameNode("b")); + + // act + var resolver = new ContextDataScopedVariableResolver(); + void Action() => resolver.Resolve(context.Object, scopedVariable, null!); + + // assert + Assert.Equal("targetType", Assert.Throws(Action).ParamName); + } + + [Fact] + public void InvalidScope() + { + // arrange + var schema = SchemaBuilder.New() + .AddDocumentFromString("type Query { foo(a: String = \"bar\") : String }") + .Use(_ => _) + .ModifyOptions(o => o.StrictValidation = false) + .Create(); + + var context = new Mock(); + + var scopedVariable = new ScopedVariableNode( + null, + new NameNode("foo"), + new NameNode("b")); + + // act + var resolver = new ContextDataScopedVariableResolver(); + Action a = () => resolver.Resolve( + context.Object, + scopedVariable, + schema.GetType("String")); + + // assert + Assert.Equal("variable", Assert.Throws(a).ParamName); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/DelegateDirectiveTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/DelegateDirectiveTests.cs new file mode 100644 index 00000000000..59bfc27ab35 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/DelegateDirectiveTests.cs @@ -0,0 +1,30 @@ +using System.Linq; +using HotChocolate.Language; +using HotChocolate.Language.Utilities; +using Snapshooter.Xunit; +using Xunit; + +namespace HotChocolate.Stitching.Delegation; + +public class DelegateDirectiveTests +{ + [Fact] + public void Directive_Definition_PrintIsMtch() + { + // arrange + var schemaDocument = SchemaBuilder.New() + .ModifyOptions(x => x.StrictValidation = false) + .AddDirectiveType() + .Create() + .ToDocument(); + + // act + var printed = schemaDocument.Definitions + .OfType() + .First(x => x.Name.Value == "delegate") + .Print(); + + // assert + printed.MatchSnapshot(); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/DictionaryDeserializerTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/DictionaryDeserializerTests.cs new file mode 100644 index 00000000000..1e5ae774cda --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/DictionaryDeserializerTests.cs @@ -0,0 +1,725 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using HotChocolate.Execution; +using HotChocolate.Language; +using HotChocolate.Types; +using HotChocolate.Utilities; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace HotChocolate.Stitching.Delegation; + +public class DictionaryDeserializerTests +{ + [Fact] + public async Task Deserialize_NullValueNode() + { + // arrange + var inputParser = new InputParser(new DefaultTypeConverter()); + Path path = PathFactory.Instance.New("root"); + + var schema = + await new ServiceCollection() + .AddGraphQLServer() + .AddQueryType() + .BuildSchemaAsync(); + + IType person = schema.GetType("Person"); + + // act + var value = DictionaryDeserializer.DeserializeResult( + person, + NullValueNode.Default, + inputParser, + path); + + // assert + Assert.Null(value); + } + + [Fact] + public async Task Deserialize_Null() + { + // arrange + var inputParser = new InputParser(new DefaultTypeConverter()); + Path path = PathFactory.Instance.New("root"); + + var schema = + await new ServiceCollection() + .AddGraphQLServer() + .AddQueryType() + .BuildSchemaAsync(); + + IType person = schema.GetType("Person"); + + // act + var value = DictionaryDeserializer.DeserializeResult( + person, + null, + inputParser, + path); + + // assert + Assert.Null(value); + } + + [Fact] + public async Task Deserialize_Dictionary() + { + // arrange + var inputParser = new InputParser(new DefaultTypeConverter()); + Path path = PathFactory.Instance.New("root"); + + var schema = + await new ServiceCollection() + .AddGraphQLServer() + .AddQueryType() + .BuildSchemaAsync(); + + IType person = schema.GetType("Person"); + + var dict = new Dictionary(); + + // act + var value = DictionaryDeserializer.DeserializeResult( + person, + dict, + inputParser, + path); + + // assert + Assert.Same(dict, value); + } + + [Fact] + public async Task Deserialize_String() + { + // arrange + var inputParser = new InputParser(new DefaultTypeConverter()); + Path path = PathFactory.Instance.New("root"); + + var schema = + await new ServiceCollection() + .AddGraphQLServer() + .AddQueryType() + .BuildSchemaAsync(); + + IType stringType = schema.GetType("String"); + + // act + var value = DictionaryDeserializer.DeserializeResult( + stringType, + "abc", + inputParser, + path); + + // assert + Assert.Equal("abc", value); + } + + [Fact] + public async Task Deserialize_StringValueNode() + { + // arrange + var inputParser = new InputParser(new DefaultTypeConverter()); + Path path = PathFactory.Instance.New("root"); + + var schema = + await new ServiceCollection() + .AddGraphQLServer() + .AddQueryType() + .BuildSchemaAsync(); + + IType stringType = schema.GetType("String"); + + // act + var value = DictionaryDeserializer.DeserializeResult( + stringType, + new StringValueNode("abc"), + inputParser, + path); + + // assert + Assert.Equal("abc", value); + } + + [Fact] + public async Task Deserialize_StringList_StringList() + { + // arrange + var inputParser = new InputParser(new DefaultTypeConverter()); + Path path = PathFactory.Instance.New("root"); + + var schema = + await new ServiceCollection() + .AddGraphQLServer() + .AddQueryType() + .BuildSchemaAsync(); + + IType stringListType = new ListType(schema.GetType("String")); + + // act + var value = DictionaryDeserializer.DeserializeResult( + stringListType, + new List { "abc" }, + inputParser, + path); + + // assert + Assert.Collection( + ((IEnumerable)value)!, + v => Assert.Equal("abc", v)); + } + + [Fact] + public async Task Deserialize_StringList_ListOfStringValueNode() + { + // arrange + var inputParser = new InputParser(new DefaultTypeConverter()); + Path path = PathFactory.Instance.New("root"); + + var schema = + await new ServiceCollection() + .AddGraphQLServer() + .AddQueryType() + .BuildSchemaAsync(); + + IType stringListType = new ListType(schema.GetType("String")); + + // act + var value = DictionaryDeserializer.DeserializeResult( + stringListType, + new List { new StringValueNode("abc") }, + inputParser, + path); + + // assert + Assert.Collection( + ((IEnumerable)value)!, + v => Assert.Equal("abc", v)); + } + + [Fact] + public async Task Deserialize_ListValueNode_Enum() + { + // arrange + var inputParser = new InputParser(new DefaultTypeConverter()); + Path path = PathFactory.Instance.New("root"); + + var schema = + await new ServiceCollection() + .AddGraphQLServer() + .AddQueryType(x => + x.Name("Query") + .Field("Foo") + .Resolve(Array.Empty()) + .Type>>()) + .BuildSchemaAsync(); + + var queryType = schema.GetType("Query"); + queryType.Fields.TryGetField("Foo", out var fooField); + + // act + var value = DictionaryDeserializer.DeserializeResult( + fooField!.Type, + new ListValueNode(new EnumValueNode(Foo.Bar), new EnumValueNode(Foo.Baz)), + inputParser, + path); + + // assert + Assert.Collection( + Assert.IsType>(value)!, + x => Assert.Equal(Foo.Bar, x), + x => Assert.Equal(Foo.Baz, x)); + } + + [Fact] + public async Task Deserialize_StringValueNode_Enum() + { + // arrange + var inputParser = new InputParser(new DefaultTypeConverter()); + Path path = PathFactory.Instance.New("root"); + + var schema = + await new ServiceCollection() + .AddGraphQLServer() + .AddQueryType(x => + x.Name("Query") + .Field("Foo") + .Resolve(default(Foo)) + .Type>()) + .BuildSchemaAsync(); + + var queryType = schema.GetType("Query"); + queryType.Fields.TryGetField("Foo", out var fooField); + + // act + var value = DictionaryDeserializer.DeserializeResult( + fooField!.Type, + new StringValueNode("BAZ"), + inputParser, + path); + + // assert + Assert.Equal(Foo.Baz, value); + } + + [Fact] + public async Task Deserialize_String_Enum() + { + // arrange + var inputParser = new InputParser(new DefaultTypeConverter()); + Path path = PathFactory.Instance.New("root"); + + var schema = + await new ServiceCollection() + .AddGraphQLServer() + .AddQueryType(x => + x.Name("Query") + .Field("Foo") + .Resolve(default(Foo)) + .Type>()) + .BuildSchemaAsync(); + + var queryType = schema.GetType("Query"); + queryType.Fields.TryGetField("Foo", out var fooField); + + // act + var value = DictionaryDeserializer.DeserializeResult( + fooField!.Type, + "BAZ", + inputParser, + path); + + // assert + Assert.Equal(Foo.Baz, value); + } + + [Fact] + public async Task Deserialize_EnumValueNode_Enum() + { + // arrange + var inputParser = new InputParser(new DefaultTypeConverter()); + Path path = PathFactory.Instance.New("root"); + + var schema = + await new ServiceCollection() + .AddGraphQLServer() + .AddQueryType(x => + x.Name("Query") + .Field("Foo") + .Resolve(default(Foo)) + .Type>()) + .BuildSchemaAsync(); + + var queryType = schema.GetType("Query"); + queryType.Fields.TryGetField("Foo", out var fooField); + + // act + var value = DictionaryDeserializer.DeserializeResult( + fooField!.Type, + new EnumValueNode(Foo.Baz), + inputParser, + path); + + // assert + Assert.Equal(Foo.Baz, value); + } + + [Fact] + public async Task Deserialize_ListEnum() + { + // arrange + var inputParser = new InputParser(new DefaultTypeConverter()); + Path path = PathFactory.Instance.New("root"); + + var schema = + await new ServiceCollection() + .AddGraphQLServer() + .AddQueryType(x => + x.Name("Query") + .Field("Foo") + .Resolve(Array.Empty()) + .Type>>()) + .BuildSchemaAsync(); + + var queryType = schema.GetType("Query"); + queryType.Fields.TryGetField("Foo", out var fooField); + + // act + var value = DictionaryDeserializer.DeserializeResult( + fooField!.Type, + new List { new EnumValueNode(Foo.Bar), new EnumValueNode(Foo.Baz) }, + inputParser, + path); + + // assert + Assert.Collection( + Assert.IsType>(value)!, + x => Assert.Equal(Foo.Bar, x), + x => Assert.Equal(Foo.Baz, x)); + } + + [Fact] + public async Task Deserialize_ListNestedEnum() + { + // arrange + var inputParser = new InputParser(new DefaultTypeConverter()); + Path path = PathFactory.Instance.New("root"); + + var schema = + await new ServiceCollection() + .AddGraphQLServer() + .AddQueryType(x => + x.Name("Query") + .Field("Foo") + .Resolve(Array.Empty()) + .Type>>>()) + .BuildSchemaAsync(); + + var queryType = schema.GetType("Query"); + queryType.Fields.TryGetField("Foo", out var fooField); + + + // act + var value = DictionaryDeserializer.DeserializeResult( + fooField!.Type, + new List + { + new List { new EnumValueNode(Foo.Bar), new EnumValueNode(Foo.Baz) } + }, + inputParser, + path); + + // assert + Assert.Collection( + Assert.IsType>(Assert.IsType>>(value)!.First())!, + x => Assert.Equal(Foo.Bar, x), + x => Assert.Equal(Foo.Baz, x)); + } + + [Fact] + public async Task Deserialize_NonNull_ListEnum() + { + // arrange + var inputParser = new InputParser(new DefaultTypeConverter()); + Path path = PathFactory.Instance.New("root"); + + var schema = + await new ServiceCollection() + .AddGraphQLServer() + .AddQueryType(x => + x.Name("Query") + .Field("Foo") + .Resolve(Array.Empty()) + .Type>>>()) + .BuildSchemaAsync(); + + var queryType = schema.GetType("Query"); + queryType.Fields.TryGetField("Foo", out var fooField); + + // act + var value = DictionaryDeserializer.DeserializeResult( + fooField!.Type, + new List { new EnumValueNode(Foo.Bar), new EnumValueNode(Foo.Baz) }, + inputParser, + path); + + // assert + Assert.Collection( + Assert.IsType>(value)!, + x => Assert.Equal(Foo.Bar, x), + x => Assert.Equal(Foo.Baz, x)); + } + + [Fact] + public async Task Deserialize_NonNull_ListNestedEnum() + { + // arrange + var inputParser = new InputParser(new DefaultTypeConverter()); + Path path = PathFactory.Instance.New("root"); + + var schema = + await new ServiceCollection() + .AddGraphQLServer() + .AddQueryType(x => + x.Name("Query") + .Field("Foo") + .Resolve(Array.Empty()) + .Type>>>>()) + .BuildSchemaAsync(); + + var queryType = schema.GetType("Query"); + queryType.Fields.TryGetField("Foo", out var fooField); + + + // act + var value = DictionaryDeserializer.DeserializeResult( + fooField!.Type, + new List + { + new List { new EnumValueNode(Foo.Bar), new EnumValueNode(Foo.Baz) } + }, + inputParser, + path); + + // assert + Assert.Collection( + Assert.IsType>(Assert.IsType>>(value)!.First())!, + x => Assert.Equal(Foo.Bar, x), + x => Assert.Equal(Foo.Baz, x)); + } + + + [Fact] + public async Task Deserialize_ListValueNode_Int() + { + // arrange + var inputParser = new InputParser(new DefaultTypeConverter()); + Path path = PathFactory.Instance.New("root"); + + var schema = + await new ServiceCollection() + .AddGraphQLServer() + .AddQueryType(x => + x.Name("Query") + .Field("Foo") + .Resolve(Array.Empty()) + .Type>()) + .BuildSchemaAsync(); + + var queryType = schema.GetType("Query"); + queryType.Fields.TryGetField("Foo", out var fooField); + + // act + var value = DictionaryDeserializer.DeserializeResult( + fooField!.Type, + new ListValueNode(new IntValueNode(1), new IntValueNode(2)), + inputParser, + path); + + // assert + Assert.Collection( + Assert.IsType>(value)!, + x => Assert.Equal(1, x), + x => Assert.Equal(2, x)); + } + + [Fact] + public async Task Deserialize_IntValueNode_Int() + { + // arrange + var inputParser = new InputParser(new DefaultTypeConverter()); + Path path = PathFactory.Instance.New("root"); + + var schema = + await new ServiceCollection() + .AddGraphQLServer() + .AddQueryType(x => + x.Name("Query") + .Field("Foo") + .Resolve(1) + .Type()) + .BuildSchemaAsync(); + + var queryType = schema.GetType("Query"); + queryType.Fields.TryGetField("Foo", out var fooField); + + // act + var value = DictionaryDeserializer.DeserializeResult( + fooField!.Type, + new IntValueNode(2), + inputParser, + path); + + // assert + Assert.Equal(2, value); + } + + [Fact] + public async Task Deserialize_Int_Int() + { + // arrange + var inputParser = new InputParser(new DefaultTypeConverter()); + Path path = PathFactory.Instance.New("root"); + + var schema = + await new ServiceCollection() + .AddGraphQLServer() + .AddQueryType(x => + x.Name("Query") + .Field("Foo") + .Resolve(1) + .Type()) + .BuildSchemaAsync(); + + var queryType = schema.GetType("Query"); + queryType.Fields.TryGetField("Foo", out var fooField); + + // act + var value = DictionaryDeserializer.DeserializeResult( + fooField!.Type, + 2, + inputParser, + path); + + // assert + Assert.Equal(2, value); + } + + [Fact] + public async Task Deserialize_ListInt() + { + // arrange + var inputParser = new InputParser(new DefaultTypeConverter()); + Path path = PathFactory.Instance.New("root"); + + var schema = + await new ServiceCollection() + .AddGraphQLServer() + .AddQueryType(x => + x.Name("Query") + .Field("Foo") + .Resolve(Array.Empty()) + .Type>()) + .BuildSchemaAsync(); + + var queryType = schema.GetType("Query"); + queryType.Fields.TryGetField("Foo", out var fooField); + + // act + var value = DictionaryDeserializer.DeserializeResult( + fooField!.Type, + new List { new IntValueNode(1), new IntValueNode(2) }, + inputParser, + path); + + // assert + Assert.Collection( + Assert.IsType>(value)!, + x => Assert.Equal(1, x), + x => Assert.Equal(2, x)); + } + + [Fact] + public async Task Deserialize_ListNestedInt() + { + // arrange + var inputParser = new InputParser(new DefaultTypeConverter()); + Path path = PathFactory.Instance.New("root"); + + var schema = + await new ServiceCollection() + .AddGraphQLServer() + .AddQueryType(x => + x.Name("Query") + .Field("Foo") + .Resolve(Array.Empty()) + .Type>>()) + .BuildSchemaAsync(); + + var queryType = schema.GetType("Query"); + queryType.Fields.TryGetField("Foo", out var fooField); + + + // act + var value = DictionaryDeserializer.DeserializeResult( + fooField!.Type, + new List { new List { new IntValueNode(1), new IntValueNode(2) } }, + inputParser, + path); + + // assert + Assert.Collection( + Assert.IsType>(Assert.IsType>>(value)!.First())!, + x => Assert.Equal(1, x), + x => Assert.Equal(2, x)); + } + + [Fact] + public async Task Deserialize_NonNull_ListInt() + { + // arrange + var inputParser = new InputParser(new DefaultTypeConverter()); + Path path = PathFactory.Instance.New("root"); + + var schema = + await new ServiceCollection() + .AddGraphQLServer() + .AddQueryType(x => + x.Name("Query") + .Field("Foo") + .Resolve(Array.Empty()) + .Type>>()) + .BuildSchemaAsync(); + + var queryType = schema.GetType("Query"); + queryType.Fields.TryGetField("Foo", out var fooField); + + // act + var value = DictionaryDeserializer.DeserializeResult( + fooField!.Type, + new List { new IntValueNode(1), new IntValueNode(2) }, + inputParser, + path); + + // assert + Assert.Collection( + Assert.IsType>(value)!, + x => Assert.Equal(1, x), + x => Assert.Equal(2, x)); + } + + [Fact] + public async Task Deserialize_NonNull_ListNestedInt() + { + // arrange + var inputParser = new InputParser(new DefaultTypeConverter()); + Path path = PathFactory.Instance.New("root"); + + var schema = + await new ServiceCollection() + .AddGraphQLServer() + .AddQueryType(x => + x.Name("Query") + .Field("Foo") + .Resolve(Array.Empty()) + .Type>>>()) + .BuildSchemaAsync(); + + var queryType = schema.GetType("Query"); + queryType.Fields.TryGetField("Foo", out var fooField); + + + // act + var value = DictionaryDeserializer.DeserializeResult( + fooField!.Type, + new List { new List { new IntValueNode(1), new IntValueNode(2) } }, + inputParser, + path); + + // assert + Assert.Collection( + Assert.IsType>(Assert.IsType>>(value)!.First())!, + x => Assert.Equal(1, x), + x => Assert.Equal(2, x)); + } + + public class Query + { + public Person GetPerson() => new Person(); + } + + public class Person + { + public string Name { get; } = "Jon Doe"; + } + + public enum Foo + { + Bar, + Baz + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/FieldScopedVariableResolverTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/FieldScopedVariableResolverTests.cs new file mode 100644 index 00000000000..1a79899a43a --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/FieldScopedVariableResolverTests.cs @@ -0,0 +1,180 @@ +using HotChocolate.Execution; +using HotChocolate.Language; +using HotChocolate.Resolvers; +using HotChocolate.Stitching.Delegation.ScopedVariables; +using HotChocolate.Types; +using Moq; + +namespace HotChocolate.Stitching.Delegation; + +public class FieldScopedVariableResolverTests +{ + [Fact] + public void CreateVariableValue() + { + // arrange + var inputFormatter = new InputFormatter(); + + var schema = SchemaBuilder.New() + .AddDocumentFromString("type Query { foo(a: String = \"bar\") : String a: String }") + .Use(_ => _) + .ModifyOptions(o => o.StrictValidation = false) + .Create(); + + var context = new Mock(MockBehavior.Strict); + context.SetupGet(t => t.ObjectType).Returns( + schema.GetType("Query")); + context.SetupGet(t => t.Selection.Field).Returns( + schema.GetType("Query").Fields["foo"]); + context.Setup(t => t.Parent()) + .Returns(new Dictionary { { "a", "baz" } }); + context.Setup(t => t.Service()).Returns(inputFormatter); + + var scopedVariable = new ScopedVariableNode( + null, + new NameNode("fields"), + new NameNode("a")); + + // act + var resolver = new FieldScopedVariableResolver(); + var value = resolver.Resolve( + context.Object, + scopedVariable, + schema.GetType("String")); + + // assert + Assert.Null(value.DefaultValue); + Assert.Equal("__fields_a", value.Name); + Assert.IsType(value.Type); + Assert.Equal("baz", value.Value.Value); + } + + [Fact] + public void FieldDoesNotExist() + { + // arrange + var schema = SchemaBuilder.New() + .AddDocumentFromString("type Query { foo(a: String = \"bar\") : String }") + .Use(_ => _) + .ModifyOptions(o => o.StrictValidation = false) + .Create(); + + var context = new Mock(MockBehavior.Strict); + context.SetupGet(t => t.ObjectType).Returns( + schema.GetType("Query")); + context.SetupGet(t => t.Selection.Field).Returns( + schema.GetType("Query").Fields["foo"]); + context.Setup(t => t.Parent>()) + .Returns(new Dictionary { { "a", "baz" } }); + context.Setup(t => t.Selection.SyntaxNode) + .Returns(new FieldNode( + null, + new NameNode("foo"), + null, + null, + Array.Empty(), + Array.Empty(), + null)); + context.Setup(t => t.Path).Returns(PathFactory.Instance.New("foo")); + + var scopedVariable = new ScopedVariableNode( + null, + new NameNode("fields"), + new NameNode("b")); + + // act + var resolver = new FieldScopedVariableResolver(); + Action a = () => resolver.Resolve( + context.Object, + scopedVariable, + schema.GetType("String")); + + // assert + Assert.Collection( + Assert.Throws(a).Errors, + t => Assert.Equal(ErrorCodes.Stitching.FieldNotDefined, t.Code)); + } + + [Fact] + public void ContextIsNull() + { + // arrange + var schema = SchemaBuilder.New() + .AddDocumentFromString("type Query { foo(a: String = \"bar\") : String }") + .Use(_ => _) + .ModifyOptions(o => o.StrictValidation = false) + .Create(); + + var scopedVariable = new ScopedVariableNode( + null, + new NameNode("fields"), + new NameNode("b")); + + // act + var resolver = new FieldScopedVariableResolver(); + void Action() + => resolver.Resolve(null!, scopedVariable, schema.GetType("String")); + + // assert + Assert.Equal("context", Assert.Throws((Action)Action).ParamName); + } + + [Fact] + public void ScopedVariableIsNull() + { + // arrange + var schema = SchemaBuilder.New() + .AddDocumentFromString("type Query { foo(a: String = \"bar\") : String }") + .Use(_ => _) + .ModifyOptions(o => o.StrictValidation = false) + .Create(); + + var context = new Mock(); + context.SetupGet(t => t.Selection.Field).Returns( + schema.GetType("Query").Fields["foo"]); + context.Setup(t => t.ArgumentValue(It.IsAny())) + .Returns("Baz"); + + // act + var resolver = new FieldScopedVariableResolver(); + Action a = () => resolver.Resolve( + context.Object, + null, + schema.GetType("String")); + + // assert + Assert.Equal("variable", Assert.Throws(a).ParamName); + } + + [Fact] + public void InvalidScope() + { + // arrange + var schema = SchemaBuilder.New() + .AddDocumentFromString("type Query { foo(a: String = \"bar\") : String }") + .Use(_ => _) + .ModifyOptions(o => o.StrictValidation = false) + .Create(); + + var context = new Mock(); + context.SetupGet(t => t.Selection.Field).Returns( + schema.GetType("Query").Fields["foo"]); + context.Setup(t => t.ArgumentValue(It.IsAny())) + .Returns("Baz"); + + var scopedVariable = new ScopedVariableNode( + null, + new NameNode("foo"), + new NameNode("b")); + + // act + var resolver = new FieldScopedVariableResolver(); + Action a = () => resolver.Resolve( + context.Object, + scopedVariable, + schema.GetType("String")); + + // assert + Assert.Equal("variable", Assert.Throws(a).ParamName); + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/RemoteQueryBuilderTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/RemoteQueryBuilderTests.cs new file mode 100644 index 00000000000..4842896e323 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/RemoteQueryBuilderTests.cs @@ -0,0 +1,97 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using HotChocolate.Language; +using HotChocolate.Language.Utilities; +using Snapshooter.Xunit; +using Xunit; + +namespace HotChocolate.Stitching.Delegation; + +public class RemoteQueryBuilderTests +{ + [Fact] + public void BuildRemoteQuery() + { + // arrange + var path = + SelectionPathParser.Parse("a.b.c.d(a: $fields:bar)"); + + var initialQuery = + Utf8GraphQLParser.Parse( + @"{ + foo { + bar { + baz { + ... on Baz { + qux + } + } + } + } + } + "); + + var field = initialQuery.Definitions + .OfType().Single() + .SelectionSet.Selections + .OfType().Single() + .SelectionSet.Selections + .OfType().Single(); + + // act + var newQuery = RemoteQueryBuilder.New() + .SetOperation(null, OperationType.Query) + .SetSelectionPath(path) + .SetRequestField(field) + .AddVariable("__fields_bar", new NamedTypeNode(null, new NameNode("String"))) + .Build("abc", new Dictionary<(string Type, string Schema), string>()); + + // assert + newQuery.Print().MatchSnapshot(); + } + + [Fact] + public void BuildRemoteQueryCanOverrideOperationName() + { + // arrange + var path = + SelectionPathParser.Parse("a.b.c.d(a: $fields:bar)"); + + var initialQuery = + Utf8GraphQLParser.Parse( + @"{ + foo { + bar { + baz { + ... on Baz { + qux + } + } + } + } + } + "); + + var field = initialQuery.Definitions + .OfType().Single() + .SelectionSet.Selections + .OfType().Single() + .SelectionSet!.Selections + .OfType().Single(); + + + // act + var newQuery = RemoteQueryBuilder.New() + .SetOperation(new NameNode( + nameof(BuildRemoteQueryCanOverrideOperationName)), + OperationType.Query) + .SetSelectionPath(path) + .SetRequestField(field) + .AddVariable("__fields_bar", new NamedTypeNode(null, new NameNode("String"))) + .Build("abc", new Dictionary<(string Type, string Schema), string>()); + + // assert + newQuery.Print().MatchSnapshot(); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/ScopedContextDataScopedVariableResolverTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/ScopedContextDataScopedVariableResolverTests.cs new file mode 100644 index 00000000000..63ba43e12bc --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/ScopedContextDataScopedVariableResolverTests.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Immutable; +using HotChocolate.Language; +using HotChocolate.Resolvers; +using HotChocolate.Stitching.Delegation.ScopedVariables; +using HotChocolate.Types; +using Moq; +using Xunit; + +namespace HotChocolate.Stitching.Delegation; + +public class ScopedContextDataScopedVariableResolverTests +{ + [Fact] + public void CreateVariableValue() + { + // arrange + var inputFormatter = new InputFormatter(); + + var schema = SchemaBuilder.New() + .AddDocumentFromString("type Query { foo(a: String = \"bar\") : String }") + .Use(_ => _) + .ModifyOptions(o => o.StrictValidation = false) + .Create(); + + var contextData = + ImmutableDictionary.Empty + .Add("a", "AbcDef"); + + var context = new Mock(MockBehavior.Strict); + context.SetupGet(t => t.ScopedContextData).Returns(contextData); + context.Setup(t => t.Service()).Returns(inputFormatter); + + var scopedVariable = new ScopedVariableNode( + null, + new NameNode("scopedContextData"), + new NameNode("a")); + + // act + var resolver = new ScopedContextDataScopedVariableResolver(); + var value = resolver.Resolve( + context.Object, + scopedVariable, + schema.GetType("String")); + + // assert + Assert.Null(value.DefaultValue); + Assert.Equal("__scopedContextData_a", value.Name); + Assert.Equal("String", Assert.IsType(value.Type).Name.Value); + Assert.Equal("AbcDef", value.Value!.Value); + } + + [Fact] + public void ContextDataEntryDoesNotExist() + { + // arrange + var inputFormatter = new InputFormatter(); + + var schema = SchemaBuilder.New() + .AddDocumentFromString("type Query { foo(a: String = \"bar\") : String }") + .Use(_ => _) + .ModifyOptions(o => o.StrictValidation = false) + .Create(); + + var contextData = + ImmutableDictionary.Empty; + + var context = new Mock(MockBehavior.Strict); + context.SetupGet(t => t.ScopedContextData).Returns(contextData); + context.Setup(t => t.Service()).Returns(inputFormatter); + + var scopedVariable = new ScopedVariableNode( + null, + new NameNode("scopedContextData"), + new NameNode("a")); + + // act + var resolver = new ScopedContextDataScopedVariableResolver(); + var value = resolver.Resolve( + context.Object, + scopedVariable, + schema.GetType("String")); + + // assert + Assert.Null(value.DefaultValue); + Assert.Equal("__scopedContextData_a", value.Name); + Assert.Equal("String", Assert.IsType(value.Type).Name.Value); + Assert.Equal(NullValueNode.Default, value.Value); + } + + + [Fact] + public void ContextIsNull() + { + // arrange + var schema = SchemaBuilder.New() + .AddDocumentFromString("type Query { foo(a: String = \"bar\") : String }") + .Use(_ => _) + .ModifyOptions(o => o.StrictValidation = false) + .Create(); + + var scopedVariable = new ScopedVariableNode( + null, + new NameNode("scopedContextData"), + new NameNode("b")); + + // act + var resolver = new ScopedContextDataScopedVariableResolver(); + void Action() => resolver.Resolve( + null!, + scopedVariable, + schema.GetType("String")); + + // assert + Assert.Equal("context", Assert.Throws(Action).ParamName); + } + + [Fact] + public void ScopedVariableIsNull() + { + // arrange + var schema = SchemaBuilder.New() + .AddDocumentFromString("type Query { foo(a: String = \"bar\") : String }") + .Use(_ => _) + .ModifyOptions(o => o.StrictValidation = false) + .Create(); + + var context = new Mock(); + + // act + var resolver = new ScopedContextDataScopedVariableResolver(); + void Action() => resolver.Resolve( + context.Object, + null!, + schema.GetType("String")); + + // assert + Assert.Equal("variable", Assert.Throws(Action).ParamName); + } + + [Fact] + public void TargetTypeIsNull() + { + // arrange + var context = new Mock(); + + var scopedVariable = new ScopedVariableNode( + null, + new NameNode("scopedContextData"), + new NameNode("b")); + + // act + var resolver = new ScopedContextDataScopedVariableResolver(); + + void Action() => resolver.Resolve(context.Object, scopedVariable, null!); + + // assert + Assert.Equal("targetType", Assert.Throws(Action).ParamName); + } + + [Fact] + public void InvalidScope() + { + // arrange + var schema = SchemaBuilder.New() + .AddDocumentFromString("type Query { foo(a: String = \"bar\") : String }") + .Use(_ => _) + .ModifyOptions(o => o.StrictValidation = false) + .Create(); + + var context = new Mock(); + + var scopedVariable = new ScopedVariableNode( + null, + new NameNode("foo"), + new NameNode("b")); + + // act + var resolver = new ScopedContextDataScopedVariableResolver(); + void Action() => resolver.Resolve( + context.Object, + scopedVariable, + schema.GetType("String")); + + // assert + Assert.Equal("variable", Assert.Throws(Action).ParamName); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/__snapshots__/DelegateDirectiveTests.Directive_Definition_PrintIsMtch.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/__snapshots__/DelegateDirectiveTests.Directive_Definition_PrintIsMtch.snap new file mode 100644 index 00000000000..7438c6c19e3 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/__snapshots__/DelegateDirectiveTests.Directive_Definition_PrintIsMtch.snap @@ -0,0 +1,2 @@ +"Delegates a resolver to a remote schema." +directive @delegate("The path to the field on the remote schema." path: String "The name of the schema to which this field shall be delegated to." schema: String!) on FIELD_DEFINITION diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/__snapshots__/RemoteQueryBuilderTests.BuildRemoteQuery.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/__snapshots__/RemoteQueryBuilderTests.BuildRemoteQuery.snap new file mode 100644 index 00000000000..0b072409d62 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/__snapshots__/RemoteQueryBuilderTests.BuildRemoteQuery.snap @@ -0,0 +1,15 @@ +query fetch($__fields_bar: String) { + a { + b { + c { + bar: d(a: $__fields_bar) { + baz { + ... on Baz { + qux + } + } + } + } + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/__snapshots__/RemoteQueryBuilderTests.BuildRemoteQueryCanOverrideOperationName.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/__snapshots__/RemoteQueryBuilderTests.BuildRemoteQueryCanOverrideOperationName.snap new file mode 100644 index 00000000000..1aadab04595 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/__snapshots__/RemoteQueryBuilderTests.BuildRemoteQueryCanOverrideOperationName.snap @@ -0,0 +1,15 @@ +query BuildRemoteQueryCanOverrideOperationName($__fields_bar: String) { + a { + b { + c { + bar: d(a: $__fields_bar) { + baz { + ... on Baz { + qux + } + } + } + } + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/__snapshots__/RemoteQueryMiddlewareTests.ExecuteQueryOnRemoteSchema.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/__snapshots__/RemoteQueryMiddlewareTests.ExecuteQueryOnRemoteSchema.snap new file mode 100644 index 00000000000..c55ca18982e --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Delegation/__snapshots__/RemoteQueryMiddlewareTests.ExecuteQueryOnRemoteSchema.snap @@ -0,0 +1,18 @@ +{ + "Data": { + "contracts": [ + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQx" + }, + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQy" + }, + { + "id": "U29tZU90aGVyQ29udHJhY3QKZDE=" + } + ] + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Directives/SelectionPathParserTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Directives/SelectionPathParserTests.cs new file mode 100644 index 00000000000..d6facad794a --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Directives/SelectionPathParserTests.cs @@ -0,0 +1,177 @@ +using System.Collections.Immutable; +using System.Linq; +using Xunit; + +namespace HotChocolate.Stitching; + +public class SelectionPathParserTests +{ + [Fact] + public void Parse_Single_Chain_No_Arguments() + { + // arrange + var pathString = "foo"; + + // act + var path = + SelectionPathParser.Parse(pathString); + + // assert + Assert.Collection(path, + segment => + { + Assert.Equal("foo", segment.Name.Value); + Assert.Empty(segment.Arguments); + }); + } + + [Fact] + public void Parse_Single_Chain_With_Literal() + { + // arrange + var pathString = "foo(bar: 1)"; + + // act + var path = + SelectionPathParser.Parse(pathString); + + // assert + Assert.Collection(path, + segment => + { + Assert.Equal("foo", segment.Name.Value); + Assert.Collection(segment.Arguments, + argument => + { + Assert.Equal("bar", argument.Name.Value); + Assert.Equal("1", argument.Value.Value); + }); + }); + } + + [Fact] + public void Parse_Two_Chain_No_Arguments() + { + // arrange + var pathString = "foo.bar"; + + // act + var path = + SelectionPathParser.Parse(pathString); + + // assert + Assert.Collection(path.Reverse(), + segment => + { + Assert.Equal("foo", segment.Name.Value); + Assert.Empty(segment.Arguments); + }, + segment => + { + Assert.Equal("bar", segment.Name.Value); + Assert.Empty(segment.Arguments); + }); + } + + [Fact] + public void Parse_Two_Chain_With_Literal() + { + // arrange + var pathString = "foo(bar: 1).baz(quox: 2)"; + + // act + var path = + SelectionPathParser.Parse(pathString); + + // assert + Assert.Collection(path.Reverse(), + segment => + { + Assert.Equal("foo", segment.Name.Value); + Assert.Collection(segment.Arguments, + argument => + { + Assert.Equal("bar", argument.Name.Value); + Assert.Equal("1", argument.Value.Value); + }); + }, + segment => + { + Assert.Equal("baz", segment.Name.Value); + Assert.Collection(segment.Arguments, + argument => + { + Assert.Equal("quox", argument.Name.Value); + Assert.Equal("2", argument.Value.Value); + }); + }); + } + + [Fact] + public void Parse_Single_Chain_With_ScopedVariable() + { + // arrange + var pathString = "foo(bar: $fields:foo)"; + + // act + var path = + SelectionPathParser.Parse(pathString); + + // assert + Assert.Collection(path, + segment => + { + Assert.Equal("foo", segment.Name.Value); + Assert.Collection(segment.Arguments, + argument => + { + Assert.Equal("bar", argument.Name.Value); + + var variable = + Assert.IsType(argument.Value); + + Assert.Equal("fields", variable.Scope.Value); + Assert.Equal("foo", variable.Name.Value); + }); + }); + } + + [Fact] + public void Parse_Two_Chain_With_ScopedVariable() + { + // arrange + var pathString = "foo(bar: $fields:foo).baz(quox: 1)"; + + // act + var path = + SelectionPathParser.Parse(pathString); + + // assert + Assert.Collection(path.Reverse(), + segment => + { + Assert.Equal("foo", segment.Name.Value); + Assert.Collection(segment.Arguments, + argument => + { + Assert.Equal("bar", argument.Name.Value); + + var variable = + Assert.IsType(argument.Value); + + Assert.Equal("fields", variable.Scope.Value); + Assert.Equal("foo", variable.Name.Value); + }); + }, + segment => + { + Assert.Equal("baz", segment.Name.Value); + Assert.Collection(segment.Arguments, + argument => + { + Assert.Equal("quox", argument.Name.Value); + Assert.Equal("1", argument.Value.Value); + }); + }); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/HotChocolate.Stitching.Tests.csproj b/src/HotChocolate/Stitching/test/Stitching.Tests/HotChocolate.Stitching.Tests.csproj new file mode 100644 index 00000000000..84069231022 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/HotChocolate.Stitching.Tests.csproj @@ -0,0 +1,36 @@ + + + + HotChocolate.Stitching.Tests + HotChocolate.Stitching + + + + + + + + + + + + + + Always + + + + + + + + + + Always + + + Always + + + + diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/BaseTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/BaseTests.cs new file mode 100644 index 00000000000..1d915088767 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/BaseTests.cs @@ -0,0 +1,948 @@ +using HotChocolate.Execution; +using HotChocolate.Language; +using HotChocolate.Resolvers; +using HotChocolate.Stitching.Schemas.Customers; +using HotChocolate.Tests; +using HotChocolate.Types; +using Microsoft.Extensions.DependencyInjection; +using Snapshooter.Xunit; + +namespace HotChocolate.Stitching.Integration; + +public class BaseTests : IClassFixture +{ + public BaseTests(StitchingTestContext context) + { + Context = context; + } + + protected StitchingTestContext Context { get; } + + [Fact] + public async Task AutoMerge_Schema() + { + // arrange + var httpClientFactory = + Context.CreateDefaultRemoteSchemas(); + + // act + var schema = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddRemoteSchema(Context.ContractSchema) + .AddRemoteSchema(Context.CustomerSchema) + .BuildSchemaAsync(); + + // assert + schema.Print().MatchSnapshot(); + } + + [Fact] + public async Task AutoMerge_Execute() + { + // arrange + var httpClientFactory = + Context.CreateDefaultRemoteSchemas(); + + var executor = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddRemoteSchema(Context.ContractSchema) + .AddRemoteSchema(Context.CustomerSchema) + .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) + .BuildRequestExecutorAsync(); + + // act + var result = await executor.ExecuteAsync( + @"{ + allCustomers { + id + name + } + }"); + + // assert + result.MatchSnapshot(); + } + + [Fact] + public async Task LocalField_Execute() + { + // arrange + var httpClientFactory = + Context.CreateDefaultRemoteSchemas(); + + var executor = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddTypeExtension(new ObjectTypeExtension(d + => d.Name("Query").Field("local").Resolve("I am local."))) + .AddRemoteSchema(Context.ContractSchema) + .AddRemoteSchema(Context.CustomerSchema) + .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) + .BuildRequestExecutorAsync(); + + // act + var result = await executor.ExecuteAsync( + @"{ + local + allCustomers { + id + name + } + }"); + + // assert + result.MatchSnapshot(); + } + + [Fact] + public async Task Schema_AddResolver() + { + // arrange + var httpClientFactory = + Context.CreateDefaultRemoteSchemas(); + + var executor = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddRemoteSchema(Context.ContractSchema) + .AddRemoteSchema(Context.CustomerSchema) + .AddResolver("Query", "local", "I am local") + .AddTypeExtensionsFromString("extend type Query { local: String }") + .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) + .BuildRequestExecutorAsync(); + + // act + var result = await executor.ExecuteAsync( + @"{ + local + allCustomers { + id + name + } + }"); + + // assert + result.MatchSnapshot(); + } + + [Fact] + public async Task AutoMerge_Execute_Inline_Fragment() + { + // arrange + var httpClientFactory = + Context.CreateDefaultRemoteSchemas(); + + var executor = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddRemoteSchema(Context.ContractSchema) + .AddRemoteSchema(Context.CustomerSchema) + .AddTypeExtensionsFromString( + @"extend type Customer { + contracts: [Contract!] + @delegate( + schema: ""contract"", + path: ""contracts(customerId:$fields:id)"") + }") + .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) + .BuildRequestExecutorAsync(); + + // act + var result = await executor.ExecuteAsync( + @"{ + customer(id: ""Q3VzdG9tZXIKZDE="") { + name + consultant { + name + } + contracts { + id + ... on LifeInsuranceContract { + premium + } + ... on SomeOtherContract { + expiryDate + } + } + } + }"); + + // assert + result.MatchSnapshot(); + } + + [Fact] + public async Task AutoMerge_Execute_Fragment_Definition() + { + // arrange + var httpClientFactory = + Context.CreateDefaultRemoteSchemas(); + + var executor = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddRemoteSchema(Context.ContractSchema) + .AddRemoteSchema(Context.CustomerSchema) + .AddTypeExtensionsFromString( + @"extend type Customer { + contracts: [Contract!] + @delegate( + schema: ""contract"", + path: ""contracts(customerId:$fields:id)"") + }") + .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) + .BuildRequestExecutorAsync(); + + // act + var result = await executor.ExecuteAsync( + @"{ + customer(id: ""Q3VzdG9tZXIKZDE="") { + name + consultant { + name + } + contracts { + id + ...a + ...b + } + } + } + + fragment a on LifeInsuranceContract { + premium + } + + fragment b on SomeOtherContract { + expiryDate + }"); + + // assert + result.MatchSnapshot(); + } + + [Fact] + public async Task AutoMerge_Execute_Variables() + { + // arrange + var httpClientFactory = + Context.CreateDefaultRemoteSchemas(); + + var executor = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddRemoteSchema(Context.ContractSchema) + .AddRemoteSchema(Context.CustomerSchema) + .AddTypeExtensionsFromString( + @"extend type Customer { + contracts: [Contract!] + @delegate( + schema: ""contract"", + path: ""contracts(customerId:$fields:id)"") + }") + .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) + .BuildRequestExecutorAsync(); + + var variables = new Dictionary + { + { "customerId", "Q3VzdG9tZXIKZDE=" }, + { "deep", "deep" }, { "deeper", "deeper" } + }; + + // act + var result = await executor.ExecuteAsync( + @"query customer_query( + $customerId: ID! + $deep: String! + $deeper: String! + $deeperArray: String + $complex: ComplexInputType + $deeperInArray: String + ) { + customer(id: $customerId) { + name + consultant { + name + } + complexArg( + arg: { + value: $deep + deeper: { + value: ""CONSTANT"" + deeper: { + value: $deeper + deeperArray: [ + { + value: ""CONSTANT_ARRAY"", + deeper: { + value: $deeperInArray + } + } + ] + } + } + deeperArray: [ + { + value: ""CONSTANT_ARRAY"", + deeper: { + value: $deeperArray + } + } + $complex + ] + } + ) + contracts { + id + ... on LifeInsuranceContract { + premium + } + ... on SomeOtherContract { + expiryDate + } + } + } + }", + variables); + + // assert + result.MatchSnapshot(); + } + + [Fact] + public async Task AutoMerge_Execute_Union() + { + // arrange + var httpClientFactory = + Context.CreateDefaultRemoteSchemas(); + + var executor = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddRemoteSchema(Context.ContractSchema) + .AddRemoteSchema(Context.CustomerSchema) + .AddTypeExtensionsFromString( + @"extend type Customer { + contracts: [Contract!] + @delegate( + schema: ""contract"", + path: ""contracts(customerId:$fields:id)"") + }") + .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) + .BuildRequestExecutorAsync(); + + // act + var result = await executor.ExecuteAsync( + @"{ + customer: customerOrConsultant(id: ""Q3VzdG9tZXIKZDE="") { + ...customer + ...consultant + } + consultant: customerOrConsultant(id: ""Q29uc3VsdGFudApkMQ=="") { + ...customer + ...consultant + } + } + + fragment customer on Customer { + name + consultant { + name + } + contracts { + id + ... on LifeInsuranceContract { + premium + } + ... on SomeOtherContract { + expiryDate + } + } + } + + fragment consultant on Consultant { + name + }"); + + // assert + result.MatchSnapshot(); + } + + [Fact] + public async Task Directive_Delegation() + { + // arrange + var httpClientFactory = + Context.CreateDefaultRemoteSchemas(); + + var executor = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddRemoteSchema(Context.ContractSchema) + .AddRemoteSchema(Context.CustomerSchema) + .AddTypeExtensionsFromString( + @"extend type Customer { + contracts: [Contract!] + @delegate( + schema: ""contract"", + path: ""contracts(customerId:$fields:id)"") + }") + .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) + .BuildRequestExecutorAsync(); + + // act + var result = await executor.ExecuteAsync( + @"{ + customer: customerOrConsultant(id: ""Q3VzdG9tZXIKZDE="") { + ...customer + ...consultant + } + consultant: customerOrConsultant(id: ""Q29uc3VsdGFudApkMQ=="") { + ...customer + ...consultant + } + } + + fragment customer on Customer { + name + consultant { + name + } + contracts @include(if: true) { + id + ... on LifeInsuranceContract { + premium + } + } + contracts @include(if: true) { + id + ... on SomeOtherContract { + expiryDate + } + } + } + + fragment consultant on Consultant { + name + }"); + + // assert + result.MatchSnapshot(); + } + + [Fact] + public async Task AutoMerge_Execute_Arguments() + { + // arrange + var httpClientFactory = + Context.CreateDefaultRemoteSchemas(); + + var executor = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddRemoteSchema(Context.ContractSchema) + .AddRemoteSchema(Context.CustomerSchema) + .AddTypeExtensionsFromString( + @"extend type Customer { + contracts: [Contract!] + @delegate( + schema: ""contract"", + path: ""contracts(customerId:$fields:id)"") + }") + .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) + .BuildRequestExecutorAsync(); + + // act + var result = await executor.ExecuteAsync( + @"{ + contracts(id: ""Q3VzdG9tZXIKZDE="") { + id + } + }"); + + // assert + result.MatchSnapshot(); + } + + [Fact] + public async Task AutoMerge_Execute_List_Aggregations() + { + // arrange + var httpClientFactory = + Context.CreateDefaultRemoteSchemas(); + + var executor = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddRemoteSchema(Context.ContractSchema) + .AddRemoteSchema(Context.CustomerSchema) + .AddTypeExtensionsFromString( + @"extend type Customer { + contractIds: [ID!] + @delegate( + schema: ""contract"", + path: ""contracts(customerId:$fields:id).id"") + }") + .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) + .BuildRequestExecutorAsync(); + + // act + var result = await executor.ExecuteAsync( + @"{ + customer(id: ""Q3VzdG9tZXIKZDE="") { + contractIds + } + }"); + + // assert + result.MatchSnapshot(); + } + + [Fact] + public async Task AutoMerge_Execute_Object_Aggregations() + { + // arrange + var httpClientFactory = + Context.CreateDefaultRemoteSchemas(); + + var executor = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddRemoteSchema(Context.ContractSchema) + .AddRemoteSchema(Context.CustomerSchema) + .AddTypeExtensionsFromString( + @"extend type Query { + consultant: Consultant + @delegate( + schema: ""customer"" + path: ""customer(id:\""Q3VzdG9tZXIKZDE=\"").consultant"") + }") + .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) + .BuildRequestExecutorAsync(); + + // act + var result = await executor.ExecuteAsync( + @"{ + consultant { + name + } + }"); + + // assert + result.MatchSnapshot(); + } + + [Fact] + public async Task AutoMerge_Execute_Scalar_Aggregations() + { + // arrange + var httpClientFactory = + Context.CreateDefaultRemoteSchemas(); + + var executor = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddRemoteSchema(Context.ContractSchema) + .AddRemoteSchema(Context.CustomerSchema) + .AddTypeExtensionsFromString( + @"extend type Query { + consultantName: String! + @delegate( + schema: ""customer"" + path: ""customer(id:\""Q3VzdG9tZXIKZDE=\"").consultant.name"") + }") + .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) + .BuildRequestExecutorAsync(); + + // act + var result = await executor.ExecuteAsync( + @"{ + consultantName + }"); + + // assert + result.MatchSnapshot(); + } + + [Fact] + public async Task AutoMerge_Execute_Computed() + { + // arrange + var httpClientFactory = + Context.CreateDefaultRemoteSchemas(); + + var executor = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddRemoteSchema(Context.ContractSchema) + .AddRemoteSchema(Context.CustomerSchema) + .AddTypeExtensionsFromString( + @"extend type Customer { + foo: String @computed(dependantOn: [""id"", ""name""]) + }") + .MapField( + new FieldReference("Customer", "foo"), + next => context => + { + var obj = context.Parent>(); + context.Result = obj["name"] + "_" + obj["id"]; + return default; + }) + .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) + .BuildRequestExecutorAsync(); + + // act + var result = await executor.ExecuteAsync( + @"{ + customer(id: ""Q3VzdG9tZXIKZDE="") { + foo + } + }"); + + // assert + result.MatchSnapshot(); + } + + [Fact] + public async Task AutoMerge_Execute_RenameScalar() + { + // arrange + var httpClientFactory = + Context.CreateDefaultRemoteSchemas(); + + var executor = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddType(new FloatType("Foo")) + .AddRemoteSchema(Context.ContractSchema) + .AddRemoteSchema(Context.CustomerSchema) + .RenameType("Float", "Foo") + .AddTypeExtensionsFromString( + @"extend type Customer { + contracts: [Contract!] + @delegate( + schema: ""contract"", + path: ""contracts(customerId:$fields:id)"") + }") + .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) + .BuildRequestExecutorAsync(); + + var variables = new Dictionary { { "v", new FloatValueNode(1.2f) } }; + + // act + var result = await executor.ExecuteAsync( + @"query ($v: Foo) { + customer: customerOrConsultant(id: ""Q3VzdG9tZXIKZDE="") { + ...customer + ...consultant + } + consultant: customerOrConsultant(id: ""Q29uc3VsdGFudApkMQ=="") { + ...customer + ...consultant + } + } + + fragment customer on Customer { + name + consultant { + name + } + contracts { + id + ... on LifeInsuranceContract { + premium + a: float_field(f: 1.1) + b: float_field(f: $v) + } + ... on SomeOtherContract { + expiryDate + } + } + } + + fragment consultant on Consultant { + name + }", + variables); + + // assert + result.MatchSnapshot(); + } + + [Fact] + public async Task AutoMerge_Execute_IntField() + { + // arrange + var httpClientFactory = + Context.CreateDefaultRemoteSchemas(); + + var executor = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddRemoteSchema(Context.ContractSchema) + .AddRemoteSchema(Context.CustomerSchema) + .AddTypeExtensionsFromString( + @"extend type Customer { + int: Int! + @delegate( + schema: ""contract"", + path: ""int(i:$fields:someInt)"") + }") + .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) + .BuildRequestExecutorAsync(); + + // act + var result = await executor.ExecuteAsync( + @"{ + customer(id: ""Q3VzdG9tZXIKZDE="") { + int + } + }"); + + // assert + result.MatchSnapshot(); + } + + [Fact] + public async Task AutoMerge_Execute_Customer_DoesNotExist_And_Is_Correctly_Null() + { + // arrange + var httpClientFactory = + Context.CreateDefaultRemoteSchemas(); + + var executor = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddRemoteSchema(Context.ContractSchema) + .AddRemoteSchema(Context.CustomerSchema) + .AddTypeExtensionsFromString( + @"extend type Customer { + int: Int! + @delegate( + schema: ""contract"", + path: ""int(i:$fields:someInt)"") + }") + .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) + .BuildRequestExecutorAsync(); + + // act + var result = await executor.ExecuteAsync( + @"{ + customer(id: ""Q3VzdG9tZXIKaTI5OTk="") { + int + } + }"); + + // assert + result.MatchSnapshot(); + } + + [Fact] + public async Task AutoMerge_Execute_GuidField() + { + // arrange + var httpClientFactory = + Context.CreateDefaultRemoteSchemas(); + + var executor = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddRemoteSchema(Context.ContractSchema) + .AddRemoteSchema(Context.CustomerSchema) + .AddTypeExtensionsFromString( + @"extend type Customer { + guid: UUID! + @delegate( + schema: ""contract"", + path: ""guid(guid:$fields:someGuid)"") + }") + .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) + .BuildRequestExecutorAsync(); + + // act + var result = await executor.ExecuteAsync( + @"{ + customer(id: ""Q3VzdG9tZXIKZDE="") { + guid + } + }"); + + // assert + result.MatchSnapshot(); + } + + [Fact] + public async Task AutoMerge_Execute_Schema_GuidField() + { + // arrange + var httpClientFactory = + Context.CreateDefaultRemoteSchemas(); + + var schema = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddRemoteSchema(Context.ContractSchema) + .AddRemoteSchema(Context.CustomerSchema) + .AddTypeExtensionsFromString( + @"extend type Customer { + guid: UUID! + @delegate( + schema: ""contract"", + path: ""guid(guid:$fields:someGuid)"") + }") + .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) + .BuildSchemaAsync(); + + // assert + schema.ToString().MatchSnapshot(); + } + + [Fact] + public async Task Add_Dummy_Directive() + { + // arrange + var httpClientFactory = + Context.CreateDefaultRemoteSchemas(); + + // act + var schema = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddRemoteSchema(Context.ContractSchema) + .AddRemoteSchema(Context.CustomerSchema) + .AddTypeExtensionsFromString( + @"directive @foo on FIELD_DEFINITION") + .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) + .BuildSchemaAsync(); + + // assert + Assert.NotNull(schema.GetDirectiveType("foo")); + } + + [Fact] + public async Task Add_Dummy_Directive_From_Resource() + { + // arrange + var httpClientFactory = + Context.CreateDefaultRemoteSchemas(); + + // act + var schema = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddRemoteSchema(Context.ContractSchema) + .AddRemoteSchema(Context.CustomerSchema) + .AddTypeExtensionsFromResource( + GetType().Assembly, + "HotChocolate.Stitching.__resources__.DummyDirective.graphql") + .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) + .BuildSchemaAsync(); + + // assert + Assert.NotNull(schema.GetDirectiveType("foo")); + } + + [Fact] + public async Task Add_Dummy_Directive_From_Resource_Key_Does_Not_Exist() + { + // arrange + var httpClientFactory = + Context.CreateDefaultRemoteSchemas(); + + // act + async Task Configure() => + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddRemoteSchema(Context.ContractSchema) + .AddRemoteSchema(Context.CustomerSchema) + .AddTypeExtensionsFromResource( + GetType().Assembly, + "HotChocolate.Stitching.__resources__.abc") + .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) + .BuildSchemaAsync(); + + // assert + var exception = await Assert.ThrowsAsync(Configure); + Assert.Contains( + "The resource `HotChocolate.Stitching.__resources__.abc` was not found!", + exception.Message); + } + + [Fact] + public async Task AddLocalSchema() + { + // arrange + var connections = new Dictionary + { + { Context.ContractSchema, Context.CreateContractService().CreateClient() } + }; + + var httpClientFactory = + StitchingTestContext.CreateRemoteSchemas(connections); + + var executor = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddRemoteSchema(Context.ContractSchema) + .AddLocalSchema(Context.CustomerSchema) + .AddTypeExtensionsFromString( + @"extend type Customer { + contracts: [Contract!] + @delegate( + schema: ""contract"", + path: ""contracts(customerId:$fields:id)"") + }") + .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) + .AddGraphQL(Context.CustomerSchema) + .AddCustomerSchema() + .BuildRequestExecutorAsync(); + + // act + var result = await executor.ExecuteAsync( + @"{ + customer(id: ""Q3VzdG9tZXIKZDE="") { + name + consultant { + name + } + contracts { + id + ... on LifeInsuranceContract { + premium + } + ... on SomeOtherContract { + expiryDate + } + } + } + }"); + + // assert + result.MatchSnapshot(); + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/FederatedRedisSchemaTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/FederatedRedisSchemaTests.cs new file mode 100644 index 00000000000..33554da37ad --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/FederatedRedisSchemaTests.cs @@ -0,0 +1,429 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using ChilliCream.Testing; +using HotChocolate.Execution; +using HotChocolate.Execution.Caching; +using HotChocolate.Language; +using HotChocolate.Stitching.Schemas.Accounts; +using HotChocolate.Stitching.Schemas.Inventory; +using HotChocolate.Stitching.Schemas.Products; +using HotChocolate.Stitching.Schemas.Reviews; +using HotChocolate.Types; +using Snapshooter.Xunit; +using Squadron; +using StackExchange.Redis; +using static HotChocolate.Tests.TestHelper; + +namespace HotChocolate.Stitching.Integration; + +public class FederatedRedisSchemaTests + : IClassFixture + , IClassFixture +{ + private const string _accounts = "accounts"; + private const string _inventory = "inventory"; + private const string _products = "products"; + private const string _reviews = "reviews"; + + private readonly ConnectionMultiplexer _connection; + + public FederatedRedisSchemaTests(StitchingTestContext context, RedisResource redisResource) + { + Context = context; + _connection = redisResource.GetConnection(); + } + + private StitchingTestContext Context { get; } + + [Fact] + public async Task AutoMerge_Schema() + { + // arrange + using var cts = new CancellationTokenSource(20_000); + var configurationName = "C" + Guid.NewGuid().ToString("N"); + var httpClientFactory = CreateDefaultRemoteSchemas(configurationName); + + var database = _connection.GetDatabase(); + while(!cts.IsCancellationRequested) + { + if (await database.SetLengthAsync(configurationName) == 4) + { + break; + } + + await Task.Delay(150, cts.Token); + } + + // act + var schema = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddQueryType(d => d.Name("Query")) + .AddRemoteSchemasFromRedis(configurationName, _ => _connection) + .ModifyOptions(o => o.SortFieldsByName = true) + .BuildSchemaAsync(cancellationToken: cts.Token); + + // assert + schema.Print().MatchSnapshot(); + } + + [Fact] + public async Task AutoMerge_HotReload_Schema() + { + // arrange + using var cts = new CancellationTokenSource(20_000); + var configurationName = "C" + Guid.NewGuid().ToString("N"); + var schemaDefinitionV2 = FileResource.Open("AccountSchemaDefinition.json"); + var httpClientFactory = CreateDefaultRemoteSchemas(configurationName); + + var database = _connection.GetDatabase(); + while(!cts.IsCancellationRequested) + { + if (await database.SetLengthAsync(configurationName) == 4) + { + break; + } + + await Task.Delay(150, cts.Token); + } + + var executorResolver = + new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddQueryType(d => d.Name("Query")) + .AddRemoteSchemasFromRedis(configurationName, _ => _connection) + .Services + .BuildServiceProvider() + .GetRequiredService(); + + await executorResolver.GetRequestExecutorAsync(cancellationToken: cts.Token); + var raised = false; + + executorResolver.RequestExecutorEvicted += (_, args) => + { + if (args.Name.Equals(Schema.DefaultName)) + { + raised = true; + } + }; + + // act + Assert.False(raised, "eviction was raised before act."); + await database.StringSetAsync($"{configurationName}.{_accounts}", schemaDefinitionV2); + await _connection.GetSubscriber().PublishAsync(configurationName, _accounts); + + while(!cts.IsCancellationRequested) + { + if (raised) + { + break; + } + + await Task.Delay(150, cts.Token); + } + + // assert + Assert.True(raised, "schema evicted."); + var executor = + await executorResolver.GetRequestExecutorAsync(cancellationToken: cts.Token); + var type = executor.Schema.GetType("User"); + Assert.True(type.Fields.ContainsField("foo"), "foo field exists."); + } + + [Fact] + public async Task AutoMerge_HotReload_ClearOperationCaches() + { + // arrange + using var cts = new CancellationTokenSource(20_000); + var configurationName = "C" + Guid.NewGuid().ToString("N"); + var schemaDefinitionV2 = FileResource.Open("AccountSchemaDefinition.json"); + var httpClientFactory = CreateDefaultRemoteSchemas(configurationName); + var document = Utf8GraphQLParser.Parse("{ foo }"); + var queryHash = "abc"; + + var database = _connection.GetDatabase(); + while(!cts.IsCancellationRequested) + { + if (await database.SetLengthAsync(configurationName) == 4) + { + break; + } + + await Task.Delay(150, cts.Token); + } + + var serviceProvider = + new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddQueryType(d => d.Name("Query").Field("foo").Resolve("foo")) + .AddRemoteSchemasFromRedis(configurationName, _ => _connection) + .Services + .BuildServiceProvider(); + + var executorResolver = + serviceProvider.GetRequiredService(); + var documentCache = + serviceProvider.GetRequiredService(); + var preparedOperationCache = + serviceProvider.GetRequiredService(); + + await executorResolver.GetRequestExecutorAsync(cancellationToken: cts.Token); + var raised = false; + + executorResolver.RequestExecutorEvicted += (_, args) => + { + if (args.Name.Equals(Schema.DefaultName)) + { + raised = true; + } + }; + + Assert.False(documentCache.TryGetDocument(queryHash, out _)); + Assert.False(preparedOperationCache.TryGetOperation(queryHash, out _)); + + var requestExecutor = + await executorResolver.GetRequestExecutorAsync(cancellationToken: cts.Token); + + await requestExecutor + .ExecuteAsync(QueryRequestBuilder + .New() + .SetQuery(document) + .SetQueryHash(queryHash) + .Create(), + cts.Token); + + Assert.True(preparedOperationCache.TryGetOperation("_Default-1-abc", out _)); + + // act + await database.StringSetAsync($"{configurationName}.{_accounts}", schemaDefinitionV2); + await _connection.GetSubscriber().PublishAsync(configurationName, _accounts); + + while(!cts.IsCancellationRequested) + { + if (raised) + { + break; + } + + await Task.Delay(150, cts.Token); + } + + // assert + Assert.True(documentCache.TryGetDocument(queryHash, out _)); + Assert.False(preparedOperationCache.TryGetOperation(queryHash, out _)); + } + + [Fact] + public async Task AutoMerge_Execute() + { + // arrange + using var cts = new CancellationTokenSource(20_000); + var configurationName = "C" + Guid.NewGuid().ToString("N"); + var httpClientFactory = CreateDefaultRemoteSchemas(configurationName); + + var database = _connection.GetDatabase(); + + while(!cts.IsCancellationRequested) + { + if (await database.SetLengthAsync(configurationName) == 4) + { + break; + } + + await Task.Delay(150, cts.Token); + } + + var executor = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddQueryType(d => d.Name("Query")) + .AddRemoteSchemasFromRedis(configurationName, _ => _connection) + .BuildRequestExecutorAsync(cancellationToken: cts.Token); + + // act + var result = await executor.ExecuteAsync( + @"{ + me { + id + name + reviews { + body + product { + upc + } + } + } + }", + cts.Token); + + // assert + result.ToJson().MatchSnapshot(); + } + + [Fact(Skip = "Test is flaky")] + public async Task AutoMerge_AddLocal_Field_Execute() + { + await TryTest(async ct => + { + // arrange + var configurationName = "C" + Guid.NewGuid().ToString("N"); + var httpClientFactory = + CreateDefaultRemoteSchemas(configurationName); + + var database = _connection.GetDatabase(); + while (!ct.IsCancellationRequested) + { + if (await database.SetLengthAsync(configurationName) == 4) + { + break; + } + + await Task.Delay(150, ct); + } + + var executor = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL(configurationName) + .AddQueryType(d => d.Name("Query").Field("local").Resolve("I am local.")) + .AddRemoteSchemasFromRedis(configurationName, _ => _connection) + .BuildRequestExecutorAsync(configurationName, ct); + + // act + var result = await executor.ExecuteAsync( + @"{ + me { + id + name + reviews { + body + product { + upc + } + } + } + local + }", + ct); + + // assert + result.ToJson().MatchSnapshot(); + }); + } + + public TestServer CreateAccountsService(string configurationName) => + Context.ServerFactory.Create( + services => services + .AddRouting() + .AddGraphQLServer() + .AddAccountsSchema() + .InitializeOnStartup() + .PublishSchemaDefinition(c => c + .SetName(_accounts) + .IgnoreRootTypes() + .AddTypeExtensionsFromString( + @"extend type Query { + me: User! @delegate(path: ""user(id: 1)"") + } + + extend type Review { + author: User @delegate(path: ""user(id: $fields:authorId)"") + }") + .PublishToRedis(configurationName, _ => _connection)), + app => app + .UseWebSockets() + .UseRouting() + .UseEndpoints(endpoints => endpoints.MapGraphQL("/"))); + + public TestServer CreateInventoryService(string configurationName) => + Context.ServerFactory.Create( + services => services + .AddRouting() + .AddGraphQLServer() + .AddInventorySchema() + .InitializeOnStartup() + .PublishSchemaDefinition(c => c + .SetName(_inventory) + .IgnoreRootTypes() + .AddTypeExtensionsFromString( + @"extend type Product { + inStock: Boolean + @delegate(path: ""inventoryInfo(upc: $fields:upc).isInStock"") + + shippingEstimate: Int + @delegate(path: ""shippingEstimate(price: $fields:price weight: $fields:weight)"") + }") + .PublishToRedis(configurationName, _ => _connection)), + app => app + .UseWebSockets() + .UseRouting() + .UseEndpoints(endpoints => endpoints.MapGraphQL("/"))); + + public TestServer CreateProductsService(string configurationName) => + Context.ServerFactory.Create( + services => services + .AddRouting() + .AddGraphQLServer() + .AddProductsSchema() + .InitializeOnStartup() + .PublishSchemaDefinition(c => c + .SetName(_products) + .IgnoreRootTypes() + .AddTypeExtensionsFromString( + @"extend type Query { + topProducts(first: Int = 5): [Product] @delegate + } + + extend type Review { + product: Product @delegate(path: ""product(upc: $fields:upc)"") + }") + .PublishToRedis(configurationName, _ => _connection)), + app => app + .UseWebSockets() + .UseRouting() + .UseEndpoints(endpoints => endpoints.MapGraphQL("/"))); + + public TestServer CreateReviewsService(string configurationName) => + Context.ServerFactory.Create( + services => services + .AddRouting() + .AddGraphQLServer() + .AddReviewSchema() + .InitializeOnStartup() + .PublishSchemaDefinition(c => c + .SetName(_reviews) + .IgnoreRootTypes() + .AddTypeExtensionsFromString( + @"extend type User { + reviews: [Review] + @delegate(path:""reviewsByAuthor(authorId: $fields:id)"") + } + + extend type Product { + reviews: [Review] + @delegate(path:""reviewsByProduct(upc: $fields:upc)"") + }") + .PublishToRedis(configurationName, _ => _connection)), + app => app + .UseWebSockets() + .UseRouting() + .UseEndpoints(endpoints => endpoints.MapGraphQL("/"))); + + public IHttpClientFactory CreateDefaultRemoteSchemas(string configurationName) + { + var connections = new Dictionary + { + { _accounts, CreateAccountsService(configurationName).CreateClient() }, + { _inventory, CreateInventoryService(configurationName).CreateClient() }, + { _products, CreateProductsService(configurationName).CreateClient() }, + { _reviews, CreateReviewsService(configurationName).CreateClient() }, + }; + + return StitchingTestContext.CreateRemoteSchemas(connections); + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/FederatedSchemaErrorTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/FederatedSchemaErrorTests.cs new file mode 100644 index 00000000000..f9bbb1b89ee --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/FederatedSchemaErrorTests.cs @@ -0,0 +1,238 @@ +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using HotChocolate.Execution; +using HotChocolate.Types; +using Snapshooter.Xunit; +using HotChocolate.Language; +using HotChocolate.Stitching.Schemas.Accounts; +using HotChocolate.Stitching.Schemas.Inventory; +using HotChocolate.Stitching.Schemas.Products; +using HotChocolate.Stitching.Schemas.Reviews; +using Microsoft.AspNetCore.Builder; + +namespace HotChocolate.Stitching.Integration; + +public class FederatedSchemaErrorTests : IClassFixture +{ + private const string _accounts = "accounts"; + private const string _inventory = "inventory"; + private const string _products = "products"; + private const string _reviews = "reviews"; + + public FederatedSchemaErrorTests(StitchingTestContext context) + { + Context = context; + } + + private StitchingTestContext Context { get; } + + [Fact] + public async Task AutoMerge_Schema() + { + // arrange + var httpClientFactory = CreateDefaultRemoteSchemas(); + + // act + var schema = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddQueryType(d => d.Name("Query")) + .AddRemoteSchema(_accounts) + .AddRemoteSchema(_inventory) + .AddRemoteSchema(_products) + .AddRemoteSchema(_reviews) + .BuildSchemaAsync(); + + // assert + schema.Print().MatchSnapshot(); + } + + [Fact] + public async Task Execute_Error_StatusCode_On_DownStream_Request() + { + // arrange + var httpClientFactory = CreateDefaultRemoteSchemas(); + + var executor = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddQueryType(d => d.Name("Query")) + .AddRemoteSchema(_accounts) + .AddRemoteSchema(_inventory) + .AddRemoteSchema(_products) + .AddRemoteSchema(_reviews) + .BuildRequestExecutorAsync(); + + // act + var result = await executor.ExecuteAsync( + @"{ + error + }"); + + // assert + result.ToJson().MatchSnapshot(); + } + + [Fact] + public async Task Execute_Ok_StatusCode_With_Error_On_DownStream_Request() + { + // arrange + var httpClientFactory = CreateDefaultRemoteSchemas(); + + var executor = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddQueryType(d => d.Name("Query")) + .AddRemoteSchema(_accounts) + .AddRemoteSchema(_inventory) + .AddRemoteSchema(_products) + .AddRemoteSchema(_reviews) + .BuildRequestExecutorAsync(); + + // act + var result = await executor.ExecuteAsync( + @"{ + a: topProducts(first: 1) { + upc + error + } + b: topProducts(first: 2) { + upc + error + } + }"); + + // assert + Assert.Collection( + result.ExpectQueryResult().Errors!.Select(t => t.Path!.ToString()).OrderBy(t => t), + t => Assert.Equal("/a[0]/error", t), + t => Assert.Equal("/b[0]/error", t), + t => Assert.Equal("/b[1]/error", t)); + + } + + public TestServer CreateAccountsService() => + Context.ServerFactory.Create( + services => services + .AddRouting() + .AddGraphQLServer() + .AddAccountsSchema() + .PublishSchemaDefinition(c => c + .SetName(_accounts) + .IgnoreRootTypes() + .AddTypeExtensionsFromString( + @"extend type Query { + me: User! @delegate(path: ""user(id: 1)"") + } + + extend type Review { + author: User @delegate(path: ""user(id: $fields:authorId)"") + }")), + app => app + .UseWebSockets() + .UseRouting() + .UseEndpoints(endpoints => endpoints.MapGraphQL("/"))); + + public TestServer CreateInventoryService() => + Context.ServerFactory.Create( + services => services + .AddRouting() + .AddGraphQLServer() + .AddInventorySchema() + .PublishSchemaDefinition(c => c + .SetName(_inventory) + .IgnoreRootTypes() + .AddTypeExtensionsFromString( + @"extend type Product { + inStock: Boolean + @delegate(path: ""inventoryInfo(upc: $fields:upc).isInStock"") + shippingEstimate: Int + @delegate(path: ""shippingEstimate(price: $fields:price weight: $fields:weight)"") + }")), + app => app + .UseWebSockets() + .UseRouting() + .UseEndpoints(endpoints => endpoints.MapGraphQL("/"))); + + public TestServer CreateProductsService() => + Context.ServerFactory.Create( + services => services + .AddRouting() + .AddGraphQLServer() + .AddProductsSchema() + .AddTypeExtension(new ObjectTypeExtension(d => + { + d.Name("Query") + .Field("error") + .Type(new NonNullTypeNode(new NamedTypeNode("String"))) + .Resolve(() => throw new GraphQLException("error_message_query")); + })) + .AddTypeExtension(new ObjectTypeExtension(d => + { + d.Name("Product") + .Field("error") + .Type(new NamedTypeNode("String")) + .Resolve(ctx => throw new GraphQLException( + ErrorBuilder.New() + .SetMessage("error_message_product") + .SetPath(ctx.Path) + .Build())); + })) + .PublishSchemaDefinition(c => c + .SetName(_products) + .IgnoreRootTypes() + .AddTypeExtensionsFromString( + @"extend type Query { + topProducts(first: Int = 5): [Product] @delegate + auth: String! @delegate + error: String! @delegate + } + + extend type Review { + product: Product @delegate(path: ""product(upc: $fields:upc)"") + }")), + app => app + .UseWebSockets() + .UseRouting() + .UseEndpoints(endpoints => endpoints.MapGraphQL("/"))); + + public TestServer CreateReviewsService() => + Context.ServerFactory.Create( + services => services + .AddRouting() + .AddGraphQLServer() + .AddReviewSchema() + .PublishSchemaDefinition(c => c + .SetName(_reviews) + .IgnoreRootTypes() + .AddTypeExtensionsFromString( + @"extend type User { + reviews: [Review] + @delegate(path:""reviewsByAuthor(authorId: $fields:id)"") + } + + extend type Product { + reviews: [Review] + @delegate(path:""reviewsByProduct(upc: $fields:upc)"") + }")), + app => app + .UseWebSockets() + .UseRouting() + .UseEndpoints(endpoints => endpoints.MapGraphQL("/"))); + + public IHttpClientFactory CreateDefaultRemoteSchemas() + { + var connections = new Dictionary + { + { _accounts, CreateAccountsService().CreateClient() }, + { _inventory, CreateInventoryService().CreateClient() }, + { _products, CreateProductsService().CreateClient() }, + { _reviews, CreateReviewsService().CreateClient() }, + }; + + return StitchingTestContext.CreateRemoteSchemas(connections); + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/FederatedSchemaTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/FederatedSchemaTests.cs new file mode 100644 index 00000000000..d227afc36c4 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/FederatedSchemaTests.cs @@ -0,0 +1,271 @@ +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using HotChocolate.Execution; +using HotChocolate.Types; +using Snapshooter.Xunit; +using HotChocolate.Execution.Processing; +using HotChocolate.Language; +using HotChocolate.Stitching.Schemas.Accounts; +using HotChocolate.Stitching.Schemas.Inventory; +using HotChocolate.Stitching.Schemas.Products; +using HotChocolate.Stitching.Schemas.Reviews; +using HotChocolate.Utilities; +using Microsoft.AspNetCore.Builder; + +namespace HotChocolate.Stitching.Integration; + +public class FederatedSchemaTests : IClassFixture +{ + private const string _accounts = "accounts"; + private const string _inventory = "inventory"; + private const string _products = "products"; + private const string _reviews = "reviews"; + + public FederatedSchemaTests(StitchingTestContext context) + { + Context = context; + } + + private StitchingTestContext Context { get; } + + [Fact] + public async Task AutoMerge_Schema() + { + // arrange + var httpClientFactory = CreateDefaultRemoteSchemas(); + + // act + var schema = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddQueryType(d => d.Name("Query")) + .AddRemoteSchema(_accounts) + .AddRemoteSchema(_inventory) + .AddRemoteSchema(_products) + .AddRemoteSchema(_reviews) + .BuildSchemaAsync(); + + // assert + schema.Print().MatchSnapshot(); + } + + [Fact(Skip = "This test is flaky")] + public async Task AutoMerge_Execute() + { + // arrange + var httpClientFactory = CreateDefaultRemoteSchemas(); + + var executor = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddQueryType(d => d.Name("Query")) + .AddRemoteSchema(_accounts) + .AddRemoteSchema(_inventory) + .AddRemoteSchema(_products) + .AddRemoteSchema(_reviews) + .BuildRequestExecutorAsync(); + + // act + var result = await executor.ExecuteAsync( + @"{ + me { + id + name + reviews { + body + product { + upc + } + } + } + }"); + + // assert + result.ToJson().MatchSnapshot(); + } + + [Fact] + public async Task AutoMerge_AddLocal_Field_Execute() + { + // arrange + var httpClientFactory = CreateDefaultRemoteSchemas(); + + var executor = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddQueryType(d => d.Name("Query").Field("local").Resolve("I am local.")) + .AddRemoteSchema(_accounts) + .AddRemoteSchema(_inventory) + .AddRemoteSchema(_products) + .AddRemoteSchema(_reviews) + .BuildRequestExecutorAsync(); + + // act + var result = await executor.ExecuteAsync( + @"{ + me { + id + name + reviews { + body + product { + upc + } + } + } + local + }"); + + // assert + result.ToJson().MatchSnapshot(); + } + + [Fact] + public async Task Directive_Variables_Are_Correctly_Rewritten() + { + // arrange + var httpClientFactory = CreateDefaultRemoteSchemas(); + + var executor = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddQueryType(d => d.Name("Query").Field("local").Resolve("I am local.")) + .AddRemoteSchema(_accounts) + .AddRemoteSchema(_inventory) + .AddRemoteSchema(_products) + .AddRemoteSchema(_reviews) + .BuildRequestExecutorAsync(); + + // act + var result = await executor.ExecuteAsync( + @"query ($if1: Boolean! $if2: Boolean! $if3: Boolean! $if4: Boolean!) { + me { + id + alias1: name @include(if: $if1) + alias2: reviews @include(if: $if2) { + alias3: body @include(if: $if3) + alias4: product @include(if: $if4) { + upc + } + } + } + local + }", + new Dictionary + { + { "if1", true }, + { "if2", true }, + { "if3", true }, + { "if4", true }, + }); + + // assert + result.ToJson().MatchSnapshot(); + } + + public TestServer CreateAccountsService() => + Context.ServerFactory.Create( + services => services + .AddRouting() + .AddGraphQLServer() + .AddAccountsSchema() + .PublishSchemaDefinition(c => c + .SetName(_accounts) + .IgnoreRootTypes() + .AddTypeExtensionsFromString( + @"extend type Query { + me: User! @delegate(path: ""user(id: 1)"") + } + + extend type Review { + author: User @delegate(path: ""user(id: $fields:authorId)"") + }")), + app => app + .UseWebSockets() + .UseRouting() + .UseEndpoints(endpoints => endpoints.MapGraphQL("/"))); + + public TestServer CreateInventoryService() => + Context.ServerFactory.Create( + services => services + .AddRouting() + .AddGraphQLServer() + .AddInventorySchema() + .PublishSchemaDefinition(c => c + .SetName(_inventory) + .IgnoreRootTypes() + .AddTypeExtensionsFromString( + @"extend type Product { + inStock: Boolean + @delegate(path: ""inventoryInfo(upc: $fields:upc).isInStock"") + shippingEstimate: Int + @delegate(path: ""shippingEstimate(price: $fields:price weight: $fields:weight)"") + }")), + app => app + .UseWebSockets() + .UseRouting() + .UseEndpoints(endpoints => endpoints.MapGraphQL("/"))); + + public TestServer CreateProductsService() => + Context.ServerFactory.Create( + services => services + .AddRouting() + .AddGraphQLServer() + .AddProductsSchema() + .PublishSchemaDefinition(c => c + .SetName(_products) + .IgnoreRootTypes() + .AddTypeExtensionsFromString( + @"extend type Query { + topProducts(first: Int = 5): [Product] @delegate + } + + extend type Review { + product: Product @delegate(path: ""product(upc: $fields:upc)"") + }")), + app => app + .UseWebSockets() + .UseRouting() + .UseEndpoints(endpoints => endpoints.MapGraphQL("/"))); + + public TestServer CreateReviewsService() => + Context.ServerFactory.Create( + services => services + .AddRouting() + .AddGraphQLServer() + .AddReviewSchema() + .PublishSchemaDefinition(c => c + .SetName(_reviews) + .IgnoreRootTypes() + .AddTypeExtensionsFromString( + @"extend type User { + reviews: [Review] + @delegate(path:""reviewsByAuthor(authorId: $fields:id)"") + } + + extend type Product { + reviews: [Review] + @delegate(path:""reviewsByProduct(upc: $fields:upc)"") + }")), + app => app + .UseWebSockets() + .UseRouting() + .UseEndpoints(endpoints => endpoints.MapGraphQL("/"))); + + public IHttpClientFactory CreateDefaultRemoteSchemas() + { + var connections = new Dictionary + { + { _accounts, CreateAccountsService().CreateClient() }, + { _inventory, CreateInventoryService().CreateClient() }, + { _products, CreateProductsService().CreateClient() }, + { _reviews, CreateReviewsService().CreateClient() }, + }; + + return StitchingTestContext.CreateRemoteSchemas(connections); + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/StitchingTestContext.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/StitchingTestContext.cs new file mode 100644 index 00000000000..3161f180a5e --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/StitchingTestContext.cs @@ -0,0 +1,69 @@ +using HotChocolate.AspNetCore.Tests.Utilities; +using HotChocolate.Stitching.Schemas.Contracts; +using HotChocolate.Stitching.Schemas.Customers; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Moq; + +namespace HotChocolate.Stitching.Integration; + +public class StitchingTestContext +{ + public TestServerFactory ServerFactory { get; } = new(); + + public string CustomerSchema { get; } = "customer"; + + public string ContractSchema { get; } = "contract"; + + public TestServer CreateCustomerService() => + ServerFactory.Create( + services => services + .AddRouting() + .AddGraphQLServer() + .AddCustomerSchema(), + app => app + .UseWebSockets() + .UseRouting() + .UseEndpoints(endpoints => endpoints.MapGraphQL("/"))); + + public TestServer CreateContractService() => + ServerFactory.Create( + services => services + .AddRouting() + .AddGraphQLServer() + .AddContractSchema(), + app => app + .UseWebSockets() + .UseRouting() + .UseEndpoints(endpoints => endpoints.MapGraphQL("/"))); + + public IHttpClientFactory CreateDefaultRemoteSchemas() + { + var connections = new Dictionary + { + { CustomerSchema, CreateCustomerService().CreateClient() }, + { ContractSchema, CreateContractService().CreateClient() } + }; + + return CreateRemoteSchemas(connections); + } + + public static IHttpClientFactory CreateRemoteSchemas( + Dictionary connections) + { + var httpClientFactory = new Mock(); + httpClientFactory.Setup(t => t.CreateClient(It.IsAny())) + .Returns(new Func(n => + { + if (connections.ContainsKey(n)) + { + return connections[n]; + } + + throw new Exception(); + })); + + return httpClientFactory.Object; + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AddLocalSchema.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AddLocalSchema.snap new file mode 100644 index 00000000000..491ff8462c4 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AddLocalSchema.snap @@ -0,0 +1,24 @@ +{ + "data": { + "customer": { + "name": "Freddy Freeman", + "consultant": { + "name": "Jordan Belfort" + }, + "contracts": [ + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQx", + "premium": 123456.11 + }, + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQy", + "premium": 456789.12 + }, + { + "id": "U29tZU90aGVyQ29udHJhY3QKZDE=", + "expiryDate": "2015-02-01T00:00:00.000Z" + } + ] + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute.snap new file mode 100644 index 00000000000..489e3aeb24d --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute.snap @@ -0,0 +1,18 @@ +{ + "data": { + "allCustomers": [ + { + "id": "Q3VzdG9tZXIKZDE=", + "name": "Freddy Freeman" + }, + { + "id": "Q3VzdG9tZXIKZDI=", + "name": "Carol Danvers" + }, + { + "id": "Q3VzdG9tZXIKZDM=", + "name": "Walter Lawson" + } + ] + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Arguments.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Arguments.snap new file mode 100644 index 00000000000..33248356a4e --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Arguments.snap @@ -0,0 +1,34 @@ +{ + "errors": [ + { + "message": "The argument \u0060id\u0060 does not exist.", + "locations": [ + { + "line": 2, + "column": 31 + } + ], + "extensions": { + "type": "Query", + "field": "contracts", + "argument": "id", + "specifiedBy": "http://spec.graphql.org/October2021/#sec-Required-Arguments" + } + }, + { + "message": "The argument \u0060customerId\u0060 is required.", + "locations": [ + { + "line": 2, + "column": 21 + } + ], + "extensions": { + "type": "Query", + "field": "contracts", + "argument": "customerId", + "specifiedBy": "http://spec.graphql.org/October2021/#sec-Required-Arguments" + } + } + ] +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Computed.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Computed.snap new file mode 100644 index 00000000000..d37e6dcf99f --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Computed.snap @@ -0,0 +1,7 @@ +{ + "data": { + "customer": { + "foo": "\u0022Freddy Freeman\u0022_\u0022Q3VzdG9tZXIKZDE=\u0022" + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Customer_DoesNotExist_And_Is_Correctly_Null.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Customer_DoesNotExist_And_Is_Correctly_Null.snap new file mode 100644 index 00000000000..e410a178d1b --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Customer_DoesNotExist_And_Is_Correctly_Null.snap @@ -0,0 +1,5 @@ +{ + "data": { + "customer": null + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Fragment_Definition.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Fragment_Definition.snap new file mode 100644 index 00000000000..491ff8462c4 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Fragment_Definition.snap @@ -0,0 +1,24 @@ +{ + "data": { + "customer": { + "name": "Freddy Freeman", + "consultant": { + "name": "Jordan Belfort" + }, + "contracts": [ + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQx", + "premium": 123456.11 + }, + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQy", + "premium": 456789.12 + }, + { + "id": "U29tZU90aGVyQ29udHJhY3QKZDE=", + "expiryDate": "2015-02-01T00:00:00.000Z" + } + ] + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_GuidField.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_GuidField.snap new file mode 100644 index 00000000000..03811817c8e --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_GuidField.snap @@ -0,0 +1,7 @@ +{ + "data": { + "customer": { + "guid": "01e2f5dc-0f19-4305-99d3-3c5c234a6524" + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Inline_Fragment.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Inline_Fragment.snap new file mode 100644 index 00000000000..491ff8462c4 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Inline_Fragment.snap @@ -0,0 +1,24 @@ +{ + "data": { + "customer": { + "name": "Freddy Freeman", + "consultant": { + "name": "Jordan Belfort" + }, + "contracts": [ + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQx", + "premium": 123456.11 + }, + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQy", + "premium": 456789.12 + }, + { + "id": "U29tZU90aGVyQ29udHJhY3QKZDE=", + "expiryDate": "2015-02-01T00:00:00.000Z" + } + ] + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_IntField.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_IntField.snap new file mode 100644 index 00000000000..dc09205cce1 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_IntField.snap @@ -0,0 +1,7 @@ +{ + "data": { + "customer": { + "int": 1 + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_List_Aggregations.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_List_Aggregations.snap new file mode 100644 index 00000000000..9ce2965e182 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_List_Aggregations.snap @@ -0,0 +1,11 @@ +{ + "data": { + "customer": { + "contractIds": [ + "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQx", + "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQy", + "U29tZU90aGVyQ29udHJhY3QKZDE=" + ] + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Object_Aggregations.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Object_Aggregations.snap new file mode 100644 index 00000000000..0f25f607a20 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Object_Aggregations.snap @@ -0,0 +1,7 @@ +{ + "data": { + "consultant": { + "name": "Jordan Belfort" + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_RenameScalar.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_RenameScalar.snap new file mode 100644 index 00000000000..27455c0b161 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_RenameScalar.snap @@ -0,0 +1,31 @@ +{ + "data": { + "customer": { + "name": "Freddy Freeman", + "consultant": { + "name": "Jordan Belfort" + }, + "contracts": [ + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQx", + "premium": 123456.11, + "a": 1.1, + "b": 1.2000000476837158 + }, + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQy", + "premium": 456789.12, + "a": 1.1, + "b": 1.2000000476837158 + }, + { + "id": "U29tZU90aGVyQ29udHJhY3QKZDE=", + "expiryDate": "2015-02-01T00:00:00.000Z" + } + ] + }, + "consultant": { + "name": "Jordan Belfort" + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Scalar_Aggregations.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Scalar_Aggregations.snap new file mode 100644 index 00000000000..a661e7e75c6 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Scalar_Aggregations.snap @@ -0,0 +1,5 @@ +{ + "data": { + "consultantName": "Jordan Belfort" + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Schema_GuidField.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Schema_GuidField.snap new file mode 100644 index 00000000000..f467bcf5d3d --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Schema_GuidField.snap @@ -0,0 +1,177 @@ +schema { + query: Query + mutation: Mutation +} + +interface Contract @source(name: "Contract", schema: "contract") { + id: ID! + customerId: ID! +} + +"The node interface is implemented by entities that have a global unique identifier." +interface Node @source(name: "Node", schema: "contract") @source(name: "Node", schema: "customer") { + id: ID! +} + +type Consultant implements Node @source(name: "Consultant", schema: "customer") { + id: ID! + name: String! + customers("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): CustomersConnection +} + +type CreateCustomerPayload @source(name: "CreateCustomerPayload", schema: "customer") { + customer: Customer! +} + +type Customer implements Node @source(name: "Customer", schema: "customer") { + id: ID! + name: String! + street: String! + consultant: Consultant + say(input: SayInput!): String + complexArg(arg: ComplexInputType): String + someInt: Int! + someGuid: UUID! + kind: CustomerKind! + guid: UUID! @delegate(schema: "contract", path: "guid(guid:$fields:someGuid)") +} + +"A connection to a list of items." +type CustomersConnection @source(name: "CustomersConnection", schema: "customer") { + "Information to aid in pagination." + pageInfo: PageInfo! + "A list of edges." + edges: [CustomersEdge!] + "A flattened list of the nodes." + nodes: [Customer] +} + +"An edge in a connection." +type CustomersEdge @source(name: "CustomersEdge", schema: "customer") { + "A cursor for use in pagination." + cursor: String! + "The item at the end of the edge." + node: Customer +} + +type LifeInsuranceContract implements Node & Contract @source(name: "LifeInsuranceContract", schema: "contract") { + id: ID! + customerId: ID! + foo(bar: String): String + error: String + date_field: Date + date_time_field: DateTime + string_field: String + id_field: ID + byte_field: Byte + int_field: Int + long_field: Long + float_field(f: Float): Float + decimal_field: Decimal + premium: Float! +} + +type Mutation { + createCustomer(input: CreateCustomerInput!): CreateCustomerPayload! @delegate(schema: "customer") + createCustomers(inputs: [CreateCustomerInput!]!): [CreateCustomerPayload!]! @delegate(schema: "customer") +} + +"Information about pagination in a connection." +type PageInfo @source(name: "PageInfo", schema: "customer") { + "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 +} + +type Query { + "Fetches an object given its ID." + node("ID of the object." id: ID!): Node @delegate(schema: "contract") + "Lookup nodes by a list of IDs." + nodes("The list of node IDs." ids: [ID!]!): [Node]! @delegate(schema: "contract") + contract(contractId: ID!): Contract @delegate(schema: "contract") + contracts(customerId: ID!): [Contract!] @delegate(schema: "contract") + extendedScalar(d: DateTime): DateTime @delegate(schema: "contract") + int(i: Int!): Int! @delegate(schema: "contract") + guid(guid: UUID!): UUID! @delegate(schema: "contract") + "Fetches an object given its ID." + customer_node("ID of the object." id: ID!): Node @delegate(schema: "customer", path: "node(id: $arguments:id)") + "Lookup nodes by a list of IDs." + customer_nodes("The list of node IDs." ids: [ID!]!): [Node]! @delegate(schema: "customer", path: "nodes(ids: $arguments:ids)") + customer(id: ID!): Customer @delegate(schema: "customer") + customers(ids: [ID!]!): [Customer] @delegate(schema: "customer") + consultant(id: ID!): Consultant @delegate(schema: "customer") + customerOrConsultant(id: ID!): CustomerOrConsultant @delegate(schema: "customer") + customerByKind(kind: CustomerKind!): Customer @delegate(schema: "customer") + allCustomers: [Customer!]! @delegate(schema: "customer") +} + +type SomeOtherContract implements Node & Contract @source(name: "SomeOtherContract", schema: "contract") { + id: ID! + customerId: ID! + expiryDate: DateTime! +} + +union CustomerOrConsultant @source(name: "CustomerOrConsultant", schema: "customer") = Customer | Consultant + +input ComplexInputType @source(name: "ComplexInputType", schema: "customer") { + value: String + deeper: ComplexInputType + valueArray: [String] + deeperArray: [ComplexInputType] +} + +input CreateCustomerInput @source(name: "CreateCustomerInput", schema: "customer") { + name: String + street: String + consultantId: String +} + +input SayInput @source(name: "SayInput", schema: "customer") { + words: [String!] +} + +enum CustomerKind @source(name: "CustomerKind", schema: "customer") { + STANDARD + PREMIUM +} + +directive @computed("Specifies the fields on which a computed field is dependent on." dependantOn: [String!]) on FIELD_DEFINITION + +directive @custom(d: DateTime) on FIELD + +"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 + +"Delegates a resolver to a remote schema." +directive @delegate("The path to the field on the remote schema." path: String "The name of the schema to which this field shall be delegated to." schema: String!) on FIELD_DEFINITION + +"Annotates the original name of a type." +directive @source("The original name of the annotated type." name: String! "The name of the schema to which this type belongs to." schema: String!) repeatable on ENUM | OBJECT | INTERFACE | UNION | INPUT_OBJECT | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION | ENUM_VALUE + +"The `@specifiedBy` directive is used within the type system definition language to provide a URL for specifying the behavior of custom scalar definitions." +directive @specifiedBy("The specifiedBy URL points to a human-readable specification. This field will only read a result for scalar types." url: String!) on SCALAR + +"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! = 0 "Streamed when true." if: Boolean) on FIELD + +"The `Byte` scalar type represents non-fractional whole numeric values. Byte can represent values between 0 and 255." +scalar Byte + +"The `Date` scalar represents an ISO-8601 compliant date type." +scalar Date + +"The `DateTime` scalar represents an ISO-8601 compliant date time type." +scalar DateTime @specifiedBy(url: "https:\/\/www.graphql-scalars.com\/date-time") + +"The built-in `Decimal` scalar type." +scalar Decimal + +"The `Long` scalar type represents non-fractional signed whole 64-bit numeric values. Long can represent values between -(2^63) and 2^63 - 1." +scalar Long + +scalar UUID @specifiedBy(url: "https:\/\/tools.ietf.org\/html\/rfc4122") diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Union.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Union.snap new file mode 100644 index 00000000000..27e51c41735 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Union.snap @@ -0,0 +1,27 @@ +{ + "data": { + "customer": { + "name": "Freddy Freeman", + "consultant": { + "name": "Jordan Belfort" + }, + "contracts": [ + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQx", + "premium": 123456.11 + }, + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQy", + "premium": 456789.12 + }, + { + "id": "U29tZU90aGVyQ29udHJhY3QKZDE=", + "expiryDate": "2015-02-01T00:00:00.000Z" + } + ] + }, + "consultant": { + "name": "Jordan Belfort" + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Variables.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Variables.snap new file mode 100644 index 00000000000..00518b756c8 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Execute_Variables.snap @@ -0,0 +1,25 @@ +{ + "data": { + "customer": { + "name": "Freddy Freeman", + "consultant": { + "name": "Jordan Belfort" + }, + "complexArg": "{ value: \u0022deep\u0022, deeper: { value: \u0022CONSTANT\u0022, deeper: { value: \u0022deeper\u0022, deeperArray: [ { value: \u0022CONSTANT_ARRAY\u0022, deeper: { value: null } } ] } }, deeperArray: [ { value: \u0022CONSTANT_ARRAY\u0022, deeper: { value: null } }, null ] }", + "contracts": [ + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQx", + "premium": 123456.11 + }, + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQy", + "premium": 456789.12 + }, + { + "id": "U29tZU90aGVyQ29udHJhY3QKZDE=", + "expiryDate": "2015-02-01T00:00:00.000Z" + } + ] + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Schema.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Schema.snap new file mode 100644 index 00000000000..991e88d3163 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.AutoMerge_Schema.snap @@ -0,0 +1,176 @@ +schema { + query: Query + mutation: Mutation +} + +interface Contract @source(name: "Contract", schema: "contract") { + id: ID! + customerId: ID! +} + +"The node interface is implemented by entities that have a global unique identifier." +interface Node @source(name: "Node", schema: "contract") @source(name: "Node", schema: "customer") { + id: ID! +} + +type Consultant implements Node @source(name: "Consultant", schema: "customer") { + id: ID! + name: String! + customers("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): CustomersConnection +} + +type CreateCustomerPayload @source(name: "CreateCustomerPayload", schema: "customer") { + customer: Customer! +} + +type Customer implements Node @source(name: "Customer", schema: "customer") { + id: ID! + name: String! + street: String! + consultant: Consultant + say(input: SayInput!): String + complexArg(arg: ComplexInputType): String + someInt: Int! + someGuid: UUID! + kind: CustomerKind! +} + +"A connection to a list of items." +type CustomersConnection @source(name: "CustomersConnection", schema: "customer") { + "Information to aid in pagination." + pageInfo: PageInfo! + "A list of edges." + edges: [CustomersEdge!] + "A flattened list of the nodes." + nodes: [Customer] +} + +"An edge in a connection." +type CustomersEdge @source(name: "CustomersEdge", schema: "customer") { + "A cursor for use in pagination." + cursor: String! + "The item at the end of the edge." + node: Customer +} + +type LifeInsuranceContract implements Node & Contract @source(name: "LifeInsuranceContract", schema: "contract") { + id: ID! + customerId: ID! + foo(bar: String): String + error: String + date_field: Date + date_time_field: DateTime + string_field: String + id_field: ID + byte_field: Byte + int_field: Int + long_field: Long + float_field(f: Float): Float + decimal_field: Decimal + premium: Float! +} + +type Mutation { + createCustomer(input: CreateCustomerInput!): CreateCustomerPayload! @delegate(schema: "customer") + createCustomers(inputs: [CreateCustomerInput!]!): [CreateCustomerPayload!]! @delegate(schema: "customer") +} + +"Information about pagination in a connection." +type PageInfo @source(name: "PageInfo", schema: "customer") { + "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 +} + +type Query { + "Fetches an object given its ID." + node("ID of the object." id: ID!): Node @delegate(schema: "contract") + "Lookup nodes by a list of IDs." + nodes("The list of node IDs." ids: [ID!]!): [Node]! @delegate(schema: "contract") + contract(contractId: ID!): Contract @delegate(schema: "contract") + contracts(customerId: ID!): [Contract!] @delegate(schema: "contract") + extendedScalar(d: DateTime): DateTime @delegate(schema: "contract") + int(i: Int!): Int! @delegate(schema: "contract") + guid(guid: UUID!): UUID! @delegate(schema: "contract") + "Fetches an object given its ID." + customer_node("ID of the object." id: ID!): Node @delegate(schema: "customer", path: "node(id: $arguments:id)") + "Lookup nodes by a list of IDs." + customer_nodes("The list of node IDs." ids: [ID!]!): [Node]! @delegate(schema: "customer", path: "nodes(ids: $arguments:ids)") + customer(id: ID!): Customer @delegate(schema: "customer") + customers(ids: [ID!]!): [Customer] @delegate(schema: "customer") + consultant(id: ID!): Consultant @delegate(schema: "customer") + customerOrConsultant(id: ID!): CustomerOrConsultant @delegate(schema: "customer") + customerByKind(kind: CustomerKind!): Customer @delegate(schema: "customer") + allCustomers: [Customer!]! @delegate(schema: "customer") +} + +type SomeOtherContract implements Node & Contract @source(name: "SomeOtherContract", schema: "contract") { + id: ID! + customerId: ID! + expiryDate: DateTime! +} + +union CustomerOrConsultant @source(name: "CustomerOrConsultant", schema: "customer") = Customer | Consultant + +input ComplexInputType @source(name: "ComplexInputType", schema: "customer") { + value: String + deeper: ComplexInputType + valueArray: [String] + deeperArray: [ComplexInputType] +} + +input CreateCustomerInput @source(name: "CreateCustomerInput", schema: "customer") { + name: String + street: String + consultantId: String +} + +input SayInput @source(name: "SayInput", schema: "customer") { + words: [String!] +} + +enum CustomerKind @source(name: "CustomerKind", schema: "customer") { + STANDARD + PREMIUM +} + +directive @computed("Specifies the fields on which a computed field is dependent on." dependantOn: [String!]) on FIELD_DEFINITION + +directive @custom(d: DateTime) on FIELD + +"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 + +"Delegates a resolver to a remote schema." +directive @delegate("The path to the field on the remote schema." path: String "The name of the schema to which this field shall be delegated to." schema: String!) on FIELD_DEFINITION + +"Annotates the original name of a type." +directive @source("The original name of the annotated type." name: String! "The name of the schema to which this type belongs to." schema: String!) repeatable on ENUM | OBJECT | INTERFACE | UNION | INPUT_OBJECT | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION | ENUM_VALUE + +"The `@specifiedBy` directive is used within the type system definition language to provide a URL for specifying the behavior of custom scalar definitions." +directive @specifiedBy("The specifiedBy URL points to a human-readable specification. This field will only read a result for scalar types." url: String!) on SCALAR + +"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! = 0 "Streamed when true." if: Boolean) on FIELD + +"The `Byte` scalar type represents non-fractional whole numeric values. Byte can represent values between 0 and 255." +scalar Byte + +"The `Date` scalar represents an ISO-8601 compliant date type." +scalar Date + +"The `DateTime` scalar represents an ISO-8601 compliant date time type." +scalar DateTime @specifiedBy(url: "https:\/\/www.graphql-scalars.com\/date-time") + +"The built-in `Decimal` scalar type." +scalar Decimal + +"The `Long` scalar type represents non-fractional signed whole 64-bit numeric values. Long can represent values between -(2^63) and 2^63 - 1." +scalar Long + +scalar UUID @specifiedBy(url: "https:\/\/tools.ietf.org\/html\/rfc4122") diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.Directive_Delegation.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.Directive_Delegation.snap new file mode 100644 index 00000000000..27e51c41735 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.Directive_Delegation.snap @@ -0,0 +1,27 @@ +{ + "data": { + "customer": { + "name": "Freddy Freeman", + "consultant": { + "name": "Jordan Belfort" + }, + "contracts": [ + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQx", + "premium": 123456.11 + }, + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQy", + "premium": 456789.12 + }, + { + "id": "U29tZU90aGVyQ29udHJhY3QKZDE=", + "expiryDate": "2015-02-01T00:00:00.000Z" + } + ] + }, + "consultant": { + "name": "Jordan Belfort" + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.LocalField_Execute.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.LocalField_Execute.snap new file mode 100644 index 00000000000..88d1918cd11 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.LocalField_Execute.snap @@ -0,0 +1,19 @@ +{ + "data": { + "local": "I am local.", + "allCustomers": [ + { + "id": "Q3VzdG9tZXIKZDE=", + "name": "Freddy Freeman" + }, + { + "id": "Q3VzdG9tZXIKZDI=", + "name": "Carol Danvers" + }, + { + "id": "Q3VzdG9tZXIKZDM=", + "name": "Walter Lawson" + } + ] + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.Schema_AddResolver.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.Schema_AddResolver.snap new file mode 100644 index 00000000000..a4a7041a777 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/BaseTests.Schema_AddResolver.snap @@ -0,0 +1,19 @@ +{ + "data": { + "local": "I am local", + "allCustomers": [ + { + "id": "Q3VzdG9tZXIKZDE=", + "name": "Freddy Freeman" + }, + { + "id": "Q3VzdG9tZXIKZDI=", + "name": "Carol Danvers" + }, + { + "id": "Q3VzdG9tZXIKZDM=", + "name": "Walter Lawson" + } + ] + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedRedisSchemaTests.AutoMerge_AddLocal_Field_Execute.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedRedisSchemaTests.AutoMerge_AddLocal_Field_Execute.snap new file mode 100644 index 00000000000..eff8ca1d1f6 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedRedisSchemaTests.AutoMerge_AddLocal_Field_Execute.snap @@ -0,0 +1,23 @@ +{ + "data": { + "me": { + "id": 1, + "name": "Ada Lovelace", + "reviews": [ + { + "body": "Love it!", + "product": { + "upc": 1 + } + }, + { + "body": "Too expensive.", + "product": { + "upc": 2 + } + } + ] + }, + "local": "I am local." + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedRedisSchemaTests.AutoMerge_Execute.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedRedisSchemaTests.AutoMerge_Execute.snap new file mode 100644 index 00000000000..c4678d52031 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedRedisSchemaTests.AutoMerge_Execute.snap @@ -0,0 +1,22 @@ +{ + "data": { + "me": { + "id": 1, + "name": "Ada Lovelace", + "reviews": [ + { + "body": "Love it!", + "product": { + "upc": 1 + } + }, + { + "body": "Too expensive.", + "product": { + "upc": 2 + } + } + ] + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedRedisSchemaTests.AutoMerge_Schema.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedRedisSchemaTests.AutoMerge_Schema.snap new file mode 100644 index 00000000000..de434792e72 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedRedisSchemaTests.AutoMerge_Schema.snap @@ -0,0 +1,60 @@ +schema { + query: Query +} + +type InventoryInfo @source(name: "InventoryInfo", schema: "inventory") { + isInStock: Boolean! + upc: Int! +} + +type Product @source(name: "Product", schema: "products") { + inStock: Boolean @delegate(path: "inventoryInfo(upc: $fields:upc).isInStock", schema: "inventory") + name: String! + price: Int! + reviews: [Review] @delegate(path: "reviewsByProduct(upc: $fields:upc)", schema: "reviews") + shippingEstimate: Int @delegate(path: "shippingEstimate(price: $fields:price weight: $fields:weight)", schema: "inventory") + upc: Int! + weight: Int! +} + +type Query { + me: User! @delegate(path: "user(id: 1)", schema: "accounts") + topProducts(first: Int = 5): [Product] @delegate(schema: "products") +} + +type Review @source(name: "Review", schema: "reviews") { + author: User @delegate(path: "user(id: $fields:authorId)", schema: "accounts") + authorId: Int! + body: String! + id: Int! + product: Product @delegate(path: "product(upc: $fields:upc)", schema: "products") + upc: Int! +} + +type User @source(name: "User", schema: "accounts") { + birthdate: DateTime! + id: Int! + name: String! + reviews: [Review] @delegate(path: "reviewsByAuthor(authorId: $fields:id)", schema: "reviews") + username: String! +} + +directive @computed("Specifies the fields on which a computed field is dependent on." dependantOn: [String!]) on FIELD_DEFINITION + +"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("Deferred when true." if: Boolean "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) on FRAGMENT_SPREAD | INLINE_FRAGMENT + +"Delegates a resolver to a remote schema." +directive @delegate("The path to the field on the remote schema." path: String "The name of the schema to which this field shall be delegated to." schema: String!) on FIELD_DEFINITION + +"Annotates the original name of a type." +directive @source("The original name of the annotated type." name: String! "The name of the schema to which this type belongs to." schema: String!) repeatable on ENUM | OBJECT | INTERFACE | UNION | INPUT_OBJECT | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION | ENUM_VALUE + +"The `@specifiedBy` directive is used within the type system definition language to provide a URL for specifying the behavior of custom scalar definitions." +directive @specifiedBy("The specifiedBy URL points to a human-readable specification. This field will only read a result for scalar types." url: String!) on SCALAR + +"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("Streamed when true." if: Boolean "The initial elements that shall be send down to the consumer." initialCount: Int! = 0 "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) on FIELD + +"The `DateTime` scalar represents an ISO-8601 compliant date time type." +scalar DateTime @specifiedBy(url: "https:\/\/www.graphql-scalars.com\/date-time") diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaErrorTests.AutoMerge_Schema.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaErrorTests.AutoMerge_Schema.snap new file mode 100644 index 00000000000..497e956e4f8 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaErrorTests.AutoMerge_Schema.snap @@ -0,0 +1,63 @@ +schema { + query: Query +} + +type InventoryInfo @source(name: "InventoryInfo", schema: "inventory") { + upc: Int! + isInStock: Boolean! +} + +type Product @source(name: "Product", schema: "products") { + upc: Int! + name: String! + price: Int! + weight: Int! + error: String + inStock: Boolean @delegate(path: "inventoryInfo(upc: $fields:upc).isInStock", schema: "inventory") + shippingEstimate: Int @delegate(path: "shippingEstimate(price: $fields:price weight: $fields:weight)", schema: "inventory") + reviews: [Review] @delegate(path: "reviewsByProduct(upc: $fields:upc)", schema: "reviews") +} + +type Query { + me: User! @delegate(path: "user(id: 1)", schema: "accounts") + topProducts(first: Int = 5): [Product] @delegate(schema: "products") + auth: String! @delegate(schema: "products") + error: String! @delegate(schema: "products") +} + +type Review @source(name: "Review", schema: "reviews") { + id: Int! + authorId: Int! + upc: Int! + body: String! + author: User @delegate(path: "user(id: $fields:authorId)", schema: "accounts") + product: Product @delegate(path: "product(upc: $fields:upc)", schema: "products") +} + +type User @source(name: "User", schema: "accounts") { + id: Int! + name: String! + birthdate: DateTime! + username: String! + reviews: [Review] @delegate(path: "reviewsByAuthor(authorId: $fields:id)", schema: "reviews") +} + +directive @computed("Specifies the fields on which a computed field is dependent on." dependantOn: [String!]) on FIELD_DEFINITION + +"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 + +"Delegates a resolver to a remote schema." +directive @delegate("The path to the field on the remote schema." path: String "The name of the schema to which this field shall be delegated to." schema: String!) on FIELD_DEFINITION + +"Annotates the original name of a type." +directive @source("The original name of the annotated type." name: String! "The name of the schema to which this type belongs to." schema: String!) repeatable on ENUM | OBJECT | INTERFACE | UNION | INPUT_OBJECT | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION | ENUM_VALUE + +"The `@specifiedBy` directive is used within the type system definition language to provide a URL for specifying the behavior of custom scalar definitions." +directive @specifiedBy("The specifiedBy URL points to a human-readable specification. This field will only read a result for scalar types." url: String!) on SCALAR + +"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! = 0 "Streamed when true." if: Boolean) on FIELD + +"The `DateTime` scalar represents an ISO-8601 compliant date time type." +scalar DateTime @specifiedBy(url: "https:\/\/www.graphql-scalars.com\/date-time") diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaErrorTests.Execute_Error_StatusCode_On_DownStream_Request.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaErrorTests.Execute_Error_StatusCode_On_DownStream_Request.snap new file mode 100644 index 00000000000..2e6c9e67bd5 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaErrorTests.Execute_Error_StatusCode_On_DownStream_Request.snap @@ -0,0 +1,13 @@ +{ + "errors": [ + { + "message": "error_message_query", + "extensions": { + "remote": { + "message": "error_message_query" + }, + "schemaName": "products" + } + } + ] +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaTests.AutoMerge_AddLocal_Field_Execute.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaTests.AutoMerge_AddLocal_Field_Execute.snap new file mode 100644 index 00000000000..eff8ca1d1f6 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaTests.AutoMerge_AddLocal_Field_Execute.snap @@ -0,0 +1,23 @@ +{ + "data": { + "me": { + "id": 1, + "name": "Ada Lovelace", + "reviews": [ + { + "body": "Love it!", + "product": { + "upc": 1 + } + }, + { + "body": "Too expensive.", + "product": { + "upc": 2 + } + } + ] + }, + "local": "I am local." + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaTests.AutoMerge_CompileOperation.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaTests.AutoMerge_CompileOperation.snap new file mode 100644 index 00000000000..9c573ceb115 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaTests.AutoMerge_CompileOperation.snap @@ -0,0 +1,21 @@ +{ + ... on Query { + me @__execute(id: 0, kind: DEFAULT, type: COMPOSITE) { + ... on User { + id @__execute(id: 2, kind: PURE, type: LEAF) + name @__execute(id: 3, kind: PURE, type: LEAF) + reviews @__execute(id: 4, kind: DEFAULT, type: COMPOSITE_LIST) { + ... on Review { + body @__execute(id: 5, kind: PURE, type: LEAF) + product @__execute(id: 6, kind: DEFAULT, type: COMPOSITE) { + ... on Product { + upc @__execute(id: 7, kind: PURE, type: LEAF) + } + } + } + } + } + } + local @__execute(id: 1, kind: DEFAULT, type: LEAF) + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaTests.AutoMerge_Execute.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaTests.AutoMerge_Execute.snap new file mode 100644 index 00000000000..c4678d52031 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaTests.AutoMerge_Execute.snap @@ -0,0 +1,22 @@ +{ + "data": { + "me": { + "id": 1, + "name": "Ada Lovelace", + "reviews": [ + { + "body": "Love it!", + "product": { + "upc": 1 + } + }, + { + "body": "Too expensive.", + "product": { + "upc": 2 + } + } + ] + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaTests.AutoMerge_Schema.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaTests.AutoMerge_Schema.snap new file mode 100644 index 00000000000..b05cd5bbf82 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaTests.AutoMerge_Schema.snap @@ -0,0 +1,60 @@ +schema { + query: Query +} + +type InventoryInfo @source(name: "InventoryInfo", schema: "inventory") { + upc: Int! + isInStock: Boolean! +} + +type Product @source(name: "Product", schema: "products") { + upc: Int! + name: String! + price: Int! + weight: Int! + inStock: Boolean @delegate(path: "inventoryInfo(upc: $fields:upc).isInStock", schema: "inventory") + shippingEstimate: Int @delegate(path: "shippingEstimate(price: $fields:price weight: $fields:weight)", schema: "inventory") + reviews: [Review] @delegate(path: "reviewsByProduct(upc: $fields:upc)", schema: "reviews") +} + +type Query { + me: User! @delegate(path: "user(id: 1)", schema: "accounts") + topProducts(first: Int = 5): [Product] @delegate(schema: "products") +} + +type Review @source(name: "Review", schema: "reviews") { + id: Int! + authorId: Int! + upc: Int! + body: String! + author: User @delegate(path: "user(id: $fields:authorId)", schema: "accounts") + product: Product @delegate(path: "product(upc: $fields:upc)", schema: "products") +} + +type User @source(name: "User", schema: "accounts") { + id: Int! + name: String! + birthdate: DateTime! + username: String! + reviews: [Review] @delegate(path: "reviewsByAuthor(authorId: $fields:id)", schema: "reviews") +} + +directive @computed("Specifies the fields on which a computed field is dependent on." dependantOn: [String!]) on FIELD_DEFINITION + +"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 + +"Delegates a resolver to a remote schema." +directive @delegate("The path to the field on the remote schema." path: String "The name of the schema to which this field shall be delegated to." schema: String!) on FIELD_DEFINITION + +"Annotates the original name of a type." +directive @source("The original name of the annotated type." name: String! "The name of the schema to which this type belongs to." schema: String!) repeatable on ENUM | OBJECT | INTERFACE | UNION | INPUT_OBJECT | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION | ENUM_VALUE + +"The `@specifiedBy` directive is used within the type system definition language to provide a URL for specifying the behavior of custom scalar definitions." +directive @specifiedBy("The specifiedBy URL points to a human-readable specification. This field will only read a result for scalar types." url: String!) on SCALAR + +"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! = 0 "Streamed when true." if: Boolean) on FIELD + +"The `DateTime` scalar represents an ISO-8601 compliant date time type." +scalar DateTime @specifiedBy(url: "https:\/\/www.graphql-scalars.com\/date-time") diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaTests.Directive_Variables_Are_Correctly_Rewritten.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaTests.Directive_Variables_Are_Correctly_Rewritten.snap new file mode 100644 index 00000000000..626d0a7718e --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaTests.Directive_Variables_Are_Correctly_Rewritten.snap @@ -0,0 +1,23 @@ +{ + "data": { + "me": { + "id": 1, + "alias1": "Ada Lovelace", + "alias2": [ + { + "alias3": "Love it!", + "alias4": { + "upc": 1 + } + }, + { + "alias3": "Too expensive.", + "alias4": { + "upc": 2 + } + } + ] + }, + "local": "I am local." + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/AddSchemaExtensionRewriterTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/AddSchemaExtensionRewriterTests.cs new file mode 100644 index 00000000000..bf143aeb656 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/AddSchemaExtensionRewriterTests.cs @@ -0,0 +1,494 @@ +using System; +using Snapshooter.Xunit; +using Xunit; +using HotChocolate.Language; +using HotChocolate.Language.Utilities; + +namespace HotChocolate.Stitching.Merge; + +public class AddSchemaExtensionRewriterTests +{ + [Fact] + public void ObjectType_AddScalarField() + { + // arrange + const string schema = "type Foo { bar: String }"; + const string extensions = "extend type Foo { baz: Int }"; + + // act + var rewriter = new AddSchemaExtensionRewriter(); + var merged = rewriter.AddExtensions( + Utf8GraphQLParser.Parse(schema), + Utf8GraphQLParser.Parse(extensions)); + + // assert + merged.ToString().MatchSnapshot(); + } + + [Fact] + public void ObjectType_AddScalarField_2() + { + // arrange + const string schema = "type Foo { bar: String }"; + const string extensions = "extend type Foo { baz: Int } extend type Baz { a: String }"; + + // act + var rewriter = new AddSchemaExtensionRewriter(); + var merged = rewriter.AddExtensions( + Utf8GraphQLParser.Parse(schema), + Utf8GraphQLParser.Parse(extensions)); + + // assert + merged.ToString().MatchSnapshot(); + } + + [Fact] + public void ObjectType_AddObjectField() + { + // arrange + const string schema = "type Foo { bar: String }"; + const string extensions = "extend type Foo { baz: Bar } " + + "type Bar { baz: String }"; + + // act + var rewriter = new AddSchemaExtensionRewriter(); + var merged = rewriter.AddExtensions( + Utf8GraphQLParser.Parse(schema), + Utf8GraphQLParser.Parse(extensions)); + + // assert + merged.ToString().MatchSnapshot(); + } + + [Fact] + public void ObjectType_AddDirectives() + { + // arrange + const string schema = "type Foo { bar: String } " + + "directive @foo on OBJECT"; + const string extensions = "extend type Foo @foo"; + + // act + var rewriter = new AddSchemaExtensionRewriter(); + var merged = rewriter.AddExtensions( + Utf8GraphQLParser.Parse(schema), + Utf8GraphQLParser.Parse(extensions)); + + // assert + merged.ToString().MatchSnapshot(); + } + + [Fact] + public void ObjectType_AddDirectivesToField() + { + // arrange + const string schema = "type Foo { bar: String } " + + "directive @foo on FIELD"; + const string extensions = "extend type Foo { bar: String @foo }"; + + // act + var rewriter = new AddSchemaExtensionRewriter(); + var merged = rewriter.AddExtensions( + Utf8GraphQLParser.Parse(schema), + Utf8GraphQLParser.Parse(extensions)); + + // assert + merged.ToString().MatchSnapshot(); + } + + [Fact] + public void ObjectType_DirectiveDeclaredInExtensionDoc() + { + // arrange + const string schema = "type Foo { bar: String }"; + const string extensions = "extend type Foo @foo { bar: String }" + + "directive @foo on OBJECT"; + + // act + var rewriter = new AddSchemaExtensionRewriter(); + var merged = rewriter.AddExtensions( + Utf8GraphQLParser.Parse(schema), + Utf8GraphQLParser.Parse(extensions)); + + // assert + merged.ToString().MatchSnapshot(); + } + + [Fact] + public void ObjectType_AddDuplicateDirectives() + { + // arrange + const string schema = "type Foo @foo { bar: String } " + + "directive @foo on OBJECT"; + const string extensions = "extend type Foo @foo"; + + // act + var rewriter = new AddSchemaExtensionRewriter(); + Action action = () => rewriter.AddExtensions( + Utf8GraphQLParser.Parse(schema), + Utf8GraphQLParser.Parse(extensions)); + + // assert + Assert.Throws(action).Message.MatchSnapshot(); + } + + [Fact] + public void ObjectType_AddUndeclaredDirectives() + { + // arrange + const string schema = "type Foo @foo { bar: String }"; + const string extensions = "extend type Foo @foo"; + + // act + var rewriter = new AddSchemaExtensionRewriter(); + Action action = () => rewriter.AddExtensions( + Utf8GraphQLParser.Parse(schema), + Utf8GraphQLParser.Parse(extensions)); + + // assert + Assert.Throws(action).Message.MatchSnapshot(); + } + + [Fact] + public void InterfaceType_AddScalarField() + { + // arrange + const string schema = "interface Foo { bar: String }"; + const string extensions = "extend interface Foo { baz: Int }"; + + // act + var rewriter = new AddSchemaExtensionRewriter(); + var merged = rewriter.AddExtensions( + Utf8GraphQLParser.Parse(schema), + Utf8GraphQLParser.Parse(extensions)); + + // assert + merged.ToString().MatchSnapshot(); + } + + [Fact] + public void InterfaceType_AddObjectField() + { + // arrange + const string schema = "interface Foo { bar: String }"; + const string extensions = "extend interface Foo { baz: Bar } " + + "interface Bar { baz: String }"; + + // act + var rewriter = new AddSchemaExtensionRewriter(); + var merged = rewriter.AddExtensions( + Utf8GraphQLParser.Parse(schema), + Utf8GraphQLParser.Parse(extensions)); + + // assert + merged.ToString().MatchSnapshot(); + } + + [Fact] + public void InterfaceType_AddDirectives() + { + // arrange + const string schema = "interface Foo { bar: String } " + + "directive @foo on INTERFACE"; + const string extensions = "extend interface Foo @foo"; + + // act + var rewriter = new AddSchemaExtensionRewriter(); + var merged = rewriter.AddExtensions( + Utf8GraphQLParser.Parse(schema), + Utf8GraphQLParser.Parse(extensions)); + + // assert + merged.ToString().MatchSnapshot(); + } + + [Fact] + public void InterfaceType_AddDuplicateDirectives() + { + // arrange + const string schema = "interface Foo @foo { bar: String } " + + "directive @foo on INTERFACE"; + const string extensions = "extend interface Foo @foo"; + + // act + var rewriter = new AddSchemaExtensionRewriter(); + Action action = () => rewriter.AddExtensions( + Utf8GraphQLParser.Parse(schema), + Utf8GraphQLParser.Parse(extensions)); + + // assert + Assert.Throws(action).Message.MatchSnapshot(); + } + + [Fact] + public void InterfaceType_AddUndeclaredDirectives() + { + // arrange + const string schema = "interface Foo @foo { bar: String }"; + const string extensions = "extend interface Foo @foo"; + + // act + var rewriter = new AddSchemaExtensionRewriter(); + Action action = () => rewriter.AddExtensions( + Utf8GraphQLParser.Parse(schema), + Utf8GraphQLParser.Parse(extensions)); + + // assert + Assert.Throws(action).Message.MatchSnapshot(); + } + + [Fact] + public void UnionType_AddType() + { + // arrange + const string schema = "union Foo = A | B " + + "type A { a: String } " + + "type B { b: String }"; + const string extensions = "extend union Foo = C " + + "type C { c: String }"; + + // act + var rewriter = new AddSchemaExtensionRewriter(); + var merged = rewriter.AddExtensions( + Utf8GraphQLParser.Parse(schema), + Utf8GraphQLParser.Parse(extensions)); + + // assert + merged.ToString().MatchSnapshot(); + } + + [Fact] + public void UnionType_AddDirectives() + { + // arrange + const string schema = "union Foo = A | B " + + "type A { a: String } " + + "type B { b: String } " + + "directive @foo on INTERFACE"; + const string extensions = "extend union Foo @foo"; + + // act + var rewriter = new AddSchemaExtensionRewriter(); + var merged = rewriter.AddExtensions( + Utf8GraphQLParser.Parse(schema), + Utf8GraphQLParser.Parse(extensions)); + + // assert + merged.ToString().MatchSnapshot(); + } + + [Fact] + public void UnionType_AddDuplicateDirectives() + { + // arrange + const string schema = "union Foo @foo = A | B " + + "type A { a: String } " + + "type B { b: String } " + + "directive @foo on INTERFACE"; + const string extensions = "extend union Foo @foo"; + + // act + var rewriter = new AddSchemaExtensionRewriter(); + Action action = () => rewriter.AddExtensions( + Utf8GraphQLParser.Parse(schema), + Utf8GraphQLParser.Parse(extensions)); + + // assert + Assert.Throws(action).Message.MatchSnapshot(); + } + + [Fact] + public void UnionType_AddUndeclaredDirectives() + { + // arrange + const string schema = "union Foo = A | B " + + "type A { a: String } " + + "type B { b: String }"; + const string extensions = "extend union Foo @foo"; + + // act + var rewriter = new AddSchemaExtensionRewriter(); + Action action = () => rewriter.AddExtensions( + Utf8GraphQLParser.Parse(schema), + Utf8GraphQLParser.Parse(extensions)); + + // assert + Assert.Throws(action).Message.MatchSnapshot(); + } + + [Fact] + public void InputObjectType_AddScalarField() + { + // arrange + const string schema = "input Foo { bar: String }"; + const string extensions = "extend input Foo { baz: Int }"; + + // act + var rewriter = new AddSchemaExtensionRewriter(); + var merged = rewriter.AddExtensions( + Utf8GraphQLParser.Parse(schema), + Utf8GraphQLParser.Parse(extensions)); + + // assert + merged.ToString().MatchSnapshot(); + } + + [Fact] + public void InputObjectType_AddObjectField() + { + // arrange + const string schema = "input Foo { bar: String }"; + const string extensions = "extend input Foo { baz: Bar } " + + "input Bar { baz: String }"; + + // act + var rewriter = new AddSchemaExtensionRewriter(); + var merged = rewriter.AddExtensions( + Utf8GraphQLParser.Parse(schema), + Utf8GraphQLParser.Parse(extensions)); + + // assert + merged.ToString().MatchSnapshot(); + } + + [Fact] + public void InputObjectType_AddDirectives() + { + // arrange + const string schema = "input Foo { bar: String } " + + "directive @foo on INPUT_OBJECT"; + const string extensions = "extend input Foo @foo"; + + // act + var rewriter = new AddSchemaExtensionRewriter(); + var merged = rewriter.AddExtensions( + Utf8GraphQLParser.Parse(schema), + Utf8GraphQLParser.Parse(extensions)); + + // assert + merged.ToString().MatchSnapshot(); + } + + [Fact] + public void InputObjectType_AddDuplicateDirectives() + { + // arrange + const string schema = "input Foo @foo { bar: String } " + + "directive @foo on INPUT_OBJECT"; + const string extensions = "extend input Foo @foo"; + + // act + var rewriter = new AddSchemaExtensionRewriter(); + Action action = () => rewriter.AddExtensions( + Utf8GraphQLParser.Parse(schema), + Utf8GraphQLParser.Parse(extensions)); + + // assert + Assert.Throws(action).Message.MatchSnapshot(); + } + + [Fact] + public void InputObjectType_AddUndeclaredDirectives() + { + // arrange + const string schema = "input Foo @foo { bar: String }"; + const string extensions = "extend input Foo @foo"; + + // act + var rewriter = new AddSchemaExtensionRewriter(); + Action action = () => rewriter.AddExtensions( + Utf8GraphQLParser.Parse(schema), + Utf8GraphQLParser.Parse(extensions)); + + // assert + Assert.Throws(action).Message.MatchSnapshot(); + } + + [Fact] + public void EnumType_AddValue() + { + // arrange + const string schema = "enum Foo { BAR BAZ }"; + const string extensions = "extend enum Foo { QUX }"; + + // act + var rewriter = new AddSchemaExtensionRewriter(); + var merged = rewriter.AddExtensions( + Utf8GraphQLParser.Parse(schema), + Utf8GraphQLParser.Parse(extensions)); + + // assert + merged.Print().MatchSnapshot(); + } + + [Fact] + public void EnumType_AddDirectives() + { + // arrange + const string schema = "enum Foo { BAR BAZ } " + + "directive @foo on ENUM"; + const string extensions = "extend enum Foo @foo"; + + // act + var rewriter = new AddSchemaExtensionRewriter(); + var merged = rewriter.AddExtensions( + Utf8GraphQLParser.Parse(schema), + Utf8GraphQLParser.Parse(extensions)); + + // assert + merged.Print().MatchSnapshot(); + } + + [Fact] + public void EnumType_TypeMismatch() + { + // arrange + const string schema = "enum Foo @foo { BAR BAZ } " + + "directive @foo on ENUM"; + const string extensions = "extend input Foo @foo"; + + // act + var rewriter = new AddSchemaExtensionRewriter(); + Action action = () => rewriter.AddExtensions( + Utf8GraphQLParser.Parse(schema), + Utf8GraphQLParser.Parse(extensions)); + + // assert + Assert.Throws(action).Message.MatchSnapshot(); + } + + [Fact] + public void EnumType_AddDuplicateDirectives() + { + // arrange + const string schema = "enum Foo @foo { BAR BAZ } " + + "directive @foo on ENUM"; + const string extensions = "extend enum Foo @foo"; + + // act + var rewriter = new AddSchemaExtensionRewriter(); + Action action = () => rewriter.AddExtensions( + Utf8GraphQLParser.Parse(schema), + Utf8GraphQLParser.Parse(extensions)); + + // assert + Assert.Throws(action).Message.MatchSnapshot(); + } + + [Fact] + public void EnumType_AddUndeclaredDirectives() + { + // arrange + const string schema = "enum Foo { BAR BAZ }"; + const string extensions = "extend enum Foo @foo"; + + // act + var rewriter = new AddSchemaExtensionRewriter(); + Action action = () => rewriter.AddExtensions( + Utf8GraphQLParser.Parse(schema), + Utf8GraphQLParser.Parse(extensions)); + + // assert + Assert.Throws(action).Message.MatchSnapshot(); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/DirectiveTypeMergeHandlerTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/DirectiveTypeMergeHandlerTests.cs new file mode 100644 index 00000000000..5f13707c0ed --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/DirectiveTypeMergeHandlerTests.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using HotChocolate.Language; +using HotChocolate.Language.Utilities; +using Snapshooter.Xunit; +using Xunit; + +namespace HotChocolate.Stitching.Merge.Handlers; + +public class DirectiveTypeMergeHandlerTests +{ + [Fact] + public void Merge_SimpleIdenticalDirectives_TypeMerges() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse( + "directive @test(arg: String) on OBJECT"); + var schema_b = + Utf8GraphQLParser.Parse( + "directive @test(arg: String) on OBJECT"); + + var types = new List + { + new DirectiveTypeInfo(schema_a.Definitions + .OfType().First(), + new SchemaInfo("Schema_A", schema_a)), + new DirectiveTypeInfo(schema_b.Definitions + .OfType().First(), + new SchemaInfo("Schema_B", schema_b)), + }; + + var context = new SchemaMergeContext(); + + // act + var typeMerger = new DirectiveTypeMergeHandler((c, t) => { }); + typeMerger.Merge(context, types); + + // assert + context + .CreateSchema() + .Print() + .MatchSnapshot(); + } + + [Fact] + public void Merge_DifferentArguments_ThrowsException() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse("directive @test(arg: Int) on OBJECT"); + var schema_b = + Utf8GraphQLParser.Parse("directive @test(arg: String) on OBJECT"); + + var types = new List + { + new DirectiveTypeInfo(schema_a.Definitions + .OfType().First(), + new SchemaInfo("Schema_A", schema_a)), + new DirectiveTypeInfo(schema_b.Definitions + .OfType().First(), + new SchemaInfo("Schema_B", schema_b)) + }; + + var context = new SchemaMergeContext(); + + // act + var typeMerger = new DirectiveTypeMergeHandler((c, t) => { }); + + Assert.Throws( + () => typeMerger.Merge(context, types)); + } + + [Fact] + public void Merge_DifferentLocations_ThrowsException() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse( + "directive @test(arg: String) on OBJECT | INTERFACE"); + var schema_b = + Utf8GraphQLParser.Parse( + "directive @test(arg: String) on OBJECT"); + + var types = new List + { + new DirectiveTypeInfo(schema_a.Definitions + .OfType().First(), + new SchemaInfo("Schema_A", schema_a)), + new DirectiveTypeInfo(schema_b.Definitions + .OfType().First(), + new SchemaInfo("Schema_B", schema_b)) + }; + + var context = new SchemaMergeContext(); + + // act + var typeMerger = new DirectiveTypeMergeHandler((c, t) => { }); + + Assert.Throws( + () => typeMerger.Merge(context, types)); + } + + [Fact] + public void Merge_DifferentRepeatable_ThrowsException() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse( + "directive @test(arg: String) repeatable on OBJECT"); + var schema_b = + Utf8GraphQLParser.Parse( + "directive @test(arg: String) on OBJECT"); + + var types = new List + { + new DirectiveTypeInfo(schema_a.Definitions + .OfType().First(), + new SchemaInfo("Schema_A", schema_a)), + new DirectiveTypeInfo(schema_b.Definitions + .OfType().First(), + new SchemaInfo("Schema_B", schema_b)) + }; + + var context = new SchemaMergeContext(); + + // act + var typeMerger = new DirectiveTypeMergeHandler((c, t) => { }); + + Assert.Throws( + () => typeMerger.Merge(context, types)); + } + + [Fact] + public void Merge_ThreeDirectivessWhereTwoAreIdentical_TwoTypesAfterMerge() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse( + "directive @test(arg: String) on OBJECT"); + var schema_b = + Utf8GraphQLParser.Parse( + "directive @test1(arg: String) on OBJECT"); + var schema_c = + Utf8GraphQLParser.Parse( + "directive @test(arg: String) on OBJECT"); + + var types = new List + { + new DirectiveTypeInfo(schema_a.Definitions + .OfType().First(), + new SchemaInfo("Schema_A", schema_a)), + new DirectiveTypeInfo(schema_b.Definitions + .OfType().First(), + new SchemaInfo("Schema_B", schema_b)), + new DirectiveTypeInfo(schema_c.Definitions + .OfType().First(), + new SchemaInfo("Schema_C", schema_c)) + }; + + var context = new SchemaMergeContext(); + + // act + var typeMerger = new DirectiveTypeMergeHandler((c, t) => { }); + typeMerger.Merge(context, types); + + // assert + context + .CreateSchema() + .Print() + .MatchSnapshot(); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/EnumTypeMergeHandlerTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/EnumTypeMergeHandlerTests.cs new file mode 100644 index 00000000000..16b3d8c2610 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/EnumTypeMergeHandlerTests.cs @@ -0,0 +1,187 @@ +using System.Linq; +using System.Collections.Generic; +using HotChocolate.Language; +using HotChocolate.Language.Utilities; +using Xunit; +using Snapshooter.Xunit; + +namespace HotChocolate.Stitching.Merge.Handlers; + +public class EnumTypeMergeHandlerTests +{ + [Fact] + public void MergeIdenticalEnums() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse("enum Foo { BAR BAZ }"); + var schema_b = + Utf8GraphQLParser.Parse("enum Foo { BAR BAZ }"); + + var types = new List + { + TypeInfo.Create( + schema_a.Definitions.OfType().First(), + new SchemaInfo("Schema_A", schema_a)), + TypeInfo.Create( + schema_b.Definitions.OfType().First(), + new SchemaInfo("Schema_B", schema_b)) + }; + + var context = new SchemaMergeContext(); + + // act + var typeMerger = new EnumTypeMergeHandler((c, t) => { }); + typeMerger.Merge(context, types); + + // assert + context.CreateSchema() + .Print() + .MatchSnapshot(); + } + + [Fact] + public void MergeIdenticalEnumsTakeDescriptionFromSecondType() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse("enum Foo { BAR BAZ }"); + var schema_b = + Utf8GraphQLParser.Parse(@"""Foo Bar"" enum Foo { BAR BAZ }"); + + var types = new List + { + TypeInfo.Create( + schema_a.Definitions.OfType().First(), + new SchemaInfo("Schema_A", schema_a)), + TypeInfo.Create( + schema_b.Definitions.OfType().First(), + new SchemaInfo("Schema_B", schema_b)) + }; + + var context = new SchemaMergeContext(); + + // act + var typeMerger = new EnumTypeMergeHandler((c, t) => { }); + typeMerger.Merge(context, types); + + // assert + context + .CreateSchema() + .Print() + .MatchSnapshot(); + } + + [Fact] + public void MergeNonIdenticalEnums() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse("enum Foo { BAR BAZ }"); + var schema_b = + Utf8GraphQLParser.Parse("enum Foo { BAR BAZ }"); + var schema_c = + Utf8GraphQLParser.Parse("enum Foo { BAR BAZ QUX }"); + + var types = new List + { + TypeInfo.Create( + schema_a.Definitions.OfType().First(), + new SchemaInfo("Schema_A", schema_a)), + TypeInfo.Create( + schema_b.Definitions.OfType().First(), + new SchemaInfo("Schema_B", schema_b)), + TypeInfo.Create( + schema_c.Definitions.OfType().First(), + new SchemaInfo("Schema_C", schema_c)) + }; + + var context = new SchemaMergeContext(); + + // act + var typeMerger = new EnumTypeMergeHandler((c, t) => { }); + typeMerger.Merge(context, types); + + // assert + context + .CreateSchema() + .Print() + .MatchSnapshot(); + } + + [Fact] + public void MergeNonIdenticalEnums2() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse("enum Foo { BAR BAZ }"); + var schema_b = + Utf8GraphQLParser.Parse("enum Foo { BAR BAZ QUX }"); + var schema_c = + Utf8GraphQLParser.Parse("enum Foo { BAR BAZ QUX }"); + + var types = new List + { + TypeInfo.Create( + schema_a.Definitions.OfType().First(), + new SchemaInfo("Schema_A", schema_a)), + TypeInfo.Create( + schema_b.Definitions.OfType().First(), + new SchemaInfo("Schema_B", schema_b)), + TypeInfo.Create( + schema_c.Definitions.OfType().First(), + new SchemaInfo("Schema_C", schema_c)) + }; + + var context = new SchemaMergeContext(); + + // act + var typeMerger = new EnumTypeMergeHandler((c, t) => { }); + typeMerger.Merge(context, types); + + // assert + context + .CreateSchema() + .Print() + .MatchSnapshot(); + } + + [Fact] + public void Merge_DifferentTypes_InputMergesLeftoversArePassed() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse("input A { b: String }"); + var schema_b = + Utf8GraphQLParser.Parse("enum A { B C }"); + + var types = new List + { + TypeInfo.Create( + schema_a.Definitions.OfType().First(), + new SchemaInfo("Schema_A", schema_a)), + TypeInfo.Create( + schema_b.Definitions.OfType().First(), + new SchemaInfo("Schema_B", schema_b)), + }; + + var context = new SchemaMergeContext(); + + var leftovers = new List(); + + // act + var typeMerger = new EnumTypeMergeHandler( + (c, t) => leftovers.AddRange(t)); + typeMerger.Merge(context, types); + + // assert + Assert.Collection(leftovers, + t => Assert.IsType(t)); + + Snapshot.Match(new List + { + context.CreateSchema().Print(), + leftovers + }); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/InputObjectTypeMergeHandlerTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/InputObjectTypeMergeHandlerTests.cs new file mode 100644 index 00000000000..2475a65291a --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/InputObjectTypeMergeHandlerTests.cs @@ -0,0 +1,119 @@ +using System.Linq; +using System.Collections.Generic; +using HotChocolate.Language; +using HotChocolate.Language.Utilities; +using Xunit; +using Snapshooter.Xunit; + +namespace HotChocolate.Stitching.Merge.Handlers; + +public class InputObjectTypeMergeHandlerTests +{ + [Fact] + public void Merge_SimpleIdenticalInputs_TypeMerges() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse("input A { b: String }"); + var schema_b = + Utf8GraphQLParser.Parse("input A { b: String }"); + + var types = new List + { + TypeInfo.Create( + schema_a.Definitions.OfType().First(), + new SchemaInfo("Schema_A", schema_a)), + TypeInfo.Create( + schema_b.Definitions.OfType().First(), + new SchemaInfo("Schema_B", schema_b)) + }; + + var context = new SchemaMergeContext(); + + // act + var typeMerger = new InputObjectTypeMergeHandler((c, t) => { }); + typeMerger.Merge(context, types); + + // assert + context + .CreateSchema() + .Print() + .MatchSnapshot(); + } + + [Fact] + public void Merge_ThreeInputsWhereTwoAreIdentical_TwoTypesAfterMerge() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse("input A { b: String }"); + var schema_b = + Utf8GraphQLParser.Parse("input A { b: String c: String }"); + var schema_c = + Utf8GraphQLParser.Parse("input A { b: String }"); + + var types = new List + { + TypeInfo.Create( + schema_a.Definitions.OfType().First(), + new SchemaInfo("Schema_A", schema_a)), + TypeInfo.Create( + schema_b.Definitions.OfType().First(), + new SchemaInfo("Schema_B", schema_b)), + TypeInfo.Create( + schema_c.Definitions.OfType().First(), + new SchemaInfo("Schema_C", schema_c)) + }; + + var context = new SchemaMergeContext(); + + // act + var typeMerger = new InputObjectTypeMergeHandler((c, t) => { }); + typeMerger.Merge(context, types); + + // assert + context + .CreateSchema() + .Print() + .MatchSnapshot(); + } + + [Fact] + public void Merge_DifferentTypes_InputMergesLeftoversArePassed() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse("input A { b: String }"); + var schema_b = + Utf8GraphQLParser.Parse("enum A { B C }"); + + var types = new List + { + TypeInfo.Create( + schema_a.Definitions.OfType().First(), + new SchemaInfo("Schema_A", schema_a)), + TypeInfo.Create( + schema_b.Definitions.OfType().First(), + new SchemaInfo("Schema_B", schema_b)), + }; + + var context = new SchemaMergeContext(); + + var leftovers = new List(); + + // act + var typeMerger = new InputObjectTypeMergeHandler( + (c, t) => leftovers.AddRange(t)); + typeMerger.Merge(context, types); + + // assert + Assert.Collection(leftovers, + t => Assert.IsType(t)); + + Snapshot.Match(new List + { + context.CreateSchema().Print(), + leftovers + }); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/InterfaceTypeMergeHandlerTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/InterfaceTypeMergeHandlerTests.cs new file mode 100644 index 00000000000..34cd312fb0b --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/InterfaceTypeMergeHandlerTests.cs @@ -0,0 +1,119 @@ +using System.Linq; +using System.Collections.Generic; +using HotChocolate.Language; +using HotChocolate.Language.Utilities; +using Xunit; +using Snapshooter.Xunit; + +namespace HotChocolate.Stitching.Merge.Handlers; + +public class InterfaceTypeMergeHandlerTests +{ + [Fact] + public void Merge_SimpleIdenticalInterfaces_TypeMerges() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse("interface A { b: String }"); + var schema_b = + Utf8GraphQLParser.Parse("interface A { b: String }"); + + var types = new List + { + TypeInfo.Create( + schema_a.Definitions.OfType().First(), + new SchemaInfo("Schema_A", schema_a)), + TypeInfo.Create( + schema_b.Definitions.OfType().First(), + new SchemaInfo("Schema_B", schema_b)) + }; + + var context = new SchemaMergeContext(); + + // act + var typeMerger = new InterfaceTypeMergeHandler((c, t) => { }); + typeMerger.Merge(context, types); + + // assert + context + .CreateSchema() + .Print() + .MatchSnapshot(); + } + + [Fact] + public void Merge_ThreeInterfWhereTwoAreIdentical_TwoTypesAfterMerge() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse("interface A { b: String }"); + var schema_b = + Utf8GraphQLParser.Parse("interface A { b(a: String): String }"); + var schema_c = + Utf8GraphQLParser.Parse("interface A { b: String }"); + + var types = new List + { + TypeInfo.Create( + schema_a.Definitions.OfType().First(), + new SchemaInfo("Schema_A", schema_a)), + TypeInfo.Create( + schema_b.Definitions.OfType().First(), + new SchemaInfo("Schema_B", schema_b)), + TypeInfo.Create( + schema_c.Definitions.OfType().First(), + new SchemaInfo("Schema_C", schema_c)) + }; + + var context = new SchemaMergeContext(); + + // act + var typeMerger = new InterfaceTypeMergeHandler((c, t) => { }); + typeMerger.Merge(context, types); + + // assert + context + .CreateSchema() + .Print() + .MatchSnapshot(); + } + + [Fact] + public void Merge_DifferentTypes_InterfMergesLeftoversArePassed() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse("interface A { b: String }"); + var schema_b = + Utf8GraphQLParser.Parse("enum A { B C }"); + + var types = new List + { + TypeInfo.Create( + schema_a.Definitions.OfType().First(), + new SchemaInfo("Schema_A", schema_a)), + TypeInfo.Create( + schema_b.Definitions.OfType().First(), + new SchemaInfo("Schema_B", schema_b)), + }; + + var context = new SchemaMergeContext(); + + var leftovers = new List(); + + // act + var typeMerger = new InterfaceTypeMergeHandler( + (c, t) => leftovers.AddRange(t)); + typeMerger.Merge(context, types); + + // assert + Assert.Collection(leftovers, + t => Assert.IsType(t)); + + Snapshot.Match(new List + { + context.CreateSchema().Print(), + leftovers + }); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/ObjectTypeMergeHandlerTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/ObjectTypeMergeHandlerTests.cs new file mode 100644 index 00000000000..393af2820bb --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/ObjectTypeMergeHandlerTests.cs @@ -0,0 +1,156 @@ +using System.Linq; +using System.Collections.Generic; +using HotChocolate.Language; +using HotChocolate.Language.Utilities; +using Xunit; +using Snapshooter.Xunit; + +namespace HotChocolate.Stitching.Merge.Handlers; + +public class ObjectTypeMergeHandlerTests +{ + [Fact] + public void Merge_SimpleIdenticalObjects_TypeMerges() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse("type A { b: String }"); + var schema_b = + Utf8GraphQLParser.Parse("type A { b: String }"); + + var types = new List + { + TypeInfo.Create( + schema_a.Definitions.OfType().First(), + new SchemaInfo("Schema_A", schema_a)), + TypeInfo.Create( + schema_b.Definitions.OfType().First(), + new SchemaInfo("Schema_B", schema_b)) + }; + + var context = new SchemaMergeContext(); + + // act + var typeMerger = new ObjectTypeMergeHandler((c, t) => { }); + typeMerger.Merge(context, types); + + // assert + context + .CreateSchema() + .Print() + .MatchSnapshot(); + } + + [Fact] + public void Merge_ThreeObjectsWhereTwoAreIdentical_TwoTypesAfterMerge() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse("type A { b: String }"); + var schema_b = + Utf8GraphQLParser.Parse("type A { b(a: String): String }"); + var schema_c = + Utf8GraphQLParser.Parse("type A { b: String }"); + + var types = new List + { + TypeInfo.Create( + schema_a.Definitions.OfType().First(), + new SchemaInfo("Schema_A", schema_a)), + TypeInfo.Create( + schema_b.Definitions.OfType().First(), + new SchemaInfo("Schema_B", schema_b)), + TypeInfo.Create( + schema_c.Definitions.OfType().First(), + new SchemaInfo("Schema_C", schema_c)) + }; + + var context = new SchemaMergeContext(); + + // act + var typeMerger = new ObjectTypeMergeHandler((c, t) => { }); + typeMerger.Merge(context, types); + + // assert + context + .CreateSchema() + .Print() + .MatchSnapshot(); + } + + [Fact] + public void Merge_ObjectWithDifferentInterfaces_TypesMerge() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse("type A implements IA { b: String }"); + var schema_b = + Utf8GraphQLParser.Parse("type A implements IB { b : String }"); + var schema_c = + Utf8GraphQLParser.Parse("type A implements IC { b: String }"); + + var types = new List + { + TypeInfo.Create( + schema_a.Definitions.OfType().First(), + new SchemaInfo("Schema_A", schema_a)), + TypeInfo.Create( + schema_b.Definitions.OfType().First(), + new SchemaInfo("Schema_B", schema_b)), + TypeInfo.Create( + schema_c.Definitions.OfType().First(), + new SchemaInfo("Schema_C", schema_c)) + }; + + var context = new SchemaMergeContext(); + + // act + var typeMerger = new ObjectTypeMergeHandler((c, t) => { }); + typeMerger.Merge(context, types); + + // assert + context + .CreateSchema() + .Print() + .MatchSnapshot(); + } + + [Fact] + public void Merge_DifferentTypes_ObjectMergesLeftoversArePassed() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse("type A { b: String }"); + var schema_b = + Utf8GraphQLParser.Parse("enum A { B C }"); + + var types = new List + { + TypeInfo.Create( + schema_a.Definitions.OfType().First(), + new SchemaInfo("Schema_A", schema_a)), + TypeInfo.Create( + schema_b.Definitions.OfType().First(), + new SchemaInfo("Schema_B", schema_b)), + }; + + var context = new SchemaMergeContext(); + + var leftovers = new List(); + + // act + var typeMerger = new ObjectTypeMergeHandler( + (c, t) => leftovers.AddRange(t)); + typeMerger.Merge(context, types); + + // assert + Assert.Collection(leftovers, + t => Assert.IsType(t)); + + Snapshot.Match(new List + { + context.CreateSchema().Print(), + leftovers + }); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/RootTypeMergeHandlerTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/RootTypeMergeHandlerTests.cs new file mode 100644 index 00000000000..f51578bd00c --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/RootTypeMergeHandlerTests.cs @@ -0,0 +1,75 @@ +using System.Linq; +using System.Collections.Generic; +using HotChocolate.Language; +using HotChocolate.Language.Utilities; +using Xunit; +using Snapshooter.Xunit; + +namespace HotChocolate.Stitching.Merge.Handlers; + +public class RootTypeMergeHandlerTests +{ + [Fact] + public void Merge_RootTypeWithNoCollisions_TypeMerges() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse("type Query { a: String }"); + var schema_b = + Utf8GraphQLParser.Parse("type Query { b: String }"); + + var types = new List + { + TypeInfo.Create( + schema_a.Definitions.OfType().First(), + new SchemaInfo("Schema_A", schema_a)), + TypeInfo.Create( + schema_b.Definitions.OfType().First(), + new SchemaInfo("Schema_B", schema_b)) + }; + + var context = new SchemaMergeContext(); + + // act + var typeMerger = new RootTypeMergeHandler((c, t) => { }); + typeMerger.Merge(context, types); + + // assert + context + .CreateSchema() + .Print() + .MatchSnapshot(); + } + + [Fact] + public void Merge_RootTypeWithCollisions_CollidingFieldsAreRenamed() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse("type Query { a: String }"); + var schema_b = + Utf8GraphQLParser.Parse("type Query { a: String }"); + + var types = new List + { + TypeInfo.Create( + schema_a.Definitions.OfType().First(), + new SchemaInfo("Schema_A", schema_a)), + TypeInfo.Create( + schema_b.Definitions.OfType().First(), + new SchemaInfo("Schema_B", schema_b)) + }; + + var context = new SchemaMergeContext(); + + // act + var typeMerger = new RootTypeMergeHandler((c, t) => { }); + typeMerger.Merge(context, types); + + // assert + context + .CreateSchema() + .Print() + .MatchSnapshot(); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/UnionTypeMergeHandlerTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/UnionTypeMergeHandlerTests.cs new file mode 100644 index 00000000000..a3545dbbeea --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/UnionTypeMergeHandlerTests.cs @@ -0,0 +1,43 @@ +using System.Linq; +using System.Collections.Generic; +using HotChocolate.Language; +using HotChocolate.Language.Utilities; +using Xunit; +using Snapshooter.Xunit; + +namespace HotChocolate.Stitching.Merge.Handlers; + +public class UnionTypeMergeHandlerTests +{ + [Fact] + public void MergeUnionTypes() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse("union Foo = Bar | Baz"); + var schema_b = + Utf8GraphQLParser.Parse("union Foo = Bar | Baz"); + + var types = new List + { + TypeInfo.Create( + schema_a.Definitions.OfType().First(), + new SchemaInfo("Schema_A", schema_a)), + TypeInfo.Create( + schema_b.Definitions.OfType().First(), + new SchemaInfo("Schema_B", schema_b)) + }; + + var context = new SchemaMergeContext(); + + // act + var typeMerger = new UnionTypeMergeHandler((c, t) => { }); + typeMerger.Merge(context, types); + + // assert + context + .CreateSchema() + .Print() + .MatchSnapshot(); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/DirectiveTypeMergeHandlerTests.Merge_SimpleIdenticalDirectives_TypeMerges.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/DirectiveTypeMergeHandlerTests.Merge_SimpleIdenticalDirectives_TypeMerges.snap new file mode 100644 index 00000000000..64d416284ed --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/DirectiveTypeMergeHandlerTests.Merge_SimpleIdenticalDirectives_TypeMerges.snap @@ -0,0 +1 @@ +directive @test(arg: String) on OBJECT diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/DirectiveTypeMergeHandlerTests.Merge_ThreeDirectivessWhereTwoAreIdentical_TwoTypesAfterMerge.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/DirectiveTypeMergeHandlerTests.Merge_ThreeDirectivessWhereTwoAreIdentical_TwoTypesAfterMerge.snap new file mode 100644 index 00000000000..b08e9180a85 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/DirectiveTypeMergeHandlerTests.Merge_ThreeDirectivessWhereTwoAreIdentical_TwoTypesAfterMerge.snap @@ -0,0 +1,3 @@ +directive @test(arg: String) on OBJECT + +directive @test1(arg: String) on OBJECT diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/EnumTypeMergeHandlerTests.MergeIdenticalEnums.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/EnumTypeMergeHandlerTests.MergeIdenticalEnums.snap new file mode 100644 index 00000000000..1d8b01b01ea --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/EnumTypeMergeHandlerTests.MergeIdenticalEnums.snap @@ -0,0 +1,4 @@ +enum Foo @source(name: "Foo", schema: "Schema_A") @source(name: "Foo", schema: "Schema_B") { + BAR + BAZ +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/EnumTypeMergeHandlerTests.MergeIdenticalEnumsTakeDescriptionFromSecondType.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/EnumTypeMergeHandlerTests.MergeIdenticalEnumsTakeDescriptionFromSecondType.snap new file mode 100644 index 00000000000..fd22eb62b35 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/EnumTypeMergeHandlerTests.MergeIdenticalEnumsTakeDescriptionFromSecondType.snap @@ -0,0 +1,5 @@ +"Foo Bar" +enum Foo @source(name: "Foo", schema: "Schema_A") @source(name: "Foo", schema: "Schema_B") { + BAR + BAZ +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/EnumTypeMergeHandlerTests.MergeNonIdenticalEnums.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/EnumTypeMergeHandlerTests.MergeNonIdenticalEnums.snap new file mode 100644 index 00000000000..947adfa226b --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/EnumTypeMergeHandlerTests.MergeNonIdenticalEnums.snap @@ -0,0 +1,10 @@ +enum Foo @source(name: "Foo", schema: "Schema_A") @source(name: "Foo", schema: "Schema_B") { + BAR + BAZ +} + +enum Schema_C_Foo @source(name: "Foo", schema: "Schema_C") { + BAR + BAZ + QUX +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/EnumTypeMergeHandlerTests.MergeNonIdenticalEnums2.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/EnumTypeMergeHandlerTests.MergeNonIdenticalEnums2.snap new file mode 100644 index 00000000000..25ff6898775 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/EnumTypeMergeHandlerTests.MergeNonIdenticalEnums2.snap @@ -0,0 +1,10 @@ +enum Foo @source(name: "Foo", schema: "Schema_A") { + BAR + BAZ +} + +enum Schema_B_Foo @source(name: "Foo", schema: "Schema_B") @source(name: "Foo", schema: "Schema_C") { + BAR + BAZ + QUX +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/EnumTypeMergeHandlerTests.Merge_DifferentTypes_InputMergesLeftoversArePassed.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/EnumTypeMergeHandlerTests.Merge_DifferentTypes_InputMergesLeftoversArePassed.snap new file mode 100644 index 00000000000..80e70b786df --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/EnumTypeMergeHandlerTests.Merge_DifferentTypes_InputMergesLeftoversArePassed.snap @@ -0,0 +1,221 @@ +[ + "enum A @source(name: \"A\", schema: \"Schema_B\") {\n B\n C\n}", + [ + { + "Definition": { + "Kind": "InputObjectTypeDefinition", + "Description": null, + "Fields": [ + { + "Kind": "InputValueDefinition", + "Description": null, + "Type": { + "Kind": "NamedType", + "Location": { + "Start": 13, + "End": 21, + "Line": 1, + "Column": 14 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 13, + "End": 21, + "Line": 1, + "Column": 14 + }, + "Value": "String" + } + }, + "DefaultValue": null, + "Location": { + "Start": 10, + "End": 21, + "Line": 1, + "Column": 11 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 10, + "End": 12, + "Line": 1, + "Column": 11 + }, + "Value": "b" + }, + "Directives": [] + } + ], + "Location": { + "Start": 0, + "End": 21, + "Line": 1, + "Column": 1 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 6, + "End": 9, + "Line": 1, + "Column": 7 + }, + "Value": "A" + }, + "Directives": [] + }, + "Schema": { + "Name": "Schema_A", + "Document": { + "Kind": "Document", + "Location": { + "Start": 0, + "End": 21, + "Line": 1, + "Column": 1 + }, + "Definitions": [ + { + "Kind": "InputObjectTypeDefinition", + "Description": null, + "Fields": [ + { + "Kind": "InputValueDefinition", + "Description": null, + "Type": { + "Kind": "NamedType", + "Location": { + "Start": 13, + "End": 21, + "Line": 1, + "Column": 14 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 13, + "End": 21, + "Line": 1, + "Column": 14 + }, + "Value": "String" + } + }, + "DefaultValue": null, + "Location": { + "Start": 10, + "End": 21, + "Line": 1, + "Column": 11 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 10, + "End": 12, + "Line": 1, + "Column": 11 + }, + "Value": "b" + }, + "Directives": [] + } + ], + "Location": { + "Start": 0, + "End": 21, + "Line": 1, + "Column": 1 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 6, + "End": 9, + "Line": 1, + "Column": 7 + }, + "Value": "A" + }, + "Directives": [] + } + ], + "Count": 7 + }, + "Types": { + "A": { + "Kind": "InputObjectTypeDefinition", + "Description": null, + "Fields": [ + { + "Kind": "InputValueDefinition", + "Description": null, + "Type": { + "Kind": "NamedType", + "Location": { + "Start": 13, + "End": 21, + "Line": 1, + "Column": 14 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 13, + "End": 21, + "Line": 1, + "Column": 14 + }, + "Value": "String" + } + }, + "DefaultValue": null, + "Location": { + "Start": 10, + "End": 21, + "Line": 1, + "Column": 11 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 10, + "End": 12, + "Line": 1, + "Column": 11 + }, + "Value": "b" + }, + "Directives": [] + } + ], + "Location": { + "Start": 0, + "End": 21, + "Line": 1, + "Column": 1 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 6, + "End": 9, + "Line": 1, + "Column": 7 + }, + "Value": "A" + }, + "Directives": [] + } + }, + "Directives": {}, + "QueryType": null, + "MutationType": null, + "SubscriptionType": null + }, + "IsRootType": false + } + ] +] diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/InputObjectTypeMergeHandlerTests.Merge_DifferentTypes_InputMergesLeftoversArePassed.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/InputObjectTypeMergeHandlerTests.Merge_DifferentTypes_InputMergesLeftoversArePassed.snap new file mode 100644 index 00000000000..c4078c96074 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/InputObjectTypeMergeHandlerTests.Merge_DifferentTypes_InputMergesLeftoversArePassed.snap @@ -0,0 +1,224 @@ +[ + "input A @source(name: \"A\", schema: \"Schema_A\") {\n b: String\n}", + [ + { + "Definition": { + "Kind": "EnumTypeDefinition", + "Description": null, + "Values": [ + { + "Kind": "EnumValueDefinition", + "Description": null, + "Location": { + "Start": 9, + "End": 12, + "Line": 1, + "Column": 10 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 9, + "End": 12, + "Line": 1, + "Column": 10 + }, + "Value": "B" + }, + "Directives": [] + }, + { + "Kind": "EnumValueDefinition", + "Description": null, + "Location": { + "Start": 11, + "End": 14, + "Line": 1, + "Column": 12 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 11, + "End": 14, + "Line": 1, + "Column": 12 + }, + "Value": "C" + }, + "Directives": [] + } + ], + "Location": { + "Start": 0, + "End": 14, + "Line": 1, + "Column": 1 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 5, + "End": 8, + "Line": 1, + "Column": 6 + }, + "Value": "A" + }, + "Directives": [] + }, + "Schema": { + "Name": "Schema_B", + "Document": { + "Kind": "Document", + "Location": { + "Start": 0, + "End": 14, + "Line": 1, + "Column": 1 + }, + "Definitions": [ + { + "Kind": "EnumTypeDefinition", + "Description": null, + "Values": [ + { + "Kind": "EnumValueDefinition", + "Description": null, + "Location": { + "Start": 9, + "End": 12, + "Line": 1, + "Column": 10 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 9, + "End": 12, + "Line": 1, + "Column": 10 + }, + "Value": "B" + }, + "Directives": [] + }, + { + "Kind": "EnumValueDefinition", + "Description": null, + "Location": { + "Start": 11, + "End": 14, + "Line": 1, + "Column": 12 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 11, + "End": 14, + "Line": 1, + "Column": 12 + }, + "Value": "C" + }, + "Directives": [] + } + ], + "Location": { + "Start": 0, + "End": 14, + "Line": 1, + "Column": 1 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 5, + "End": 8, + "Line": 1, + "Column": 6 + }, + "Value": "A" + }, + "Directives": [] + } + ], + "Count": 7 + }, + "Types": { + "A": { + "Kind": "EnumTypeDefinition", + "Description": null, + "Values": [ + { + "Kind": "EnumValueDefinition", + "Description": null, + "Location": { + "Start": 9, + "End": 12, + "Line": 1, + "Column": 10 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 9, + "End": 12, + "Line": 1, + "Column": 10 + }, + "Value": "B" + }, + "Directives": [] + }, + { + "Kind": "EnumValueDefinition", + "Description": null, + "Location": { + "Start": 11, + "End": 14, + "Line": 1, + "Column": 12 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 11, + "End": 14, + "Line": 1, + "Column": 12 + }, + "Value": "C" + }, + "Directives": [] + } + ], + "Location": { + "Start": 0, + "End": 14, + "Line": 1, + "Column": 1 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 5, + "End": 8, + "Line": 1, + "Column": 6 + }, + "Value": "A" + }, + "Directives": [] + } + }, + "Directives": {}, + "QueryType": null, + "MutationType": null, + "SubscriptionType": null + }, + "IsRootType": false + } + ] +] diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/InputObjectTypeMergeHandlerTests.Merge_SimpleIdenticalInputs_TypeMerges.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/InputObjectTypeMergeHandlerTests.Merge_SimpleIdenticalInputs_TypeMerges.snap new file mode 100644 index 00000000000..f2208cfecbb --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/InputObjectTypeMergeHandlerTests.Merge_SimpleIdenticalInputs_TypeMerges.snap @@ -0,0 +1,3 @@ +input A @source(name: "A", schema: "Schema_A") @source(name: "A", schema: "Schema_B") { + b: String +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/InputObjectTypeMergeHandlerTests.Merge_ThreeInputsWhereTwoAreIdentical_TwoTypesAfterMerge.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/InputObjectTypeMergeHandlerTests.Merge_ThreeInputsWhereTwoAreIdentical_TwoTypesAfterMerge.snap new file mode 100644 index 00000000000..e96928b5f2f --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/InputObjectTypeMergeHandlerTests.Merge_ThreeInputsWhereTwoAreIdentical_TwoTypesAfterMerge.snap @@ -0,0 +1,8 @@ +input A @source(name: "A", schema: "Schema_A") @source(name: "A", schema: "Schema_C") { + b: String +} + +input Schema_B_A @source(name: "A", schema: "Schema_B") { + b: String + c: String +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/InterfaceTypeMergeHandlerTests.Merge_DifferentTypes_InterfMergesLeftoversArePassed.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/InterfaceTypeMergeHandlerTests.Merge_DifferentTypes_InterfMergesLeftoversArePassed.snap new file mode 100644 index 00000000000..6b0ab56cc69 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/InterfaceTypeMergeHandlerTests.Merge_DifferentTypes_InterfMergesLeftoversArePassed.snap @@ -0,0 +1,224 @@ +[ + "interface A @source(name: \"A\", schema: \"Schema_A\") {\n b: String\n}", + [ + { + "Definition": { + "Kind": "EnumTypeDefinition", + "Description": null, + "Values": [ + { + "Kind": "EnumValueDefinition", + "Description": null, + "Location": { + "Start": 9, + "End": 12, + "Line": 1, + "Column": 10 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 9, + "End": 12, + "Line": 1, + "Column": 10 + }, + "Value": "B" + }, + "Directives": [] + }, + { + "Kind": "EnumValueDefinition", + "Description": null, + "Location": { + "Start": 11, + "End": 14, + "Line": 1, + "Column": 12 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 11, + "End": 14, + "Line": 1, + "Column": 12 + }, + "Value": "C" + }, + "Directives": [] + } + ], + "Location": { + "Start": 0, + "End": 14, + "Line": 1, + "Column": 1 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 5, + "End": 8, + "Line": 1, + "Column": 6 + }, + "Value": "A" + }, + "Directives": [] + }, + "Schema": { + "Name": "Schema_B", + "Document": { + "Kind": "Document", + "Location": { + "Start": 0, + "End": 14, + "Line": 1, + "Column": 1 + }, + "Definitions": [ + { + "Kind": "EnumTypeDefinition", + "Description": null, + "Values": [ + { + "Kind": "EnumValueDefinition", + "Description": null, + "Location": { + "Start": 9, + "End": 12, + "Line": 1, + "Column": 10 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 9, + "End": 12, + "Line": 1, + "Column": 10 + }, + "Value": "B" + }, + "Directives": [] + }, + { + "Kind": "EnumValueDefinition", + "Description": null, + "Location": { + "Start": 11, + "End": 14, + "Line": 1, + "Column": 12 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 11, + "End": 14, + "Line": 1, + "Column": 12 + }, + "Value": "C" + }, + "Directives": [] + } + ], + "Location": { + "Start": 0, + "End": 14, + "Line": 1, + "Column": 1 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 5, + "End": 8, + "Line": 1, + "Column": 6 + }, + "Value": "A" + }, + "Directives": [] + } + ], + "Count": 7 + }, + "Types": { + "A": { + "Kind": "EnumTypeDefinition", + "Description": null, + "Values": [ + { + "Kind": "EnumValueDefinition", + "Description": null, + "Location": { + "Start": 9, + "End": 12, + "Line": 1, + "Column": 10 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 9, + "End": 12, + "Line": 1, + "Column": 10 + }, + "Value": "B" + }, + "Directives": [] + }, + { + "Kind": "EnumValueDefinition", + "Description": null, + "Location": { + "Start": 11, + "End": 14, + "Line": 1, + "Column": 12 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 11, + "End": 14, + "Line": 1, + "Column": 12 + }, + "Value": "C" + }, + "Directives": [] + } + ], + "Location": { + "Start": 0, + "End": 14, + "Line": 1, + "Column": 1 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 5, + "End": 8, + "Line": 1, + "Column": 6 + }, + "Value": "A" + }, + "Directives": [] + } + }, + "Directives": {}, + "QueryType": null, + "MutationType": null, + "SubscriptionType": null + }, + "IsRootType": false + } + ] +] diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/InterfaceTypeMergeHandlerTests.Merge_SimpleIdenticalInterfaces_TypeMerges.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/InterfaceTypeMergeHandlerTests.Merge_SimpleIdenticalInterfaces_TypeMerges.snap new file mode 100644 index 00000000000..520a1e34d68 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/InterfaceTypeMergeHandlerTests.Merge_SimpleIdenticalInterfaces_TypeMerges.snap @@ -0,0 +1,3 @@ +interface A @source(name: "A", schema: "Schema_A") @source(name: "A", schema: "Schema_B") { + b: String +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/InterfaceTypeMergeHandlerTests.Merge_ThreeInterfWhereTwoAreIdentical_TwoTypesAfterMerge.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/InterfaceTypeMergeHandlerTests.Merge_ThreeInterfWhereTwoAreIdentical_TwoTypesAfterMerge.snap new file mode 100644 index 00000000000..b1285c2de93 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/InterfaceTypeMergeHandlerTests.Merge_ThreeInterfWhereTwoAreIdentical_TwoTypesAfterMerge.snap @@ -0,0 +1,7 @@ +interface A @source(name: "A", schema: "Schema_A") @source(name: "A", schema: "Schema_C") { + b: String +} + +interface Schema_B_A @source(name: "A", schema: "Schema_B") { + b(a: String): String +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/ObjectTypeMergeHandlerTests.Merge_DifferentTypes_ObjectMergesLeftoversArePassed.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/ObjectTypeMergeHandlerTests.Merge_DifferentTypes_ObjectMergesLeftoversArePassed.snap new file mode 100644 index 00000000000..5ffb92049a5 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/ObjectTypeMergeHandlerTests.Merge_DifferentTypes_ObjectMergesLeftoversArePassed.snap @@ -0,0 +1,224 @@ +[ + "type A @source(name: \"A\", schema: \"Schema_A\") {\n b: String\n}", + [ + { + "Definition": { + "Kind": "EnumTypeDefinition", + "Description": null, + "Values": [ + { + "Kind": "EnumValueDefinition", + "Description": null, + "Location": { + "Start": 9, + "End": 12, + "Line": 1, + "Column": 10 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 9, + "End": 12, + "Line": 1, + "Column": 10 + }, + "Value": "B" + }, + "Directives": [] + }, + { + "Kind": "EnumValueDefinition", + "Description": null, + "Location": { + "Start": 11, + "End": 14, + "Line": 1, + "Column": 12 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 11, + "End": 14, + "Line": 1, + "Column": 12 + }, + "Value": "C" + }, + "Directives": [] + } + ], + "Location": { + "Start": 0, + "End": 14, + "Line": 1, + "Column": 1 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 5, + "End": 8, + "Line": 1, + "Column": 6 + }, + "Value": "A" + }, + "Directives": [] + }, + "Schema": { + "Name": "Schema_B", + "Document": { + "Kind": "Document", + "Location": { + "Start": 0, + "End": 14, + "Line": 1, + "Column": 1 + }, + "Definitions": [ + { + "Kind": "EnumTypeDefinition", + "Description": null, + "Values": [ + { + "Kind": "EnumValueDefinition", + "Description": null, + "Location": { + "Start": 9, + "End": 12, + "Line": 1, + "Column": 10 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 9, + "End": 12, + "Line": 1, + "Column": 10 + }, + "Value": "B" + }, + "Directives": [] + }, + { + "Kind": "EnumValueDefinition", + "Description": null, + "Location": { + "Start": 11, + "End": 14, + "Line": 1, + "Column": 12 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 11, + "End": 14, + "Line": 1, + "Column": 12 + }, + "Value": "C" + }, + "Directives": [] + } + ], + "Location": { + "Start": 0, + "End": 14, + "Line": 1, + "Column": 1 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 5, + "End": 8, + "Line": 1, + "Column": 6 + }, + "Value": "A" + }, + "Directives": [] + } + ], + "Count": 7 + }, + "Types": { + "A": { + "Kind": "EnumTypeDefinition", + "Description": null, + "Values": [ + { + "Kind": "EnumValueDefinition", + "Description": null, + "Location": { + "Start": 9, + "End": 12, + "Line": 1, + "Column": 10 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 9, + "End": 12, + "Line": 1, + "Column": 10 + }, + "Value": "B" + }, + "Directives": [] + }, + { + "Kind": "EnumValueDefinition", + "Description": null, + "Location": { + "Start": 11, + "End": 14, + "Line": 1, + "Column": 12 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 11, + "End": 14, + "Line": 1, + "Column": 12 + }, + "Value": "C" + }, + "Directives": [] + } + ], + "Location": { + "Start": 0, + "End": 14, + "Line": 1, + "Column": 1 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 5, + "End": 8, + "Line": 1, + "Column": 6 + }, + "Value": "A" + }, + "Directives": [] + } + }, + "Directives": {}, + "QueryType": null, + "MutationType": null, + "SubscriptionType": null + }, + "IsRootType": false + } + ] +] diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/ObjectTypeMergeHandlerTests.Merge_ObjectWithDifferentInterfaces_TypesMerge.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/ObjectTypeMergeHandlerTests.Merge_ObjectWithDifferentInterfaces_TypesMerge.snap new file mode 100644 index 00000000000..c4f8bc34031 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/ObjectTypeMergeHandlerTests.Merge_ObjectWithDifferentInterfaces_TypesMerge.snap @@ -0,0 +1,3 @@ +type A implements IA & IB & IC @source(name: "A", schema: "Schema_A") @source(name: "A", schema: "Schema_B") @source(name: "A", schema: "Schema_C") { + b: String +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/ObjectTypeMergeHandlerTests.Merge_SimpleIdenticalObjects_TypeMerges.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/ObjectTypeMergeHandlerTests.Merge_SimpleIdenticalObjects_TypeMerges.snap new file mode 100644 index 00000000000..200b319d233 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/ObjectTypeMergeHandlerTests.Merge_SimpleIdenticalObjects_TypeMerges.snap @@ -0,0 +1,3 @@ +type A @source(name: "A", schema: "Schema_A") @source(name: "A", schema: "Schema_B") { + b: String +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/ObjectTypeMergeHandlerTests.Merge_ThreeObjectsWhereTwoAreIdentical_TwoTypesAfterMerge.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/ObjectTypeMergeHandlerTests.Merge_ThreeObjectsWhereTwoAreIdentical_TwoTypesAfterMerge.snap new file mode 100644 index 00000000000..67a6eab0d39 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/ObjectTypeMergeHandlerTests.Merge_ThreeObjectsWhereTwoAreIdentical_TwoTypesAfterMerge.snap @@ -0,0 +1,7 @@ +type A @source(name: "A", schema: "Schema_A") @source(name: "A", schema: "Schema_C") { + b: String +} + +type Schema_B_A @source(name: "A", schema: "Schema_B") { + b(a: String): String +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/RootTypeMergeHandlerTests.Merge_RootTypeWithCollisions_CollidingFieldsAreRenamed.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/RootTypeMergeHandlerTests.Merge_RootTypeWithCollisions_CollidingFieldsAreRenamed.snap new file mode 100644 index 00000000000..ec1f0fc130a --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/RootTypeMergeHandlerTests.Merge_RootTypeWithCollisions_CollidingFieldsAreRenamed.snap @@ -0,0 +1,4 @@ +type Query { + a: String @delegate(schema: "Schema_A") + Schema_B_a: String @delegate(schema: "Schema_B", path: "a") +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/RootTypeMergeHandlerTests.Merge_RootTypeWithNoCollisions_TypeMerges.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/RootTypeMergeHandlerTests.Merge_RootTypeWithNoCollisions_TypeMerges.snap new file mode 100644 index 00000000000..0e55a5da09a --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/RootTypeMergeHandlerTests.Merge_RootTypeWithNoCollisions_TypeMerges.snap @@ -0,0 +1,4 @@ +type Query { + a: String @delegate(schema: "Schema_A") + b: String @delegate(schema: "Schema_B") +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/UnionTypeMergeHandlerTests.MergeUnionTypes.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/UnionTypeMergeHandlerTests.MergeUnionTypes.snap new file mode 100644 index 00000000000..b0372a688a9 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/Handlers/__snapshots__/UnionTypeMergeHandlerTests.MergeUnionTypes.snap @@ -0,0 +1,3 @@ +union Foo @source(name: "Foo", schema: "Schema_A") = Bar | Baz + +union Schema_B_Foo @source(name: "Foo", schema: "Schema_B") = Bar | Baz diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/MergeSyntaxNodeExtensionsTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/MergeSyntaxNodeExtensionsTests.cs new file mode 100644 index 00000000000..7527dfa6418 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/MergeSyntaxNodeExtensionsTests.cs @@ -0,0 +1,372 @@ +using System; +using HotChocolate.Language; +using HotChocolate.Language.Utilities; +using Snapshooter.Xunit; +using Xunit; + +namespace HotChocolate.Stitching.Merge; + +public class MergeSyntaxNodeExtensionsTests +{ + [Fact] + public void AddDelegationPath_SingleComponent() + { + // arrange + var fieldNode = new FieldDefinitionNode( + null, + new NameNode("foo"), + null, + Array.Empty(), + new NamedTypeNode(new NameNode("Type")), + Array.Empty()); + + // act + var path = new SelectionPathComponent( + new NameNode("bar"), + new[] + { + new ArgumentNode("baz", + new ScopedVariableNode( + null, + new NameNode("qux"), + new NameNode("quux"))) + }); + + fieldNode = fieldNode.AddDelegationPath("schemName", path); + + // assert + var schema = new DocumentNode(new[] + { + new ObjectTypeDefinitionNode + ( + null, + new NameNode("Object"), + null, + Array.Empty(), + Array.Empty(), + new[] { fieldNode } + ) + }); + schema.Print().MatchSnapshot(); + } + + [Fact] + public void AddDelegationPath_SingleComponent_TwoArgs() + { + // arrange + var fieldNode = new FieldDefinitionNode( + null, + new NameNode("foo"), + null, + Array.Empty(), + new NamedTypeNode(new NameNode("Type")), + Array.Empty()); + + // act + var path = new SelectionPathComponent( + new NameNode("bar"), + new[] + { + new ArgumentNode("baz", + new ScopedVariableNode( + null, + new NameNode("qux"), + new NameNode("quux"))), + new ArgumentNode("value_arg", "value") + }); + + fieldNode = fieldNode.AddDelegationPath("schemName", path); + + // assert + var schema = new DocumentNode(new[] + { + new ObjectTypeDefinitionNode + ( + null, + new NameNode("Object"), + null, + Array.Empty(), + Array.Empty(), + new[] { fieldNode } + ) + }); + schema.Print().MatchSnapshot(); + } + + [Fact] + public void AddDelegationPath_SingleComponent_SchemNameIsEmpty() + { + // arrange + var fieldNode = new FieldDefinitionNode( + null, + new NameNode("foo"), + null, + Array.Empty(), + new NamedTypeNode(new NameNode("Type")), + Array.Empty()); + + // act + var path = new SelectionPathComponent( + new NameNode("bar"), + new[] + { + new ArgumentNode("baz", + new ScopedVariableNode( + null, + new NameNode("qux"), + new NameNode("quux"))) + }); + + void Action() => fieldNode.AddDelegationPath(default, path); + + // assert + Assert.Throws(Action); + } + + [Fact] + public void AddDelegationPath_SingleComponent_PathIsNull() + { + // arrange + var fieldNode = new FieldDefinitionNode( + null, + new NameNode("foo"), + null, + Array.Empty(), + new NamedTypeNode(new NameNode("Type")), + Array.Empty()); + + // act + void Action() => fieldNode.AddDelegationPath("Schema", ((SelectionPathComponent)null)!); + + // assert + Assert.Throws(Action); + } + + [Fact] + public void AddDelegationPath_MultipleComponents() + { + // arrange + var fieldNode = new FieldDefinitionNode( + null, + new NameNode("foo"), + null, + Array.Empty(), + new NamedTypeNode(new NameNode("Type")), + Array.Empty()); + + // act + var a = new SelectionPathComponent( + new NameNode("bar"), + new[] + { + new ArgumentNode("baz", + new ScopedVariableNode( + null, + new NameNode("qux"), + new NameNode("quux"))) + }); + + var b = new SelectionPathComponent( + new NameNode("bar2"), + new[] + { + new ArgumentNode("baz2", + new ScopedVariableNode( + null, + new NameNode("qux2"), + new NameNode("quux2"))) + }); + + fieldNode = fieldNode.AddDelegationPath( + "schemName", new[] { a, b }); + + // assert + var schema = new DocumentNode(new[] + { + new ObjectTypeDefinitionNode + ( + null, + new NameNode("Object"), + null, + Array.Empty(), + Array.Empty(), + new[] { fieldNode } + ) + }); + schema.Print().MatchSnapshot(); + } + + [Fact] + public void AddDelegationPath_MultipleComponents_SingleComponent() + { + // arrange + var fieldNode = new FieldDefinitionNode( + null, + new NameNode("foo"), + null, + Array.Empty(), + new NamedTypeNode(new NameNode("Type")), + Array.Empty()); + + // act + var path = new SelectionPathComponent( + new NameNode("bar"), + new[] + { + new ArgumentNode("baz", + new ScopedVariableNode( + null, + new NameNode("qux"), + new NameNode("quux"))) + }); + + fieldNode = fieldNode.AddDelegationPath( + "schemName", new[] { path }); + + // assert + var schema = new DocumentNode(new[] + { + new ObjectTypeDefinitionNode + ( + null, + new NameNode("Object"), + null, + Array.Empty(), + Array.Empty(), + new[] { fieldNode } + ) + }); + schema.Print().MatchSnapshot(); + } + + [Fact] + public void AddDelegationPath_MultipleComponents_EmptyPath() + { + // arrange + var fieldNode = new FieldDefinitionNode( + null, + new NameNode("foo"), + null, + Array.Empty(), + new NamedTypeNode(new NameNode("Type")), + Array.Empty()); + + // act + fieldNode = fieldNode.AddDelegationPath( + "schemName", Array.Empty()); + + // assert + var schema = new DocumentNode(new[] + { + new ObjectTypeDefinitionNode + ( + null, + new NameNode("Object"), + null, + Array.Empty(), + Array.Empty(), + new[] { fieldNode } + ) + }); + schema.Print().MatchSnapshot(); + } + + [Fact] + public void AddDelegationPath_MultipleComponents_SchemNameIsEmpty() + { + // arrange + var fieldNode = new FieldDefinitionNode( + null, + new NameNode("foo"), + null, + Array.Empty(), + new NamedTypeNode(new NameNode("Type")), + Array.Empty()); + + // act + var path = new SelectionPathComponent( + new NameNode("bar"), + new[] + { + new ArgumentNode("baz", + new ScopedVariableNode( + null, + new NameNode("qux"), + new NameNode("quux"))) + }); + + void Action() => fieldNode.AddDelegationPath(default, new[] {path}); + + // assert + Assert.Throws(Action); + } + + [Fact] + public void AddDelegationPath_MultipleComponents_PathIsNull() + { + // arrange + var fieldNode = new FieldDefinitionNode( + null, + new NameNode("foo"), + null, + Array.Empty(), + new NamedTypeNode(new NameNode("Type")), + Array.Empty()); + + // act + void Action() => + fieldNode.AddDelegationPath("Schema", ((SelectionPathComponent[])null)!); + + // assert + Assert.Throws(Action); + } + + [Fact] + public void AddDelegationPath_Overwrite() + { + // arrange + var fieldNode = new FieldDefinitionNode( + null, + new NameNode("foo"), + null, + Array.Empty(), + new NamedTypeNode(new NameNode("Type")), + Array.Empty()); + + var path = new SelectionPathComponent( + new NameNode("bar"), + new[] + { + new ArgumentNode("baz", + new ScopedVariableNode( + null, + new NameNode("qux"), + new NameNode("quux"))) + }); + + fieldNode = fieldNode.AddDelegationPath("schemName", path); + Assert.Collection(fieldNode.Directives, + d => Assert.Equal("delegate", d.Name.Value)); + + // act + fieldNode = fieldNode.AddDelegationPath( + "schemaName", + "$ContextData:fooBar", + true); + + // assert + var schema = new DocumentNode(new[] + { + new ObjectTypeDefinitionNode + ( + null, + new NameNode("Object"), + null, + Array.Empty(), + Array.Empty(), + new[] { fieldNode } + ) + }); + schema.Print().MatchSnapshot(); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/SchemaInfoTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/SchemaInfoTests.cs new file mode 100644 index 00000000000..2fd8c4fe06e --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/SchemaInfoTests.cs @@ -0,0 +1,91 @@ +using ChilliCream.Testing; +using HotChocolate.Language; +using HotChocolate.Stitching.Merge; +using Xunit; + +namespace HotChocolate.Stitching; + +public class SchemaInfoTests +{ + [Fact] + public void ResolveRootTypesWithCustomNames() + { + // arrange + const string schemaName = "foo"; + var schema = Utf8GraphQLParser.Parse( + FileResource.Open("SchemaInfoTests_Schema.graphql")); + + // act + var schemaInfo = new SchemaInfo(schemaName, schema); + + // assert + Assert.NotNull(schemaInfo.QueryType); + Assert.Equal("CustomQuery", + schemaInfo.QueryType.Name.Value); + + Assert.NotNull(schemaInfo.MutationType); + Assert.Equal("CustomMutation", + schemaInfo.MutationType.Name.Value); + + Assert.NotNull(schemaInfo.SubscriptionType); + Assert.Equal("CustomSubscription", + schemaInfo.SubscriptionType.Name.Value); + } + + [Fact] + public void GetOperationTypeByRootType_Query() + { + // arrange + const string schemaName = "foo"; + var schema = Utf8GraphQLParser.Parse( + FileResource.Open("SchemaInfoTests_Schema.graphql")); + var schemaInfo = new SchemaInfo(schemaName, schema); + + // act + var success = schemaInfo.TryGetOperationType( + schemaInfo.QueryType, + out var operationType); + + // assert + Assert.True(success); + Assert.Equal(OperationType.Query, operationType); + } + + [Fact] + public void GetOperationTypeByRootType_Mutation() + { + // arrange + const string schemaName = "foo"; + var schema = Utf8GraphQLParser.Parse( + FileResource.Open("SchemaInfoTests_Schema.graphql")); + var schemaInfo = new SchemaInfo(schemaName, schema); + + // act + var success = schemaInfo.TryGetOperationType( + schemaInfo.MutationType, + out var operationType); + + // assert + Assert.True(success); + Assert.Equal(OperationType.Mutation, operationType); + } + + [Fact] + public void GetOperationTypeByRootType_Subscription() + { + // arrange + const string schemaName = "foo"; + var schema = Utf8GraphQLParser.Parse( + FileResource.Open("SchemaInfoTests_Schema.graphql")); + var schemaInfo = new SchemaInfo(schemaName, schema); + + // act + var success = schemaInfo.TryGetOperationType( + schemaInfo.SubscriptionType, + out var operationType); + + // assert + Assert.True(success); + Assert.Equal(OperationType.Subscription, operationType); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/SchemaMergerTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/SchemaMergerTests.cs new file mode 100644 index 00000000000..221e9404c6a --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/SchemaMergerTests.cs @@ -0,0 +1,594 @@ +using System.Collections.Generic; +using System.Linq; +using ChilliCream.Testing; +using HotChocolate.Language; +using HotChocolate.Language.Utilities; +using HotChocolate.Resolvers; +using Snapshooter; +using Snapshooter.Xunit; +using Xunit; + +namespace HotChocolate.Stitching.Merge; + +public class SchemaMergerTests +{ + [Fact] + public void MergeSimpleSchemaWithDefaultHandler() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse("union Foo = Bar | Baz union A = B | C"); + var schema_b = + Utf8GraphQLParser.Parse("union Foo = Bar | Baz"); + + // act + var schema = SchemaMerger.New() + .AddSchema("A", schema_a) + .AddSchema("B", schema_b) + .Merge(); + + // assert + schema.Print().MatchSnapshot(); + } + + [Fact] + public void MergeDemoSchemaWithDefaultHandler() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse( + FileResource.Open("Contract.graphql")); + var schema_b = + Utf8GraphQLParser.Parse( + FileResource.Open("Customer.graphql")); + + // act + var schema = SchemaMerger.New() + .AddSchema("A", schema_a) + .AddSchema("B", schema_b) + .Merge(); + + // assert + schema.Print().MatchSnapshot(); + } + + [Fact] + public void MergeDemoSchemaAndRemoveRootTypes() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse( + FileResource.Open("Contract.graphql")); + var schema_b = + Utf8GraphQLParser.Parse( + FileResource.Open("Customer.graphql")); + + // act + var schema = SchemaMerger.New() + .AddSchema("A", schema_a) + .AddSchema("B", schema_b) + .IgnoreRootTypes() + .Merge(); + + // assert + schema.Print().MatchSnapshot(); + } + + [Fact] + public void MergeDemoSchemaAndRemoveRootTypesOnSchemaA() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse( + FileResource.Open("Contract.graphql")); + var schema_b = + Utf8GraphQLParser.Parse( + FileResource.Open("Customer.graphql")); + + // act + var schema = SchemaMerger.New() + .AddSchema("A", schema_a) + .AddSchema("B", schema_b) + .IgnoreRootTypes("A") + .Merge(); + + // assert + schema.Print().MatchSnapshot(); + } + + [Fact] + public void MergeSchemaAndRenameTypeAtoXyzOnAllSchemas() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse( + "type A { b1: String b2: String } type B { c: String }"); + var schema_b = + Utf8GraphQLParser.Parse( + "type A { b1: String b3: String } type B { c: String }"); + + // act + var schema = SchemaMerger.New() + .AddSchema("A", schema_a) + .AddSchema("B", schema_b) + .RenameType("A", "Xyz") + .Merge(); + + // assert + schema.Print().MatchSnapshot(); + } + + + [Fact] + public void MergeSchemaAndRenameTypeAtoXyzOnSchemaA() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse( + "type A { b1: String b2: String } type B { c: String }"); + var schema_b = + Utf8GraphQLParser.Parse( + "type A { b1: String b3: String } type B { c: String }"); + + // act + var schema = SchemaMerger.New() + .AddSchema("A", schema_a) + .AddSchema("B", schema_b) + .RenameType("A", "Xyz", "A") + .Merge(); + + // assert + schema.Print().MatchSnapshot(); + } + + [Fact] + public void MergeSchemaAndRemoveTypeAOnAllSchemas() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse( + "type A { b1: String } type B { c: String }"); + var schema_b = + Utf8GraphQLParser.Parse( + "type A { b2: String } type B { c: String }"); + + // act + var schema = SchemaMerger.New() + .AddSchema("A", schema_a) + .AddSchema("B", schema_b) + .IgnoreType("A") + .Merge(); + + // assert + schema.Print().MatchSnapshot(); + } + + + [Fact] + public void MergeSchemaAndRemoveTypeAOnSchemaA() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse( + "type A { b1: String } type B { c: String }"); + var schema_b = + Utf8GraphQLParser.Parse( + "type A { b2: String } type B { c: String }"); + + // act + var schema = SchemaMerger.New() + .AddSchema("A", schema_a) + .AddSchema("B", schema_b) + .IgnoreType("A", "A") + .Merge(); + + // assert + schema.Print().MatchSnapshot(); + } + + [Fact] + public void MergeSchemaAndRemoveFieldB1OnAllSchemas() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse( + "type A { b1: String b2: String } type B { c: String }"); + var schema_b = + Utf8GraphQLParser.Parse( + "type A { b1: String b3: String } type B { c: String }"); + + // act + var schema = SchemaMerger.New() + .AddSchema("A", schema_a) + .AddSchema("B", schema_b) + .IgnoreField(new FieldReference("A", "b1")) + .Merge(); + + // assert + schema.Print().MatchSnapshot(); + } + + + [Fact] + public void MergeSchemaAndRemoveFieldB1OnSchemaA() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse( + "type A { b1: String b2: String } type B { c: String }"); + var schema_b = + Utf8GraphQLParser.Parse( + "type A { b1: String b3: String } type B { c: String }"); + + // act + var schema = SchemaMerger.New() + .AddSchema("A", schema_a) + .AddSchema("B", schema_b) + .IgnoreField(new FieldReference("A", "b1"), "A") + .Merge(); + + // assert + schema.Print().MatchSnapshot(); + } + + [Fact] + public void MergeSchemaAndRenameFieldB1toB11OnAllSchemas() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse( + "type A { b1: String b2: String } type B { c: String }"); + var schema_b = + Utf8GraphQLParser.Parse( + "type A { b1: String b3: String } type B { c: String }"); + + // act + var schema = SchemaMerger.New() + .AddSchema("A", schema_a) + .AddSchema("B", schema_b) + .RenameField(new FieldReference("A", "b1"), "b11") + .Merge(); + + // assert + schema.Print().MatchSnapshot(); + } + + + [Fact] + public void MergeSchemaAndRenameFieldB1toB11OnSchemaA() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse( + "type A { b1: String b2: String } type B { c: String }"); + var schema_b = + Utf8GraphQLParser.Parse( + "type A { b1: String b3: String } type B { c: String }"); + + // act + var schema = SchemaMerger.New() + .AddSchema("A", schema_a) + .AddSchema("B", schema_b) + .RenameField(new FieldReference("A", "b1"), "b11", "A") + .Merge(); + + // assert + schema.Print().MatchSnapshot(); + } + + [Fact(Skip = "Fix It")] + public void RenameReferencingType() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse( + "type A { b1: B } " + + "type B implements C { c: String } " + + "interface C { c: String }"); + + var schema_b = + Utf8GraphQLParser.Parse( + "type B { b1: String b3: String } type C { c: String }"); + + // act + var a = SchemaMerger.New() + .AddSchema("A", schema_a) + .AddSchema("B", schema_b) + .RenameType("A", "B", "Foo") + .Merge(); + + var b = SchemaMerger.New() + .AddSchema("B", schema_b) + .AddSchema("A", schema_a) + .RenameType("A", "B", "Foo") + .Merge(); + + // assert + a.Print().MatchSnapshot(SnapshotNameExtension.Create("A")); + b.Print().MatchSnapshot(SnapshotNameExtension.Create("B")); + } + + [Fact] + public void Rename_Type_With_Various_Variants() + { + // arrange + var initial = + Utf8GraphQLParser.Parse( + "type A { b1: B! b2: [B!] b3: [B] b4: [B!]! } " + + "type B implements C { c: String } " + + "interface C { c: String }"); + + // act + var merged = SchemaMerger.New() + .AddSchema("A", initial) + .RenameType("B", "Foo", "A") + .Merge(); + + // assert + merged.Print().MatchSnapshot(); + } + + [Fact] + public void FieldDefinitionDoesNotHaveSameTypeShape() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse( + "type A { b1: String b2: String } type B { c: String! }"); + var schema_b = + Utf8GraphQLParser.Parse( + "type A { b1: String b3: String } type B { c: String }"); + + // act + var merged = SchemaMerger.New() + .AddSchema("A", schema_a) + .AddSchema("B", schema_b) + .Merge(); + + // assert + merged.Print().MatchSnapshot(); + } + + [Fact] + public void RenameObjectFieldThatImplementsInterface() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse( + "type A { b1: B } " + + "type B implements D { c: String } " + + "type C implements D { c: String } " + + "interface D { c: String }"); + + var schema_b = + Utf8GraphQLParser.Parse( + "type B { b1: String b3: String } type C { c: String }"); + + // act + var a = SchemaMerger.New() + .AddSchema("A", schema_a) + .AddSchema("B", schema_b) + .RenameField(new FieldReference("B", "c"), "c123", "A") + .Merge(); + + // assert + a.Print().MatchSnapshot(); + } + + [Fact] + public void RenameObjectField() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse( + "type A { b1: B } " + + "type B { c: String } " + + "type C implements D { c: String } " + + "interface D { c: String }"); + + var schema_b = + Utf8GraphQLParser.Parse( + "type B { b1: String b3: String } type C { c: String }"); + + // act + var a = SchemaMerger.New() + .AddSchema("A", schema_a) + .AddSchema("B", schema_b) + .RenameField(new FieldReference("B", "c"), "c123", "A") + .Merge(); + + // assert + a.Print().MatchSnapshot(); + } + + [Fact] + public void RenameInterfaceField() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse( + "type A { b1: B } " + + "type B implements D { c: String } " + + "type C implements D { c: String } " + + "interface D { c: String }"); + + var schema_b = + Utf8GraphQLParser.Parse( + "type B { b1: String b3: String } type C { c: String }"); + + // act + var a = SchemaMerger.New() + .AddSchema("A", schema_a) + .AddSchema("B", schema_b) + .RenameField(new FieldReference("D", "c"), "c123", "A") + .Merge(); + + // assert + a.Print().MatchSnapshot(); + } + + [Fact] + public void LastFieldRenameWins() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse( + "type A { b1: B } " + + "type B implements D { c: String } " + + "type C implements D { c: String } " + + "interface D { c: String }"); + + var schema_b = + Utf8GraphQLParser.Parse( + "type B { b1: String b3: String } type C { c: String }"); + + // act + var a = SchemaMerger.New() + .AddSchema("A", schema_a) + .AddSchema("B", schema_b) + .RenameField(new FieldReference("B", "c"), "c123", "A") + .RenameField(new FieldReference("C", "c"), "c456", "A") + .RenameField(new FieldReference("D", "c"), "c789", "A") + .Merge(); + + var b = SchemaMerger.New() + .AddSchema("A", schema_a) + .AddSchema("B", schema_b) + .RenameField(new FieldReference("B", "c"), "c123", "A") + .RenameField(new FieldReference("D", "c"), "c789", "A") + .RenameField(new FieldReference("C", "c"), "c456", "A") + .Merge(); + + // assert + a.Print().MatchSnapshot(SnapshotNameExtension.Create("A")); + b.Print().MatchSnapshot(SnapshotNameExtension.Create("B")); + } + + [Fact] + public void MergeDirectivesWithCustomRule() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse("directive @foo on FIELD"); + var schema_b = + Utf8GraphQLParser.Parse("directive @foo(a: String) on FIELD"); + + // act + var a = SchemaMerger.New() + .AddSchema("A", schema_a) + .AddSchema("B", schema_b) + .AddDirectiveMergeRule(next => (context, directives) => + { + context.AddDirective( + directives.First(t => + t.Definition.Arguments.Any()).Definition); + }) + .Merge(); + + // assert + a.Print().MatchSnapshot(); + } + + [Fact] + public void MergeDirectivesWithCustomHandler() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse("directive @foo on FIELD"); + var schema_b = + Utf8GraphQLParser.Parse("directive @foo(a: String) on FIELD"); + + // act + var a = SchemaMerger.New() + .AddSchema("A", schema_a) + .AddSchema("B", schema_b) + .AddDirectiveMergeHandler() + .Merge(); + + // assert + a.Print().MatchSnapshot(); + } + + [Fact] + public void MergeTypeWithCustomRule() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse("type Foo { a: String }"); + var schema_b = + Utf8GraphQLParser.Parse("type Foo { b: String }"); + + // act + var a = SchemaMerger.New() + .AddSchema("A", schema_a) + .AddSchema("B", schema_b) + .AddTypeMergeRule(next => (context, types) => + { + var typeInfos = types.OfType().ToArray(); + var fields = typeInfos[0].Definition.Fields.ToList(); + fields.AddRange(typeInfos[1].Definition.Fields); + context.AddType( + typeInfos[0].Definition.WithFields(fields)); + }) + .Merge(); + + // assert + a.Print().MatchSnapshot(); + } + + [Fact] + public void MergeTypeWithCustomHandler() + { + // arrange + var schema_a = + Utf8GraphQLParser.Parse("type Foo { a: String }"); + var schema_b = + Utf8GraphQLParser.Parse("type Foo { b: String }"); + + // act + var a = SchemaMerger.New() + .AddSchema("A", schema_a) + .AddSchema("B", schema_b) + .AddTypeMergeHandler() + .Merge(); + + // assert + a.Print().MatchSnapshot(); + } + + public class CustomDirectiveMergeHandler + : IDirectiveMergeHandler + { + public CustomDirectiveMergeHandler(MergeDirectiveRuleDelegate next) + { + } + + public void Merge( + ISchemaMergeContext context, + IReadOnlyList directives) + { + context.AddDirective( + directives.First(t => + t.Definition.Arguments.Any()).Definition); + } + } + + public class CustomTypeMergeHandler + : ITypeMergeHandler + { + public CustomTypeMergeHandler(MergeTypeRuleDelegate next) + { + } + + public void Merge( + ISchemaMergeContext context, + IReadOnlyList types) + { + var typeInfos = types.OfType().ToArray(); + var fields = typeInfos[0].Definition.Fields.ToList(); + fields.AddRange(typeInfos[1].Definition.Fields); + context.AddType( + typeInfos[0].Definition.WithFields(fields)); + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/TypeInfoExtensionsTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/TypeInfoExtensionsTests.cs new file mode 100644 index 00000000000..f2aa0abb83f --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/TypeInfoExtensionsTests.cs @@ -0,0 +1,116 @@ +using System.Linq; +using HotChocolate.Language; +using Xunit; + +namespace HotChocolate.Stitching.Merge; + +public class TypeInfoExtensionsTests +{ + [Fact] + public void IsQueryType_True() + { + // arrange + var schema = Utf8GraphQLParser.Parse( + "type Query { a: String } type Abc { a: String }"); + var schemaInfo = new SchemaInfo("foo", schema); + var queryType = schema.Definitions + .OfType().First(); + var type = new ObjectTypeInfo(queryType, schemaInfo); + + // act + var isQuery = type.IsQueryType(); + + // assert + Assert.True(isQuery); + } + + [Fact] + public void IsQueryType_False() + { + // arrange + var schema = Utf8GraphQLParser.Parse( + "type Query { a: String } type Abc { a: String }"); + var schemaInfo = new SchemaInfo("foo", schema); + var queryType = schema.Definitions + .OfType().Last(); + var type = new ObjectTypeInfo(queryType, schemaInfo); + + // act + var isQuery = type.IsQueryType(); + + // assert + Assert.False(isQuery); + } + + [Fact] + public void IsMutationType_True() + { + // arrange + var schema = Utf8GraphQLParser.Parse( + "type Mutation { a: String } type Abc { a: String }"); + var schemaInfo = new SchemaInfo("foo", schema); + var queryType = schema.Definitions + .OfType().First(); + var type = new ObjectTypeInfo(queryType, schemaInfo); + + // act + var isQuery = type.IsMutationType(); + + // assert + Assert.True(isQuery); + } + + [Fact] + public void IsMutationType_False() + { + // arrange + var schema = Utf8GraphQLParser.Parse( + "type Mutation { a: String } type Abc { a: String }"); + var schemaInfo = new SchemaInfo("foo", schema); + var queryType = schema.Definitions + .OfType().Last(); + var type = new ObjectTypeInfo(queryType, schemaInfo); + + // act + var isQuery = type.IsMutationType(); + + // assert + Assert.False(isQuery); + } + + [Fact] + public void IsSubscriptionType_True() + { + // arrange + var schema = Utf8GraphQLParser.Parse( + "type Subscription { a: String } type Abc { a: String }"); + var schemaInfo = new SchemaInfo("foo", schema); + var queryType = schema.Definitions + .OfType().First(); + var type = new ObjectTypeInfo(queryType, schemaInfo); + + // act + var isQuery = type.IsSubscriptionType(); + + // assert + Assert.True(isQuery); + } + + [Fact] + public void IsSubscriptionType_False() + { + // arrange + var schema = Utf8GraphQLParser.Parse( + "type Subscription { a: String } type Abc { a: String }"); + var schemaInfo = new SchemaInfo("foo", schema); + var queryType = schema.Definitions + .OfType().Last(); + var type = new ObjectTypeInfo(queryType, schemaInfo); + + // act + var isQuery = type.IsSubscriptionType(); + + // assert + Assert.False(isQuery); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.EnumType_AddDirectives.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.EnumType_AddDirectives.snap new file mode 100644 index 00000000000..798cc952123 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.EnumType_AddDirectives.snap @@ -0,0 +1,6 @@ +enum Foo @foo { + BAR + BAZ +} + +directive @foo on ENUM diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.EnumType_AddDuplicateDirectives.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.EnumType_AddDuplicateDirectives.snap new file mode 100644 index 00000000000..1852c730263 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.EnumType_AddDuplicateDirectives.snap @@ -0,0 +1 @@ +The directive `foo` is not marked as repeatable and can only be declared once. diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.EnumType_AddUndeclaredDirectives.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.EnumType_AddUndeclaredDirectives.snap new file mode 100644 index 00000000000..2fb2783d55b --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.EnumType_AddUndeclaredDirectives.snap @@ -0,0 +1 @@ +The directive `foo` was not specified in this schema and cannot be used. diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.EnumType_AddValue.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.EnumType_AddValue.snap new file mode 100644 index 00000000000..b47f0cfb633 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.EnumType_AddValue.snap @@ -0,0 +1,5 @@ +enum Foo { + BAR + BAZ + QUX +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.EnumType_TypeMismatch.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.EnumType_TypeMismatch.snap new file mode 100644 index 00000000000..0a53c36c21e --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.EnumType_TypeMismatch.snap @@ -0,0 +1 @@ +`Foo` is of type `EnumTypeDefinition` and cannot be extended with `InputObjectTypeExtension`. diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InputObjectType_AddDirectives.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InputObjectType_AddDirectives.snap new file mode 100644 index 00000000000..5990fa8827c --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InputObjectType_AddDirectives.snap @@ -0,0 +1,5 @@ +input Foo @foo { + bar: String +} + +directive @foo on INPUT_OBJECT diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InputObjectType_AddDuplicateDirectives.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InputObjectType_AddDuplicateDirectives.snap new file mode 100644 index 00000000000..1852c730263 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InputObjectType_AddDuplicateDirectives.snap @@ -0,0 +1 @@ +The directive `foo` is not marked as repeatable and can only be declared once. diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InputObjectType_AddObjectField.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InputObjectType_AddObjectField.snap new file mode 100644 index 00000000000..20d99463fdd --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InputObjectType_AddObjectField.snap @@ -0,0 +1,8 @@ +input Foo { + bar: String + baz: Bar +} + +input Bar { + baz: String +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InputObjectType_AddScalarField.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InputObjectType_AddScalarField.snap new file mode 100644 index 00000000000..7a639c39cd8 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InputObjectType_AddScalarField.snap @@ -0,0 +1,4 @@ +input Foo { + bar: String + baz: Int +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InputObjectType_AddUndeclaredDirectives.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InputObjectType_AddUndeclaredDirectives.snap new file mode 100644 index 00000000000..2fb2783d55b --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InputObjectType_AddUndeclaredDirectives.snap @@ -0,0 +1 @@ +The directive `foo` was not specified in this schema and cannot be used. diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InterfaceType_AddDirectives.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InterfaceType_AddDirectives.snap new file mode 100644 index 00000000000..a26a316cc02 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InterfaceType_AddDirectives.snap @@ -0,0 +1,5 @@ +interface Foo @foo { + bar: String +} + +directive @foo on INTERFACE diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InterfaceType_AddDuplicateDirectives.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InterfaceType_AddDuplicateDirectives.snap new file mode 100644 index 00000000000..1852c730263 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InterfaceType_AddDuplicateDirectives.snap @@ -0,0 +1 @@ +The directive `foo` is not marked as repeatable and can only be declared once. diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InterfaceType_AddObjectField.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InterfaceType_AddObjectField.snap new file mode 100644 index 00000000000..174b0d5963b --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InterfaceType_AddObjectField.snap @@ -0,0 +1,8 @@ +interface Foo { + bar: String + baz: Bar +} + +interface Bar { + baz: String +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InterfaceType_AddScalarField.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InterfaceType_AddScalarField.snap new file mode 100644 index 00000000000..fcd68c8fded --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InterfaceType_AddScalarField.snap @@ -0,0 +1,4 @@ +interface Foo { + bar: String + baz: Int +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InterfaceType_AddUndeclaredDirectives.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InterfaceType_AddUndeclaredDirectives.snap new file mode 100644 index 00000000000..2fb2783d55b --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.InterfaceType_AddUndeclaredDirectives.snap @@ -0,0 +1 @@ +The directive `foo` was not specified in this schema and cannot be used. diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_AddDirectives.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_AddDirectives.snap new file mode 100644 index 00000000000..7929ed234ad --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_AddDirectives.snap @@ -0,0 +1,5 @@ +type Foo @foo { + bar: String +} + +directive @foo on OBJECT diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_AddDirectivesToField.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_AddDirectivesToField.snap new file mode 100644 index 00000000000..ca609873e9a --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_AddDirectivesToField.snap @@ -0,0 +1,5 @@ +type Foo { + bar: String @foo +} + +directive @foo on FIELD diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_AddDuplicateDirectives.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_AddDuplicateDirectives.snap new file mode 100644 index 00000000000..1852c730263 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_AddDuplicateDirectives.snap @@ -0,0 +1 @@ +The directive `foo` is not marked as repeatable and can only be declared once. diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_AddObjectField.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_AddObjectField.snap new file mode 100644 index 00000000000..d73e31c19a1 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_AddObjectField.snap @@ -0,0 +1,8 @@ +type Foo { + bar: String + baz: Bar +} + +type Bar { + baz: String +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_AddScalarField.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_AddScalarField.snap new file mode 100644 index 00000000000..745a787c104 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_AddScalarField.snap @@ -0,0 +1,4 @@ +type Foo { + bar: String + baz: Int +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_AddScalarField_2.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_AddScalarField_2.snap new file mode 100644 index 00000000000..ce27010e924 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_AddScalarField_2.snap @@ -0,0 +1,8 @@ +type Foo { + bar: String + baz: Int +} + +extend type Baz { + a: String +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_AddUndeclaredDirectives.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_AddUndeclaredDirectives.snap new file mode 100644 index 00000000000..2fb2783d55b --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_AddUndeclaredDirectives.snap @@ -0,0 +1 @@ +The directive `foo` was not specified in this schema and cannot be used. diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_DirectiveDeclaredInExtensionDoc.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_DirectiveDeclaredInExtensionDoc.snap new file mode 100644 index 00000000000..7929ed234ad --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_DirectiveDeclaredInExtensionDoc.snap @@ -0,0 +1,5 @@ +type Foo @foo { + bar: String +} + +directive @foo on OBJECT diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.UnionType_AddDirectives.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.UnionType_AddDirectives.snap new file mode 100644 index 00000000000..ece3ffdd7f7 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.UnionType_AddDirectives.snap @@ -0,0 +1,11 @@ +union Foo @foo = A | B + +type A { + a: String +} + +type B { + b: String +} + +directive @foo on INTERFACE diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.UnionType_AddDuplicateDirectives.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.UnionType_AddDuplicateDirectives.snap new file mode 100644 index 00000000000..1852c730263 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.UnionType_AddDuplicateDirectives.snap @@ -0,0 +1 @@ +The directive `foo` is not marked as repeatable and can only be declared once. diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.UnionType_AddType.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.UnionType_AddType.snap new file mode 100644 index 00000000000..5427cc8af12 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.UnionType_AddType.snap @@ -0,0 +1,13 @@ +union Foo = A | B | C + +type A { + a: String +} + +type B { + b: String +} + +type C { + c: String +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.UnionType_AddUndeclaredDirectives.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.UnionType_AddUndeclaredDirectives.snap new file mode 100644 index 00000000000..2fb2783d55b --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.UnionType_AddUndeclaredDirectives.snap @@ -0,0 +1 @@ +The directive `foo` was not specified in this schema and cannot be used. diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/MergeSyntaxNodeExtensionsTests.AddDelegationPath_MultipleComponents.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/MergeSyntaxNodeExtensionsTests.AddDelegationPath_MultipleComponents.snap new file mode 100644 index 00000000000..3a65a207091 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/MergeSyntaxNodeExtensionsTests.AddDelegationPath_MultipleComponents.snap @@ -0,0 +1,3 @@ +type Object { + foo: Type @delegate(schema: "schemName", path: "bar(baz: $qux:quux).bar2(baz2: $qux2:quux2)") +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/MergeSyntaxNodeExtensionsTests.AddDelegationPath_MultipleComponents_EmptyPath.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/MergeSyntaxNodeExtensionsTests.AddDelegationPath_MultipleComponents_EmptyPath.snap new file mode 100644 index 00000000000..c087159517d --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/MergeSyntaxNodeExtensionsTests.AddDelegationPath_MultipleComponents_EmptyPath.snap @@ -0,0 +1,3 @@ +type Object { + foo: Type @delegate(schema: "schemName") +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/MergeSyntaxNodeExtensionsTests.AddDelegationPath_MultipleComponents_SingleComponent.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/MergeSyntaxNodeExtensionsTests.AddDelegationPath_MultipleComponents_SingleComponent.snap new file mode 100644 index 00000000000..69c826b2a61 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/MergeSyntaxNodeExtensionsTests.AddDelegationPath_MultipleComponents_SingleComponent.snap @@ -0,0 +1,3 @@ +type Object { + foo: Type @delegate(schema: "schemName", path: "bar(baz: $qux:quux)") +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/MergeSyntaxNodeExtensionsTests.AddDelegationPath_Overwrite.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/MergeSyntaxNodeExtensionsTests.AddDelegationPath_Overwrite.snap new file mode 100644 index 00000000000..b176d0f3fef --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/MergeSyntaxNodeExtensionsTests.AddDelegationPath_Overwrite.snap @@ -0,0 +1,3 @@ +type Object { + foo: Type @delegate(schema: "schemaName", path: "$ContextData:fooBar") +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/MergeSyntaxNodeExtensionsTests.AddDelegationPath_SingleComponent.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/MergeSyntaxNodeExtensionsTests.AddDelegationPath_SingleComponent.snap new file mode 100644 index 00000000000..69c826b2a61 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/MergeSyntaxNodeExtensionsTests.AddDelegationPath_SingleComponent.snap @@ -0,0 +1,3 @@ +type Object { + foo: Type @delegate(schema: "schemName", path: "bar(baz: $qux:quux)") +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/MergeSyntaxNodeExtensionsTests.AddDelegationPath_SingleComponent_TwoArgs.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/MergeSyntaxNodeExtensionsTests.AddDelegationPath_SingleComponent_TwoArgs.snap new file mode 100644 index 00000000000..1a4bfea35d1 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/MergeSyntaxNodeExtensionsTests.AddDelegationPath_SingleComponent_TwoArgs.snap @@ -0,0 +1,3 @@ +type Object { + foo: Type @delegate(schema: "schemName", path: "bar(baz: $qux:quux, value_arg: \"value\")") +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.FieldDefinitionDoesNotHaveSameTypeShape.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.FieldDefinitionDoesNotHaveSameTypeShape.snap new file mode 100644 index 00000000000..7d9a839c4e5 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.FieldDefinitionDoesNotHaveSameTypeShape.snap @@ -0,0 +1,17 @@ +type A @source(name: "A", schema: "A") { + b1: String + b2: String +} + +type B_A @source(name: "A", schema: "B") { + b1: String + b3: String +} + +type B @source(name: "B", schema: "A") { + c: String! +} + +type B_B @source(name: "B", schema: "B") { + c: String +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.LastFieldRenameWins_A.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.LastFieldRenameWins_A.snap new file mode 100644 index 00000000000..4fd7d4f7dee --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.LastFieldRenameWins_A.snap @@ -0,0 +1,24 @@ +type A @source(name: "A", schema: "A") { + b1: B +} + +type B implements D @source(name: "B", schema: "A") { + c789: String @source(name: "c", schema: "A") +} + +type B_B @source(name: "B", schema: "B") { + b1: String + b3: String +} + +type C implements D @source(name: "C", schema: "A") { + c789: String @source(name: "c", schema: "A") +} + +type B_C @source(name: "C", schema: "B") { + c: String +} + +interface D @source(name: "D", schema: "A") { + c789: String @source(name: "c", schema: "A") +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.LastFieldRenameWins_B.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.LastFieldRenameWins_B.snap new file mode 100644 index 00000000000..4fd7d4f7dee --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.LastFieldRenameWins_B.snap @@ -0,0 +1,24 @@ +type A @source(name: "A", schema: "A") { + b1: B +} + +type B implements D @source(name: "B", schema: "A") { + c789: String @source(name: "c", schema: "A") +} + +type B_B @source(name: "B", schema: "B") { + b1: String + b3: String +} + +type C implements D @source(name: "C", schema: "A") { + c789: String @source(name: "c", schema: "A") +} + +type B_C @source(name: "C", schema: "B") { + c: String +} + +interface D @source(name: "D", schema: "A") { + c789: String @source(name: "c", schema: "A") +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeDemoSchemaAndRemoveRootTypes.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeDemoSchemaAndRemoveRootTypes.snap new file mode 100644 index 00000000000..3df3aed3ccf --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeDemoSchemaAndRemoveRootTypes.snap @@ -0,0 +1,34 @@ +interface Contract @source(name: "Contract", schema: "A") { + id: ID! +} + +type LifeInsuranceContract implements Contract @source(name: "LifeInsuranceContract", schema: "A") { + id: ID! + premium: Float +} + +type SomeOtherContract implements Contract @source(name: "SomeOtherContract", schema: "A") { + id: ID! + expiryDate: DateTime +} + +type Customer @source(name: "Customer", schema: "B") { + id: ID! + name: String! + consultant: Consultant + complexArg(arg: ComplexInputType): String +} + +type Consultant @source(name: "Consultant", schema: "B") { + id: ID! + name: String! +} + +union CustomerOrConsultant @source(name: "CustomerOrConsultant", schema: "B") = Customer | Consultant + +input ComplexInputType @source(name: "ComplexInputType", schema: "B") { + deeper: ComplexInputType + deeperArray: [ComplexInputType] + value: String + valueArray: [String] +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeDemoSchemaAndRemoveRootTypesOnSchemaA.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeDemoSchemaAndRemoveRootTypesOnSchemaA.snap new file mode 100644 index 00000000000..71a64ee8b57 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeDemoSchemaAndRemoveRootTypesOnSchemaA.snap @@ -0,0 +1,40 @@ +type Query { + customer(id: ID!): Customer @delegate(schema: "B") + consultant(id: ID!): Consultant @delegate(schema: "B") + customerOrConsultant(id: ID!): CustomerOrConsultant @delegate(schema: "B") +} + +interface Contract @source(name: "Contract", schema: "A") { + id: ID! +} + +type LifeInsuranceContract implements Contract @source(name: "LifeInsuranceContract", schema: "A") { + id: ID! + premium: Float +} + +type SomeOtherContract implements Contract @source(name: "SomeOtherContract", schema: "A") { + id: ID! + expiryDate: DateTime +} + +type Customer @source(name: "Customer", schema: "B") { + id: ID! + name: String! + consultant: Consultant + complexArg(arg: ComplexInputType): String +} + +type Consultant @source(name: "Consultant", schema: "B") { + id: ID! + name: String! +} + +union CustomerOrConsultant @source(name: "CustomerOrConsultant", schema: "B") = Customer | Consultant + +input ComplexInputType @source(name: "ComplexInputType", schema: "B") { + deeper: ComplexInputType + deeperArray: [ComplexInputType] + value: String + valueArray: [String] +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeDemoSchemaWithDefaultHandler.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeDemoSchemaWithDefaultHandler.snap new file mode 100644 index 00000000000..ffb4ae3ad42 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeDemoSchemaWithDefaultHandler.snap @@ -0,0 +1,42 @@ +type Query { + contract(contractId: ID!): Contract @delegate(schema: "A") + contracts(customerId: ID!): [Contract!] @delegate(schema: "A") + customer(id: ID!): Customer @delegate(schema: "B") + consultant(id: ID!): Consultant @delegate(schema: "B") + customerOrConsultant(id: ID!): CustomerOrConsultant @delegate(schema: "B") +} + +interface Contract @source(name: "Contract", schema: "A") { + id: ID! +} + +type LifeInsuranceContract implements Contract @source(name: "LifeInsuranceContract", schema: "A") { + id: ID! + premium: Float +} + +type SomeOtherContract implements Contract @source(name: "SomeOtherContract", schema: "A") { + id: ID! + expiryDate: DateTime +} + +type Customer @source(name: "Customer", schema: "B") { + id: ID! + name: String! + consultant: Consultant + complexArg(arg: ComplexInputType): String +} + +type Consultant @source(name: "Consultant", schema: "B") { + id: ID! + name: String! +} + +union CustomerOrConsultant @source(name: "CustomerOrConsultant", schema: "B") = Customer | Consultant + +input ComplexInputType @source(name: "ComplexInputType", schema: "B") { + deeper: ComplexInputType + deeperArray: [ComplexInputType] + value: String + valueArray: [String] +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeDirectivesWithCustomHandler.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeDirectivesWithCustomHandler.snap new file mode 100644 index 00000000000..5124f570bcd --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeDirectivesWithCustomHandler.snap @@ -0,0 +1 @@ +directive @foo(a: String) on FIELD diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeDirectivesWithCustomRule.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeDirectivesWithCustomRule.snap new file mode 100644 index 00000000000..5124f570bcd --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeDirectivesWithCustomRule.snap @@ -0,0 +1 @@ +directive @foo(a: String) on FIELD diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSchemaAndRemoveFieldB1OnAllSchemas.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSchemaAndRemoveFieldB1OnAllSchemas.snap new file mode 100644 index 00000000000..889c58d3c1b --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSchemaAndRemoveFieldB1OnAllSchemas.snap @@ -0,0 +1,11 @@ +type A @source(name: "A", schema: "A") { + b2: String +} + +type B_A @source(name: "A", schema: "B") { + b3: String +} + +type B @source(name: "B", schema: "A") @source(name: "B", schema: "B") { + c: String +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSchemaAndRemoveFieldB1OnSchemaA.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSchemaAndRemoveFieldB1OnSchemaA.snap new file mode 100644 index 00000000000..f57581ce2eb --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSchemaAndRemoveFieldB1OnSchemaA.snap @@ -0,0 +1,12 @@ +type A @source(name: "A", schema: "A") { + b2: String +} + +type B_A @source(name: "A", schema: "B") { + b1: String + b3: String +} + +type B @source(name: "B", schema: "A") @source(name: "B", schema: "B") { + c: String +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSchemaAndRemoveTypeAOnAllSchemas.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSchemaAndRemoveTypeAOnAllSchemas.snap new file mode 100644 index 00000000000..e083788f1f3 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSchemaAndRemoveTypeAOnAllSchemas.snap @@ -0,0 +1,3 @@ +type B @source(name: "B", schema: "A") @source(name: "B", schema: "B") { + c: String +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSchemaAndRemoveTypeAOnSchemaA.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSchemaAndRemoveTypeAOnSchemaA.snap new file mode 100644 index 00000000000..8cf91c317fd --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSchemaAndRemoveTypeAOnSchemaA.snap @@ -0,0 +1,7 @@ +type B @source(name: "B", schema: "A") @source(name: "B", schema: "B") { + c: String +} + +type A @source(name: "A", schema: "B") { + b2: String +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSchemaAndRenameFieldB1toB11OnAllSchemas.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSchemaAndRenameFieldB1toB11OnAllSchemas.snap new file mode 100644 index 00000000000..1a0b75d2e65 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSchemaAndRenameFieldB1toB11OnAllSchemas.snap @@ -0,0 +1,13 @@ +type A @source(name: "A", schema: "A") { + b11: String @source(name: "b1", schema: "A") + b2: String +} + +type B_A @source(name: "A", schema: "B") { + b11: String @source(name: "b1", schema: "B") + b3: String +} + +type B @source(name: "B", schema: "A") @source(name: "B", schema: "B") { + c: String +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSchemaAndRenameFieldB1toB11OnSchemaA.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSchemaAndRenameFieldB1toB11OnSchemaA.snap new file mode 100644 index 00000000000..4e65b494637 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSchemaAndRenameFieldB1toB11OnSchemaA.snap @@ -0,0 +1,13 @@ +type A @source(name: "A", schema: "A") { + b11: String @source(name: "b1", schema: "A") + b2: String +} + +type B_A @source(name: "A", schema: "B") { + b1: String + b3: String +} + +type B @source(name: "B", schema: "A") @source(name: "B", schema: "B") { + c: String +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSchemaAndRenameTypeAtoXyzOnAllSchemas.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSchemaAndRenameTypeAtoXyzOnAllSchemas.snap new file mode 100644 index 00000000000..2314d78c942 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSchemaAndRenameTypeAtoXyzOnAllSchemas.snap @@ -0,0 +1,13 @@ +type Xyz @source(name: "A", schema: "A") { + b1: String + b2: String +} + +type B_Xyz @source(name: "A", schema: "B") { + b1: String + b3: String +} + +type B @source(name: "B", schema: "A") @source(name: "B", schema: "B") { + c: String +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSchemaAndRenameTypeAtoXyzOnSchemaA.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSchemaAndRenameTypeAtoXyzOnSchemaA.snap new file mode 100644 index 00000000000..3acfc1a1bd6 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSchemaAndRenameTypeAtoXyzOnSchemaA.snap @@ -0,0 +1,13 @@ +type Xyz @source(name: "A", schema: "A") { + b1: String + b2: String +} + +type B @source(name: "B", schema: "A") @source(name: "B", schema: "B") { + c: String +} + +type A @source(name: "A", schema: "B") { + b1: String + b3: String +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSimpleSchemaWithDefaultHandler.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSimpleSchemaWithDefaultHandler.snap new file mode 100644 index 00000000000..add932b2314 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeSimpleSchemaWithDefaultHandler.snap @@ -0,0 +1,5 @@ +union Foo @source(name: "Foo", schema: "A") = Bar | Baz + +union B_Foo @source(name: "Foo", schema: "B") = Bar | Baz + +union A @source(name: "A", schema: "A") = B | C diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeTypeWithCustomHandler.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeTypeWithCustomHandler.snap new file mode 100644 index 00000000000..d54a0d4cb19 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeTypeWithCustomHandler.snap @@ -0,0 +1,4 @@ +type Foo @source(name: "Foo", schema: "A") { + a: String + b: String +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeTypeWithCustomRule.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeTypeWithCustomRule.snap new file mode 100644 index 00000000000..d54a0d4cb19 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.MergeTypeWithCustomRule.snap @@ -0,0 +1,4 @@ +type Foo @source(name: "Foo", schema: "A") { + a: String + b: String +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.RenameInterfaceField.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.RenameInterfaceField.snap new file mode 100644 index 00000000000..d4897bd5474 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.RenameInterfaceField.snap @@ -0,0 +1,24 @@ +type A @source(name: "A", schema: "A") { + b1: B +} + +type B implements D @source(name: "B", schema: "A") { + c123: String @source(name: "c", schema: "A") +} + +type B_B @source(name: "B", schema: "B") { + b1: String + b3: String +} + +type C implements D @source(name: "C", schema: "A") { + c123: String @source(name: "c", schema: "A") +} + +type B_C @source(name: "C", schema: "B") { + c: String +} + +interface D @source(name: "D", schema: "A") { + c123: String @source(name: "c", schema: "A") +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.RenameObjectField.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.RenameObjectField.snap new file mode 100644 index 00000000000..68a65b6742b --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.RenameObjectField.snap @@ -0,0 +1,20 @@ +type A @source(name: "A", schema: "A") { + b1: B +} + +type B @source(name: "B", schema: "A") { + c123: String @source(name: "c", schema: "A") +} + +type B_B @source(name: "B", schema: "B") { + b1: String + b3: String +} + +type C implements D @source(name: "C", schema: "A") @source(name: "C", schema: "B") { + c: String +} + +interface D @source(name: "D", schema: "A") { + c: String +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.RenameObjectFieldThatImplementsInterface.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.RenameObjectFieldThatImplementsInterface.snap new file mode 100644 index 00000000000..d4897bd5474 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.RenameObjectFieldThatImplementsInterface.snap @@ -0,0 +1,24 @@ +type A @source(name: "A", schema: "A") { + b1: B +} + +type B implements D @source(name: "B", schema: "A") { + c123: String @source(name: "c", schema: "A") +} + +type B_B @source(name: "B", schema: "B") { + b1: String + b3: String +} + +type C implements D @source(name: "C", schema: "A") { + c123: String @source(name: "c", schema: "A") +} + +type B_C @source(name: "C", schema: "B") { + c: String +} + +interface D @source(name: "D", schema: "A") { + c123: String @source(name: "c", schema: "A") +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.RenameReferencingType_A.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.RenameReferencingType_A.snap new file mode 100644 index 00000000000..b516b607063 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.RenameReferencingType_A.snap @@ -0,0 +1,20 @@ +type A @source(name: "A", schema: "A") { + b1: Foo +} + +type Foo implements A_C @source(name: "B", schema: "A") { + c: String +} + +type C @source(name: "C", schema: "B") { + c: String +} + +interface A_C @source(name: "C", schema: "A") { + c: String +} + +type B @source(name: "B", schema: "B") { + b1: String + b3: String +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.RenameReferencingType_B.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.RenameReferencingType_B.snap new file mode 100644 index 00000000000..5c28e50b326 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.RenameReferencingType_B.snap @@ -0,0 +1,20 @@ +type B @source(name: "B", schema: "B") { + b1: String + b3: String +} + +type C @source(name: "C", schema: "B") { + c: String +} + +interface A_C @source(name: "C", schema: "A") { + c: String +} + +type A @source(name: "A", schema: "A") { + b1: Foo +} + +type Foo implements A_C @source(name: "B", schema: "A") { + c: String +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.Rename_Type_With_Various_Variants.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.Rename_Type_With_Various_Variants.snap new file mode 100644 index 00000000000..61cfdb3811d --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/SchemaMergerTests.Rename_Type_With_Various_Variants.snap @@ -0,0 +1,14 @@ +type A @source(name: "A", schema: "A") { + b1: Foo! + b2: [Foo!] + b3: [Foo] + b4: [Foo!]! +} + +type Foo implements C @source(name: "B", schema: "A") { + c: String +} + +interface C @source(name: "C", schema: "A") { + c: String +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/ConfigurationApiTests.txt b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/ConfigurationApiTests.txt new file mode 100644 index 00000000000..4d3cbbc0e50 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/ConfigurationApiTests.txt @@ -0,0 +1,83 @@ + +using System; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Xunit; +using HotChocolate.Execution; +using HotChocolate.AspNetCore.Tests.Utilities; + +namespace HotChocolate.Stitching +{ + public class ConfigurationApiTests + : StitchingTestBase + { + public ConfigurationApiTests(TestServerFactory testServerFactory) + : base(testServerFactory) + { + } + + [Fact] + public async Task IsDirectiveMergeRuleTriggered() + { + // arrange + IHttpClientFactory clientFactory = CreateRemoteSchemas(); + + var serviceCollection = + new ServiceCollection() + .AddGraphQL() + .AddHttpRemoteSchema("contract") + .AddHttpRemoteSchema("customer") + .AddTypeExtension(); + + + + + + + + serviceCollection.AddSingleton(clientFactory); + serviceCollection.AddStitchedSchema(builder => builder + .AddSchemaFromHttp("contract") + .AddSchemaFromHttp("customer") + .AddDirectiveMergeRule(next => (c, d) => + { + triggered = true; + next(c, d); + })); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + IExecutionResult result = null; + + // act + using (IServiceScope scope = services.CreateScope()) + { + // some query to trigger the merging of the schemas + IQueryRequest request = + QueryRequestBuilder.New() + .SetQuery($@" + query a($contractId: ID!) {{ + contract(contractId: $contractId) {{ + ... on LifeInsuranceContract {{ + id + }} + }} + }}") + .SetVariableValue( + "contractId", + "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQx") + .SetServices(scope.ServiceProvider) + .Create(); + + result = await executor.ExecuteAsync(request); + } + + // assert + Assert.True(triggered); + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/DelegateToRemoteSchemaMiddlewareTests.txt b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/DelegateToRemoteSchemaMiddlewareTests.txt new file mode 100644 index 00000000000..3820bdee091 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/DelegateToRemoteSchemaMiddlewareTests.txt @@ -0,0 +1,935 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Snapshooter.Xunit; +using Xunit; +using HotChocolate.Execution; +using HotChocolate.Types; +using HotChocolate.Resolvers; +using FileResource = ChilliCream.Testing.FileResource; +using HotChocolate.AspNetCore.Tests.Utilities;< +using System.Threading; + +namespace HotChocolate.Stitching +{ + public class DelegateToRemoteSchemaMiddlewareTests + : StitchingTestBase + { + public DelegateToRemoteSchemaMiddlewareTests( + TestServerFactory testServerFactory) + : base(testServerFactory) + { + } + + [Fact] + public async Task ExecuteStitchedQueryBuilder() + { + // arrange + IHttpClientFactory clientFactory = CreateRemoteSchemas(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(clientFactory); + serviceCollection.AddStitchedSchema(builder => + builder.AddSchemaFromHttp("contract") + .AddSchemaFromHttp("customer") + .RenameField("customer", + new FieldReference("Customer", "name"), "foo") + .AddExtensionsFromString( + FileResource.Open("StitchingExtensions.graphql")) + .AddSchemaConfiguration(c => + c.RegisterType())); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + IExecutionResult result = null; + + // act + using (IServiceScope scope = services.CreateScope()) + { + IQueryRequest request = + QueryRequestBuilder.New() + .SetQuery(@" + { + a: customer(id: ""Q3VzdG9tZXIKZDE="") { + bar: foo + contracts { + id + } + } + + b: customer(id: ""Q3VzdG9tZXIKZDE="") { + foo + contracts { + id + } + } + }") + .SetServices(scope.ServiceProvider) + .Create(); + + result = await executor.ExecuteAsync(request); + } + + // assert + Snapshot.Match(result); + } + + [Fact] + public async Task ExecuteStitchedQueryBuilderVariableArguments() + { + // arrange + IHttpClientFactory clientFactory = CreateRemoteSchemas(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(clientFactory); + serviceCollection.AddStitchedSchema(builder => + builder.AddSchemaFromHttp("contract") + .AddSchemaFromHttp("customer") + .RenameField("customer", + new FieldReference("Customer", "name"), "foo") + .AddExtensionsFromString( + FileResource.Open("StitchingExtensions.graphql")) + .AddSchemaConfiguration(c => + c.RegisterType())); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + IExecutionResult result = null; + + // act + using (IServiceScope scope = services.CreateScope()) + { + IQueryRequest request = + QueryRequestBuilder.New() + .SetQuery(@" + query a($id: ID! $bar: String) { + contracts(customerId: $id) + { + id + customerId + ... foo + } + } + + fragment foo on LifeInsuranceContract + { + foo(bar: $bar) + } + ") + .SetVariableValue("id", "Q3VzdG9tZXIKZDE=") + .SetVariableValue("bar", "this variable is passed to remote query!") + .SetServices(scope.ServiceProvider) + .Create(); + + result = await executor.ExecuteAsync(request); + } + + // assert + Snapshot.Match(result); + } + + [Fact] + public async Task ExecuteStitchedQueryBuilderWithRenamedType() + { + // arrange + IHttpClientFactory clientFactory = CreateRemoteSchemas(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(clientFactory); + serviceCollection.AddStitchedSchema(builder => + builder.AddSchemaFromHttp("contract") + .AddSchemaFromHttp("customer") + .RenameField("customer", + new FieldReference("Customer", "name"), "foo") + .RenameType("SomeOtherContract", "Other") + .RenameType("LifeInsuranceContract", "Life") + .AddExtensionsFromString( + FileResource.Open("StitchingExtensions.graphql")) + .AddSchemaConfiguration(c => + c.RegisterType())); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + IExecutionResult result = null; + + // act + using (IServiceScope scope = services.CreateScope()) + { + IQueryRequest request = + QueryRequestBuilder.New() + .SetQuery(@" + query a($id: ID!) { + a: customer2(customerId: $id) { + bar: foo + contracts { + id + ... life + ... on Other { + expiryDate + } + } + } + } + + fragment life on Life + { + premium + }") + .SetVariableValue("id", "Q3VzdG9tZXIKZDE=") + .SetServices(scope.ServiceProvider) + .Create(); + + result = await executor.ExecuteAsync(request); + } + + // assert + Snapshot.Match(result); + } + + [Fact] + public async Task ExecuteStitchedQueryBuilderWithLocalSchema() + { + // arrange + IHttpClientFactory clientFactory = CreateRemoteSchemas(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(clientFactory); + serviceCollection.AddStitchedSchema(builder => + builder.AddSchemaFromHttp("contract") + .AddSchemaFromHttp("customer") + .AddSchema("hello", + Schema.Create( + "type Query { hello: String! }", + c => c.BindResolver(ctx => "Hello World") + .To("Query", "hello"))) + .RenameField("customer", + new FieldReference("Customer", "name"), "foo") + .RenameType("SomeOtherContract", "Other") + .RenameType("LifeInsuranceContract", "Life") + .AddExtensionsFromString( + FileResource.Open("StitchingExtensions.graphql")) + .AddSchemaConfiguration(c => + c.RegisterType())); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + IExecutionResult result = null; + + // act + using (IServiceScope scope = services.CreateScope()) + { + IQueryRequest request = + QueryRequestBuilder.New() + .SetQuery(@" + query a($id: ID!) { + a: customer2(customerId: $id) { + bar: foo + contracts { + id + ... life + ... on Other { + expiryDate + } + } + } + hello + } + + fragment life on Life + { + premium + } + + ") + .SetVariableValue("id", "Q3VzdG9tZXIKZDE=") + .SetServices(scope.ServiceProvider) + .Create(); + + result = await executor.ExecuteAsync(request); + } + + // assert + Snapshot.Match(result); + } + + [Fact] + public async Task ReplaceField() + { + // arrange + IHttpClientFactory clientFactory = CreateRemoteSchemas(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(clientFactory); + serviceCollection.AddStitchedSchema(builder => + builder.AddSchemaFromHttp("contract") + .AddSchemaFromHttp("customer") + .IgnoreField("customer", + new FieldReference("Customer", "name")) + .RenameField("customer", + new FieldReference("Customer", "street"), "name") + .AddSchemaConfiguration(c => + c.RegisterType())); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + IExecutionResult result = null; + + // act + using (IServiceScope scope = services.CreateScope()) + { + IQueryRequest request = + QueryRequestBuilder.New() + .SetQuery(@" + query a($id: ID!) { + a: customer(id: $id) { + name + } + }") + .SetVariableValue("id", "Q3VzdG9tZXIKZDE=") + .SetServices(scope.ServiceProvider) + .Create(); + + result = await executor.ExecuteAsync(request); + } + + // assert + Snapshot.Match(result); + } + + [Fact] + public async Task ExtendedScalarAsInAndOutputType() + { + // arrange + IHttpClientFactory clientFactory = CreateRemoteSchemas(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(clientFactory); + serviceCollection.AddStitchedSchema(builder => + builder.AddSchemaFromHttp("contract") + .AddSchemaFromHttp("customer") + .AddSchemaConfiguration(c => + { + c.RegisterExtendedScalarTypes(); + }) + .AddSchemaConfiguration(c => + c.RegisterType())); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + IExecutionResult result = null; + + // act + using (IServiceScope scope = services.CreateScope()) + { + IQueryRequest request = + QueryRequestBuilder.New() + .SetQuery(@" + query a($d: DateTime!) { + a: extendedScalar(d: ""2018-01-01T01:00:00.000Z"") + b: extendedScalar(d: $d) + }") + .SetVariableValue("d", "2019-01-01T01:00:00.000Z") + .SetServices(scope.ServiceProvider) + .Create(); + + result = await executor.ExecuteAsync(request); + } + + // assert + Snapshot.Match(result); + } + + [Fact] + public async Task CustomDirectiveIsPassedOn() + { + // arrange + IHttpClientFactory clientFactory = CreateRemoteSchemas(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(clientFactory); + serviceCollection.AddStitchedSchema(builder => + builder.AddSchemaFromHttp("contract") + .AddSchemaFromHttp("customer") + .AddSchemaConfiguration(c => + { + c.RegisterExtendedScalarTypes(); + }) + .AddSchemaConfiguration(c => + c.RegisterType())); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + IExecutionResult result = null; + + // act + using (IServiceScope scope = services.CreateScope()) + { + IQueryRequest request = + QueryRequestBuilder.New() + .SetQuery(@" + query a($d: DateTime!) { + a: extendedScalar(d: ""2018-01-01T01:00:00.000Z"") + b: extendedScalar(d: $d) + @custom(d: ""2020-09-01T01:00:00.000Z"") + }") + .SetVariableValue("d", "2019-01-01T01:00:00.000Z") + .SetServices(scope.ServiceProvider) + .Create(); + + result = await executor.ExecuteAsync(request); + } + + // assert + Snapshot.Match(result); + } + + [Fact] + public async Task DateTimeIsHandledCorrectly() + { + // arrange + IHttpClientFactory clientFactory = CreateRemoteSchemas(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(clientFactory); + serviceCollection.AddStitchedSchema(builder => + builder.AddSchemaFromHttp("contract") + .AddSchemaFromHttp("customer") + .AddSchemaConfiguration(c => + { + c.RegisterExtendedScalarTypes(); + }) + .AddSchemaConfiguration(c => + c.RegisterType())); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + IExecutionResult result = null; + + // act + using (IServiceScope scope = services.CreateScope()) + { + IQueryRequest request = + QueryRequestBuilder.New() + .SetQuery(@" + query a($d: DateTime!) { + a: extendedScalar(d: ""2018-01-01T01:00:00.000Z"") + b: extendedScalar(d: $d) + c: extendedScalar(d: $d) + @custom(d: ""2020-09-01T01:00:00.000Z"") + }") + .SetVariableValue("d", "2019-01-01T01:00:00.000Z") + .SetServices(scope.ServiceProvider) + .Create(); + + result = await executor.ExecuteAsync(request); + } + + // assert + Snapshot.Match(result); + } + + [Fact] + public async Task StitchedMutation() + { + // arrange + IHttpClientFactory clientFactory = CreateRemoteSchemas(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(clientFactory); + serviceCollection.AddStitchedSchema(builder => + builder.AddSchemaFromHttp("contract") + .AddSchemaFromHttp("customer") + .AddExtensionsFromString( + FileResource.Open("StitchingExtensions.graphql")) + .AddSchemaConfiguration(c => + c.RegisterType())); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + IExecutionResult result = null; + + // act + using (IServiceScope scope = services.CreateScope()) + { + IQueryRequest request = + QueryRequestBuilder.New() + .SetQuery(@" + mutation { + createCustomer(input: { name: ""a"" }) + { + customer { + name + contracts { + id + } + } + } + }") + .SetServices(scope.ServiceProvider) + .Create(); + + result = await executor.ExecuteAsync(request); + } + + // assert + Snapshot.Match(result); + } + + [Fact] + public async Task StitchedMutationWithRenamedInputType() + { + // arrange + IHttpClientFactory clientFactory = CreateRemoteSchemas(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(clientFactory); + serviceCollection.AddStitchedSchema(builder => + builder.AddSchemaFromHttp("contract") + .AddSchemaFromHttp("customer") + .RenameType("CreateCustomerInput", "CreateCustomerInput2") + .AddExtensionsFromString( + FileResource.Open("StitchingExtensions.graphql")) + .AddSchemaConfiguration(c => + c.RegisterType())); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + IExecutionResult result = null; + + // act + using (IServiceScope scope = services.CreateScope()) + { + IQueryRequest request = + QueryRequestBuilder.New() + .SetQuery(@" + mutation { + createCustomer(input: { name: ""a"" }) + { + customer { + name + contracts { + id + } + } + } + }") + .SetServices(scope.ServiceProvider) + .Create(); + + result = await executor.ExecuteAsync(request); + } + + // assert + Snapshot.Match(result); + } + + [Fact] + public async Task StitchedMutationWithRenamedFieldArgument() + { + // arrange + IHttpClientFactory clientFactory = CreateRemoteSchemas(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(clientFactory); + serviceCollection.AddStitchedSchema(builder => + builder.AddSchemaFromHttp("contract") + .AddSchemaFromHttp("customer") + .RenameFieldArgument( + "Mutation", "createCustomer", "input", "input2") + .AddExtensionsFromString( + FileResource.Open("StitchingExtensions.graphql")) + .AddSchemaConfiguration(c => + c.RegisterType())); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + IExecutionResult result = null; + + // act + using (IServiceScope scope = services.CreateScope()) + { + IQueryRequest request = + QueryRequestBuilder.New() + .SetQuery(@" + mutation { + createCustomer(input2: { name: ""a"" }) + { + customer { + name + contracts { + id + } + } + } + }") + .SetServices(scope.ServiceProvider) + .Create(); + + result = await executor.ExecuteAsync(request); + } + + // assert + Snapshot.Match(result); + } + + [Fact] + public async Task StitchedMutationWithRenamedInputField() + { + // arrange + var requestBuilder = new QueryRequestBuilder(); + requestBuilder.SetQuery(@" + mutation { + createCustomer(input: { foo: ""a"" }) + { + customer { + name + contracts { + id + } + } + } + }"); + + // act + IExecutionResult result = + await ExecutedMutationWithRenamedInputField( + requestBuilder); + + // assert + Snapshot.Match(result); + } + + [Fact] + public async Task StitchedMutationWithRenamedInputFieldList() + { + // arrange + var requestBuilder = new QueryRequestBuilder(); + requestBuilder.SetQuery(@" + mutation { + createCustomers(inputs: [{ foo: ""a"" } { foo: ""b"" }]) + { + customer { + name + contracts { + id + } + } + } + }"); + + // act + IExecutionResult result = + await ExecutedMutationWithRenamedInputField( + requestBuilder); + + // assert + Snapshot.Match(result); + } + + [Fact] + public async Task StitchedMutationWithRenamedInputFieldInVariables() + { + // arrange + var requestBuilder = new QueryRequestBuilder(); + requestBuilder.SetQuery(@" + mutation a($input: CreateCustomerInput) { + createCustomer(input: $input) + { + customer { + name + contracts { + id + } + } + } + }"); + requestBuilder.AddVariableValue("input", + new Dictionary + { + { "foo", "abc" } + }); + + // act + IExecutionResult result = + await ExecutedMutationWithRenamedInputField( + requestBuilder); + + // assert + Snapshot.Match(result); + } + + [Fact] + public async Task StitchedMutationWithRenamedInputFieldInVariablesList() + { + // arrange + var requestBuilder = new QueryRequestBuilder(); + requestBuilder.SetQuery(@" + mutation a($input: [CreateCustomerInput]) { + createCustomers(inputs: $input) + { + customer { + name + contracts { + id + } + } + } + }"); + requestBuilder.AddVariableValue("input", + new List + { + new Dictionary + { + { "foo", "abc" } + }, + new Dictionary + { + { "foo", "def" } + } + }); + + // act + IExecutionResult result = + await ExecutedMutationWithRenamedInputField( + requestBuilder); + + // assert + Snapshot.Match(result); + } + + [Fact] + public async Task Query_WithEnumArgument_EnumIsCorrectlyPassed() + { + // arrange + IHttpClientFactory clientFactory = CreateRemoteSchemas(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(clientFactory); + serviceCollection.AddStitchedSchema(builder => + builder.AddSchemaFromHttp("contract") + .AddSchemaFromHttp("customer") + .AddSchemaConfiguration(c => + c.RegisterType())); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + IExecutionResult result = null; + + // act + using (IServiceScope scope = services.CreateScope()) + { + IQueryRequest request = + QueryRequestBuilder.New() + .SetQuery(@" + { + standard: customerByKind(kind: STANDARD) + { + id + kind + } + + premium: customerByKind(kind: PREMIUM) + { + id + kind + } + }") + .SetServices(scope.ServiceProvider) + .Create(); + + result = await executor.ExecuteAsync(request); + } + + // assert + Snapshot.Match(result); + } + + [Fact] + public async Task HttpErrorsHavePathSet() + { + // arrange + var connections = new Dictionary(); + IHttpClientFactory clientFactory = CreateRemoteSchemas(connections); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(clientFactory); + serviceCollection.AddStitchedSchema(builder => + builder.AddSchemaFromHttp("contract") + .AddSchemaFromHttp("customer") + .AddExtensionsFromString( + FileResource.Open("StitchingExtensions.graphql")) + .AddSchemaConfiguration(c => + c.RegisterType())); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + ISchema schema = services.GetRequiredService(); + IQueryExecutor executor = services + .GetRequiredService(); + IExecutionResult result = null; + + // have to replace the http client after the schema is built + connections["customer"] = new HttpClient(new ServiceUnavailableDelegatingHandler()) + { + BaseAddress = connections["customer"].BaseAddress + }; + + + // act + using (IServiceScope scope = services.CreateScope()) + { + IQueryRequest request = + QueryRequestBuilder.New() + .SetQuery(@" + { + customer(id: ""Q3VzdG9tZXIKZDE="") { + contracts { + id + } + } + }") + .SetServices(scope.ServiceProvider) + .Create(); + + result = await executor.ExecuteAsync(request); + } + + // assert + result.MatchSnapshot(options => options.IgnoreField("Errors[0].Exception.StackTrace")); + } + + private class ServiceUnavailableDelegatingHandler : DelegatingHandler + { + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + var response = new HttpResponseMessage(System.Net.HttpStatusCode.ServiceUnavailable); + return Task.FromResult(response); + } + } + + [Fact] + public async Task AddErrorFilter() + { + // arrange + IHttpClientFactory clientFactory = CreateRemoteSchemas(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(clientFactory); + serviceCollection.AddStitchedSchema(builder => + builder.AddSchemaFromHttp("contract") + .AddSchemaFromHttp("customer") + .AddExtensionsFromString( + FileResource.Open("StitchingExtensions.graphql")) + .AddSchemaConfiguration(c => + c.RegisterType()) + .AddExecutionConfiguration(b => + { + b.AddErrorFilter(error => + error.AddExtension("STITCH", "SOMETHING")); + })); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + IExecutionResult result = null; + + // act + using (IServiceScope scope = services.CreateScope()) + { + IQueryRequest request = + QueryRequestBuilder.New() + .SetQuery(@" + { + customer(id: ""Q3VzdG9tZXIKZDE="") { + contracts { + id + ... on LifeInsuranceContract { + error + } + } + } + }") + .SetServices(scope.ServiceProvider) + .Create(); + + result = await executor.ExecuteAsync(request); + } + + // assert + Snapshot.Match(result); + } + + public async Task ExecutedMutationWithRenamedInputField( + IQueryRequestBuilder requestBuilder) + { + IHttpClientFactory clientFactory = CreateRemoteSchemas(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(clientFactory); + serviceCollection.AddStitchedSchema(builder => + builder.AddSchemaFromHttp("contract") + .AddSchemaFromHttp("customer") + .RenameField(new FieldReference("CreateCustomerInput", "name"), "foo") + .AddExtensionsFromString(FileResource.Open("StitchingExtensions.graphql")) + .AddSchemaConfiguration(c => + { + c.RegisterType(); + c.RegisterExtendedScalarTypes(); + })); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + + using (IServiceScope scope = services.CreateScope()) + { + + requestBuilder.SetServices(scope.ServiceProvider); + return await executor.ExecuteAsync(requestBuilder.Create()); + } + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/ErrorBehaviour.txt b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/ErrorBehaviour.txt new file mode 100644 index 00000000000..4b927541879 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/ErrorBehaviour.txt @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using Xunit; +using HotChocolate.Execution; +using System.Threading.Tasks; +using ChilliCream.Testing; +using HotChocolate.Types; +using Microsoft.Extensions.DependencyInjection; +using Snapshooter.Xunit; +using HotChocolate.AspNetCore.Tests.Utilities; + +namespace HotChocolate.Stitching +{ + public class Errorbehavior + : StitchingTestBase + { + public Errorbehavior(TestServerFactory testServerFactory) + : base(testServerFactory) + { + } + + [Fact(Skip = "FIX THIS ONE ___ NULLREF ON WINDOWS BUILD SERVER")] + public async Task ConnectionLost() + { + // arrange + var connections = new Dictionary(); + IHttpClientFactory clientFactory = CreateRemoteSchemas(connections); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(clientFactory); + serviceCollection.AddStitchedSchema(builder => + builder.AddSchemaFromHttp("contract") + .AddSchemaFromHttp("customer") + .RenameType("CreateCustomerInput", "CreateCustomerInput2") + .AddExtensionsFromString( + FileResource.Open("StitchingExtensions.graphql")) + .AddSchemaConfiguration(c => + c.RegisterType()) + .AddExecutionConfiguration(b => + { + b.AddErrorFilter(error => + { + if (error.Exception is Exception ex) + { + return ErrorBuilder.FromError(error) + .ClearExtensions() + .SetMessage(ex.GetType().FullName) + .SetException(null) + .Build(); + }; + return error; + }); + })); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + IExecutionResult result = null; + + using (IServiceScope scope = services.CreateScope()) + { + IQueryRequest request = + QueryRequestBuilder.New() + .SetQuery(@" + mutation { + createCustomer(input: { name: ""a"" }) + { + customer { + name + contracts { + id + } + } + } + }") + .SetServices(scope.ServiceProvider) + .Create(); + + result = await executor.ExecuteAsync(request); + } + + var client = new HttpClient + { + BaseAddress = new Uri("http://127.0.0.1") + }; ; + connections["contract"] = client; + connections["customer"] = client; + + // act + using (IServiceScope scope = services.CreateScope()) + { + IQueryRequest request = + QueryRequestBuilder.New() + .SetQuery(@" + mutation { + createCustomer(input: { name: ""a"" }) + { + customer { + name + contracts { + id + } + } + } + }") + .SetServices(scope.ServiceProvider) + .Create(); + + result = await executor.ExecuteAsync(request); + } + + // assert + result.MatchSnapshot(); + } + + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/HttpInterceptorTests.txt b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/HttpInterceptorTests.txt new file mode 100644 index 00000000000..22bf393d3f5 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/HttpInterceptorTests.txt @@ -0,0 +1,93 @@ +using System; +using System.Net.Http; +using Xunit; +using HotChocolate.Execution; +using System.Threading.Tasks; +using ChilliCream.Testing; +using HotChocolate.Types; +using Microsoft.Extensions.DependencyInjection; +using Snapshooter.Xunit; +using HotChocolate.AspNetCore.Tests.Utilities; + +namespace HotChocolate.Stitching +{ + public class HttpInterceptorTests + : StitchingTestBase + { + public HttpInterceptorTests(TestServerFactory testServerFactory) + : base(testServerFactory) + { + } + + [Fact] + public async Task InterceptHttpRequestAndDelegateHeaders() + { + // arrange + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton( + new DummyInterceptor()); + serviceCollection.AddSingleton(CreateRemoteSchemas()); + serviceCollection.AddStitchedSchema(builder => + builder.AddSchemaFromHttp("contract") + .AddSchemaFromHttp("customer") + .AddExtensionsFromString( + FileResource.Open("StitchingExtensions.graphql")) + .AddSchemaConfiguration(c => + c.RegisterType()) + .AddSchemaConfiguration(c => + c.RegisterType(new ObjectTypeExtension(d => d + .Name("Customer") + .Field("inter") + .Type() + .Directive() + .Resolver(ctx => + { + return ctx.ScopedContextData["foo"]; + }))))); + + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + + IExecutionResult result = null; + + // act + using (IServiceScope scope = services.CreateScope()) + { + IQueryRequest request = + QueryRequestBuilder.New() + .SetQuery(@" + { + customer(id: ""Q3VzdG9tZXIKZDE="") { + inter + } + }") + .SetServices(scope.ServiceProvider) + .Create(); + + result = await executor.ExecuteAsync(request); + } + + // assert + result.MatchSnapshot(); + } + + public class DummyInterceptor + : IHttpStitchingRequestInterceptor + { + public Task OnResponseReceivedAsync( + IQueryRequest request, + HttpResponseMessage response, + IQueryResult result) + { + return Task.FromResult( + QueryResultBuilder.FromResult(result) + .SetContextData("foo", "bar") + .Create()); + } + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/InputObjectDelegationTests.txt b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/InputObjectDelegationTests.txt new file mode 100644 index 00000000000..8c1502dbea3 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/InputObjectDelegationTests.txt @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using Xunit; +using HotChocolate.AspNetCore; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using HotChocolate.Execution; +using Snapshooter.Xunit; +using Moq; +using Microsoft.AspNetCore.TestHost; +using Snapshooter; +using HotChocolate.AspNetCore.Tests.Utilities; + +namespace HotChocolate.Stitching +{ + public class InputObjectDelegationTests + : StitchingTestBase + { + public InputObjectDelegationTests(TestServerFactory testServerFactory) + : base(testServerFactory) + { + } + + [Fact] + public async Task AllowInputObjectTypesAsArguments() + { + // arrange + var serviceCollection = new ServiceCollection(); + + var connections = new Dictionary(); + serviceCollection.AddSingleton(CreateRemoteSchemas(connections)); + + serviceCollection.AddStitchedSchema(builder => builder + .AddSchemaFromHttp("server_1") + .AddExtensionsFromString( + @" + extend type Query { + baz(a: Bar): String + @delegate( + schema: ""server_1"" + path: ""foo(a:$arguments:a)"") + } + ")); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + + // act + IExecutionResult result = null; + + using (IServiceScope scope = services.CreateScope()) + { + IQueryRequest request = QueryRequestBuilder.New() + .SetQuery("{ baz(a: { a: \"String 123\" }) }") + .SetServices(scope.ServiceProvider) + .Create(); + + result = await executor.ExecuteAsync(request); + } + + // assert + result.MatchSnapshot(new SnapshotNameExtension("result")); + executor.Schema.ToString().MatchSnapshot( + new SnapshotNameExtension("schema")); + } + protected override IHttpClientFactory CreateRemoteSchemas( + Dictionary connections) + { + TestServer server_1 = TestServerFactory.Create( + services => services.AddGraphQL( + SchemaBuilder.New() + .AddDocumentFromString + ( + @" + type Query { foo(a: Bar): String } + input Bar { a: String } + " + ) + .BindComplexType(t => t.To("Query")) + .BindComplexType() + .Create()), + app => app.UseGraphQL()); + + connections["server_1"] = server_1.CreateClient(); + + var httpClientFactory = new Mock(); + httpClientFactory.Setup(t => t.CreateClient(It.IsAny())) + .Returns(new Func(n => + { + if (connections.ContainsKey(n)) + { + return connections[n]; + } + + throw new Exception(); + })); + return httpClientFactory.Object; + } + + public class Query1 + { + public string Foo(Bar a) => a.A; + } + + public class Bar + { + public string A { get; set; } + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/ScalarTests.txt b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/ScalarTests.txt new file mode 100644 index 00000000000..f4681497e33 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/ScalarTests.txt @@ -0,0 +1,270 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Xunit; +using Snapshooter.Xunit; +using HotChocolate.Execution; +using HotChocolate.AspNetCore.Tests.Utilities; +using Snapshooter; +using HotChocolate.Types; +using HotChocolate.Language; +using System.Collections.Generic; +using Microsoft.AspNetCore.TestHost; +using HotChocolate.Stitching.Schemas.Contracts; +using HotChocolate.Stitching.Schemas.Customers; +using HotChocolate.AspNetCore; +using HotChocolate.Stitching.Schemas.SpecialCases; +using Moq; + +namespace HotChocolate.Stitching +{ + public class ScalarTests + : StitchingTestBase + { + public ScalarTests(TestServerFactory testServerFactory) + : base(testServerFactory) + { + } + + [InlineData("date_field")] + [InlineData("date_time_field")] + [InlineData("string_field")] + [InlineData("id_field")] + [InlineData("byte_field")] + [InlineData("int_field")] + [InlineData("long_field")] + [InlineData("float_field")] + [InlineData("decimal_field")] + [Theory] + public async Task Scalar_Serializes_And_Deserializes_Correctly( + string fieldName) + { + // arrange + IHttpClientFactory clientFactory = CreateRemoteSchemas(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(clientFactory); + serviceCollection.AddStitchedSchema(builder => builder + .AddSchemaFromHttp("contract") + .AddSchemaFromHttp("customer")); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + IExecutionResult result = null; + + // act + using (IServiceScope scope = services.CreateScope()) + { + IQueryRequest request = + QueryRequestBuilder.New() + .SetQuery($@" + query a($contractId: ID!) {{ + contract(contractId: $contractId) {{ + ... on LifeInsuranceContract {{ + {fieldName} + }} + }} + }}") + .SetVariableValue( + "contractId", + "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQx") + .SetServices(scope.ServiceProvider) + .Create(); + + result = await executor.ExecuteAsync(request); + } + + // assert + result.MatchSnapshot(new SnapshotNameExtension(fieldName)); + } + + [Fact] + public async Task Custom_Scalar_Types() + { + // arrange + IHttpClientFactory clientFactory = CreateRemoteSchemas(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(clientFactory); + serviceCollection.AddStitchedSchema(builder => builder + .AddSchemaFromHttp("special") + .AddSchemaConfiguration(c => + c.RegisterType())); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + IExecutionResult result = null; + + // act + using (IServiceScope scope = services.CreateScope()) + { + IQueryRequest request = + QueryRequestBuilder.New() + .SetQuery("{ custom_scalar(bar: \"custom_string\") }") + .SetServices(scope.ServiceProvider) + .Create(); + + result = await executor.ExecuteAsync(request); + } + + // assert + result.MatchSnapshot(); + } + + [Fact] + public async Task Custom_Scalar_Delegated_Argument() + { + // arrange + IHttpClientFactory clientFactory = CreateRemoteSchemas(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(clientFactory); + serviceCollection.AddStitchedSchema(builder => builder + .AddSchemaFromHttp("special") + .AddExtensionsFromString("extend type Query { custom_scalar_stitched(bar: MyCustomScalarValue): MyCustomScalarValue @delegate(schema: \"special\", path: \"custom_scalar(bar: $arguments:bar)\") }") + .AddSchemaConfiguration(c => { + c.RegisterType(); + })); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + IExecutionResult result = null; + + // act + using (IServiceScope scope = services.CreateScope()) + { + IQueryRequest request = + QueryRequestBuilder.New() + .SetQuery("{ custom_scalar_stitched(bar: \"2019-11-11\") }") + .SetServices(scope.ServiceProvider) + .Create(); + + result = await executor.ExecuteAsync(request); + } + + // assert + result.MatchSnapshot(); + } + + [Fact] + public async Task Custom_Scalar_Delegated_Input_Argument() + { + // arrange + IHttpClientFactory clientFactory = CreateRemoteSchemas(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(clientFactory); + serviceCollection.AddStitchedSchema(builder => builder + .AddSchemaFromHttp("special") + .AddExtensionsFromString("extend type Query { custom_scalar_complex_stitched(bar: CustomInputValueInput): MyCustomScalarValue @delegate(schema: \"special\", path: \"custom_scalar_complex(bar: $arguments:bar)\") }") + .AddSchemaConfiguration(c => { + c.RegisterType(); + })); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + IExecutionResult result = null; + + // act + using (IServiceScope scope = services.CreateScope()) + { + IQueryRequest request = + QueryRequestBuilder.New() + .SetQuery("query ($bar: CustomInputValueInput) { custom_scalar_complex_stitched(bar: $bar) }") + .SetVariableValue("bar", new Dictionary { { "from", "2019-11-11" }, { "to", "2019-11-17" } }) + .SetServices(scope.ServiceProvider) + .Create(); + + result = await executor.ExecuteAsync(request); + } + + // assert + result.MatchSnapshot(); + } + + [Fact] + public async Task Custom_Scalar_Delegated_Input_Argument_Unstitched() + { + // arrange + IHttpClientFactory clientFactory = CreateRemoteSchemas(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(clientFactory); + serviceCollection.AddStitchedSchema(builder => builder + .AddSchemaFromHttp("special") + .AddSchemaConfiguration(c => { + c.RegisterType(); + })); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + IExecutionResult result = null; + + // act + using (IServiceScope scope = services.CreateScope()) + { + IQueryRequest request = + QueryRequestBuilder.New() + .SetQuery("query ($bar: CustomInputValueInput) { custom_scalar_complex(bar: $bar) }") + .SetVariableValue("bar", new Dictionary { { "from", "2019-11-11" }, { "to", "2019-11-17" } }) + .SetServices(scope.ServiceProvider) + .Create(); + + result = await executor.ExecuteAsync(request); + } + + // assert + result.MatchSnapshot(); + } + + protected override IHttpClientFactory CreateRemoteSchemas( + Dictionary connections) + { + TestServer server_contracts = TestServerFactory.Create( + ContractSchemaFactory.ConfigureServices, + app => app.UseGraphQL()); + + TestServer server_customers = TestServerFactory.Create( + CustomerSchemaFactory.ConfigureServices, + app => app.UseGraphQL()); + + TestServer server_special = TestServerFactory.Create( + SpecialCasesSchemaFactory.ConfigureServices, + app => app.UseGraphQL()); + + connections["contract"] = server_contracts.CreateClient(); + connections["customer"] = server_customers.CreateClient(); + connections["special"] = server_special.CreateClient(); + + var httpClientFactory = new Mock(); + httpClientFactory.Setup(t => t.CreateClient(It.IsAny())) + .Returns(new Func(n => + { + if (connections.ContainsKey(n)) + { + return connections[n]; + } + + throw new Exception(); + })); + + return httpClientFactory.Object; + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/SchemaCreationTests.txt b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/SchemaCreationTests.txt new file mode 100644 index 00000000000..642cd6018a4 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/SchemaCreationTests.txt @@ -0,0 +1,46 @@ +using System; +using System.Net.Http; +using Microsoft.Extensions.DependencyInjection; +using Xunit; +using HotChocolate.Execution; +using HotChocolate.AspNetCore.Tests.Utilities; + +namespace HotChocolate.Stitching +{ + public class SchemaCreationTests + : StitchingTestBase + { + public SchemaCreationTests(TestServerFactory testServerFactory) + : base(testServerFactory) + { + } + + [InlineData(SchemaCreation.OnFirstRequest, "LazyQueryExecutor")] + [InlineData(SchemaCreation.OnStartup, "QueryExecutor")] + [Theory] + public void QueryExecutorOptions( + SchemaCreation creation, + string executorName) + { + // arrange + IHttpClientFactory clientFactory = CreateRemoteSchemas(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(clientFactory); + + // act + serviceCollection.AddStitchedSchema(b => b + .AddSchemaFromHttp("contract") + .SetSchemaCreation(creation)); + + // assert + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + + Assert.Equal(executorName, executor.GetType().Name); + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/SchemaMergeTests.txt b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/SchemaMergeTests.txt new file mode 100644 index 00000000000..08c905e780f --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/SchemaMergeTests.txt @@ -0,0 +1,126 @@ + +using System; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Xunit; +using HotChocolate.Execution; +using HotChocolate.AspNetCore.Tests.Utilities; + +namespace HotChocolate.Stitching +{ + public class SchemaMergeTests + : StitchingTestBase + { + public SchemaMergeTests(TestServerFactory testServerFactory) + : base(testServerFactory) + { + } + + [Fact] + public async Task IsDirectiveMergeRuleTriggered() + { + // arrange + IHttpClientFactory clientFactory = CreateRemoteSchemas(); + + bool triggered = false; + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(clientFactory); + serviceCollection.AddStitchedSchema(builder => builder + .AddSchemaFromHttp("contract") + .AddSchemaFromHttp("customer") + .AddDirectiveMergeRule(next => (c, d) => + { + triggered = true; + next(c, d); + })); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + IExecutionResult result = null; + + // act + using (IServiceScope scope = services.CreateScope()) + { + // some query to trigger the merging of the schemas + IQueryRequest request = + QueryRequestBuilder.New() + .SetQuery($@" + query a($contractId: ID!) {{ + contract(contractId: $contractId) {{ + ... on LifeInsuranceContract {{ + id + }} + }} + }}") + .SetVariableValue( + "contractId", + "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQx") + .SetServices(scope.ServiceProvider) + .Create(); + + result = await executor.ExecuteAsync(request); + } + + // assert + Assert.True(triggered); + } + + [Fact] + public async Task IsTypeMergeRuleTriggered() + { + // arrange + IHttpClientFactory clientFactory = CreateRemoteSchemas(); + + bool triggered = false; + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(clientFactory); + serviceCollection.AddStitchedSchema(builder => builder + .AddSchemaFromHttp("contract") + .AddSchemaFromHttp("customer") + .AddTypeMergeRule(next => (c, t) => + { + triggered = true; + next(c, t); + })); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + IExecutionResult result = null; + + // act + using (IServiceScope scope = services.CreateScope()) + { + // some query to trigger the merging of the schemas + IQueryRequest request = + QueryRequestBuilder.New() + .SetQuery($@" + query a($contractId: ID!) {{ + contract(contractId: $contractId) {{ + ... on LifeInsuranceContract {{ + id + }} + }} + }}") + .SetVariableValue( + "contractId", + "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQx") + .SetServices(scope.ServiceProvider) + .Create(); + + result = await executor.ExecuteAsync(request); + } + + // assert + Assert.True(triggered); + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/StitchingTestBase.txt b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/StitchingTestBase.txt new file mode 100644 index 00000000000..6fc4ac8bdba --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/StitchingTestBase.txt @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using Microsoft.AspNetCore.TestHost; +using Moq; +using Xunit; +using HotChocolate.AspNetCore; +using HotChocolate.Stitching.Schemas.Contracts; +using HotChocolate.Stitching.Schemas.Customers; +using HotChocolate.AspNetCore.Tests.Utilities; + +namespace HotChocolate.Stitching +{ + public class StitchingTestBase + : IClassFixture + { + public StitchingTestBase( + TestServerFactory testServerFactory) + { + TestServerFactory = testServerFactory; + } + + protected TestServerFactory TestServerFactory { get; set; } + + protected IHttpClientFactory CreateRemoteSchemas() + { + return CreateRemoteSchemas(new Dictionary()); + } + + protected virtual IHttpClientFactory CreateRemoteSchemas( + Dictionary connections) + { + TestServer server_contracts = TestServerFactory.Create( + ContractSchemaFactory.ConfigureServices, + app => app.UseGraphQL()); + + TestServer server_customers = TestServerFactory.Create( + CustomerSchemaFactory.ConfigureServices, + app => app.UseGraphQL()); + + connections["contract"] = server_contracts.CreateClient(); + connections["customer"] = server_customers.CreateClient(); + + var httpClientFactory = new Mock(); + httpClientFactory.Setup(t => t.CreateClient(It.IsAny())) + .Returns(new Func(n => + { + if (connections.ContainsKey(n)) + { + return connections[n]; + } + + throw new Exception(); + })); + + return httpClientFactory.Object; + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/TypeExtensionTests.txt b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/TypeExtensionTests.txt new file mode 100644 index 00000000000..1f7b2ca9074 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/TypeExtensionTests.txt @@ -0,0 +1,102 @@ +using System; +using System.Net.Http; +using Xunit; +using Snapshooter.Xunit; +using Microsoft.Extensions.DependencyInjection; +using HotChocolate.Execution; +using HotChocolate.Types; +using System.Threading.Tasks; +using HotChocolate.AspNetCore.Tests.Utilities; + +namespace HotChocolate.Stitching +{ + public class TypeExtensionTests + : StitchingTestBase + { + public TypeExtensionTests(TestServerFactory testServerFactory) + : base(testServerFactory) + { + } + + [Fact] + public async Task AddObjectTypeExtension() + { + // arrange + IHttpClientFactory clientFactory = CreateRemoteSchemas(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(clientFactory); + serviceCollection.AddStitchedSchema(builder => + builder.AddSchemaFromHttp("contract") + .AddSchemaFromHttp("customer") + .AddSchemaConfiguration(c => c + .RegisterType() + .RegisterType(new ObjectTypeExtension(d => d + .Name("Query") + .Field("foo") + .Type() + .Resolver("bar"))))); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + IExecutionResult result = null; + + // act + using (IServiceScope scope = services.CreateScope()) + { + result = await executor.ExecuteAsync( + QueryRequestBuilder.New() + .SetQuery("{ foo }") + .SetServices(scope.ServiceProvider) + .Create()); + } + + // assert + Snapshot.Match(result); + } + + [Fact] + public async Task UseSchemaBuilder() + { + // arrange + IHttpClientFactory clientFactory = CreateRemoteSchemas(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(clientFactory); + serviceCollection.AddStitchedSchema(builder => + builder.AddSchemaFromHttp("contract") + .AddSchemaFromHttp("customer") + .AddSchemaConfiguration(c => c + .RegisterType() + .Extend().OnBeforeBuild(b => b.AddType( + new ObjectTypeExtension(d => d + .Name("Query") + .Field("foo") + .Type() + .Resolver("bar")))))); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + IExecutionResult result = null; + + // act + using (IServiceScope scope = services.CreateScope()) + { + result = await executor.ExecuteAsync( + QueryRequestBuilder.New() + .SetQuery("{ foo }") + .SetServices(scope.ServiceProvider) + .Create()); + } + + // assert + Snapshot.Match(result); + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/TypeRewriterTests.txt b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/TypeRewriterTests.txt new file mode 100644 index 00000000000..c601ebcbefc --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/TypeRewriterTests.txt @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using Xunit; +using HotChocolate.AspNetCore; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using HotChocolate.Execution; +using Snapshooter.Xunit; +using Moq; +using Microsoft.AspNetCore.TestHost; +using HotChocolate.Stitching.Merge.Rewriters; +using HotChocolate.Language; +using HotChocolate.Stitching.Merge; +using HotChocolate.Stitching.Delegation; +using System.Linq; +using Snapshooter; +using HotChocolate.AspNetCore.Tests.Utilities; + +namespace HotChocolate.Stitching +{ + public class TypeRewriterTests + : StitchingTestBase + { + public TypeRewriterTests(TestServerFactory testServerFactory) + : base(testServerFactory) + { + } + + [Fact] + public async Task CustomRewriterTakesPriority() + { + // arrange + var serviceCollection = new ServiceCollection(); + + var connections = new Dictionary(); + serviceCollection.AddSingleton(CreateRemoteSchemas(connections)); + + serviceCollection.AddStitchedSchema(builder => builder + .AddSchemaFromHttp("someSchema") + .AddTypeRewriter(new TypeRewriter())); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + + // act + IExecutionResult result = null; + + using (IServiceScope scope = services.CreateScope()) + { + IQueryRequest request = QueryRequestBuilder.New() + .SetQuery("{ foo }") + .AddProperty("foo_a", "bar") + .SetServices(scope.ServiceProvider) + .Create(); + + result = await executor.ExecuteAsync(request); + } + + // assert + result.MatchSnapshot(new SnapshotNameExtension("result")); + executor.Schema.ToString().MatchSnapshot( + new SnapshotNameExtension("schema")); + } + protected override IHttpClientFactory CreateRemoteSchemas( + Dictionary connections) + { + TestServer server = TestServerFactory.Create( + services => services.AddGraphQL( + SchemaBuilder.New() + .AddDocumentFromString( + "type Query { foo(a: String): String }") + .AddResolver("Query", "foo", c => c.Argument("a")) + .Create()), + app => app.UseGraphQL()); + + connections["someSchema"] = server.CreateClient(); + + var httpClientFactory = new Mock(); + httpClientFactory.Setup(t => t.CreateClient(It.IsAny())) + .Returns(new Func(n => + { + if (connections.ContainsKey(n)) + { + return connections[n]; + } + + throw new Exception(); + })); + return httpClientFactory.Object; + } + + public class TypeRewriter + : ITypeRewriter + { + public ITypeDefinitionNode Rewrite( + ISchemaInfo schema, + ITypeDefinitionNode typeDefinition) + { + if (typeDefinition.Name.Value.Equals("Query") + && typeDefinition is ObjectTypeDefinitionNode objectType) + { + var path = new SelectionPathComponent( + new NameNode("foo"), + new[] + { + new ArgumentNode( + "a", + new ScopedVariableNode( + ScopeNames.ContextData, + "foo_a")) + }); + + Dictionary fields = + objectType.Fields.ToDictionary(t => t.Name.Value); + fields["foo"] = fields["foo"] + .WithArguments(Array.Empty()) + .AddDelegationPath("someSchema", path); + return objectType.WithFields(fields.Values.ToArray()); + } + return typeDefinition; + } + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/TypeSystemDirectivesTests.txt b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/TypeSystemDirectivesTests.txt new file mode 100644 index 00000000000..9f32603a517 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/TypeSystemDirectivesTests.txt @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using Xunit; +using HotChocolate.AspNetCore; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using HotChocolate.Execution; +using Snapshooter.Xunit; +using Moq; +using Microsoft.AspNetCore.TestHost; +using Snapshooter; +using HotChocolate.AspNetCore.Tests.Utilities; + +namespace HotChocolate.Stitching +{ + public class TypeSystemDirectivesTests + : StitchingTestBase + { + public TypeSystemDirectivesTests(TestServerFactory testServerFactory) + : base(testServerFactory) + { + } + + [Fact] + public async Task SourceSchemaHasTypeSystemDirectives() + { + // arrange + var serviceCollection = new ServiceCollection(); + + var connections = new Dictionary(); + serviceCollection.AddSingleton(CreateRemoteSchemas(connections)); + + serviceCollection.AddStitchedSchema(builder => builder + .AddSchemaFromString( + "server_1", + @" + type Query { foo: String @bar } + directive @bar on FIELD_DEFINITION + ")); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + + // act + IExecutionResult result = null; + + using (IServiceScope scope = services.CreateScope()) + { + IQueryRequest request = QueryRequestBuilder.New() + .SetQuery("{ foo }") + .SetServices(scope.ServiceProvider) + .Create(); + + result = await executor.ExecuteAsync(request); + } + + // assert + result.MatchSnapshot(new SnapshotNameExtension("result")); + executor.Schema.ToString().MatchSnapshot( + new SnapshotNameExtension("schema")); + } + + protected override IHttpClientFactory CreateRemoteSchemas( + Dictionary connections) + { + TestServer server_1 = TestServerFactory.Create( + services => services.AddGraphQL( + SchemaBuilder.New() + .AddDocumentFromString + ( + @" + type Query { foo: String @bar } + directive @bar on FIELD_DEFINITION + " + ) + .AddResolver("Query", "foo", "bar") + .Create()), + app => app.UseGraphQL()); + + connections["server_1"] = server_1.CreateClient(); + + var httpClientFactory = new Mock(); + httpClientFactory.Setup(t => t.CreateClient(It.IsAny())) + .Returns(new Func(n => + { + if (connections.ContainsKey(n)) + { + return connections[n]; + } + + throw new Exception(); + })); + return httpClientFactory.Object; + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/VariableDelegationTests.txt b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/VariableDelegationTests.txt new file mode 100644 index 00000000000..05add464cd9 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/VariableDelegationTests.txt @@ -0,0 +1,167 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using Xunit; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using HotChocolate.Execution; +using Snapshooter.Xunit; +using Snapshooter; +using HotChocolate.AspNetCore.Tests.Utilities; + +namespace HotChocolate.Stitching +{ + public class VariableDelegationTests + : StitchingTestBase + { + public VariableDelegationTests(TestServerFactory testServerFactory) + : base(testServerFactory) + { + } + + [Fact] + public async Task ListVariableIsCorrectlyPassed() + { + // arrange + var serviceCollection = new ServiceCollection(); + + var connections = new Dictionary(); + serviceCollection.AddSingleton(CreateRemoteSchemas(connections)); + + serviceCollection.AddStitchedSchema(builder => builder + .AddSchemaFromHttp("contract") + .AddSchemaFromHttp("customer")); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + + // act + IExecutionResult result = null; + + using (IServiceScope scope = services.CreateScope()) + { + IQueryRequest request = QueryRequestBuilder.New() + .SetQuery("query foo ($ids: [ID!]!) " + + "{ customers(ids: $ids) { id } }") + .SetServices(scope.ServiceProvider) + .SetVariableValue("ids", new List + { + "Q3VzdG9tZXIKZDE=", + "Q3VzdG9tZXIKZDE=" + }) + .Create(); + + result = await executor.ExecuteAsync(request); + } + + // assert + result.MatchSnapshot(new SnapshotNameExtension("result")); + executor.Schema.ToString().MatchSnapshot( + new SnapshotNameExtension("schema")); + } + + [Fact] + public async Task ScopedListVariableIsCorrectlyPassed() + { + // arrange + var serviceCollection = new ServiceCollection(); + + var connections = new Dictionary(); + serviceCollection.AddSingleton(CreateRemoteSchemas(connections)); + + serviceCollection.AddStitchedSchema(builder => builder + .AddSchemaFromHttp("contract") + .AddSchemaFromHttp("customer") + .AddExtensionsFromString( + @" + extend type Query { + allCustomers: [Customer!] + @delegate( + path: ""customers(ids: $contextData:ids)"" + schema: ""customer"") + } + ")); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + + // act + IExecutionResult result = null; + + using (IServiceScope scope = services.CreateScope()) + { + IQueryRequest request = QueryRequestBuilder.New() + .SetQuery("query foo { allCustomers { id } }") + .SetServices(scope.ServiceProvider) + .SetProperty("ids", new List + { + "Q3VzdG9tZXIKZDE=", + "Q3VzdG9tZXIKZDE=" + }) + .Create(); + + result = await executor.ExecuteAsync(request); + } + + // assert + result.MatchSnapshot(new SnapshotNameExtension("result")); + executor.Schema.ToString().MatchSnapshot( + new SnapshotNameExtension("schema")); + } + + [Fact] + public async Task ObjectFieldVariableIsCorrectlyPassed() + { + // arrange + var serviceCollection = new ServiceCollection(); + + var connections = new Dictionary(); + serviceCollection.AddSingleton(CreateRemoteSchemas(connections)); + + serviceCollection.AddStitchedSchema(builder => builder + .AddSchemaFromHttp("contract") + .AddSchemaFromHttp("customer")); + + IServiceProvider services = + serviceCollection.BuildServiceProvider(); + + IQueryExecutor executor = services + .GetRequiredService(); + + // act + IExecutionResult result = null; + + using (IServiceScope scope = services.CreateScope()) + { + IQueryRequest request = QueryRequestBuilder.New() + .SetQuery(@" + mutation createCustomer($name: String!) { + createCustomer(input: { name: $name }) + { + customer { + name + kind + } + } + }") + .SetServices(scope.ServiceProvider) + .SetVariableValue("name", "someName") + .Create(); + + result = await executor.ExecuteAsync(request); + Console.WriteLine(result); + } + + // assert + result.MatchSnapshot(new SnapshotNameExtension("result")); + executor.Schema.ToString().MatchSnapshot( + new SnapshotNameExtension("schema")); + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.AddErrorFilter.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.AddErrorFilter.snap new file mode 100644 index 00000000000..83a188fe54c --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.AddErrorFilter.snap @@ -0,0 +1,93 @@ +{ + "Data": { + "customer": { + "contracts": [ + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQx", + "error": null + }, + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQy", + "error": null + }, + { + "id": "U29tZU90aGVyQ29udHJhY3QKZDE=" + } + ] + } + }, + "Errors": [ + { + "Message": "Error_Message", + "Code": "ERROR_CODE", + "Path": [ + "customer", + "contracts", + "error" + ], + "Locations": [ + { + "Line": 4, + "Column": 33 + } + ], + "Exception": null, + "Extensions": { + "code": "ERROR_CODE", + "remote": { + "Message": "Error_Message", + "Code": "ERROR_CODE", + "Path": [ + "contracts", + 0, + "error" + ], + "Locations": [], + "Exception": null, + "Extensions": { + "code": "ERROR_CODE" + } + }, + "schemaName": "contract", + "STITCH": "SOMETHING" + } + }, + { + "Message": "Error_Message", + "Code": "ERROR_CODE", + "Path": [ + "customer", + "contracts", + "error" + ], + "Locations": [ + { + "Line": 4, + "Column": 33 + } + ], + "Exception": null, + "Extensions": { + "code": "ERROR_CODE", + "remote": { + "Message": "Error_Message", + "Code": "ERROR_CODE", + "Path": [ + "contracts", + 1, + "error" + ], + "Locations": [], + "Exception": null, + "Extensions": { + "code": "ERROR_CODE" + } + }, + "schemaName": "contract", + "STITCH": "SOMETHING" + } + } + ], + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.CustomDirectiveIsPassedOn.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.CustomDirectiveIsPassedOn.snap new file mode 100644 index 00000000000..f1f2fde14ef --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.CustomDirectiveIsPassedOn.snap @@ -0,0 +1,9 @@ +{ + "Data": { + "a": "2018-01-01T01:00:00.000Z", + "b": "2020-09-01T01:00:00.000Z" + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.DateTimeIsHandledCorrectly.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.DateTimeIsHandledCorrectly.snap new file mode 100644 index 00000000000..cbd7d9ca243 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.DateTimeIsHandledCorrectly.snap @@ -0,0 +1,10 @@ +{ + "Data": { + "a": "2018-01-01T01:00:00.000Z", + "b": "2019-01-01T01:00:00.000Z", + "c": "2020-09-01T01:00:00.000Z" + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.DelegateWithGuidFieldArgument.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.DelegateWithGuidFieldArgument.snap new file mode 100644 index 00000000000..4d22d403877 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.DelegateWithGuidFieldArgument.snap @@ -0,0 +1,10 @@ +{ + "Data": { + "customer": { + "guid": "01e2f5dc0f19430599d33c5c234a6524" + } + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.DelegateWithIntFieldArgument.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.DelegateWithIntFieldArgument.snap new file mode 100644 index 00000000000..03c8f2064a3 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.DelegateWithIntFieldArgument.snap @@ -0,0 +1,10 @@ +{ + "Data": { + "customer": { + "int": 1 + } + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchedQueryBuilder.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchedQueryBuilder.snap new file mode 100644 index 00000000000..2919175a263 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchedQueryBuilder.snap @@ -0,0 +1,35 @@ +{ + "Data": { + "a": { + "bar": "Freddy Freeman", + "contracts": [ + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQx" + }, + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQy" + }, + { + "id": "U29tZU90aGVyQ29udHJhY3QKZDE=" + } + ] + }, + "b": { + "foo": "Freddy Freeman", + "contracts": [ + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQx" + }, + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQy" + }, + { + "id": "U29tZU90aGVyQ29udHJhY3QKZDE=" + } + ] + } + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchedQueryBuilderVariableArguments.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchedQueryBuilderVariableArguments.snap new file mode 100644 index 00000000000..e27a5c5bb37 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchedQueryBuilderVariableArguments.snap @@ -0,0 +1,23 @@ +{ + "Data": { + "contracts": [ + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQx", + "customerId": "1", + "foo": "this variable is passed to remote query!" + }, + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQy", + "customerId": "1", + "foo": "this variable is passed to remote query!" + }, + { + "id": "U29tZU90aGVyQ29udHJhY3QKZDE=", + "customerId": "1" + } + ] + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchedQueryBuilderWithLocalSchema.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchedQueryBuilderWithLocalSchema.snap new file mode 100644 index 00000000000..f19467bbd54 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchedQueryBuilderWithLocalSchema.snap @@ -0,0 +1,25 @@ +{ + "Data": { + "a": { + "bar": "Freddy Freeman", + "contracts": [ + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQx", + "premium": 123456.11 + }, + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQy", + "premium": 456789.12 + }, + { + "id": "U29tZU90aGVyQ29udHJhY3QKZDE=", + "expiryDate": "2015-02-01T00:00:00.000Z" + } + ] + }, + "hello": "Hello World" + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchedQueryBuilderWithRenamedType.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchedQueryBuilderWithRenamedType.snap new file mode 100644 index 00000000000..2dff4ba80ba --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchedQueryBuilderWithRenamedType.snap @@ -0,0 +1,24 @@ +{ + "Data": { + "a": { + "bar": "Freddy Freeman", + "contracts": [ + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQx", + "premium": 123456.11 + }, + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQy", + "premium": 456789.12 + }, + { + "id": "U29tZU90aGVyQ29udHJhY3QKZDE=", + "expiryDate": "2015-02-01T00:00:00.000Z" + } + ] + } + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchedQueryWithComputedField.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchedQueryWithComputedField.snap new file mode 100644 index 00000000000..06c973c6402 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchedQueryWithComputedField.snap @@ -0,0 +1,10 @@ +{ + "Data": { + "customer": { + "foo": "Freddy Freeman_Q3VzdG9tZXIKZDE=" + } + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchingQueryDeepObjectPath.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchingQueryDeepObjectPath.snap new file mode 100644 index 00000000000..826b29be597 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchingQueryDeepObjectPath.snap @@ -0,0 +1,10 @@ +{ + "Data": { + "consultant": { + "name": "Jordan Belfort" + } + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchingQueryDeepScalarPath.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchingQueryDeepScalarPath.snap new file mode 100644 index 00000000000..d4772cd2a88 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchingQueryDeepScalarPath.snap @@ -0,0 +1,8 @@ +{ + "Data": { + "consultantName": "Jordan Belfort" + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchingQueryWithArguments.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchingQueryWithArguments.snap new file mode 100644 index 00000000000..c55ca18982e --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchingQueryWithArguments.snap @@ -0,0 +1,18 @@ +{ + "Data": { + "contracts": [ + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQx" + }, + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQy" + }, + { + "id": "U29tZU90aGVyQ29udHJhY3QKZDE=" + } + ] + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchingQueryWithFragmentDefinition.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchingQueryWithFragmentDefinition.snap new file mode 100644 index 00000000000..fc1cfdddcf2 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchingQueryWithFragmentDefinition.snap @@ -0,0 +1,27 @@ +{ + "Data": { + "customer": { + "name": "Freddy Freeman", + "consultant": { + "name": "Jordan Belfort" + }, + "contracts": [ + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQx", + "premium": 123456.11 + }, + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQy", + "premium": 456789.12 + }, + { + "id": "U29tZU90aGVyQ29udHJhY3QKZDE=", + "expiryDate": "2015-02-01T00:00:00.000Z" + } + ] + } + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchingQueryWithInlineFragment.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchingQueryWithInlineFragment.snap new file mode 100644 index 00000000000..fc1cfdddcf2 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchingQueryWithInlineFragment.snap @@ -0,0 +1,27 @@ +{ + "Data": { + "customer": { + "name": "Freddy Freeman", + "consultant": { + "name": "Jordan Belfort" + }, + "contracts": [ + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQx", + "premium": 123456.11 + }, + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQy", + "premium": 456789.12 + }, + { + "id": "U29tZU90aGVyQ29udHJhY3QKZDE=", + "expiryDate": "2015-02-01T00:00:00.000Z" + } + ] + } + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchingQueryWithUnion.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchingQueryWithUnion.snap new file mode 100644 index 00000000000..d201e47046f --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchingQueryWithUnion.snap @@ -0,0 +1,30 @@ +{ + "Data": { + "customer": { + "name": "Freddy Freeman", + "consultant": { + "name": "Jordan Belfort" + }, + "contracts": [ + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQx", + "premium": 123456.11 + }, + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQy", + "premium": 456789.12 + }, + { + "id": "U29tZU90aGVyQ29udHJhY3QKZDE=", + "expiryDate": "2015-02-01T00:00:00.000Z" + } + ] + }, + "consultant": { + "name": "Jordan Belfort" + } + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchingQueryWithVariables.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchingQueryWithVariables.snap new file mode 100644 index 00000000000..574a3406f17 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExecuteStitchingQueryWithVariables.snap @@ -0,0 +1,28 @@ +{ + "Data": { + "customer": { + "name": "Freddy Freeman", + "consultant": { + "name": "Jordan Belfort" + }, + "complexArg": "", + "contracts": [ + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQx", + "premium": 123456.11 + }, + { + "id": "TGlmZUluc3VyYW5jZUNvbnRyYWN0CmQy", + "premium": 456789.12 + }, + { + "id": "U29tZU90aGVyQ29udHJhY3QKZDE=", + "expiryDate": "2015-02-01T00:00:00.000Z" + } + ] + } + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExtendedScalarAsInAndOutputType.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExtendedScalarAsInAndOutputType.snap new file mode 100644 index 00000000000..247febeb456 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ExtendedScalarAsInAndOutputType.snap @@ -0,0 +1,9 @@ +{ + "Data": { + "a": "2018-01-01T01:00:00.000Z", + "b": "2019-01-01T01:00:00.000Z" + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.HttpErrorsHavePathSet.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.HttpErrorsHavePathSet.snap new file mode 100644 index 00000000000..360869a0c68 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.HttpErrorsHavePathSet.snap @@ -0,0 +1,45 @@ +{ + "Data": { + "customer": null + }, + "Errors": [ + { + "Message": "Unexpected Execution Error", + "Code": "STITCHING_HTTP_REQUEST_EXCEPTION", + "Path": [ + "customer" + ], + "Locations": [ + { + "Line": 3, + "Column": 29 + } + ], + "Exception": { + "StackTrace": " at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()\n at HotChocolate.Stitching.Utilities.HttpQueryClient.FetchInternalAsync(HttpContent requestContent, HttpClient httpClient) in /Users/michael/local/hc/src/HotChocolate/Stitching/src/Stitching/Utilities/HttpQueryClient.cs:line 151\n at HotChocolate.Stitching.Utilities.HttpQueryClient.FetchAsync(IQueryRequest request, HttpContent requestContent, HttpClient httpClient, IEnumerable`1 interceptors, CancellationToken cancellationToken) in /Users/michael/local/hc/src/HotChocolate/Stitching/src/Stitching/Utilities/HttpQueryClient.cs:line 69\n at HotChocolate.Stitching.Utilities.HttpQueryClient.FetchAsync(IQueryRequest request, HttpClient httpClient, IEnumerable`1 interceptors, CancellationToken cancellationToken) in /Users/michael/local/hc/src/HotChocolate/Stitching/src/Stitching/Utilities/HttpQueryClient.cs:line 53\n at HotChocolate.Stitching.Delegation.RemoteQueryMiddleware.InvokeAsync(IQueryContext context) in /Users/michael/local/hc/src/HotChocolate/Stitching/src/Stitching/Delegation/RemoteQueryMiddleware.cs:line 39", + "Message": "Response status code does not indicate success: 503 (Service Unavailable).", + "Data": {}, + "InnerException": null, + "HelpLink": null, + "Source": "System.Net.Http", + "HResult": -2146233088 + }, + "Extensions": { + "code": "STITCHING_HTTP_REQUEST_EXCEPTION", + "remote": { + "Message": "Unexpected Execution Error", + "Code": "STITCHING_HTTP_REQUEST_EXCEPTION", + "Path": null, + "Locations": [], + "Exception": null, + "Extensions": { + "code": "STITCHING_HTTP_REQUEST_EXCEPTION" + } + }, + "schemaName": "customer" + } + } + ], + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.Query_WithEnumArgument_EnumIsCorrectlyPassed.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.Query_WithEnumArgument_EnumIsCorrectlyPassed.snap new file mode 100644 index 00000000000..dcf00b2f3e8 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.Query_WithEnumArgument_EnumIsCorrectlyPassed.snap @@ -0,0 +1,15 @@ +{ + "Data": { + "standard": { + "id": "Q3VzdG9tZXIKZDI=", + "kind": "STANDARD" + }, + "premium": { + "id": "Q3VzdG9tZXIKZDE=", + "kind": "PREMIUM" + } + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ReplaceField.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ReplaceField.snap new file mode 100644 index 00000000000..700a34d4395 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.ReplaceField.snap @@ -0,0 +1,10 @@ +{ + "Data": { + "a": { + "name": "Far far away 1" + } + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.StitchedMutation.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.StitchedMutation.snap new file mode 100644 index 00000000000..254f2207dc5 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.StitchedMutation.snap @@ -0,0 +1,13 @@ +{ + "Data": { + "createCustomer": { + "customer": { + "name": "a", + "contracts": [] + } + } + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.StitchedMutationWithRenamedFieldArgument.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.StitchedMutationWithRenamedFieldArgument.snap new file mode 100644 index 00000000000..254f2207dc5 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.StitchedMutationWithRenamedFieldArgument.snap @@ -0,0 +1,13 @@ +{ + "Data": { + "createCustomer": { + "customer": { + "name": "a", + "contracts": [] + } + } + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.StitchedMutationWithRenamedInputField.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.StitchedMutationWithRenamedInputField.snap new file mode 100644 index 00000000000..254f2207dc5 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.StitchedMutationWithRenamedInputField.snap @@ -0,0 +1,13 @@ +{ + "Data": { + "createCustomer": { + "customer": { + "name": "a", + "contracts": [] + } + } + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.StitchedMutationWithRenamedInputFieldInVariables.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.StitchedMutationWithRenamedInputFieldInVariables.snap new file mode 100644 index 00000000000..1da4b782edd --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.StitchedMutationWithRenamedInputFieldInVariables.snap @@ -0,0 +1,13 @@ +{ + "Data": { + "createCustomer": { + "customer": { + "name": "abc", + "contracts": [] + } + } + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.StitchedMutationWithRenamedInputFieldInVariablesList.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.StitchedMutationWithRenamedInputFieldInVariablesList.snap new file mode 100644 index 00000000000..58711e9155c --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.StitchedMutationWithRenamedInputFieldInVariablesList.snap @@ -0,0 +1,21 @@ +{ + "Data": { + "createCustomers": [ + { + "customer": { + "name": "abc", + "contracts": [] + } + }, + { + "customer": { + "name": "def", + "contracts": [] + } + } + ] + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.StitchedMutationWithRenamedInputFieldList.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.StitchedMutationWithRenamedInputFieldList.snap new file mode 100644 index 00000000000..e2e4469f924 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.StitchedMutationWithRenamedInputFieldList.snap @@ -0,0 +1,21 @@ +{ + "Data": { + "createCustomers": [ + { + "customer": { + "name": "a", + "contracts": [] + } + }, + { + "customer": { + "name": "b", + "contracts": [] + } + } + ] + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.StitchedMutationWithRenamedInputType.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.StitchedMutationWithRenamedInputType.snap new file mode 100644 index 00000000000..254f2207dc5 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/DelegateToRemoteSchemaMiddlewareTests.StitchedMutationWithRenamedInputType.snap @@ -0,0 +1,13 @@ +{ + "Data": { + "createCustomer": { + "customer": { + "name": "a", + "contracts": [] + } + } + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ErrorBehaviour.ConnectionLost.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ErrorBehaviour.ConnectionLost.snap new file mode 100644 index 00000000000..bdc544d46f3 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ErrorBehaviour.ConnectionLost.snap @@ -0,0 +1,17 @@ +{ + "Data": { + "createCustomer": null + }, + "Extensions": {}, + "Errors": [ + { + "Message": "System.Net.Http.HttpRequestException", + "Code": null, + "Path": null, + "Locations": [], + "Exception": null, + "Extensions": {} + } + ], + "ContextData": {} +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/Errorbehavior.ConnectionLost.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/Errorbehavior.ConnectionLost.snap new file mode 100644 index 00000000000..bdc544d46f3 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/Errorbehavior.ConnectionLost.snap @@ -0,0 +1,17 @@ +{ + "Data": { + "createCustomer": null + }, + "Extensions": {}, + "Errors": [ + { + "Message": "System.Net.Http.HttpRequestException", + "Code": null, + "Path": null, + "Locations": [], + "Exception": null, + "Extensions": {} + } + ], + "ContextData": {} +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/HttpInterceptorTests.InterceptHttpRequestAndDelegateHeaders.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/HttpInterceptorTests.InterceptHttpRequestAndDelegateHeaders.snap new file mode 100644 index 00000000000..8c583cb438d --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/HttpInterceptorTests.InterceptHttpRequestAndDelegateHeaders.snap @@ -0,0 +1,10 @@ +{ + "Data": { + "customer": { + "inter": "bar" + } + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/InputObjectDelegationTests.AllowInputObjectTypesAsArguments_result.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/InputObjectDelegationTests.AllowInputObjectTypesAsArguments_result.snap new file mode 100644 index 00000000000..abc17eea785 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/InputObjectDelegationTests.AllowInputObjectTypesAsArguments_result.snap @@ -0,0 +1,8 @@ +{ + "Data": { + "baz": "String 123" + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/InputObjectDelegationTests.AllowInputObjectTypesAsArguments_schema.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/InputObjectDelegationTests.AllowInputObjectTypesAsArguments_schema.snap new file mode 100644 index 00000000000..748c38c36f3 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/InputObjectDelegationTests.AllowInputObjectTypesAsArguments_schema.snap @@ -0,0 +1,23 @@ +schema { + query: Query +} + +type Query { + baz(a: Bar): String @delegate(schema: "server_1", path: "foo(a:$arguments:a)") + foo(a: Bar): String @delegate(schema: "server_1") +} + +input Bar @source(name: "Bar", schema: "server_1") { + a: String +} + +directive @delegate(path: String "The name of the schema to which this field shall be delegated to." schema: Name!) on FIELD_DEFINITION + +"Annotates the original name of a type." +directive @source("The original name of the annotated type." name: Name! "The name of the schema to which this type belongs to." schema: Name!) repeatable on ENUM | OBJECT | INTERFACE | UNION | INPUT_OBJECT | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION | ENUM_VALUE + +"The name scalar represents a valid GraphQL name as specified in the spec and can be used to refer to fields or types." +scalar Name + +"The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text." +scalar String diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Custom_Scalar_Delegated_Argument.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Custom_Scalar_Delegated_Argument.snap new file mode 100644 index 00000000000..092e327bdd3 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Custom_Scalar_Delegated_Argument.snap @@ -0,0 +1,8 @@ +{ + "Data": { + "custom_scalar_stitched": "2019-11-11" + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Custom_Scalar_Delegated_Input_Argument.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Custom_Scalar_Delegated_Input_Argument.snap new file mode 100644 index 00000000000..708b9eb7006 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Custom_Scalar_Delegated_Input_Argument.snap @@ -0,0 +1,8 @@ +{ + "Data": { + "custom_scalar_complex_stitched": "2019-11-11-2019-11-17" + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Custom_Scalar_Delegated_Input_Argument_Unstitched.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Custom_Scalar_Delegated_Input_Argument_Unstitched.snap new file mode 100644 index 00000000000..8bd9f5872b2 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Custom_Scalar_Delegated_Input_Argument_Unstitched.snap @@ -0,0 +1,8 @@ +{ + "Data": { + "custom_scalar_complex": "2019-11-11-2019-11-17" + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Custom_Scalar_Types.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Custom_Scalar_Types.snap new file mode 100644 index 00000000000..dac8b32d19a --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Custom_Scalar_Types.snap @@ -0,0 +1,8 @@ +{ + "Data": { + "custom_scalar": "custom_string" + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_byte_field.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_byte_field.snap new file mode 100644 index 00000000000..c81e907d753 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_byte_field.snap @@ -0,0 +1,10 @@ +{ + "Data": { + "contract": { + "byte_field": 123 + } + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_date_field.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_date_field.snap new file mode 100644 index 00000000000..b23422c00ac --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_date_field.snap @@ -0,0 +1,10 @@ +{ + "Data": { + "contract": { + "date_field": "2018-05-17" + } + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_date_time_field.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_date_time_field.snap new file mode 100644 index 00000000000..7b819a85cea --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_date_time_field.snap @@ -0,0 +1,10 @@ +{ + "Data": { + "contract": { + "date_time_field": "2018-05-17T08:59:00.000Z" + } + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_decimal_field.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_decimal_field.snap new file mode 100644 index 00000000000..d6ec7defe4e --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_decimal_field.snap @@ -0,0 +1,10 @@ +{ + "Data": { + "contract": { + "decimal_field": 123.123 + } + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_float_field.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_float_field.snap new file mode 100644 index 00000000000..54b4e4da657 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_float_field.snap @@ -0,0 +1,10 @@ +{ + "Data": { + "contract": { + "float_field": 123.123 + } + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_id_field.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_id_field.snap new file mode 100644 index 00000000000..fa7f3de0bef --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_id_field.snap @@ -0,0 +1,10 @@ +{ + "Data": { + "contract": { + "id_field": "abc_123" + } + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_int_field.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_int_field.snap new file mode 100644 index 00000000000..c7a14aeff21 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_int_field.snap @@ -0,0 +1,10 @@ +{ + "Data": { + "contract": { + "int_field": 123 + } + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_long_field.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_long_field.snap new file mode 100644 index 00000000000..547e79cc91a --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_long_field.snap @@ -0,0 +1,10 @@ +{ + "Data": { + "contract": { + "long_field": 123 + } + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_string_field.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_string_field.snap new file mode 100644 index 00000000000..483a2316d5a --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/ScalarTests.Scalar_Serializes_And_Deserializes_Correctly_string_field.snap @@ -0,0 +1,10 @@ +{ + "Data": { + "contract": { + "string_field": "abc" + } + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/TypeExtensionTests.AddObjectTypeExtension.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/TypeExtensionTests.AddObjectTypeExtension.snap new file mode 100644 index 00000000000..66cbea87738 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/TypeExtensionTests.AddObjectTypeExtension.snap @@ -0,0 +1,8 @@ +{ + "Data": { + "foo": "bar" + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/TypeExtensionTests.UseSchemaBuilder.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/TypeExtensionTests.UseSchemaBuilder.snap new file mode 100644 index 00000000000..66cbea87738 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/TypeExtensionTests.UseSchemaBuilder.snap @@ -0,0 +1,8 @@ +{ + "Data": { + "foo": "bar" + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/TypeRewriterTests.CustomRewriterTakesPriority_result.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/TypeRewriterTests.CustomRewriterTakesPriority_result.snap new file mode 100644 index 00000000000..66cbea87738 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/TypeRewriterTests.CustomRewriterTakesPriority_result.snap @@ -0,0 +1,8 @@ +{ + "Data": { + "foo": "bar" + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/TypeRewriterTests.CustomRewriterTakesPriority_schema.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/TypeRewriterTests.CustomRewriterTakesPriority_schema.snap new file mode 100644 index 00000000000..068285c3edf --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/TypeRewriterTests.CustomRewriterTakesPriority_schema.snap @@ -0,0 +1,15 @@ +schema { + query: Query +} + +type Query { + foo: String @delegate(schema: "someSchema", path: "foo(a: $contextData:foo_a)") +} + +directive @delegate(path: String "The name of the schema to which this field shall be delegated to." schema: Name!) on FIELD_DEFINITION + +"The name scalar represents a valid GraphQL name as specified in the spec and can be used to refer to fields or types." +scalar Name + +"The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text." +scalar String diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/TypeSystemDirectivesTests.SourceSchemaHasTypeSystemDirectives_result.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/TypeSystemDirectivesTests.SourceSchemaHasTypeSystemDirectives_result.snap new file mode 100644 index 00000000000..66cbea87738 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/TypeSystemDirectivesTests.SourceSchemaHasTypeSystemDirectives_result.snap @@ -0,0 +1,8 @@ +{ + "Data": { + "foo": "bar" + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/TypeSystemDirectivesTests.SourceSchemaHasTypeSystemDirectives_schema.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/TypeSystemDirectivesTests.SourceSchemaHasTypeSystemDirectives_schema.snap new file mode 100644 index 00000000000..7f25f6bf1a1 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/TypeSystemDirectivesTests.SourceSchemaHasTypeSystemDirectives_schema.snap @@ -0,0 +1,17 @@ +schema { + query: Query +} + +type Query { + foo: String @bar @delegate(schema: "server_1") +} + +directive @bar on FIELD_DEFINITION + +directive @delegate(path: String "The name of the schema to which this field shall be delegated to." schema: Name!) on FIELD_DEFINITION + +"The name scalar represents a valid GraphQL name as specified in the spec and can be used to refer to fields or types." +scalar Name + +"The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text." +scalar String diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/VariableDelegationTests.ListVariableIsCorrectlyPassed_result.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/VariableDelegationTests.ListVariableIsCorrectlyPassed_result.snap new file mode 100644 index 00000000000..57bc5dbc5ea --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/VariableDelegationTests.ListVariableIsCorrectlyPassed_result.snap @@ -0,0 +1,15 @@ +{ + "Data": { + "customers": [ + { + "id": "Q3VzdG9tZXIKZDE=" + }, + { + "id": "Q3VzdG9tZXIKZDE=" + } + ] + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/VariableDelegationTests.ListVariableIsCorrectlyPassed_schema.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/VariableDelegationTests.ListVariableIsCorrectlyPassed_schema.snap new file mode 100644 index 00000000000..fec8748ff60 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/VariableDelegationTests.ListVariableIsCorrectlyPassed_schema.snap @@ -0,0 +1,179 @@ +schema { + query: Query + mutation: Mutation +} + +interface Contract @source(name: "Contract", schema: "contract") { + customerId: ID! + id: ID! +} + +type Consultant @source(name: "Consultant", schema: "customer") { + customers(after: String before: String first: PaginationAmount last: PaginationAmount): CustomerConnection + id: ID! + name: String! +} + +type CreateCustomerPayload @source(name: "CreateCustomerPayload", schema: "customer") { + customer: Customer +} + +type Customer @source(name: "Customer", schema: "customer") { + complexArg(arg: ComplexInputType): String + consultant(customer: CustomerInput): Consultant + id: ID! + kind: CustomerKind! + name: String! + say(input: SayInput!): String + someGuid: UUID! + someInt: Int! + street: String! +} + +"A connection to a list of items." +type CustomerConnection @source(name: "CustomerConnection", schema: "customer") { + "A list of edges." + edges: [CustomerEdge!] + "A flattened list of the nodes." + nodes: [Customer] + "Information to aid in pagination." + pageInfo: PageInfo! + totalCount: Int! +} + +"An edge in a connection." +type CustomerEdge @source(name: "CustomerEdge", schema: "customer") { + "A cursor for use in pagination." + cursor: String! + "The item at the end of the edge." + node: Customer +} + +type LifeInsuranceContract implements Contract @source(name: "LifeInsuranceContract", schema: "contract") { + byte_field: Byte + customerId: ID! + date_field: Date + date_time_field: DateTime + decimal_field: Decimal + error: String + float_field: Float + foo(bar: String): String + id: ID! + id_field: ID + int_field: Int + long_field: Long + premium: Float! + string_field: String +} + +type Mutation { + createCustomer(input: CreateCustomerInput): CreateCustomerPayload @delegate(schema: "customer") + createCustomers(inputs: [CreateCustomerInput]): [CreateCustomerPayload] @delegate(schema: "customer") +} + +"Information about pagination in a connection." +type PageInfo @source(name: "PageInfo", schema: "customer") { + "When paginating forwards, the cursor to continue." + endCursor: String + "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 +} + +type Query { + consultant(id: ID!): Consultant @delegate(schema: "customer") + contract(contractId: ID!): Contract @delegate(schema: "contract") + contracts(customerId: ID!): [Contract!] @delegate(schema: "contract") + customer(id: ID!): Customer @delegate(schema: "customer") + customerByKind(kind: CustomerKind!): Customer @delegate(schema: "customer") + customerOrConsultant(id: ID!): CustomerOrConsultant @delegate(schema: "customer") + customers(ids: [ID!]!): [Customer] @delegate(schema: "customer") + extendedScalar(d: DateTime): DateTime @delegate(schema: "contract") + guid(guid: UUID!): UUID! @delegate(schema: "contract") + int(i: Int!): Int! @delegate(schema: "contract") +} + +type SomeOtherContract implements Contract @source(name: "SomeOtherContract", schema: "contract") { + customerId: ID! + expiryDate: DateTime! + id: ID! +} + +union CustomerOrConsultant @source(name: "CustomerOrConsultant", schema: "customer") = Customer | Consultant + +input ComplexInputType @source(name: "ComplexInputType", schema: "customer") { + deeper: ComplexInputType + deeperArray: [ComplexInputType] + value: String + valueArray: [String] +} + +input CreateCustomerInput @source(name: "CreateCustomerInput", schema: "customer") { + consultantId: String + name: String + street: String +} + +input CustomerInput @source(name: "CustomerInput", schema: "customer") { + consultantId: String + id: String + kind: CustomerKind! + name: String + someGuid: UUID! + someInt: Int! + street: String +} + +input SayInput @source(name: "SayInput", schema: "customer") { + words: [String] +} + +enum CustomerKind @source(name: "CustomerKind", schema: "customer") { + STANDARD + PREMIUM +} + +directive @delegate(path: String "The name of the schema to which this field shall be delegated to." schema: Name!) on FIELD_DEFINITION + +"Annotates the original name of a type." +directive @source("The original name of the annotated type." name: Name! "The name of the schema to which this type belongs to." schema: Name!) repeatable on ENUM | OBJECT | INTERFACE | UNION | INPUT_OBJECT | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION | ENUM_VALUE + +"The `Boolean` scalar type represents `true` or `false`." +scalar Boolean + +"The `Byte` scalar type represents non-fractional whole numeric values. Byte can represent values between 0 and 255." +scalar Byte + +"The `Date` scalar represents an ISO-8601 compliant date type." +scalar Date + +"The `DateTime` scalar represents an ISO-8601 compliant date time type." +scalar DateTime + +"The built-in `Decimal` scalar type." +scalar Decimal + +"The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](http:\/\/en.wikipedia.org\/wiki\/IEEE_floating_point)." +scalar Float + +"The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID." +scalar ID + +"The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1." +scalar Int + +"The `Long` scalar type represents non-fractional signed whole 64-bit numeric values. Long can represent values between -(2^63) and 2^63 - 1." +scalar Long + +"The name scalar represents a valid GraphQL name as specified in the spec and can be used to refer to fields or types." +scalar Name + +scalar PaginationAmount + +"The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text." +scalar String + +scalar UUID diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/VariableDelegationTests.ObjectFieldVariableIsCorrectlyPassed_result.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/VariableDelegationTests.ObjectFieldVariableIsCorrectlyPassed_result.snap new file mode 100644 index 00000000000..d7379de080e --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/VariableDelegationTests.ObjectFieldVariableIsCorrectlyPassed_result.snap @@ -0,0 +1,13 @@ +{ + "Data": { + "createCustomer": { + "customer": { + "name": "someName", + "kind": "STANDARD" + } + } + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/VariableDelegationTests.ObjectFieldVariableIsCorrectlyPassed_schema.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/VariableDelegationTests.ObjectFieldVariableIsCorrectlyPassed_schema.snap new file mode 100644 index 00000000000..6d85c906bff --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/VariableDelegationTests.ObjectFieldVariableIsCorrectlyPassed_schema.snap @@ -0,0 +1,179 @@ +schema { + query: Query + mutation: Mutation +} + +interface Contract @source(name: "Contract", schema: "contract") { + customerId: ID! + id: ID! +} + +type Consultant @source(name: "Consultant", schema: "customer") { + customers(after: String before: String first: PaginationAmount last: PaginationAmount): CustomerConnection + id: ID! + name: String! +} + +type CreateCustomerPayload @source(name: "CreateCustomerPayload", schema: "customer") { + customer: Customer +} + +type Customer @source(name: "Customer", schema: "customer") { + complexArg(arg: ComplexInputType): String + consultant(customer: CustomerInput): Consultant + id: ID! + kind: CustomerKind! + name: String! + say(input: SayInput!): String + someGuid: UUID! + someInt: Int! + street: String! +} + +"A connection to a list of items." +type CustomerConnection @source(name: "CustomerConnection", schema: "customer") { + "A list of edges." + edges: [CustomerEdge!] + "A flattened list of the nodes." + nodes: [Customer] + "Information to aid in pagination." + pageInfo: PageInfo! + totalCount: Int! +} + +"An edge in a connection." +type CustomerEdge @source(name: "CustomerEdge", schema: "customer") { + "A cursor for use in pagination." + cursor: String! + "The item at the end of the edge." + node: Customer +} + +type LifeInsuranceContract implements Contract @source(name: "LifeInsuranceContract", schema: "contract") { + byte_field: Byte + customerId: ID! + date_field: Date + date_time_field: DateTime + decimal_field: Decimal + error: String + float_field: Float + foo(bar: String): String + id: ID! + id_field: ID + int_field: Int + long_field: Long + premium: Float! + string_field: String +} + +type Mutation { + createCustomer(input: CreateCustomerInput): CreateCustomerPayload @delegate(schema: "customer") + createCustomers(inputs: [CreateCustomerInput]): [CreateCustomerPayload] @delegate(schema: "customer") +} + +"Information about pagination in a connection." +type PageInfo @source(name: "PageInfo", schema: "customer") { + "When paginating forwards, the cursor to continue." + endCursor: String + "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 +} + +type Query { + consultant(id: ID!): Consultant @delegate(schema: "customer") + contract(contractId: ID!): Contract @delegate(schema: "contract") + contracts(customerId: ID!): [Contract!] @delegate(schema: "contract") + customer(id: ID!): Customer @delegate(schema: "customer") + customerByKind(kind: CustomerKind!): Customer @delegate(schema: "customer") + customerOrConsultant(id: ID!): CustomerOrConsultant @delegate(schema: "customer") + customers(ids: [ID!]!): [Customer] @delegate(schema: "customer") + extendedScalar(d: DateTime): DateTime @delegate(schema: "contract") + guid(guid: UUID!): UUID! @delegate(schema: "contract") + int(i: Int!): Int! @delegate(schema: "contract") +} + +type SomeOtherContract implements Contract @source(name: "SomeOtherContract", schema: "contract") { + customerId: ID! + expiryDate: DateTime! + id: ID! +} + +union CustomerOrConsultant @source(name: "CustomerOrConsultant", schema: "customer") = Customer | Consultant + +input ComplexInputType @source(name: "ComplexInputType", schema: "customer") { + deeper: ComplexInputType + deeperArray: [ComplexInputType] + value: String + valueArray: [String] +} + +input CreateCustomerInput @source(name: "CreateCustomerInput", schema: "customer") { + consultantId: String + name: String + street: String +} + +input CustomerInput @source(name: "CustomerInput", schema: "customer") { + consultantId: String + id: String + kind: CustomerKind! + name: String + someGuid: UUID! + someInt: Int! + street: String +} + +input SayInput @source(name: "SayInput", schema: "customer") { + words: [String] +} + +enum CustomerKind @source(name: "CustomerKind", schema: "customer") { + STANDARD + PREMIUM +} + +directive @delegate(path: String "The name of the schema to which this field shall be delegated to." schema: Name!) on FIELD_DEFINITION + +"Annotates the original name of a type." +directive @source("The original name of the annotated type." name: Name! "The name of the schema to which this type belongs to." schema: Name!) repeatable on ENUM | OBJECT | INTERFACE | UNION | INPUT_OBJECT | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION | ENUM_VALUE + +"The `Boolean` scalar type represents `true` or `false`." +scalar Boolean + +"The `Byte` scalar type represents non-fractional whole numeric values. Byte can represent values between 0 and 255." +scalar Byte + +"The `Date` scalar represents an ISO-8601 compliant date type." +scalar Date + +"The `DateTime` scalar represents an ISO-8601 compliant date time type." +scalar DateTime + +"The built-in `Decimal` scalar type." +scalar Decimal + +"The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](http:\/\/en.wikipedia.org\/wiki\/IEEE_floating_point)." +scalar Float + +"The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID." +scalar ID + +"The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1." +scalar Int + +"The `Long` scalar type represents non-fractional signed whole 64-bit numeric values. Long can represent values between -(2^63) and 2^63 - 1." +scalar Long + +"The name scalar represents a valid GraphQL name as specified in the spec and can be used to refer to fields or types." +scalar Name + +scalar PaginationAmount + +"The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text." +scalar String + +scalar UUID diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/VariableDelegationTests.ScopedListVariableIsCorrectlyPassed_result.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/VariableDelegationTests.ScopedListVariableIsCorrectlyPassed_result.snap new file mode 100644 index 00000000000..83a411b8d07 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/VariableDelegationTests.ScopedListVariableIsCorrectlyPassed_result.snap @@ -0,0 +1,15 @@ +{ + "Data": { + "allCustomers": [ + { + "id": "Q3VzdG9tZXIKZDE=" + }, + { + "id": "Q3VzdG9tZXIKZDE=" + } + ] + }, + "Errors": null, + "Extensions": null, + "ContextData": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/VariableDelegationTests.ScopedListVariableIsCorrectlyPassed_schema.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/VariableDelegationTests.ScopedListVariableIsCorrectlyPassed_schema.snap new file mode 100644 index 00000000000..73de3011f0b --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Middleware/__snapshots__/VariableDelegationTests.ScopedListVariableIsCorrectlyPassed_schema.snap @@ -0,0 +1,180 @@ +schema { + query: Query + mutation: Mutation +} + +interface Contract @source(name: "Contract", schema: "contract") { + customerId: ID! + id: ID! +} + +type Consultant @source(name: "Consultant", schema: "customer") { + customers(after: String before: String first: PaginationAmount last: PaginationAmount): CustomerConnection + id: ID! + name: String! +} + +type CreateCustomerPayload @source(name: "CreateCustomerPayload", schema: "customer") { + customer: Customer +} + +type Customer @source(name: "Customer", schema: "customer") { + complexArg(arg: ComplexInputType): String + consultant(customer: CustomerInput): Consultant + id: ID! + kind: CustomerKind! + name: String! + say(input: SayInput!): String + someGuid: UUID! + someInt: Int! + street: String! +} + +"A connection to a list of items." +type CustomerConnection @source(name: "CustomerConnection", schema: "customer") { + "A list of edges." + edges: [CustomerEdge!] + "A flattened list of the nodes." + nodes: [Customer] + "Information to aid in pagination." + pageInfo: PageInfo! + totalCount: Int! +} + +"An edge in a connection." +type CustomerEdge @source(name: "CustomerEdge", schema: "customer") { + "A cursor for use in pagination." + cursor: String! + "The item at the end of the edge." + node: Customer +} + +type LifeInsuranceContract implements Contract @source(name: "LifeInsuranceContract", schema: "contract") { + byte_field: Byte + customerId: ID! + date_field: Date + date_time_field: DateTime + decimal_field: Decimal + error: String + float_field: Float + foo(bar: String): String + id: ID! + id_field: ID + int_field: Int + long_field: Long + premium: Float! + string_field: String +} + +type Mutation { + createCustomer(input: CreateCustomerInput): CreateCustomerPayload @delegate(schema: "customer") + createCustomers(inputs: [CreateCustomerInput]): [CreateCustomerPayload] @delegate(schema: "customer") +} + +"Information about pagination in a connection." +type PageInfo @source(name: "PageInfo", schema: "customer") { + "When paginating forwards, the cursor to continue." + endCursor: String + "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 +} + +type Query { + allCustomers: [Customer!] @delegate(path: "customers(ids: $contextData:ids)", schema: "customer") + consultant(id: ID!): Consultant @delegate(schema: "customer") + contract(contractId: ID!): Contract @delegate(schema: "contract") + contracts(customerId: ID!): [Contract!] @delegate(schema: "contract") + customer(id: ID!): Customer @delegate(schema: "customer") + customerByKind(kind: CustomerKind!): Customer @delegate(schema: "customer") + customerOrConsultant(id: ID!): CustomerOrConsultant @delegate(schema: "customer") + customers(ids: [ID!]!): [Customer] @delegate(schema: "customer") + extendedScalar(d: DateTime): DateTime @delegate(schema: "contract") + guid(guid: UUID!): UUID! @delegate(schema: "contract") + int(i: Int!): Int! @delegate(schema: "contract") +} + +type SomeOtherContract implements Contract @source(name: "SomeOtherContract", schema: "contract") { + customerId: ID! + expiryDate: DateTime! + id: ID! +} + +union CustomerOrConsultant @source(name: "CustomerOrConsultant", schema: "customer") = Customer | Consultant + +input ComplexInputType @source(name: "ComplexInputType", schema: "customer") { + deeper: ComplexInputType + deeperArray: [ComplexInputType] + value: String + valueArray: [String] +} + +input CreateCustomerInput @source(name: "CreateCustomerInput", schema: "customer") { + consultantId: String + name: String + street: String +} + +input CustomerInput @source(name: "CustomerInput", schema: "customer") { + consultantId: String + id: String + kind: CustomerKind! + name: String + someGuid: UUID! + someInt: Int! + street: String +} + +input SayInput @source(name: "SayInput", schema: "customer") { + words: [String] +} + +enum CustomerKind @source(name: "CustomerKind", schema: "customer") { + STANDARD + PREMIUM +} + +directive @delegate(path: String "The name of the schema to which this field shall be delegated to." schema: Name!) on FIELD_DEFINITION + +"Annotates the original name of a type." +directive @source("The original name of the annotated type." name: Name! "The name of the schema to which this type belongs to." schema: Name!) repeatable on ENUM | OBJECT | INTERFACE | UNION | INPUT_OBJECT | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION | ENUM_VALUE + +"The `Boolean` scalar type represents `true` or `false`." +scalar Boolean + +"The `Byte` scalar type represents non-fractional whole numeric values. Byte can represent values between 0 and 255." +scalar Byte + +"The `Date` scalar represents an ISO-8601 compliant date type." +scalar Date + +"The `DateTime` scalar represents an ISO-8601 compliant date time type." +scalar DateTime + +"The built-in `Decimal` scalar type." +scalar Decimal + +"The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](http:\/\/en.wikipedia.org\/wiki\/IEEE_floating_point)." +scalar Float + +"The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID." +scalar ID + +"The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1." +scalar Int + +"The `Long` scalar type represents non-fractional signed whole 64-bit numeric values. Long can represent values between -(2^63) and 2^63 - 1." +scalar Long + +"The name scalar represents a valid GraphQL name as specified in the spec and can be used to refer to fields or types." +scalar Name + +scalar PaginationAmount + +"The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text." +scalar String + +scalar UUID diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Requests/BufferedRequestTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Requests/BufferedRequestTests.cs new file mode 100644 index 00000000000..412b151092c --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Requests/BufferedRequestTests.cs @@ -0,0 +1,215 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using HotChocolate.Execution; +using HotChocolate.Language; +using HotChocolate.Stitching.Schemas.Customers; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace HotChocolate.Stitching.Requests; + +public class BufferedRequestTests +{ + [Fact] + public async Task Create_BufferedRequest() + { + // arrange + var schema = + await new ServiceCollection() + .AddGraphQL() + .AddCustomerSchema() + .BuildSchemaAsync(); + + var query = "query abc($id: ID) { customer(id: $id) { name } }"; + + var request = + QueryRequestBuilder.New() + .SetQuery(query) + .Create(); + + // act + var bufferedRequest = BufferedRequest.Create(request, schema); + + // assert + Assert.Equal(request, bufferedRequest.Request); + Assert.Equal(query, bufferedRequest.Document.ToString(false)); + Assert.Equal( + bufferedRequest.Document.Definitions.OfType().First(), + bufferedRequest.Operation); + Assert.NotNull(bufferedRequest.Promise); + Assert.Null(bufferedRequest.Aliases); + } + + [Fact] + public async Task Create_BufferedRequest_Operation_Correctly_Resolved() + { + // arrange + var schema = + await new ServiceCollection() + .AddGraphQL() + .AddCustomerSchema() + .BuildSchemaAsync(); + + var query = "query abc($id: ID) { customer(id: $id) { name } } " + + "query def($id: ID) { customer(id: $id) { name } }"; + + var request = + QueryRequestBuilder.New() + .SetQuery(query) + .SetOperation("def") + .Create(); + + // act + var bufferedRequest = BufferedRequest.Create(request, schema); + + // assert + Assert.Equal(request, bufferedRequest.Request); + Assert.Equal(query, bufferedRequest.Document.ToString(false)); + Assert.Equal( + bufferedRequest.Document.Definitions.OfType().Last(), + bufferedRequest.Operation); + Assert.NotNull(bufferedRequest.Promise); + Assert.Null(bufferedRequest.Aliases); + } + + [Fact] + public async Task Create_BufferedRequest_Rewrite_Variables_To_Literals() + { + // arrange + var schema = + await new ServiceCollection() + .AddGraphQL() + .AddCustomerSchema() + .BuildSchemaAsync(); + + var query = "query abc($id: ID) { customer(id: $id) { name } }"; + + var request = + QueryRequestBuilder.New() + .SetQuery(query) + .SetVariableValue("id", 1) + .Create(); + + // act + var bufferedRequest = BufferedRequest.Create(request, schema); + + // assert + Assert.NotEqual(request, bufferedRequest.Request); + Assert.Collection(bufferedRequest.Request.VariableValues!, + t => Assert.IsType(t.Value)); + } + + [Fact] + public async Task Create_BufferedRequest_Rewrite_Variables_To_Literals_2() + { + // arrange + var schema = + await new ServiceCollection() + .AddGraphQL() + .AddCustomerSchema() + .BuildSchemaAsync(); + + var query = "query abc($id: ID) { customer(id: $id) { name } }"; + + var request = + QueryRequestBuilder.New() + .SetQuery(query) + .SetVariableValue("id", "1") + .Create(); + + // act + var bufferedRequest = BufferedRequest.Create(request, schema); + + // assert + Assert.NotEqual(request, bufferedRequest.Request); + Assert.Collection(bufferedRequest.Request.VariableValues!, + t => Assert.IsType(t.Value)); + } + + [Fact] + public async Task Create_BufferedRequest_Literals_Are_Not_Rewritten() + { + // arrange + var schema = + await new ServiceCollection() + .AddGraphQL() + .AddCustomerSchema() + .BuildSchemaAsync(); + + var query = "query abc($id: ID) { customer(id: $id) { name } }"; + + var idValue = new StringValueNode("1"); + + var request = + QueryRequestBuilder.New() + .SetQuery(query) + .SetVariableValue("id", idValue) + .Create(); + + // act + var bufferedRequest = BufferedRequest.Create(request, schema); + + // assert + Assert.NotEqual(request, bufferedRequest.Request); + Assert.Collection(bufferedRequest.Request.VariableValues!, + t => Assert.Same(idValue, t.Value)); + } + + [Fact] + public async Task Create_BufferedRequest_Request_Is_Null() + { + // arrange + var schema = + await new ServiceCollection() + .AddGraphQL() + .AddCustomerSchema() + .BuildSchemaAsync(); + + // act + void Action() => BufferedRequest.Create(null!, schema); + + // assert + Assert.Throws(Action); + } + + [Fact] + public async Task Create_BufferedRequest_Request_Query_Is_Null() + { + // arrange + var schema = + await new ServiceCollection() + .AddGraphQL() + .AddCustomerSchema() + .BuildSchemaAsync(); + + var request = + QueryRequestBuilder.New() + .SetQueryId("abc") + .Create(); + + // act + void Action() => BufferedRequest.Create(request, schema); + + // assert + Assert.Throws(Action); + } + + [Fact] + public void Create_BufferedRequest_Schema_Is_Null() + { + // arrange + var query = "query abc($id: ID) { customer(id: $id) { name } }"; + + var request = + QueryRequestBuilder.New() + .SetQuery(query) + .Create(); + + // act + void Action() => BufferedRequest.Create(request, null!); + + // assert + Assert.Throws(Action); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Requests/MergeRequestHelperTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Requests/MergeRequestHelperTests.cs new file mode 100644 index 00000000000..ce4d9c71f01 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Requests/MergeRequestHelperTests.cs @@ -0,0 +1,199 @@ +using Microsoft.Extensions.DependencyInjection; +using HotChocolate.Execution; +using HotChocolate.Language; +using HotChocolate.Stitching.Schemas.Customers; +using Snapshooter.Xunit; + +namespace HotChocolate.Stitching.Requests; + +public class MergeRequestHelperTests +{ + [Fact] + public async Task Create_BufferedRequest() + { + // arrange + var schema = + await new ServiceCollection() + .AddGraphQL() + .AddCustomerSchema() + .BuildSchemaAsync(); + + var queryA = "query abc($id: ID) { customer(id: $id) { name } }"; + var queryB = "query abc($id: ID) { customer(id: $id) { id } }"; + + var requestA = + QueryRequestBuilder.New() + .SetQuery(queryA) + .SetVariableValue("id", "1") + .Create(); + + var requestB = + QueryRequestBuilder.New() + .SetQuery(queryB) + .SetVariableValue("id", "1") + .Create(); + + var bufferedRequestA = BufferedRequest.Create(requestA, schema); + var bufferedRequestB = BufferedRequest.Create(requestB, schema); + + // act + var mergeResult = + MergeRequestHelper.MergeRequests( + new[] { bufferedRequestA, bufferedRequestB }); + + // assert + string.Join(Environment.NewLine + "-------" + Environment.NewLine, + mergeResult + .Select(t => t.Item1) + .Select(t => Utf8GraphQLParser.Parse(t.Query!.AsSpan()).ToString(true))) + .MatchSnapshot(); + } + + [Fact] + public async Task Create_BufferedRequest_AutoGenerated() + { + // arrange + var schema = + await new ServiceCollection() + .AddGraphQL() + .AddCustomerSchema() + .BuildSchemaAsync(); + + var queryA = "query abc($id: ID) { customer(id: $id) { name } }"; + var queryB = "query abc($id: ID) { customer(id: $id) { id } }"; + + var requestA = + QueryRequestBuilder.New() + .SetQuery(queryA) + .SetVariableValue("id", "1") + .SetGlobalState(WellKnownContextData.IsAutoGenerated, true) + .Create(); + + var requestB = + QueryRequestBuilder.New() + .SetQuery(queryB) + .SetVariableValue("id", "1") + .SetGlobalState(WellKnownContextData.IsAutoGenerated, true) + .Create(); + + var bufferedRequestA = BufferedRequest.Create(requestA, schema); + var bufferedRequestB = BufferedRequest.Create(requestB, schema); + + // act + var mergeResult = + MergeRequestHelper.MergeRequests( + new[] { bufferedRequestA, bufferedRequestB }); + + // assert + string.Join(Environment.NewLine + "-------" + Environment.NewLine, + mergeResult + .Select(t => t.Item1) + .Select(t => Utf8GraphQLParser.Parse(t.Query!.AsSpan()).ToString(true))) + .MatchSnapshot(); + } + + [Fact] + public async Task Create_BufferedRequest_With_Mixed_Operations() + { + // arrange + var schema = + await new ServiceCollection() + .AddGraphQL() + .AddCustomerSchema() + .BuildSchemaAsync(); + + var queryA = "query abc($id: ID) { customer(id: $id) { name } }"; + var queryB = "query abc($id: ID) { customer(id: $id) { id } }"; + var queryC = "mutation { createCustomer(input: { name: \"a\" }) { customer { id } } }"; + + var requestA = + QueryRequestBuilder.New() + .SetQuery(queryA) + .SetVariableValue("id", "1") + .Create(); + + var requestB = + QueryRequestBuilder.New() + .SetQuery(queryB) + .SetVariableValue("id", "1") + .Create(); + + var requestC = + QueryRequestBuilder.New() + .SetQuery(queryC) + .Create(); + + var bufferedRequestA = BufferedRequest.Create(requestA, schema); + var bufferedRequestB = BufferedRequest.Create(requestB, schema); + var bufferedRequestC = BufferedRequest.Create(requestC, schema); + + // act + var mergeResult = + MergeRequestHelper.MergeRequests(new[] + { + bufferedRequestA, + bufferedRequestB, + bufferedRequestC + }); + + // assert + string.Join(Environment.NewLine + "-------" + Environment.NewLine, + mergeResult + .Select(t => t.Item1) + .Select(t => Utf8GraphQLParser.Parse(t.Query!.AsSpan()).ToString(true))) + .MatchSnapshot(); + } + + [Fact] + public async Task Merge_Requests_With_Variables_On_Directives() + { + // arrange + var schema = + await new ServiceCollection() + .AddGraphQL() + .AddCustomerSchema() + .BuildSchemaAsync(); + + var queryA = + @"query abc($id: ID $if: Boolean) { + customer(id: $id) { + name @include(id: $if) + } + }"; + + var queryB = + @"query abc($id: ID $if: Boolean) { + customer(id: $id) { + id @include(id: $if) + } + }"; + + var requestA = + QueryRequestBuilder.New() + .SetQuery(queryA) + .SetVariableValue("id", "1") + .SetVariableValue("if", true) + .Create(); + + var requestB = + QueryRequestBuilder.New() + .SetQuery(queryB) + .SetVariableValue("id", "1") + .SetVariableValue("if", true) + .Create(); + + var bufferedRequestA = BufferedRequest.Create(requestA, schema); + var bufferedRequestB = BufferedRequest.Create(requestB, schema); + + // act + var mergeResult = + MergeRequestHelper.MergeRequests(new[] { bufferedRequestA, bufferedRequestB }); + + // assert + string.Join(Environment.NewLine + "-------" + Environment.NewLine, + mergeResult + .Select(t => t.Item1) + .Select(t => Utf8GraphQLParser.Parse(t.Query!.AsSpan()).ToString(true))) + .MatchSnapshot(); + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Requests/__snapshots__/MergeRequestHelperTests.Create_BufferedRequest.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Requests/__snapshots__/MergeRequestHelperTests.Create_BufferedRequest.snap new file mode 100644 index 00000000000..aac799030ef --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Requests/__snapshots__/MergeRequestHelperTests.Create_BufferedRequest.snap @@ -0,0 +1,8 @@ +query exec_batch($__0__id: ID, $__1__id: ID) { + __0__customer: customer(id: $__0__id) { + name + } + __1__customer: customer(id: $__1__id) { + id + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Requests/__snapshots__/MergeRequestHelperTests.Create_BufferedRequest_AutoGenerated.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Requests/__snapshots__/MergeRequestHelperTests.Create_BufferedRequest_AutoGenerated.snap new file mode 100644 index 00000000000..aac799030ef --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Requests/__snapshots__/MergeRequestHelperTests.Create_BufferedRequest_AutoGenerated.snap @@ -0,0 +1,8 @@ +query exec_batch($__0__id: ID, $__1__id: ID) { + __0__customer: customer(id: $__0__id) { + name + } + __1__customer: customer(id: $__1__id) { + id + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Requests/__snapshots__/MergeRequestHelperTests.Create_BufferedRequest_With_Mixed_Operations.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Requests/__snapshots__/MergeRequestHelperTests.Create_BufferedRequest_With_Mixed_Operations.snap new file mode 100644 index 00000000000..32c52cfa83c --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Requests/__snapshots__/MergeRequestHelperTests.Create_BufferedRequest_With_Mixed_Operations.snap @@ -0,0 +1,16 @@ +query exec_batch($__0__id: ID, $__1__id: ID) { + __0__customer: customer(id: $__0__id) { + name + } + __1__customer: customer(id: $__1__id) { + id + } +} +------- +mutation exec_batch { + __0__createCustomer: createCustomer(input: { name: "a" }) { + customer { + id + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Requests/__snapshots__/MergeRequestHelperTests.Merge_Requests_With_Variables_On_Directives.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Requests/__snapshots__/MergeRequestHelperTests.Merge_Requests_With_Variables_On_Directives.snap new file mode 100644 index 00000000000..0c63fba955e --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Requests/__snapshots__/MergeRequestHelperTests.Merge_Requests_With_Variables_On_Directives.snap @@ -0,0 +1,8 @@ +query exec_batch($__0__id: ID, $__0__if: Boolean, $__1__id: ID, $__1__if: Boolean) { + __0__customer: customer(id: $__0__id) { + name @include(id: $__0__if) + } + __1__customer: customer(id: $__1__id) { + id @include(id: $__1__if) + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Accounts/AccountsSchemaRequestExecutorBuilderExtensions.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Accounts/AccountsSchemaRequestExecutorBuilderExtensions.cs new file mode 100644 index 00000000000..295ba12ceb2 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Accounts/AccountsSchemaRequestExecutorBuilderExtensions.cs @@ -0,0 +1,17 @@ +using HotChocolate.Execution.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace HotChocolate.Stitching.Schemas.Accounts; + +public static class AccountsSchemaRequestExecutorBuilderExtensions +{ + public static IRequestExecutorBuilder AddAccountsSchema( + this IRequestExecutorBuilder builder) + { + builder.Services + .AddSingleton(); + + return builder + .AddQueryType(); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Accounts/Query.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Accounts/Query.cs new file mode 100644 index 00000000000..5a05e351c1b --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Accounts/Query.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace HotChocolate.Stitching.Schemas.Accounts; + +public class Query +{ + public IEnumerable GetUsers([Service] UserRepository repository) => + repository.GetUsers(); + + public User GetUser(int id, [Service] UserRepository repository) => + repository.GetUser(id); +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Accounts/User.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Accounts/User.cs new file mode 100644 index 00000000000..96242327d98 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Accounts/User.cs @@ -0,0 +1,22 @@ +using System; + +namespace HotChocolate.Stitching.Schemas.Accounts; + +public class User +{ + public User(int id, string name, DateTime birthdate, string username) + { + Id = id; + Name = name; + Birthdate = birthdate; + Username = username; + } + + public int Id { get; } + + public string Name { get; } + + public DateTime Birthdate { get; } + + public string Username { get; } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Accounts/UserRepository.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Accounts/UserRepository.cs new file mode 100644 index 00000000000..19ce8a89a14 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Accounts/UserRepository.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace HotChocolate.Stitching.Schemas.Accounts; + +public class UserRepository +{ + private readonly Dictionary _users; + + public UserRepository() + { + _users = new User[] + { + new User(1, "Ada Lovelace", new DateTime(1815, 12, 10), "@ada"), + new User(2, "Alan Turing", new DateTime(1912, 06, 23), "@complete") + }.ToDictionary(t => t.Id); + } + + public User GetUser(int id) => _users[id]; + + public IEnumerable GetUsers() => _users.Values; +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/ContractSchemaRequestExecutorBuilderExtensions.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/ContractSchemaRequestExecutorBuilderExtensions.cs new file mode 100644 index 00000000000..5106eac4607 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/ContractSchemaRequestExecutorBuilderExtensions.cs @@ -0,0 +1,22 @@ +using HotChocolate.Execution.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace HotChocolate.Stitching.Schemas.Contracts; + +public static class ContractSchemaRequestExecutorBuilderExtensions +{ + public static IRequestExecutorBuilder AddContractSchema( + this IRequestExecutorBuilder builder) + { + builder.Services + .AddSingleton() + .AddSingleton(); + + return builder + .AddQueryType() + .AddType() + .AddType() + .AddDirectiveType() + .EnableRelaySupport(); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/ContractStorage.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/ContractStorage.cs new file mode 100644 index 00000000000..f9546d7b4cd --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/ContractStorage.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; + +namespace HotChocolate.Stitching.Schemas.Contracts; + +public class ContractStorage +{ + public List Contracts { get; } = new List + { + new LifeInsuranceContract + { + Id = "1", + CustomerId= "1", + Premium = 123456.11 + }, + new LifeInsuranceContract + { + Id = "2", + CustomerId= "1", + Premium = 456789.12 + }, + new LifeInsuranceContract + { + Id = "3", + CustomerId = "2", + Premium = 789.12 + }, + new SomeOtherContract + { + Id = "1", + CustomerId= "1", + ExpiryDate = new DateTime(2015, 2, 1, 0,0,0, DateTimeKind.Utc) + }, + new SomeOtherContract + { + Id = "2", + CustomerId= "2", + ExpiryDate = new DateTime(2015, 5, 1, 0,0,0, DateTimeKind.Utc) + }, + new SomeOtherContract + { + Id = "3", + CustomerId= "3", + ExpiryDate = new DateTime(2017, 1, 30, 0,0,0, DateTimeKind.Utc) + }, + new SomeOtherContract + { + Id = "4", + CustomerId= "3", + ExpiryDate = new DateTime(2020, 1, 1, 0,0,0, DateTimeKind.Utc) + } + }; +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/ContractType.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/ContractType.cs new file mode 100644 index 00000000000..eb6fa3f71e4 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/ContractType.cs @@ -0,0 +1,15 @@ +using HotChocolate.Types; +using HotChocolate.Types.Relay; + +namespace HotChocolate.Stitching.Schemas.Contracts; + +public class ContractType : InterfaceType +{ + protected override void Configure(IInterfaceTypeDescriptor descriptor) + { + descriptor.Name("Contract"); + descriptor.Implements(); + descriptor.Field("id").Type>(); + descriptor.Field("customerId").Type>(); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/CustomDirectiveType.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/CustomDirectiveType.cs new file mode 100644 index 00000000000..5651bebc02b --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/CustomDirectiveType.cs @@ -0,0 +1,20 @@ +using System; +using HotChocolate.Types; + +namespace HotChocolate.Stitching.Schemas.Contracts; + +public class CustomDirectiveType : DirectiveType +{ + protected override void Configure(IDirectiveTypeDescriptor descriptor) + { + descriptor.Name("custom"); + descriptor.Location(DirectiveLocation.Field); + descriptor.Argument("d").Type(); + descriptor.Use(next => ctx => + { + ctx.Result = ctx.Directive.GetArgument("d") + .ToUniversalTime(); + return next.Invoke(ctx); + }); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/IContract.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/IContract.cs new file mode 100644 index 00000000000..78a99ffd425 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/IContract.cs @@ -0,0 +1,8 @@ +namespace HotChocolate.Stitching.Schemas.Contracts; + +public interface IContract +{ + string Id { get; } + + string CustomerId { get; } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/LifeInsuranceContract.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/LifeInsuranceContract.cs new file mode 100644 index 00000000000..f80a57612e0 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/LifeInsuranceContract.cs @@ -0,0 +1,10 @@ +namespace HotChocolate.Stitching.Schemas.Contracts; + +public class LifeInsuranceContract : IContract +{ + public string Id { get; set; } + + public string CustomerId { get; set; } + + public double Premium { get; set; } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/LifeInsuranceContractType.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/LifeInsuranceContractType.cs new file mode 100644 index 00000000000..3f114ae5585 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/LifeInsuranceContractType.cs @@ -0,0 +1,69 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using HotChocolate.Types; + +namespace HotChocolate.Stitching.Schemas.Contracts; + +public class LifeInsuranceContractType : ObjectType +{ + protected override void Configure( + IObjectTypeDescriptor descriptor) + { + descriptor + .AsNode() + .IdField(t => t.Id) + .NodeResolver((ctx, id) => + { + return Task.FromResult( + ctx.Service() + .Contracts + .OfType() + .FirstOrDefault(t => t.Id.Equals(id))); + }); + + descriptor.Implements(); + descriptor.Field(t => t.Id).Type>(); + descriptor.Field(t => t.CustomerId).Type>(); + descriptor.Field("foo") + .Argument("bar", a => a.Type()) + .Resolve(ctx => ctx.ArgumentValue("bar")); + descriptor.Field("error") + .Type() + .Resolve(ctx => ErrorBuilder.New() + .SetMessage("Error_Message") + .SetCode("ERROR_CODE") + .SetPath(ctx.Path) + .Build()); + descriptor.Field("date_field") + .Type() + .Resolve(new DateTime(2018, 5, 17)); + descriptor.Field("date_time_field") + .Type() + .Resolve(new DateTime( + 2018, 5, 17, 8, 59, 0, + DateTimeKind.Utc)); + descriptor.Field("string_field") + .Type() + .Resolve("abc"); + descriptor.Field("id_field") + .Type() + .Resolve("abc_123"); + descriptor.Field("byte_field") + .Type() + .Resolve(123); + descriptor.Field("int_field") + .Type() + .Resolve(123); + descriptor.Field("long_field") + .Type() + .Resolve(123); + descriptor.Field("float_field") + .Type() + .Argument("f", a => a.Type()) + .Resolve(ctx => ctx.ArgumentValue("f") ?? 123.123); + descriptor.Field("decimal_field") + .Type() + .Resolve(123.123); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/Query.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/Query.cs new file mode 100644 index 00000000000..44add358016 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/Query.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using HotChocolate.Types.Relay; + +namespace HotChocolate.Stitching.Schemas.Contracts; + +public class Query +{ + private readonly IdSerializer _idSerializer = new IdSerializer(); + private readonly ContractStorage _contractStorage; + + public Query(ContractStorage contractStorage) + { + _contractStorage = contractStorage + ?? throw new ArgumentNullException(nameof(contractStorage)); + } + + public IContract GetContract(string contractId) + { + var value = _idSerializer.Deserialize(contractId); + + if (value.TypeName == nameof(LifeInsuranceContract)) + { + return _contractStorage.Contracts + .OfType() + .FirstOrDefault(t => t.Id.Equals(value.Value)); + } + else + { + return _contractStorage.Contracts + .OfType() + .FirstOrDefault(t => t.Id.Equals(value.Value)); + } + } + + public IEnumerable GetContracts(string customerId) + { + var value = _idSerializer.Deserialize(customerId); + + return _contractStorage.Contracts + .Where(t => t.CustomerId.Equals(value.Value)); + } + + public int GetInt(int i) + { + return i; + } + + public Guid GetGuid(Guid guid) + { + return guid; + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/QueryType.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/QueryType.cs new file mode 100644 index 00000000000..ed6a60db710 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/QueryType.cs @@ -0,0 +1,28 @@ +using System; +using HotChocolate.Types; + +namespace HotChocolate.Stitching.Schemas.Contracts; + +public class QueryType : ObjectType +{ + protected override void Configure( + IObjectTypeDescriptor descriptor) + { + descriptor.Field(t => t.GetContract(default)) + .Argument("contractId", a => a.Type>()) + .Type(); + + descriptor.Field(t => t.GetContracts(default)) + .Argument("customerId", a => a.Type>()) + .Type>>(); + + descriptor.Field("extendedScalar") + .Argument("d", a => a.Type()) + .Type() + .Resolve(ctx => + { + var dateTime = ctx.ArgumentValue("d"); + return dateTime; + }); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/SomeOtherContract.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/SomeOtherContract.cs new file mode 100644 index 00000000000..181a47a5c44 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/SomeOtherContract.cs @@ -0,0 +1,12 @@ +using System; + +namespace HotChocolate.Stitching.Schemas.Contracts; + +public class SomeOtherContract : IContract +{ + public string Id { get; set; } + + public string CustomerId { get; set; } + + public DateTime ExpiryDate { get; set; } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/SomeOtherContractType.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/SomeOtherContractType.cs new file mode 100644 index 00000000000..0ab7b5410e9 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Contracts/SomeOtherContractType.cs @@ -0,0 +1,30 @@ +using System.Linq; +using System.Threading.Tasks; +using HotChocolate.Types; + +namespace HotChocolate.Stitching.Schemas.Contracts; + +public class SomeOtherContractType : ObjectType +{ + protected override void Configure( + IObjectTypeDescriptor descriptor) + { + descriptor + .ImplementsNode() + .IdField(t => t.Id) + .ResolveNode((ctx, id) => + { + return Task.FromResult( + ctx.Service() + .Contracts + .OfType() + .FirstOrDefault(t => t.Id.Equals(id))); + }); + + descriptor.Implements(); + + descriptor.Field(t => t.Id).Type>(); + descriptor.Field(t => t.CustomerId).Type>(); + descriptor.Field(t => t.ExpiryDate).Type>(); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/ComplexInput.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/ComplexInput.cs new file mode 100644 index 00000000000..218a712747c --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/ComplexInput.cs @@ -0,0 +1,12 @@ +namespace HotChocolate.Stitching.Schemas.Customers; + +public class ComplexInput +{ + public string? Value { get; set; } + + public ComplexInput? Deeper { get; set; } + + public string?[]? ValueArray { get; set; } + + public ComplexInput?[]? DeeperArray { get; set; } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/ComplexInputType.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/ComplexInputType.cs new file mode 100644 index 00000000000..bf3739ba43e --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/ComplexInputType.cs @@ -0,0 +1,16 @@ +using System.Linq; +using HotChocolate.Types; +using HotChocolate.Types.Relay; + +namespace HotChocolate.Stitching.Schemas.Customers; + +public class ComplexInputType : InputObjectType +{ + protected override void Configure( + IInputObjectTypeDescriptor descriptor) + { + descriptor.Name("ComplexInputType"); + descriptor.Field(t => t.Value).Type(); + descriptor.Field(t => t.Deeper).Type(); + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/Consultant.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/Consultant.cs new file mode 100644 index 00000000000..32729406172 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/Consultant.cs @@ -0,0 +1,7 @@ +namespace HotChocolate.Stitching.Schemas.Customers; + +public class Consultant : ICustomerOrConsultant +{ + public string? Id { get; set; } + public string? Name { get; set; } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/ConsultantType.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/ConsultantType.cs new file mode 100644 index 00000000000..58b1d91cd53 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/ConsultantType.cs @@ -0,0 +1,33 @@ +using System.Linq; +using System.Threading.Tasks; +using HotChocolate.Types; + +namespace HotChocolate.Stitching.Schemas.Customers; + +public class ConsultantType + : ObjectType +{ + protected override void Configure( + IObjectTypeDescriptor descriptor) + { + descriptor + .AsNode() + .IdField(t => t.Id) + .NodeResolver((ctx, id) => + { + return Task.FromResult( + ctx.Service() + .Consultants.FirstOrDefault(t => t.Id.Equals(id))); + }); + + descriptor.Field(t => t.Name).Type>(); + descriptor.Field("customers") + .UsePaging() + .Resolve(ctx => + { + var consultant = ctx.Parent(); + return ctx.Service().Customers + .Where(t => t.ConsultantId == consultant.Id); + }); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/CreateCustomerInput.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/CreateCustomerInput.cs new file mode 100644 index 00000000000..1cd75804f2a --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/CreateCustomerInput.cs @@ -0,0 +1,8 @@ +namespace HotChocolate.Stitching.Schemas.Customers; + +public class CreateCustomerInput +{ + public string? Name { get; set; } + public string? Street { get; set; } + public string? ConsultantId { get; set; } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/CreateCustomerPayload.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/CreateCustomerPayload.cs new file mode 100644 index 00000000000..df15e1ec9d1 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/CreateCustomerPayload.cs @@ -0,0 +1,6 @@ +namespace HotChocolate.Stitching.Schemas.Customers; + +public class CreateCustomerPayload +{ + public Customer Customer { get; set; } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/Customer.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/Customer.cs new file mode 100644 index 00000000000..bdec1934cb7 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/Customer.cs @@ -0,0 +1,12 @@ +namespace HotChocolate.Stitching.Schemas.Customers; + +public class Customer : ICustomerOrConsultant +{ + public string? Id { get; set; } + public string? Name { get; set; } + public string? Street { get; set; } + public string? ConsultantId { get; set; } + public int SomeInt { get; set; } + public Guid SomeGuid { get; set; } + public CustomerKind Kind { get; set; } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/CustomerKind.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/CustomerKind.cs new file mode 100644 index 00000000000..72d3e0f5957 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/CustomerKind.cs @@ -0,0 +1,7 @@ +namespace HotChocolate.Stitching.Schemas.Customers; + +public enum CustomerKind +{ + Standard, + Premium +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/CustomerOrConsultantType.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/CustomerOrConsultantType.cs new file mode 100644 index 00000000000..b904feaf446 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/CustomerOrConsultantType.cs @@ -0,0 +1,14 @@ +using HotChocolate.Types; + +namespace HotChocolate.Stitching.Schemas.Customers; + +public class CustomerOrConsultantType + : UnionType +{ + protected override void Configure(IUnionTypeDescriptor descriptor) + { + descriptor.Name("CustomerOrConsultant"); + descriptor.Type(); + descriptor.Type(); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/CustomerRepository.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/CustomerRepository.cs new file mode 100644 index 00000000000..3126b35245f --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/CustomerRepository.cs @@ -0,0 +1,51 @@ +namespace HotChocolate.Stitching.Schemas.Customers; + +public class CustomerRepository +{ + public List Customers { get; } = new List + { + new Customer + { + Id = "1", + Name = "Freddy Freeman", + Street = "Far far away 1", + ConsultantId = "1", + SomeInt = 1, + SomeGuid = new Guid("01e2f5dc-0f19-4305-99d3-3c5c234a6524"), + Kind = CustomerKind.Premium + }, + new Customer + { + Id = "2", + Name = "Carol Danvers", + Street = "Far far away 2", + ConsultantId = "1", + SomeInt = 2, + SomeGuid = new Guid("7f84a645-3439-4a6c-91b1-d313f699648d"), + Kind = CustomerKind.Standard + }, + new Customer + { + Id = "3", + Name = "Walter Lawson", + Street = "Far far away 3", + ConsultantId = "2", + SomeInt = 3, + SomeGuid = new Guid("c1c4ec83-a0db-4020-ad0c-9ec6e09ad949") + } + }; + + public List Consultants { get; } = new List + { + new Consultant + { + Id = "1", + Name = "Jordan Belfort", + }, + new Consultant + { + Id = "2", + Name = "Gordon Gekko", + } + }; +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/CustomerResolver.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/CustomerResolver.cs new file mode 100644 index 00000000000..f060fa39340 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/CustomerResolver.cs @@ -0,0 +1,16 @@ +namespace HotChocolate.Stitching.Schemas.Customers; + +public class CustomerResolver +{ + public Consultant? GetConsultant( + [Parent] Customer customer, + [Service] CustomerRepository repository) + { + if (customer.ConsultantId != null) + { + return repository.Consultants.Find(t => t.Id?.Equals(customer.ConsultantId) ?? false); + } + + return null; + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/CustomerSchemaRequestExecutorBuilderExtensions.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/CustomerSchemaRequestExecutorBuilderExtensions.cs new file mode 100644 index 00000000000..4336ffe78a6 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/CustomerSchemaRequestExecutorBuilderExtensions.cs @@ -0,0 +1,20 @@ +using HotChocolate.Execution.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace HotChocolate.Stitching.Schemas.Customers; + +public static class CustomerSchemaRequestExecutorBuilderExtensions +{ + public static IRequestExecutorBuilder AddCustomerSchema( + this IRequestExecutorBuilder builder) + { + builder.Services + .AddSingleton() + .AddSingleton(); + + return builder + .AddQueryType() + .AddMutationType() + .AddGlobalObjectIdentification(); + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/CustomerType.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/CustomerType.cs new file mode 100644 index 00000000000..414caea7e8f --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/CustomerType.cs @@ -0,0 +1,41 @@ +using HotChocolate.Language; +using HotChocolate.Types; + +namespace HotChocolate.Stitching.Schemas.Customers; + +public class CustomerType + : ObjectType +{ + protected override void Configure( + IObjectTypeDescriptor descriptor) + { + descriptor + .ImplementsNode() + .IdField(t => t.Id) + .ResolveNode((ctx, id) => + { + return Task.FromResult( + ctx.Service() + .Customers.Find(t => t.Id?.Equals(id) ?? false)); + }); + + descriptor.Field(t => t.Name).Type>(); + descriptor.Field(t => t.Street).Type>(); + descriptor.Field(t => t.ConsultantId).Ignore(); + + descriptor.Field( + t => t.GetConsultant(default!, default!)) + .Type(); + + descriptor.Field("say") + .Argument("input", a => + a.Type>>()) + .Type() + .Resolve(ctx => string.Join(", ", ctx.ArgumentValue("input").Words!)); + + descriptor.Field("complexArg") + .Argument("arg", a => a.Type()) + .Type() + .Resolve(ctx => ctx.ArgumentLiteral("arg").ToString()); + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/ICustomerOrConsultant.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/ICustomerOrConsultant.cs new file mode 100644 index 00000000000..d83661d16bd --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/ICustomerOrConsultant.cs @@ -0,0 +1,5 @@ +namespace HotChocolate.Stitching.Schemas.Customers; + +public interface ICustomerOrConsultant +{ +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/Mutation.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/Mutation.cs new file mode 100644 index 00000000000..f7d6454971f --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/Mutation.cs @@ -0,0 +1,39 @@ +namespace HotChocolate.Stitching.Schemas.Customers; + +public class Mutation +{ + private readonly CustomerRepository _repository; + + public Mutation(CustomerRepository repository) + { + _repository = repository + ?? throw new ArgumentNullException(nameof(repository)); + } + + public CreateCustomerPayload CreateCustomer(CreateCustomerInput input) + { + var customer = new Customer + { + Id = Guid.NewGuid().ToString(), + Name = input.Name, + Street = input.Street + }; + + _repository.Customers.Add(customer); + + return new CreateCustomerPayload { Customer = customer }; + } + + public ICollection CreateCustomers( + ICollection inputs) + { + var results = new List(); + + foreach (var input in inputs) + { + results.Add(CreateCustomer(input)); + } + + return results; + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/MutationType.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/MutationType.cs new file mode 100644 index 00000000000..83b4b30d5b7 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/MutationType.cs @@ -0,0 +1,7 @@ +using HotChocolate.Types; + +namespace HotChocolate.Stitching.Schemas.Customers; + +public class MutationType : ObjectType +{ +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/Query.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/Query.cs new file mode 100644 index 00000000000..0daf88b8b41 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/Query.cs @@ -0,0 +1,51 @@ +using HotChocolate.Types.Relay; + +namespace HotChocolate.Stitching.Schemas.Customers; + +public class Query +{ + private readonly IdSerializer _idSerializer = new(); + private readonly CustomerRepository _repository; + + public Query(CustomerRepository repository) + { + _repository = repository + ?? throw new ArgumentNullException(nameof(repository)); + } + + public Customer? GetCustomer(string id) + { + var value = _idSerializer.Deserialize(id); + return _repository.Customers.Find(t => t.Id?.Equals(value.Value) ?? false); + } + + public Customer[] GetCustomers(string[] ids) + { + var customers = new Customer[ids.Length]; + + for(var i = 0; i < ids.Length; i++) + { + customers[i] = GetCustomer(ids[i])!; + } + + return customers; + } + + public Customer[] GetAllCustomers() => + _repository.Customers.ToArray(); + + public Consultant? GetConsultant(string id) + { + var value = _idSerializer.Deserialize(id); + return _repository.Consultants.Find(t => t.Id?.Equals(value.Value) ?? false); + } + + public ICustomerOrConsultant? GetCustomerOrConsultant(string id) + { + var value = _idSerializer.Deserialize(id); + return value.TypeName == "Consultant" ? GetConsultant(id) : GetCustomer(id); + } + + public Customer? GetCustomer(CustomerKind kind) + => _repository.Customers.Find(t => t.Kind == kind); +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/QueryType.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/QueryType.cs new file mode 100644 index 00000000000..10ffc9a5e3d --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/QueryType.cs @@ -0,0 +1,31 @@ +using HotChocolate.Types; + +namespace HotChocolate.Stitching.Schemas.Customers; + +public class QueryType + : ObjectType +{ + protected override void Configure( + IObjectTypeDescriptor descriptor) + { + descriptor.Field(t => t.GetCustomer(default(string)!)) + .Argument("id", a => a.Type>()) + .Type(); + + descriptor.Field(t => t.GetCustomers(default!)) + .Argument("ids", a => a.Type>>>()) + .Type>(); + + descriptor.Field(t => t.GetConsultant(default!)) + .Argument("id", a => a.Type>()) + .Type(); + + descriptor.Field(t => t.GetCustomerOrConsultant(default!)) + .Argument("id", a => a.Type>()) + .Type(); + + descriptor.Field(t => t.GetCustomer(default(CustomerKind))) + .Name("customerByKind") + .Type(); + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/SayInput.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/SayInput.cs new file mode 100644 index 00000000000..d0e553bb11c --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Customers/SayInput.cs @@ -0,0 +1,6 @@ +namespace HotChocolate.Stitching.Schemas.Customers; + +public class SayInput +{ + public List? Words { get; set; } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Inventory/InventoryInfo.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Inventory/InventoryInfo.cs new file mode 100644 index 00000000000..5aec7795925 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Inventory/InventoryInfo.cs @@ -0,0 +1,14 @@ +namespace HotChocolate.Stitching.Schemas.Inventory; + +public class InventoryInfo +{ + public InventoryInfo(int upc, bool isInStock) + { + Upc = upc; + IsInStock = isInStock; + } + + public int Upc { get; } + + public bool IsInStock { get; } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Inventory/InventoryInfoRepository.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Inventory/InventoryInfoRepository.cs new file mode 100644 index 00000000000..b97a7630666 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Inventory/InventoryInfoRepository.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Linq; + +namespace HotChocolate.Stitching.Schemas.Inventory; + +public class InventoryInfoRepository +{ + private readonly Dictionary _infos; + + public InventoryInfoRepository() + { + _infos = new InventoryInfo[] + { + new InventoryInfo(1, true), + new InventoryInfo(2, false), + new InventoryInfo(3, true) + }.ToDictionary(t => t.Upc); + } + + public InventoryInfo GetInventoryInfo(int upc) => _infos[upc]; +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Inventory/InventorySchemaRequestExecutorBuilderExtensions.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Inventory/InventorySchemaRequestExecutorBuilderExtensions.cs new file mode 100644 index 00000000000..96574b8835a --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Inventory/InventorySchemaRequestExecutorBuilderExtensions.cs @@ -0,0 +1,17 @@ +using HotChocolate.Execution.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace HotChocolate.Stitching.Schemas.Inventory; + +public static class InventorySchemaRequestExecutorBuilderExtensions +{ + public static IRequestExecutorBuilder AddInventorySchema( + this IRequestExecutorBuilder builder) + { + builder.Services + .AddSingleton(); + + return builder + .AddQueryType(); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Inventory/Query.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Inventory/Query.cs new file mode 100644 index 00000000000..3a99d32edf0 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Inventory/Query.cs @@ -0,0 +1,12 @@ +namespace HotChocolate.Stitching.Schemas.Inventory; + +public class Query +{ + public InventoryInfo GetInventoryInfo( + int upc, + [Service] InventoryInfoRepository repository) => + repository.GetInventoryInfo(upc); + + public double GetShippingEstimate(int price, int weight) => + price > 1000 ? 0 : weight * 0.5; +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Products/Product.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Products/Product.cs new file mode 100644 index 00000000000..f8c81b7f8ff --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Products/Product.cs @@ -0,0 +1,17 @@ +namespace HotChocolate.Stitching.Schemas.Products; + +public class Product +{ + public Product(int upc, string name, int price, int weight) + { + Upc = upc; + Name = name; + Price = price; + Weight = weight; + } + + public int Upc { get; } + public string Name { get; } + public int Price { get; } + public int Weight { get; } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Products/ProductRepository.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Products/ProductRepository.cs new file mode 100644 index 00000000000..6f7f66a4223 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Products/ProductRepository.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Linq; + +namespace HotChocolate.Stitching.Schemas.Products; + +public class ProductRepository +{ + private readonly Dictionary _products; + + public ProductRepository() + { + _products = new Product[] + { + new Product(1, "Table", 899, 100), + new Product(2, "Couch", 1299, 1000), + new Product(3, "Chair", 54, 50) + }.ToDictionary(t => t.Upc); + } + + [GraphQLNonNullType] + public IEnumerable GetTopProducts(int first) => + _products.Values.OrderBy(t => t.Upc).Take(first); + + public Product GetProduct (int upc) => _products[upc]; +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Products/ProductsSchemaRequestExecutorBuilderExtensions.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Products/ProductsSchemaRequestExecutorBuilderExtensions.cs new file mode 100644 index 00000000000..8b89cb00f43 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Products/ProductsSchemaRequestExecutorBuilderExtensions.cs @@ -0,0 +1,17 @@ +using HotChocolate.Execution.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace HotChocolate.Stitching.Schemas.Products; + +public static class ProductsSchemaRequestExecutorBuilderExtensions +{ + public static IRequestExecutorBuilder AddProductsSchema( + this IRequestExecutorBuilder builder) + { + builder.Services + .AddSingleton(); + + return builder + .AddQueryType(); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Products/Query.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Products/Query.cs new file mode 100644 index 00000000000..bce43cfbb3b --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Products/Query.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace HotChocolate.Stitching.Schemas.Products; + +public class Query +{ + public IEnumerable GetTopProducts( + int first, + [Service] ProductRepository repository) => + repository.GetTopProducts(first); + + public Product GetProduct( + int upc, + [Service] ProductRepository repository) => + repository.GetProduct(upc); +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/Author.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/Author.cs new file mode 100644 index 00000000000..a6a5cdabf0c --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/Author.cs @@ -0,0 +1,13 @@ +namespace HotChocolate.Stitching.Schemas.Reviews; + +public class Author +{ + public Author(int id, string name) + { + Id = id; + Name = name; + } + + public int Id { get; } + public string Name { get; } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/Query.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/Query.cs new file mode 100644 index 00000000000..fe7afef19a5 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/Query.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace HotChocolate.Stitching.Schemas.Reviews; + +public class Query +{ + public IEnumerable GetReviews( + [Service] ReviewRepository repository) => + repository.GetReviews(); + + public IEnumerable GetReviewsByAuthor( + [Service] ReviewRepository repository, + int authorId) => + repository.GetReviewsByAuthorId(authorId); + + public IEnumerable GetReviewsByProduct( + [Service] ReviewRepository repository, + int upc) => + repository.GetReviewsByAuthorId(upc); +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/Review.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/Review.cs new file mode 100644 index 00000000000..55bacb4df79 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/Review.cs @@ -0,0 +1,17 @@ +namespace HotChocolate.Stitching.Schemas.Reviews; + +public class Review +{ + public Review(int id, int authorId, int upc, string body) + { + Id = id; + AuthorId = authorId; + Upc = upc; + Body = body; + } + + public int Id { get; } + public int AuthorId { get; } + public int Upc { get; } + public string Body { get; } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/ReviewRepository.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/ReviewRepository.cs new file mode 100644 index 00000000000..4762a0ae4a6 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/ReviewRepository.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Linq; + +namespace HotChocolate.Stitching.Schemas.Reviews; + +public class ReviewRepository +{ + private readonly Dictionary _reviews; + private readonly Dictionary _authors; + + public ReviewRepository() + { + _reviews = new Review[] + { + new Review(1, 1, 1, "Love it!"), + new Review(2, 1, 2, "Too expensive."), + new Review(3, 2, 3, "Could be better."), + new Review(4, 2, 1, "Prefer something else.") + }.ToDictionary(t => t.Id); + + _authors = new Author[] + { + new Author(1, "@ada"), + new Author(2, "@complete") + }.ToDictionary(t => t.Id); + } + + public IEnumerable GetReviews() => + _reviews.Values.OrderBy(t => t.Id); + + public IEnumerable GetReviewsByProductId(int upc) => + _reviews.Values.OrderBy(t => t.Id).Where(t => t.Upc == upc); + + public IEnumerable GetReviewsByAuthorId(int authorId) => + _reviews.Values.OrderBy(t => t.Id).Where(t => t.AuthorId == authorId); + + public Review GetReview(int id) => _reviews[id]; + + public Author GetAuthor(int id) => _authors[id]; +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/ReviewsSchemaRequestExecutorBuilderExtensions.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/ReviewsSchemaRequestExecutorBuilderExtensions.cs new file mode 100644 index 00000000000..e5729a48bdf --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/ReviewsSchemaRequestExecutorBuilderExtensions.cs @@ -0,0 +1,17 @@ +using HotChocolate.Execution.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace HotChocolate.Stitching.Schemas.Reviews; + +public static class ReviewSchemaRequestExecutorBuilderExtensions +{ + public static IRequestExecutorBuilder AddReviewSchema( + this IRequestExecutorBuilder builder) + { + builder.Services + .AddSingleton(); + + return builder + .AddQueryType(); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/SchemaTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/SchemaTests.cs new file mode 100644 index 00000000000..0bf757e25de --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/SchemaTests.cs @@ -0,0 +1,37 @@ +using System.Threading.Tasks; +using HotChocolate.Execution; +using HotChocolate.Stitching.Schemas.Contracts; +using HotChocolate.Stitching.Schemas.Customers; +using HotChocolate.Tests; +using Microsoft.Extensions.DependencyInjection; +using Snapshooter.Xunit; +using Xunit; + +namespace HotChocolate.Stitching.Schemas; + +public class SchemaTests +{ + [Fact] + public async Task CustomerSchemaSnapshot() + { + Snapshot.FullName(); + + await new ServiceCollection() + .AddGraphQL() + .AddCustomerSchema() + .BuildSchemaAsync() + .MatchSnapshotAsync(); + } + + [Fact] + public async Task ContractSchemaSnapshot() + { + Snapshot.FullName(); + + await new ServiceCollection() + .AddGraphQL() + .AddContractSchema() + .BuildSchemaAsync() + .MatchSnapshotAsync(); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/SpecialCases/ContractSchemaFactory.txt b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/SpecialCases/ContractSchemaFactory.txt new file mode 100644 index 00000000000..13faacce77a --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/SpecialCases/ContractSchemaFactory.txt @@ -0,0 +1,124 @@ +using System; +using HotChocolate.Configuration; +using HotChocolate.Language; +using HotChocolate.Types; +using Microsoft.Extensions.DependencyInjection; + +namespace HotChocolate.Stitching.Schemas.SpecialCases +{ + public static class SpecialCasesSchemaFactory + { + public static ISchema Create() + { + return SchemaBuilder.New() + .AddQueryType() + .Create(); + } + + public static void ConfigureServices(IServiceCollection services) + { + services.AddGraphQL(Create()); + } + } + + public class QueryType + : ObjectType + { + protected override void Configure(IObjectTypeDescriptor descriptor) + { + descriptor.Field("custom_scalar") + .Type() + .Argument("bar", a => a.Type()) + .Resolver(ctx => ctx.Argument("bar")); + + descriptor.Field("custom_scalar_complex") + .Type() + .Argument("bar", a => a.Type()) + .Resolver(ctx => + { + CustomInputValue input = ctx.Argument("bar"); + + return new MyCustomScalarValue { Text = $"{input.From.Text}-{input.To.Text}" }; + }); + } + } + + public class MyCustomScalarValue + { + public string Text { get; set; } + } + + public class MyCustomScalarType : ScalarType + { + public MyCustomScalarType() : base(nameof(MyCustomScalarValue)) + { + } + + protected override MyCustomScalarValue ParseLiteral(StringValueNode literal) + { + return new MyCustomScalarValue {Text = literal.Value}; + } + + protected override StringValueNode ParseValue(MyCustomScalarValue value) + { + return new StringValueNode(null, value.Text, false); + } + + public override bool TrySerialize(object value, out object serialized) + { + if (value is null) + { + serialized = null; + return true; + } + + if (value is MyCustomScalarValue s) + { + serialized= s.Text; + return true; + } + + serialized = null; + return false; + } + + public override bool TryDeserialize(object serialized, out object value) + { + if (serialized is null) + { + value = null; + return true; + } + + if (serialized is string s) + { + value = new MyCustomScalarValue { Text = s }; + return true; + } + + value = null; + return false; + } + } + + public class CustomInputValue + { + public MyCustomScalarValue From { get; set; } + + public MyCustomScalarValue To { get; set; } + } + + public class CustomInputValueType : InputObjectType + { + protected override void Configure(IInputObjectTypeDescriptor descriptor) + { + base.Configure(descriptor); + + descriptor.Field(i => i.From) + .Type>(); + + descriptor.Field(i => i.To) + .Type>(); + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/__snapshots__/SchemaTests.ContractSchemaSnapshot.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/__snapshots__/SchemaTests.ContractSchemaSnapshot.snap new file mode 100644 index 00000000000..156e80f32dc --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/__snapshots__/SchemaTests.ContractSchemaSnapshot.snap @@ -0,0 +1,76 @@ +schema { + query: Query +} + +interface Contract implements Node { + id: ID! + customerId: ID! +} + +"The node interface is implemented by entities that have a global unique identifier." +interface Node { + id: ID! +} + +type LifeInsuranceContract implements Node & Contract { + id: ID! + customerId: ID! + foo(bar: String): String + error: String + date_field: Date + date_time_field: DateTime + string_field: String + id_field: ID + byte_field: Byte + int_field: Int + long_field: Long + float_field(f: Float): Float + decimal_field: Decimal + premium: Float! +} + +type Query { + "Fetches an object given its ID." + node("ID of the object." id: ID!): Node + "Lookup nodes by a list of IDs." + nodes("The list of node IDs." ids: [ID!]!): [Node]! + contract(contractId: ID!): Contract + contracts(customerId: ID!): [Contract!] + extendedScalar(d: DateTime): DateTime + int(i: Int!): Int! + guid(guid: UUID!): UUID! +} + +type SomeOtherContract implements Node & Contract { + id: ID! + customerId: ID! + expiryDate: DateTime! +} + +directive @custom(d: DateTime) on FIELD + +"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 `@specifiedBy` directive is used within the type system definition language to provide a URL for specifying the behavior of custom scalar definitions." +directive @specifiedBy("The specifiedBy URL points to a human-readable specification. This field will only read a result for scalar types." url: String!) on SCALAR + +"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! = 0 "Streamed when true." if: Boolean) on FIELD + +"The `Byte` scalar type represents non-fractional whole numeric values. Byte can represent values between 0 and 255." +scalar Byte + +"The `Date` scalar represents an ISO-8601 compliant date type." +scalar Date + +"The `DateTime` scalar represents an ISO-8601 compliant date time type." +scalar DateTime @specifiedBy(url: "https:\/\/www.graphql-scalars.com\/date-time") + +"The built-in `Decimal` scalar type." +scalar Decimal + +"The `Long` scalar type represents non-fractional signed whole 64-bit numeric values. Long can represent values between -(2^63) and 2^63 - 1." +scalar Long + +scalar UUID @specifiedBy(url: "https:\/\/tools.ietf.org\/html\/rfc4122") diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/__snapshots__/SchemaTests.CustomerSchemaSnapshot.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/__snapshots__/SchemaTests.CustomerSchemaSnapshot.snap new file mode 100644 index 00000000000..4bfefc5a8c2 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/__snapshots__/SchemaTests.CustomerSchemaSnapshot.snap @@ -0,0 +1,114 @@ +schema { + query: Query + mutation: Mutation +} + +"The node interface is implemented by entities that have a global unique identifier." +interface Node { + id: ID! +} + +type Consultant implements Node { + id: ID! + name: String! + customers("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): CustomersConnection +} + +type CreateCustomerPayload { + customer: Customer! +} + +type Customer implements Node { + id: ID! + name: String! + street: String! + consultant: Consultant + say(input: SayInput!): String + complexArg(arg: ComplexInputType): String + someInt: Int! + someGuid: UUID! + kind: CustomerKind! +} + +"A connection to a list of items." +type CustomersConnection { + "Information to aid in pagination." + pageInfo: PageInfo! + "A list of edges." + edges: [CustomersEdge!] + "A flattened list of the nodes." + nodes: [Customer] +} + +"An edge in a connection." +type CustomersEdge { + "A cursor for use in pagination." + cursor: String! + "The item at the end of the edge." + node: Customer +} + +type Mutation { + createCustomer(input: CreateCustomerInput!): CreateCustomerPayload! + createCustomers(inputs: [CreateCustomerInput!]!): [CreateCustomerPayload!]! +} + +"Information about pagination in a connection." +type PageInfo { + "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 +} + +type Query { + "Fetches an object given its ID." + node("ID of the object." id: ID!): Node + "Lookup nodes by a list of IDs." + nodes("The list of node IDs." ids: [ID!]!): [Node]! + customer(id: ID!): Customer + customers(ids: [ID!]!): [Customer] + consultant(id: ID!): Consultant + customerOrConsultant(id: ID!): CustomerOrConsultant + customerByKind(kind: CustomerKind!): Customer + allCustomers: [Customer!]! +} + +union CustomerOrConsultant = Customer | Consultant + +input ComplexInputType { + value: String + deeper: ComplexInputType + valueArray: [String] + deeperArray: [ComplexInputType] +} + +input CreateCustomerInput { + name: String + street: String + consultantId: String +} + +input SayInput { + words: [String!] +} + +enum CustomerKind { + STANDARD + PREMIUM +} + +"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 `@specifiedBy` directive is used within the type system definition language to provide a URL for specifying the behavior of custom scalar definitions." +directive @specifiedBy("The specifiedBy URL points to a human-readable specification. This field will only read a result for scalar types." url: String!) on SCALAR + +"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! = 0 "Streamed when true." if: Boolean) on FIELD + +scalar UUID @specifiedBy(url: "https:\/\/tools.ietf.org\/html\/rfc4122") diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/AccountSchemaDefinition.json b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/AccountSchemaDefinition.json new file mode 100644 index 00000000000..ef95845a8c2 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/AccountSchemaDefinition.json @@ -0,0 +1,8 @@ +{ + "Name": "accounts", + "Document": "schema { query: Query } type Query { users: [User] user(id: Int!): User } type User { id: Int! name: String birthdate: DateTime! username: String foo: String } type _SchemaDefinition { name: String! document: String! extensionDocuments: [String!]! } \u0022The \u0060@defer\u0060 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 \u0060@defer\u0060 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. \u0060@include\u0060 and \u0060@skip\u0060 take precedence over \u0060@defer\u0060.\u0022 directive @defer(\u0022If 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.\u0022 label: String \u0022Deferred when true.\u0022 if: Boolean) on FRAGMENT_SPREAD | INLINE_FRAGMENT \u0022The @deprecated directive is used within the type system definition language to indicate deprecated portions of a GraphQL service\u2019s schema,such as deprecated fields on a type or deprecated enum values.\u0022 directive @deprecated(\u0022Deprecations include a reason for why it is deprecated, which is formatted using Markdown syntax (as specified by CommonMark).\u0022 reason: String = \u0022No longer supported\u0022) on FIELD_DEFINITION | ENUM_VALUE \u0022Directs the executor to include this field or fragment only when the \u0060if\u0060 argument is true.\u0022 directive @include(\u0022Included when true.\u0022 if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT \u0022Directs the executor to skip this field or fragment when the \u0060if\u0060 argument is true.\u0022 directive @skip(\u0022Skipped when true.\u0022 if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT \u0022The \u0060@stream\u0060 directive may be provided for a field of \u0060List\u0060 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. \u0060@include\u0060 and \u0060@skip\u0060 take precedence over \u0060@stream\u0060.\u0022 directive @stream(\u0022If 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.\u0022 label: String \u0022The initial elements that shall be send down to the consumer.\u0022 initialCount: Int! \u0022Streamed when true.\u0022 if: Boolean!) on FIELD \u0022The \u0060DateTime\u0060 scalar represents an ISO-8601 compliant date time type.\u0022 scalar DateTime", + "ExtensionDocuments": [ + "extend type Query {\n me: User! @delegate(path: \u0022user(id: 1)\u0022)\n}\n\nextend type Review {\n author: User @delegate(path: \u0022user(id: $fields:authorId)\u0022)\n}", + "extend schema @_removeRootTypes {\n\n}" + ] +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/AdvisorClient.graphql b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/AdvisorClient.graphql new file mode 100644 index 00000000000..fb69580c4fb --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/AdvisorClient.graphql @@ -0,0 +1,116 @@ +schema { + query: Query +} + +type Query { + contractAdvisor(advisorRequest: AdvisorRequestInput! = null): Advisor! + contractAdvisors(advisorRequests: [AdvisorRequestInput!]! = null): [Advisor!]! + digisAdvisor(contractNumber: String! = null): Advisor! + evContractAdvisor(contractId: String! = null): Advisor! + fzContractAdvisor(contractId: String! = null): Advisor! + mortgageAdvisor(mortgageNumber: String = null): Advisor! + nvsAdvisor(contractNumber: String! = null): Advisor! + swissLifeGeneralInfo: SwissLifeGeneralInfo! + threeAContractAdvisor(contractId: String! = null): Advisor! + zmaContractAdvisor(contractId: String! = null): Advisor! +} + +type SwissLifeAdvisor implements Advisor { + agency: GeneralAgency + email: String + firstName: String! + id: ID! + "Translated" + jobDescription: String + lastName: String! + phoneNumber: String + pictureUrl: String +} + +type BrokerCompany implements Advisor { + companyName: String! @deprecated(reason: "Use Name instead.") + email: String + id: ID! + name: BrokerCompanyName! + phoneNumber: String + pictureUrl: String + url: String +} + +type GeneralAgency implements Advisor { + address: Address @deprecated(reason: "Not used on Front") + agencyName: String! + email: String + id: ID! + phoneNumber: String + pictureUrl: String + url: String +} + +type SwissLifeGeneralInfo implements Advisor { + email: String + id: ID! + name: String! + phoneNumber: String + phoneNumbers: [SwissLifePhoneNumber]! + pictureUrl: String +} + +enum ApplyPolicy { + BEFORE_RESOLVER + AFTER_RESOLVER +} + +interface Advisor { + email: String + id: ID! + phoneNumber: String + pictureUrl: String +} + +type SwissLifePhoneNumber { + "Translated" + label: String! + phoneNumber: String! +} + +enum TranslatableLanguage { + NOTSET + DE + FR + IT + EN +} + +type BrokerCompanyName { + additionalName: String + displayName: String! + name: String! +} + +input AdvisorRequestInput { + sourceRelevantId: String! = null + sourceSystem: SourceSystem! = null +} + +type Address { + city: String! + country: String! + streetName: String! + streetNumber: String! + zipCode: String! +} + +enum SourceSystem { + HSSAG + DIGIS + NVS + NVS_FZP + EV + LPZZMA + LPZFZ + LPZ +} + +directive @authorize("Defines when when the resolver shall be executed.By default the resolver is executed after the policy has determined that the current user is allowed to access the field." apply: ApplyPolicy! = BEFORE_RESOLVER "The name of the authorization policy that determines access to the annotated resource." policy: String = null "Roles that are allowed to access the annotated resource." roles: [String!] = null) repeatable on SCHEMA | OBJECT | FIELD_DEFINITION + diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/Contract.graphql b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/Contract.graphql new file mode 100644 index 00000000000..5ba63e24812 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/Contract.graphql @@ -0,0 +1,18 @@ +type Query { + contract(contractId: ID!): Contract + contracts(customerId: ID!): [Contract!] +} + +interface Contract { + id: ID! +} + +type LifeInsuranceContract implements Contract { + id: ID! + premium: Float +} + +type SomeOtherContract implements Contract { + id: ID! + expiryDate: DateTime +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/ContractClient.graphql b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/ContractClient.graphql new file mode 100644 index 00000000000..35b019f7fb0 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/ContractClient.graphql @@ -0,0 +1,1920 @@ +schema { + query: Query + mutation: Mutation + subscription: Subscription +} + +type Query { + contract("The contract's Id" id: ID! = null): Contract + customerContract: CustomerContract! + node(id: ID! = null): Node +} + +type Mutation { + confirmThreeAPaymentSimulation(confirmations: [ThreeAPaymentConfirmationInput!]! = null): [ThreeAPaymentConfirmation!]! + portfolioCheck(contractId: ID! = null): Boolean! + requestSurrenderValueDocument(request: SurrenderValueDocument! = null): RequestSurrenderValueDocumentInfo + setPaperless(paperless: [PaperlessInput!]! = null supressNotification: Boolean = null): [PaperlessResult!]! +} + +type ZmaContract implements Contract & Investment & Paperless & Node { + accounts: [ZmaAccount]! + accounttransactions(after: String = null before: String = null filter: [FilterInfoInput] = null first: Int = null last: Int = null sortBy: String = null sortDirection: SortDirection = null): ZmaAccountTransactionConnection @deprecated(reason: "Deprecated use accountTransaction instead") + accountTransactions(after: String = null before: String = null first: Int = null last: Int = null order_by: ZmaAccountTransactionSort = null where: ZmaAccountTransactionFilter = null): ZmaAccountTransactionConnection + active: Boolean! + begin: DateTime! + category: ContractCategory! + contractId: String! + currency: String! + customer: ZmaCustomer! + depositsFromContractStart: Float + id: ID! + investmentStrategy: InvestmentStrategy! + investmentTheme: InvestmentTheme + latestContractPerformanceYear: ZmaContractPerformance! + number: String! + paperlessEnabled: Boolean + payoutPeriodicity: PaymentPeriodicity! + payoutPeriodicPayment: Float + payoutStartMonth: DateTime + portfolio: ZmaPortfolio + portfolioName: String + powerOfAttorney: ZmaPowerOfAttorney + product: ZmaProduct! + productName: String! + sourceSystemName: String + totalBalance: Float! + uniqueId: String + youthAccountDateOfBirth: DateTime + youthAccountFirstName: String + youthAccountGender: Gender + youthAccountLastName: String +} + +interface PensionFundPolicy { + active: Boolean! + additionalLumpSumDeathBenefitMarried: Float! + additionalLumpSumDeathBenefitMarriedAccident: Float! + additionalLumpSumDeathBenefitNotMarried: Float! + additionalLumpSumDeathBenefitNotMarriedAccident: Float! + begin: DateTime! + benefits: PensionFundPolicyBenefits! + category: ContractCategory! + childrensBenefitIvPension: Float! + childrensBenefitIvPensionAccident: Float! + childrensBenefitIvWaitingPeriod: Int! + childrensBenefitPension: Float! + contributionEmployee: Float! + contributionEmployer: Float! + contributionExonerationDelay: Int! + contributionTotal: Float! + conversionRateMandatory: Float! + conversionRateSupplementary: Float! + currency: String! + deathBenefit: Float! + degreeOfDisability: Float! + endDate: DateTime + "PolicyId" + id: ID! + insuranceGroup: String + interestRateMandatory: Float! + interestRateSupplementary: Float! + iVPension: Float! + iVPensionAccident: Float! + iVWaitingPeriod: Int! + levelOfEmployment: Float! + monthlyRetirementPension: Float! + number: String! + nvsPortalInsuredPersonId: String! + orphansBenefit: Float! + orphansBenefitAccident: Float! + paperlessEnabled: Boolean + pensionPlan: PensionPlan + policyId: String! + productName: String! + purchaseEarlyWithdrawalForHomeOwnership: Float! + purchasePensionFund: Float! + purchaseTotalPossible: Float! + purchaseWithdrawalDivorce: Float! + reportedSalary: Float! + retirementCapital: Float! + retirementPension: Float! + retirementSavings: Float! + sourceSystemName: String + startDateEmployment: DateTime + status: PensionFundPolicyStatus! + totalBalance: Float! + unmarriedPartnersPension: Float! + unmarriedPartnersPensionAccident: Float! + withdrawalCapital: Float! +} + +interface Address { + additional: String + careOf: String + city: String + company: String + department: String + firstName: String + id: String + lastName: String + postOfficeBox: String + salutation: String + street: String + zipCode: String +} + +type XPlanPolicy implements Contract & PensionFundPolicy & Paperless & Node { + active: Boolean! + additionalLumpSumDeathBenefitMarried: Float! + additionalLumpSumDeathBenefitMarriedAccident: Float! + additionalLumpSumDeathBenefitNotMarried: Float! + additionalLumpSumDeathBenefitNotMarriedAccident: Float! + begin: DateTime! + benefits: PensionFundPolicyBenefits! + category: ContractCategory! + childrensBenefitIvPension: Float! + childrensBenefitIvPensionAccident: Float! + childrensBenefitIvWaitingPeriod: Int! + childrensBenefitPension: Float! + contact: XPlanContactInfo + contract: XPlanContract! + contractId: String! + contributionEmployee: Float! + contributionEmployer: Float! + contributionExonerationDelay: Int! + contributionTotal: Float! + conversionRateMandatory: Float! + conversionRateSupplementary: Float! + currency: String! + currentFundBalance: XPlanPremiumFundBalance + deathBenefit: Float! + degreeOfDisability: Float! + domicileAddress: XPlanAddress + endDate: DateTime + fundDevelopments: [XPlanFundDevelopment!] + "PolicyId" + id: ID! + insuranceGroup: String + interestRateMandatory: Float! + interestRateSupplementary: Float! + iVPension: Float! + iVPensionAccident: Float! + iVWaitingPeriod: Int! + levelOfEmployment: Float! + maxRetirementCapital: Float! + monthlyRetirementPension: Float! + number: String! + nvsPortalInsuredPersonId: String! + orphansBenefit: Float! + orphansBenefitAccident: Float! + paperlessEnabled: Boolean + pensionPlan: PensionPlan + policyId: String! + productName: String! + projectedRetirement: XPlanProjectedRetirement! + purchaseEarlyWithdrawalForHomeOwnership: Float! + purchasePensionFund: Float! + purchaseTotalPossible: Float! + purchaseWithdrawalDivorce: Float! + reportedSalary: Float! + retirementCapital: Float! + retirementDevelopment: [XPlanRetirementDevelopment!]! + retirementPension: Float! + retirementSavings: Float! + "Null for inactive policies" + simulationParameters: PensionFundSimulationParameters + sourceSystemName: String + startDateEmployment: DateTime + status: PensionFundPolicyStatus! + totalBalance: Float! + transactions(after: String = null before: String = null first: Int = null last: Int = null order_by: XPlanTransactionSort = null where: XPlanPensionFundTransactionFilterInput = null): XPlanTransactionConnection + unmarriedPartnersPension: Float! + unmarriedPartnersPensionAccident: Float! + withdrawalCapital: Float! +} + +type PensionFundFzPolicy implements Contract & Paperless & Node { + active: Boolean! + ahvNumber: String + benefits: PensionFundFzPolicyBenefits! + birthDate: DateTime! + category: ContractCategory! + contractId: String! + currency: String! + displayName: String! + domicileAddress: XPlanAddress + fimCustomerId: String! + firstName: String! + "PolicyId" + id: ID! + interestRate: Float! + lastName: String! + number: String! + nvsPortalInsuredPersonId: String! + paperlessEnabled: Boolean + planType: PensionFundFzPolicyPlanType! + "Translated" + productName: String! + retirementAge: Int! + retirementDate: DateTime! + "Null for inactive policies" + simulationParameters: PensionFundSimulationParameters + startDate: DateTime! + status: PensionFundPolicyStatus! + totalBalance: Float! + transactions(after: String = null before: String = null first: Int = null last: Int = null order_by: PensionFundFzPolicyTransactionSort = null where: PensionFundFzPolicyTransactionFilter = null): PensionFundFzPolicyTransactionConnection + validFrom: DateTime! +} + +type DigisPolicy implements Contract & PensionFundPolicy & Paperless & Node { + active: Boolean! + additionalLumpSumDeathBenefitMarried: Float! + additionalLumpSumDeathBenefitMarriedAccident: Float! + additionalLumpSumDeathBenefitNotMarried: Float! + additionalLumpSumDeathBenefitNotMarriedAccident: Float! + begin: DateTime! + benefits: PensionFundPolicyBenefits! + category: ContractCategory! + childrensBenefitIvPension: Float! + childrensBenefitIvPensionAccident: Float! + childrensBenefitIvWaitingPeriod: Int! + childrensBenefitPension: Float! + contract: DigisContract! + contractId: String! + contributionEmployee: Float! + contributionEmployer: Float! + contributionExonerationDelay: Int! + contributionTotal: Float! + conversionRateMandatory: Float! + conversionRateSupplementary: Float! + currency: String! + customer: DigisCustomer! + deathAfterRetirement: DeathAfterRetirement + deathBenefit: Float! + degreeOfDisability: Float! + endDate: DateTime + "PolicyId" + id: ID! + insuranceGroup: String + interestRateMandatory: Float! + interestRateSupplementary: Float! + iVPension: Float! + iVPensionAccident: Float! + iVWaitingPeriod: Int! + lastCalcDate: DateTime! + levelOfEmployment: Float! + monthlyRetirementPension: Float! + number: String! + nvsPortalInsuredPersonId: String! + orphansBenefit: Float! + orphansBenefitAccident: Float! + paperlessEnabled: Boolean + pensionPlan: PensionPlan + policyId: String! + policyTypeId: String + productName: String! + projectedRetirement: DigisProjectedRetirement + purchaseEarlyWithdrawalForHomeOwnership: Float! + purchasePensionFund: Float! + purchaseTotalPossible: Float! + purchaseWithdrawalDivorce: Float! + reportedSalary: Float! + retirementCapital: Float! + retirementDevelopment: [DigisRetirementDevelopment!]! + retirementPension: Float! + retirementSavings: Float! + sourceSystemName: String + startDateEmployment: DateTime + status: PensionFundPolicyStatus! + statusId: Int! + totalBalance: Float! + transactionId: Int! + transactions(after: String = null before: String = null first: Int = null last: Int = null order_by: DigisTransactionSort = null where: DigisPensionFundTransactionFilterInput = null): DigisTransactionConnection + unmarriedPartnersPension: Float! + unmarriedPartnersPensionAccident: Float! + withdrawalCapital: Float! +} + +type EvContract implements Contract & ThreeA & ThreeASimulation & Paperless & Node { + accountContractId: String @deprecated(reason: "No longer required") + accountId: ID + active: Boolean! + annualPremium: Float! + annualPremiumSimulation: Float @deprecated(reason: "Use threeASimulation.annualPremiumSimulation instead") + begin: DateTime! + beneficiaryClause: String! + benefitsUponSurvivalDisclaimer: String @deprecated(reason: "use benefitUponSurvival.Disclaimer instead") + benefitUponSurvival: EvBenefitUponSurvival + bVGInsured: Boolean + bvgMismatch: Boolean! + category: ContractCategory! + contractEndDate: DateTime + contractId: String! + currency: String! + currencySimulation: String @deprecated(reason: "Use threeASimulation.currencySimulation instead") + customer: EvCustomer! + customers: [EvCustomer!]! + hasAccount: Boolean! + hasMonetaryAsset: Boolean! + id: ID! + "Translatable" + insuranceType: String! + "Translated" + insuranceTypeName: String! + isThreeA: Boolean! + maximumAmount: Float + maximumAmountSimulation: Float @deprecated(reason: "Use threeASimulation.maximumAmountSimulation instead") + maximumAnnualSimulation: Float @deprecated(reason: "Use threeASimulation.maximumAnnualSimulation instead") + minimumAmountSimulation: Float @deprecated(reason: "Use threeASimulation.minimumAmountSimulation instead") + number: String! + paperlessEnabled: Boolean + partnerId: String! + paymentPossibleSimulation: Boolean! @deprecated(reason: "Use threeASimulation.paymentPossibleSimulation instead") + "Translatable" + pillar: String + portfolio: EvPortfolio + portfolioCode: String + portfolioName: String + premium: EvPremium + premiumInvoiceProcessing: Boolean! + premiuminvoices(after: String = null before: String = null first: Int = null last: Int = null order_by: EvPremiumInvoiceSort = null where: EvPremiumInvoiceFilter = null): EvPremiumInvoiceConnection + productGroup: String! + "Translatable" + productId: String + productName: String! + productNameCode: String + retirementStart: DateTime + sourceSystemName: String + surrenderValue: EvSurrenderValue + surrenderValueDocumentPartnerId: String! + tariffs: [EvTariff!]! + threeAPaymentInfo(amount: Float! = null): ThreeASimulationPaymentInfo @deprecated(reason: "Use threeASimulation.threeAPaymentInfo instead") + threeASimulation: EvContractThreeASimulationType! + totalBalance: Float! + uniqueId: String +} + +type EvAccount implements Contract & Node { + accountBalance: Float! + accountCategory: EvAccountCategory! + accountdevelopments: [EvAccountDevelopment!]! + accounttransactions(after: String = null before: String = null filter: [FilterInfoInput] = null first: Int = null last: Int = null sortBy: String = null sortDirection: SortDirection = null): EvAccountTransactionConnection @deprecated(reason: "Deprecated use accountTransaction instead") + accountTransactions(after: String = null before: String = null first: Int = null last: Int = null order_by: EvAccountTransactionSort = null where: EvAccountTransactionFilter = null): EvAccountTransactionConnection + "Translatable" + accountType: String + active: Boolean! + availableProducts: [EvAccountProduct!]! + category: ContractCategory! + connectedContracts: [EvContract!]! + contractId: String! + currency: String! + id: ID! + number: String! + partner: EvPartner! + productName: String! + referenceNumber: String + totalBalance: Float! +} + +type MortgageContract implements Contract & Node { + active: Boolean! + amountTotal: Float! + begin: DateTime + category: ContractCategory! + collaterals: [MortgageCollateral]! + contractId: String! + contractNumber: String! + currency: String! + id: ID! + interests: [MortgageInterest]! + mortgages(order_by: MortgageSort = null): [Mortgage]! + number: String! + object: MortgageObject! + productName: String! + sourceSystemName: String + stakeholders: [MortgageStakeholder]! + totalBalance: Float! + uniqueId: String + yearlyInterestTotal: Float! +} + +type ThreeAStartContract implements Contract & Node & Paperless & ThreeA & ThreeASimulation { + accounts: [ThreeAStartFzAccount!]! + accounttransactions(after: String = null before: String = null filter: [FilterInfoInput] = null first: Int = null last: Int = null sortBy: String = null sortDirection: SortDirection = null): ThreeAStartFzTransactionConnection @deprecated(reason: "Deprecated use accountTransaction instead") + accountTransactions(after: String = null before: String = null first: Int = null last: Int = null order_by: ThreeAStartFzTransactionSort = null where: ThreeAStartFzTransactionFilter = null): ThreeAStartFzTransactionConnection + active: Boolean! + annualPremium: Float! + annualPremiumSimulation: Float @deprecated(reason: "use threeASimulation.annualPremiumSimulation instead.") + availableAccountTypes: [ThreeAStartFzAccountType]! + begin: DateTime! + bvgMismatch: Boolean! + category: ContractCategory! + closeCode: Int + commissionDiscount: Float + contractChangeDate: DateTime + contractdevelopments: [ThreeAStartFzDevelopment!]! + contractId: String! + contractProductNumber: String + currency: String! + currencySimulation: String @deprecated(reason: "use threeASimulation.currencySimulation instead.") + customer: ThreeAStartFzCustomer! + "ThreeAStartContractId" + id: ID! + investmentShare: Float + latestCalculationDate: DateTime + maximumAmount: Float + maximumAmountSimulation: Float @deprecated(reason: "Use threeASimulation.maximumAmountSimulation instead") + maximumAnnualSimulation: Float @deprecated(reason: "use threeASimulation.maximumAnnualSimulation instead.") + minimumAmountSimulation: Float @deprecated(reason: "use threeASimulation.minimumAmountSimulation instead.") + modelPortfolioNumber: String + number: String! + paperlessEnabled: Boolean + paymentPossibleSimulation: Boolean! @deprecated(reason: "Use threeASimulation.paymentPossibleSimulation instead") + portfolio: ThreeAStartFzPortfolio! + "Translatable" + productId: String + productName: String! + sourceSystemName: String + threeASimulation: ThreeAStartSimulationType! + totalBalance: Float! + uniqueId: String +} + +type FzContract implements Contract & Node & Paperless { + accounts: [ThreeAStartFzAccount!]! + accounttransactions(after: String = null before: String = null filter: [FilterInfoInput] = null first: Int = null last: Int = null sortBy: String = null sortDirection: SortDirection = null): ThreeAStartFzTransactionConnection @deprecated(reason: "Deprecated use accountTransaction instead") + accountTransactions(after: String = null before: String = null first: Int = null last: Int = null order_by: ThreeAStartFzTransactionSort = null where: ThreeAStartFzTransactionFilter = null): ThreeAStartFzTransactionConnection + active: Boolean! + availableAccountTypes: [ThreeAStartFzAccountType]! + begin: DateTime! + category: ContractCategory! + closeCode: Int + commissionDiscount: Float + contractChangeDate: DateTime + contractdevelopments: [ThreeAStartFzDevelopment!]! + contractId: String! + contractProductNumber: String + currency: String! + customer: ThreeAStartFzCustomer! + "FzContractId" + id: ID! + investmentShare: Float + modelPortfolioNumber: String + number: String! + paperlessEnabled: Boolean + portfolio: ThreeAStartFzPortfolio! + "Translatable" + productId: String + productName: String! + sourceSystemName: String + totalBalance: Float! + uniqueId: String +} + +type NotImplementedContract implements Contract & Node { + active: Boolean! + category: ContractCategory! + contractId: String! + currency: String! + id: ID! + number: String! + nvsPortalInsuredPersonId: String + productName: String! + showDocumentLink: Boolean! + sourceSystemName: String + totalBalance: Float! + uniqueId: String +} + +enum PensionFundPolicyStatus { + ACTIVE + INACTIVE + INACTIVEPAID + REVERSAL + PENSION +} + +"The node interface is implemented by entities that have a global unique identifier." +interface Node { + id: ID! +} + +type CustomerContract { + contracts(hiddenContracts: [String!] = null tag: [ContractTag] = null): [Contract!]! + "PortalCustomerId" + id: ID! + outline: ContractsOutline +} + +interface Contract { + active: Boolean! + category: ContractCategory! + contractId: String! + currency: String! + id: ID! + number: String! + productName: String! + totalBalance: Float! +} + +type PaperlessResult { + contract: Contract! + success: Boolean! +} + +input PaperlessInput { + contractId: ID! = null + enable: Boolean! = null +} + +input SurrenderValueDocument { + contractId: ID! = null + customerId: String = null + date: Date = null + partnerId: String! = null +} + +input ThreeAPaymentConfirmationInput { + contractId: ID! = null + payment: Float! = null +} + +type RequestSurrenderValueDocumentInfo { + jobId: Int! +} + +type ThreeAPaymentConfirmation { + contractId: ID! + payment: Float! +} + +type ZmaAccountTransaction implements Node { + accountBalance: Float! + accountTransactionAmount: Float! + accountType: ZmaAccountType! + accountTypeName: String! + bookingDate: DateTime! + contractId: String + currency: String! + iban: String + "AccountTransactionId" + id: ID! + marketPrice: Float! + narrativeText: String + securityNominal: Float! + transactionText: String + valueDate: DateTime! +} + +input ZmaAccountTransactionSort { + bookingDate: SortOperationKind = null + valueDate: SortOperationKind = null +} + +input ZmaAccountTransactionFilter { + accountType: ZmaAccountType = null + accountType_gt: ZmaAccountType = null + accountType_gte: ZmaAccountType = null + accountType_in: [ZmaAccountType!] = null + accountType_lt: ZmaAccountType = null + accountType_lte: ZmaAccountType = null + accountType_not: ZmaAccountType = null + accountType_not_gt: ZmaAccountType = null + accountType_not_gte: ZmaAccountType = null + accountType_not_in: [ZmaAccountType!] = null + accountType_not_lt: ZmaAccountType = null + accountType_not_lte: ZmaAccountType = null + AND: [ZmaAccountTransactionFilter!] = null + bookingDate: DateTime = null + bookingDate_gt: DateTime = null + bookingDate_gte: DateTime = null + bookingDate_in: [DateTime] = null + bookingDate_lt: DateTime = null + bookingDate_lte: DateTime = null + bookingDate_not: DateTime = null + bookingDate_not_gt: DateTime = null + bookingDate_not_gte: DateTime = null + bookingDate_not_in: [DateTime] = null + bookingDate_not_lt: DateTime = null + bookingDate_not_lte: DateTime = null + OR: [ZmaAccountTransactionFilter!] = null +} + +interface Investment { + investmentStrategy: InvestmentStrategy! + investmentTheme: InvestmentTheme +} + +interface Paperless { + paperlessEnabled: Boolean +} + +enum SortDirection { + ASCENDING + DESCENDING +} + +input FilterInfoInput { + equality: FilterEqualityOperator! = null + field: String! = null + logical: FilterLogicalOperator = null + value: String = null +} + +"The `DateTime` scalar represents an ISO-8601 compliant date time type." +scalar DateTime + +type ZmaCustomer implements Node { + beneficiaryAccountNumber: String + correspondenceAddress: ZmaAddress + customerId: String! + domicileAddress: ZmaAddress + firstName: String! + id: ID! + lastName: String! +} + +type ZmaAccount { + accountId: String + accountType: ZmaAccountType! + accountTypeName: String! + calculationDate: DateTime! + contractId: String + currency: String! + currentBalanceAccountCurrency: Float! + iban: String! + id: ID! + standingOrders: [ZmaStandingOrder]! +} + +type ZmaPortfolio { + calculationDate: DateTime + contractId: String + currency: String! + factSheetUrl: String + history: [ZmaPortfolioHistory!]! + id: ID! + portfolioId: String! + portfolioValueInBalanceSheetCurrency: Float + portfolioValueInPortfolioCurrency: Float! + positions: [ZmaPortfolioPosition!]! +} + +type ZmaProduct { + contractType: ZmaContractType! + id: ID! + investmentStrategy: String! + investmentTheme: String + productName: String! + productNumber: String! +} + +enum PaymentPeriodicity { + SINGLE + ANNUALY + SEMIANNUALY + QUARTERLY + MONTHLY +} + +type ZmaContractPerformance { + calculationType: ZmaContractPerformanceCalculation! + contractPerformanceId: String! + historicalPerformance: Float! + id: ID! + performanceDate: DateTime! + referenceCurrency: String! + zmaContractId: String +} + +"A connection to a list of items." +type ZmaAccountTransactionConnection { + "A list of edges." + edges: [ZmaAccountTransactionEdge!] + "A flattened list of the nodes." + nodes: [ZmaAccountTransaction] + "Information to aid in pagination." + pageInfo: PageInfo! + totalCount: Int! +} + +type ZmaPowerOfAttorney { + firstName: String! + id: ID! + lastName: String! + powerOfAttorneyId: String! +} + +enum ContractCategory { + UNDEFINED + PENSIONFUND + FZ + PERSONALPENSION + MORTGAGE + FZP +} + +enum InvestmentStrategy { + INCOME + BALANCED + GROWTH + EQUITY + CERTIFICATE +} + +enum InvestmentTheme { + GLOBAL + SWISSNESS + TRENDS + DIVIDEND + SWISSLIFE + SUSTAINABILITY +} + +enum Gender { + UNKNOWN + FEMALE + MALE +} + +type PensionFundPolicyBenefits { + childrensBenefitIvPension: PeriodicalValuesOfDouble! + childrensBenefitIvPensionAccident: PeriodicalValuesOfDouble! + childrensBenefitIvWaitingPeriod: Int! + childrensBenefitPension: PeriodicalValuesOfDouble! + "Contribution exoneration delay in months" + contributionExonerationDelay: Int! + iVPension: PeriodicalValuesOfDouble! + iVPensionAccident: PeriodicalValuesOfDouble! + iVWaitingPeriod: Int! + orphanPension: PeriodicalValuesOfDouble + orphanPensionPercent: Float! + orphansBenefit: PeriodicalValuesOfDouble! + orphansBenefitAccident: PeriodicalValuesOfDouble! + retirementPension: PeriodicalValuesOfDouble! + unmarriedPartnersPension: PeriodicalValuesOfDouble! + unmarriedPartnersPensionAccident: PeriodicalValuesOfDouble! + widowPension: PeriodicalValuesOfDouble + widowPensionPercent: Float! +} + +type XPlanTransaction implements Node { + amount: Float + bookingCode: String + bookingDate: DateTime! + "Translated" + bookingText: String! + bookingType: String + id: ID! + processId: String + transactionId: String! +} + +input XPlanTransactionSort { + bookingDate: SortOperationKind = null +} + +input XPlanPensionFundTransactionFilterInput { + amount: Float = null + amount_gt: Float = null + amount_gte: Float = null + amount_in: [Float] = null + amount_lt: Float = null + amount_lte: Float = null + amount_not: Float = null + amount_not_gt: Float = null + amount_not_gte: Float = null + amount_not_in: [Float] = null + amount_not_lt: Float = null + amount_not_lte: Float = null + AND: [XPlanPensionFundTransactionFilterInput!] = null + bookingDate: Date = null + bookingDate_gt: Date = null + bookingDate_gte: Date = null + bookingDate_in: [Date] = null + bookingDate_lt: Date = null + bookingDate_lte: Date = null + bookingDate_not: Date = null + bookingDate_not_gt: Date = null + bookingDate_not_gte: Date = null + bookingDate_not_in: [Date] = null + bookingDate_not_lt: Date = null + bookingDate_not_lte: Date = null + OR: [XPlanPensionFundTransactionFilterInput!] = null +} + +type PensionFundSimulationParameters { + contractSharedId: UUID! + personSharedId: UUID! + processId: String! +} + +type XPlanProjectedRetirement implements ProjectedRetirement { + isPossiblePrepensionDateInFuture: Boolean! + lastPossiblePurchaseDate: Date! + projectedAge: Int! + projectedDate: Date! + projectedInterestRate: Float! + projectedSavings: Decimal! +} + +type XPlanContract implements Node & PensionFundContract { + beginDate: DateTime! + contractNumber: String + endDate: DateTime + foundation: String + id: ID! + "Translated" + insuranceType: String! + productCode: String! + stakeHolder: String +} + +"A connection to a list of items." +type XPlanTransactionConnection { + "A list of edges." + edges: [XPlanTransactionEdge!] + "A flattened list of the nodes." + nodes: [XPlanTransaction] + "Information to aid in pagination." + pageInfo: PageInfo! + totalCount: Int! +} + +type XPlanRetirementDevelopment { + retirementCapital: Float! + retirementCapitalSupplementary: Float! + retirementDate: DateTime! + retirementPension: Float! + source: String! + transactions: [XPlanTransaction!]! +} + +type XPlanAddress implements Address { + additional: String + careOf: String + city: String + company: String + countryCode: Int + countryCodeNumeric: Int @deprecated(reason: "Use CountryCode instead") + department: String + firstName: String + id: String + lastName: String + postOfficeBox: String + "Translated" + salutation: String + street: String + street2: String @deprecated(reason: "Use Additional instead") + zipCode: String +} + +type PensionFundFzPolicyTransaction implements Node { + amount: Float! + "Translated" + bookingCode: String + bookingDate: DateTime! + id: ID! + processId: String + transactionId: String! +} + +input PensionFundFzPolicyTransactionSort { + bookingDate: SortOperationKind = null +} + +input PensionFundFzPolicyTransactionFilter { + AND: [PensionFundFzPolicyTransactionFilter!] = null + bookingDate: DateTime = null + bookingDate_gt: DateTime = null + bookingDate_gte: DateTime = null + bookingDate_in: [DateTime] = null + bookingDate_lt: DateTime = null + bookingDate_lte: DateTime = null + bookingDate_not: DateTime = null + bookingDate_not_gt: DateTime = null + bookingDate_not_gte: DateTime = null + bookingDate_not_in: [DateTime] = null + bookingDate_not_lt: DateTime = null + bookingDate_not_lte: DateTime = null + OR: [PensionFundFzPolicyTransactionFilter!] = null +} + +type PensionFundFzPolicyPlanType { + iV: Boolean! + planType: FzPolicyPlanType! + planTypeRaw: String! + relinquished: Boolean! + senior: Boolean! + tariffSpecification: Boolean! +} + +type PensionFundFzPolicyBenefits { + cumulatedExceeds: Float! + deathBenefit: Float! + iVCapital: Float! + retirementSavings: Float! + retirementSavingsProjected: Float! + totalDeathBenefit: Float! + totalRetirementSavings: Float! +} + +"A connection to a list of items." +type PensionFundFzPolicyTransactionConnection { + "A list of edges." + edges: [PensionFundFzPolicyTransactionEdge!] + "A flattened list of the nodes." + nodes: [PensionFundFzPolicyTransaction!] + "Information to aid in pagination." + pageInfo: PageInfo! + totalCount: Int! +} + +type DigisTransaction implements Node { + amount: Float + bookingCode: String + bookingDate: DateTime! + "Translated" + bookingText: String! + capital: Decimal! + capitalSupplementary: Decimal! + id: ID! + pension: Decimal! +} + +input DigisTransactionSort { + bookingDate: SortOperationKind = null +} + +input DigisPensionFundTransactionFilterInput { + amount: Float = null + amount_gt: Float = null + amount_gte: Float = null + amount_in: [Float] = null + amount_lt: Float = null + amount_lte: Float = null + amount_not: Float = null + amount_not_gt: Float = null + amount_not_gte: Float = null + amount_not_in: [Float] = null + amount_not_lt: Float = null + amount_not_lte: Float = null + AND: [DigisPensionFundTransactionFilterInput!] = null + bookingDate: DateTime = null + bookingDate_gt: DateTime = null + bookingDate_gte: DateTime = null + bookingDate_in: [DateTime] = null + bookingDate_lt: DateTime = null + bookingDate_lte: DateTime = null + bookingDate_not: DateTime = null + bookingDate_not_gt: DateTime = null + bookingDate_not_gte: DateTime = null + bookingDate_not_in: [DateTime] = null + bookingDate_not_lt: DateTime = null + bookingDate_not_lte: DateTime = null + OR: [DigisPensionFundTransactionFilterInput!] = null +} + +type DigisContract implements Node & PensionFundContract { + contractNumber: String + foundation: String + id: ID! + "Translated" + insuranceType: String! + productCode: String! + stakeHolder: String +} + +type DigisRetirementDevelopment implements RetirementDevelopment { + retirementAge: Int! + retirementCapital: Float! + retirementDate: DateTime! + retirementPension: Float! + transactions: [DigisTransaction!]! +} + +"A connection to a list of items." +type DigisTransactionConnection { + "A list of edges." + edges: [DigisTransactionEdge!] + "A flattened list of the nodes." + nodes: [DigisTransaction] + "Information to aid in pagination." + pageInfo: PageInfo! + totalCount: Int! +} + +type DigisProjectedRetirement implements ProjectedRetirement { + isPossiblePrepensionDateInFuture: Boolean! + lastPossiblePurchaseDate: Date! + projectedAge: Int! + projectedDate: Date! + projectedPension: Decimal! + projectedSavings: Decimal! +} + +type DigisCustomer { + ahvNumber: String + customerId: String + customerTypeId: Int! + dateOfBirth: DateTime! + degreeOfDisability: Decimal! + domicileAddress: DigisAddress + firstName: String + gender: String + governmentId: String + lastName: String + levelOfEmployment: Decimal! + nvsPortalInsuredPersonId: String + reportedSalary: Decimal! + sourceId: String + withdrawalCapital: Decimal! +} + +type EvPremiumInvoice { + balance: Float + bill: Float + contractId: String! + currency: String! + documentNumber: String! + dueDate: DateTime + "DocumentNumber" + id: ID! + invoiceNumber: String! + invoicePeriodEndDate: DateTime + invoicePeriodStartDate: DateTime + partnerId: Int! + premium: Float + settlementBlock: Boolean! + status: PremiumInvoiceStatus! + "Translated" + statusName: String +} + +input EvPremiumInvoiceSort { + dueDate: SortOperationKind = null +} + +input EvPremiumInvoiceFilter { + AND: [EvPremiumInvoiceFilter!] = null + dueDate: DateTime = null + dueDate_gt: DateTime = null + dueDate_gte: DateTime = null + dueDate_in: [DateTime] = null + dueDate_lt: DateTime = null + dueDate_lte: DateTime = null + dueDate_not: DateTime = null + dueDate_not_gt: DateTime = null + dueDate_not_gte: DateTime = null + dueDate_not_in: [DateTime] = null + dueDate_not_lt: DateTime = null + dueDate_not_lte: DateTime = null + OR: [EvPremiumInvoiceFilter!] = null +} + +interface ThreeA { + annualPremium: Float! + bvgMismatch: Boolean! + currency: String! + maximumAmount: Float +} + +interface ThreeASimulation { + annualPremiumSimulation: Float + currencySimulation: String + maximumAmountSimulation: Float + maximumAnnualSimulation: Float + minimumAmountSimulation: Float + paymentPossibleSimulation: Boolean! +} + +type EvCustomer implements Node { + correspondenceAddress: EvPartnerAddress + domicileAddress: EvPartnerAddress + firstName: String! + gender: Gender! + "PartnerId" + id: ID! + lastName: String! + partnerId: String! +} + +type EvTariff { + firstName: String! + insuredPerson: String! + lastName: String! + partnerId: String! + tariffId: String! + "Translated" + tariffName: String! + tariffPremium: Float! + tariffSortOrder: Int! +} + +type EvSurrenderValue { + disclaimer: String + existingLoan: Float + net: Float + validityDate: DateTime +} + +type EvBenefitUponSurvival { + additionalEstimatedFundAssets: Float + disclaimer: String + dueDate: DateTime + estimatedBonus: Float + estimatedFundAssets: Float + estimatedSecurityCapital: Float + estimatedTotal: Float + fundPerformanceUsedForEstimation: Float + guaranteed: Float +} + +type EvPremium { + annual: Float + contractDissolveDate: DateTime + paidCalculationDate: DateTime + paymentFrequency: PaymentPeriodicity! + periodicityName: String! + proRata: Float + single: Float + totalPremiumForYear: Float + totalPremiumPayment: Float +} + +type EvPortfolio { + contractId: String! + currency: String! + factSheetUrl: String + id: ID! + portfolioCode: String + portfolioId: String! + portfolioTotal: Decimal! + portfolioValue: Float! + positions: [EvPortfolioPosition!]! + securityCapital: Decimal +} + +type ThreeASimulationPaymentInfo { + codeLine: String! + memberNumber: String! + referenceNumber: String! +} + +type EvContractThreeASimulationType implements ThreeASimulation { + annualPremiumSimulation: Float + currencySimulation: String + maximumAmountSimulation: Float + maximumAnnualSimulation: Float + minimumAmountSimulation: Float + paymentPossibleSimulation: Boolean! + threeAPaymentInfo(amount: Float! = null): ThreeASimulationPaymentInfo +} + +"A connection to a list of items." +type EvPremiumInvoiceConnection { + "A list of edges." + edges: [EvPremiumInvoiceEdge!] + "A flattened list of the nodes." + nodes: [EvPremiumInvoice!] + "Information to aid in pagination." + pageInfo: PageInfo! + totalCount: Int! +} + +type EvAccountTransaction implements Node { + accountId: String + amount: Float! + bookingCode: Int + bookingDate: DateTime! + "Translated" + bookingText: String + clearingCode: String + contractId: String + currency: String! + currencyDate: DateTime! + "AccountTransactionId" + id: ID! + mainTransactionCode: String + partnerId: String + "Translatable" + productId: String + "Translated" + productName: String + subTransactionCode: String +} + +input EvAccountTransactionSort { + bookingDate: SortOperationKind = null +} + +input EvAccountTransactionFilter { + AND: [EvAccountTransactionFilter!] = null + bookingDate: Date = null + bookingDate_gt: Date = null + bookingDate_gte: Date = null + bookingDate_in: [Date] = null + bookingDate_lt: Date = null + bookingDate_lte: Date = null + bookingDate_not: Date = null + bookingDate_not_gt: Date = null + bookingDate_not_gte: Date = null + bookingDate_not_in: [Date] = null + bookingDate_not_lt: Date = null + bookingDate_not_lte: Date = null + OR: [EvAccountTransactionFilter!] = null + productId: String = null + productId_contains: String = null + productId_ends_with: String = null + productId_in: [String] = null + productId_not: String = null + productId_not_contains: String = null + productId_not_ends_with: String = null + productId_not_in: [String] = null + productId_not_starts_with: String = null + productId_starts_with: String = null +} + +type EvPartner { + correspondenceAddress: EvPartnerAddress + countryCode: String! + dateOfBirth: DateTime! + domicileAddress: EvPartnerAddress + firstName: String! + gender: Gender! + id: ID! + lastName: String! + partnerId: String! +} + +"A connection to a list of items." +type EvAccountTransactionConnection { + "A list of edges." + edges: [EvAccountTransactionEdge!] + "A flattened list of the nodes." + nodes: [EvAccountTransaction] + "Information to aid in pagination." + pageInfo: PageInfo! + totalCount: Int! +} + +type EvAccountDevelopment { + accountDevelopmentId: String! + accountValue: Float! + bookingDate: DateTime! + contractId: String + id: ID! + partnerId: String +} + +type EvAccountProduct { + contractId: String + "Translatable" + productId: String + "Translated" + productName: String +} + +enum EvAccountCategory { + UNKNOWN + HOLDING + PREMIUM +} + +input MortgageSort { + endDate: SortOperationKind = null + isActive: SortOperationKind = null + startDate: SortOperationKind = null +} + +type MortgageObject { + city: String + countryCode: String + id: ID! + street: String + zipCode: String +} + +type Mortgage { + amount: Float! + "Translated" + customerPrivileges: [TranslatedResource!]! + endDate: Date + "Translatable" + interestMaturity: String + "Translated" + InterestMaturityLabel: String + interestRate: Float! + isActive: Boolean! + number: String! + "Translatable" + privileges: [String] @deprecated(reason: "Use customerPrivileges instead.") + startDate: Date + "Translated" + type: String + "Translatable" + typeCode: String! + yearlyInterest: Float! +} + +type MortgageCollateral { + company: String! + id: ID! + policy: Contract + policyNumber: String! +} + +type MortgageStakeholder { + domicilAddress: MortgageStakeholderAddress + firstname: String! + id: ID! + lastname: String! +} + +type MortgageInterest { + interest: Float! + "Translated" + maturity: String! + maturityCode: String! +} + +type ThreeAStartFzTransaction { + accountBalance: Float! + accountTransactionAmount: Float! + accountType: ThreeAStartFzAccountType! + bookingDate: DateTime! + contractId: String! + currency: String! + iBAN: String + "ThreeAStartFzTransactionId" + id: ID! + marketPrice: Float! + narrativeText: String + securityNominal: Float! + transactionText: String + valueDate: DateTime! +} + +input ThreeAStartFzTransactionSort { + bookingDate: SortOperationKind = null + valueDate: SortOperationKind = null +} + +input ThreeAStartFzTransactionFilter { + accountType: ThreeAStartFzAccountType = null + accountType_gt: ThreeAStartFzAccountType = null + accountType_gte: ThreeAStartFzAccountType = null + accountType_in: [ThreeAStartFzAccountType!] = null + accountType_lt: ThreeAStartFzAccountType = null + accountType_lte: ThreeAStartFzAccountType = null + accountType_not: ThreeAStartFzAccountType = null + accountType_not_gt: ThreeAStartFzAccountType = null + accountType_not_gte: ThreeAStartFzAccountType = null + accountType_not_in: [ThreeAStartFzAccountType!] = null + accountType_not_lt: ThreeAStartFzAccountType = null + accountType_not_lte: ThreeAStartFzAccountType = null + AND: [ThreeAStartFzTransactionFilter!] = null + bookingDate: DateTime = null + bookingDate_gt: DateTime = null + bookingDate_gte: DateTime = null + bookingDate_in: [DateTime] = null + bookingDate_lt: DateTime = null + bookingDate_lte: DateTime = null + bookingDate_not: DateTime = null + bookingDate_not_gt: DateTime = null + bookingDate_not_gte: DateTime = null + bookingDate_not_in: [DateTime] = null + bookingDate_not_lt: DateTime = null + bookingDate_not_lte: DateTime = null + OR: [ThreeAStartFzTransactionFilter!] = null +} + +type ThreeAStartFzCustomer { + contractId: String + correspondenceAddress: ThreeAStartFzAddress + customerFirstName: String! + customerId: String! + customerLastName: String! + dateOfBirth: DateTime + domicileAddress: ThreeAStartFzAddress + endDate: DateTime + gender: Gender! + levelOfEmployment: ThreeAStartFzLevelOfEmployment! +} + +type ThreeAStartFzPortfolio { + calculationDate: DateTime! + contractId: String! + currency: String! + id: ID! + portfolioId: String! + portfolioValueInBalanceSheetCurrency: Float! + portfolioValueInPortfolioCurrency: Float! + positions: [ThreeAStartFzPortfolioPosition!]! +} + +type ThreeAStartFzAccount { + accountType: ThreeAStartFzAccountType! + contractId: String! + currency: String! + currentBalanceAccountCurrency: Float! + endDate: DateTime + iBAN: String + "ThreeAStartFzAccountId" + id: ID! + offsetFlag: Int! + totalTransactionAccountCurrency: Float! +} + +type ThreeAStartFzDevelopment { + amountInvestedValue: Float! + amountPaidOutValue: Float! + calculationDate: DateTime! + contractId: String! + currency: String! + entryAccountValue: Float! + "ThreeAStartFzDevelopmentId" + id: ID! + investmentAccountValue: Float! + portfolioValue: Float! + totalValue: Float! +} + +"A connection to a list of items." +type ThreeAStartFzTransactionConnection { + "A list of edges." + edges: [ThreeAStartFzTransactionEdge!] + "A flattened list of the nodes." + nodes: [ThreeAStartFzTransaction] + "Information to aid in pagination." + pageInfo: PageInfo! + totalCount: Int! +} + +enum ThreeAStartFzAccountType { + INVESTMENT3ANEW + INVESTMENTFZMIGRATION + INVESTMENT3AMIGRATION + INVESTMENTFZNEW + ANLAGE3ANEW + ANLAGEFZMIGRATION + ANLAGE3AMIGRATION + ANLAGEFZNEW +} + +type ThreeAStartSimulationType implements ThreeASimulation { + annualPremiumSimulation: Float + currencySimulation: String + maximumAmountSimulation: Float + maximumAnnualSimulation: Float + minimumAmountSimulation: Float + paymentPossibleSimulation: Boolean! +} + +enum TranslatableLanguage { + NOTSET + DE + FR + IT + EN +} + +enum ApplyPolicy { + BEFORE_RESOLVER + AFTER_RESOLVER +} + +enum ContractTag { + THREEA + PENSIONFUND + PAPERLESS +} + +type ContractsOutline { + pensionFund: PensionFundOutline +} + +"The `Date` scalar represents an ISO-8601 compliant date type." +scalar Date + +enum SortOperationKind { + ASC + DESC +} + +enum FilterEqualityOperator { + EQUALS + NOTEQUALS + GREATERTHANOREQUALS + SMALLERTHANOREQUALS + GREATERTHAN + SMALLERTHAN +} + +enum FilterLogicalOperator { + AND + OR +} + +type ZmaAddress implements Address { + additional: String + careOf: String + city: String + company: String + countryCode: String + department: String + firstName: String + id: String + lastName: String + postOfficeBox: String + "Translated" + salutation: String + street: String + zipCode: String +} + +type ZmaStandingOrder { + amount: Float! + frequency: PaymentPeriodicity! + id: ID! + standingOrderId: String! +} + +enum ZmaAccountType { + UNKNOWN + INVESTMENT + ENTRY +} + +type ZmaPortfolioHistory { + calculationDate: DateTime! + contractId: String + id: ID! + investmentValueInContractCurrency: Float + payoutValueInContractCurrency: Float + portfolioHistoryId: String! + portfolioId: String + portfolioValueInBalanceSheetCurrency: Float + portfolioValueInPortfolioCurrency: Float! +} + +type ZmaPortfolioPosition { + factSheet: Link + factSheetUrl: String @deprecated(reason: "use factSheet instead") + investmentCategory: InvestmentCategory! + "Translated" + investmentCategoryName: String! + investmentCategoryPercentage: Float! + investmentCurrencyPercentage: Float! + isin: String! + monetaryAssetName: String! + monetaryAssetTitleCurrency: String! + nominalValueOrQuantity: Float! + portfolioPositionId: String! + portfolioShare: Float! + position: Int! + provider: String + valorPerformance: Float! + valuationReferenceCurrency: Float! +} + +enum ZmaContractType { + UNDEFINED + NONWITHDRAWALPLAN + WITHDRAWALPLAN +} + +enum ZmaContractPerformanceCalculation { + MONTH + YEAR +} + +"Information about pagination in a connection." +type PageInfo { + "When paginating forwards, the cursor to continue." + endCursor: String + "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 +} + +"An edge in a connection." +type ZmaAccountTransactionEdge { + "A cursor for use in pagination." + cursor: String! + "The item at the end of the edge." + node: ZmaAccountTransaction +} + +type PeriodicalValuesOfDouble { + monthly: Float! + yearly: Float! +} + +interface ProjectedRetirement { + projectedAge: Int! + projectedDate: Date! + projectedSavings: Decimal! +} + +interface PensionFundContract { + contractNumber: String + foundation: String + id: ID! + insuranceType: String! + productCode: String! + stakeHolder: String +} + +"An edge in a connection." +type XPlanTransactionEdge { + "A cursor for use in pagination." + cursor: String! + "The item at the end of the edge." + node: XPlanTransaction +} + +"An edge in a connection." +type PensionFundFzPolicyTransactionEdge { + "A cursor for use in pagination." + cursor: String! + "The item at the end of the edge." + node: PensionFundFzPolicyTransaction! +} + +interface RetirementDevelopment { + retirementCapital: Float! + retirementDate: DateTime! + retirementPension: Float! +} + +"An edge in a connection." +type DigisTransactionEdge { + "A cursor for use in pagination." + cursor: String! + "The item at the end of the edge." + node: DigisTransaction +} + +type DigisAddress implements Address { + additional: String + careOf: String + city: String + company: String + countryCode: String + department: String + firstName: String + id: String + lastName: String + postOfficeBox: String + "Translated" + salutation: String + street: String + zipCode: String +} + +enum PremiumInvoiceStatus { + PENDING + PAYED +} + +type EvPartnerAddress implements Address { + additional: String + careOf: String + city: String + company: String + countryCode: String + department: String + firstName: String + id: String + lastName: String + postOfficeBox: String + "Translated" + salutation: String + street: String + zipCode: String +} + +type EvPortfolioPosition { + calculationDate: DateTime! + factSheet: Link + factSheetUrl: String @deprecated(reason: "use factSheet instead") + investmentCategory: InvestmentCategory! + "Translated" + investmentCategoryName: String! + investmentCategoryPercentage: Float! + investmentCurrencyPercentage: Float! + isin: String! + monetaryAssetName: String! + monetaryAssetTitleCurrency: String! + nominalValueOrQuantity: Float! + portfolioPositionId: String! + portfolioPositionValue: Float! + portfolioShare: Float! + position: Int! + provider: String + valorNumber: Float! + valorPerformance: Float! +} + +"An edge in a connection." +type EvPremiumInvoiceEdge { + "A cursor for use in pagination." + cursor: String! + "The item at the end of the edge." + node: EvPremiumInvoice! +} + +"An edge in a connection." +type EvAccountTransactionEdge { + "A cursor for use in pagination." + cursor: String! + "The item at the end of the edge." + node: EvAccountTransaction +} + +type TranslatedResource { + key: String! + label: String! +} + +type MortgageStakeholderAddress implements Address { + additional: String + careOf: String + city: String + company: String + countryCode: String! + department: String + firstName: String + gender: Gender! + id: String + lastName: String + postOfficeBox: String + salutation: String + street: String + zipCode: String +} + +enum ThreeAStartFzLevelOfEmployment { + UNKNOWN + EMPLOYEE + SELFEMPLOYED +} + +type ThreeAStartFzAddress implements Address { + additional: String + careOf: String + city: String + company: String + countryCode: String + department: String + firstName: String + id: String + lastName: String + postOfficeBox: String + "Translated" + salutation: String + street: String + zipCode: String +} + +type ThreeAStartFzPortfolioPosition { + contractId: String! + currency: String! + factSheet: Link + id: ID! + isin: String! + monetaryAssetName: String! + monetaryAssetShortName: String! + nominalValueOrQuantity: Float! + portfolioId: String! + portfolioPositionId: String! + portfolioPositionValue: Float! + portfolioShare: Float + position: Int! + provider: String + valorPerformance: Float! +} + +"An edge in a connection." +type ThreeAStartFzTransactionEdge { + "A cursor for use in pagination." + cursor: String! + "The item at the end of the edge." + node: ThreeAStartFzTransaction +} + +type PensionFundOutline { + purchaseOptions: PensionFundPurchaseOptions + "Use PensionFundPolicy.projectedRetirement.projectedDate instead" + retirementDate: DateTime @deprecated(reason: "Use PensionFundPolicy.projectedRetirement.projectedDate instead") +} + +enum InvestmentCategory { + MONEYMARKETFUNDS + GUARANTEEFUND + CAPITALPROTECTEDFUND + CAPITALPROTECTEDNOTE + BONDFUND + PENSIONFUND + REALESTATEFUND + CONVERTIBLEBONDS + STRATEGYFUNDBALANCEDFUND + EQUITYFUND + FUNDOFFUNDS + SPECIALFUND + RAWMATERIALSGOLD + HEDGEFUND + CERTIFICATE +} + +type Link { + title: String! + type: LinkDestinationType! + url: String! +} + +type PensionFundPurchaseOptions { + maxRetirementCapital: Float! + pensionPlan: PensionPlan + purchaseEarlyWithdrawalForHomeOwnership: Float! + purchasePensionFund: Float! + purchaseTotalPossible: Float! + purchaseWithdrawalDivorce: Float! + retirementSavings: Float! +} + +enum LinkDestinationType { + DOCUMENT + WEBSITE +} + +enum PensionPlan { + EMPLOYEE + SENIOR + IV + RETIREMENT +} + +type Subscription { + onPaperlessChanged: Boolean! +} + +type XPlanContactInfo { + companyName: String + department: String + firstName: String + lastName: String + salutationCode: Int +} + +type XPlanFundDevelopment { + calculationDate: DateTime! + totalCapital: Decimal! +} + +type XPlanPremiumFundBalance { + calculationDate: DateTime! + fundCapital: Decimal! + fundFactSheetUrl: String + fundSharePrice: Decimal + fundShares: Long! + investmentStrategy: String! + isinNumber: String + liquidityAccountBalance: Decimal! + totalCapital: Decimal! +} + +type DeathAfterRetirement { + orphanPension: Float + widowPension: Float +} + +scalar UUID + +"The built-in `Decimal` scalar type." +scalar Decimal + +enum FzPolicyPlanType { + YEARLONG + PERENNIALLY +} + +"The `Long` scalar type represents non-fractional signed whole 64-bit numeric values. Long can represent values between -(2^63) and 2^63 - 1." +scalar Long + +directive @translate(language: TranslatableLanguage = null) on FIELD | FIELD_DEFINITION + +directive @translatable(resourceKeyPrefix: String! = null toCodeLabelArray: Boolean! = null) on FIELD_DEFINITION + +directive @authorize("Defines when when the resolver shall be executed.By default the resolver is executed after the policy has determined that the current user is allowed to access the field." apply: ApplyPolicy! = BEFORE_RESOLVER "The name of the authorization policy that determines access to the annotated resource." policy: String = null "Roles that are allowed to access the annotated resource." roles: [String!] = null) repeatable on SCHEMA | OBJECT | FIELD_DEFINITION + +directive @trackable on FIELD_DEFINITION + +directive @track(if: Boolean = null) on FIELD + +directive @tracked on FIELD_DEFINITION diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/Customer.graphql b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/Customer.graphql new file mode 100644 index 00000000000..b336c3ecbc4 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/Customer.graphql @@ -0,0 +1,26 @@ +type Query { + customer(id: ID!): Customer + consultant(id: ID!): Consultant + customerOrConsultant(id: ID!): CustomerOrConsultant +} + +type Customer { + id: ID! + name: String! + consultant: Consultant + complexArg(arg: ComplexInputType): String +} + +type Consultant { + id: ID! + name: String! +} + +union CustomerOrConsultant = Customer | Consultant + +input ComplexInputType { + deeper: ComplexInputType + deeperArray: [ComplexInputType] + value: String + valueArray: [String] +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/DocumentClient.graphql b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/DocumentClient.graphql new file mode 100644 index 00000000000..3125f8e2b90 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/DocumentClient.graphql @@ -0,0 +1,339 @@ +schema { + query: Query + mutation: Mutation +} + +type Query { + envelope(processId: String = null): CustomerEnvelope + envelopes(after: String = null before: String = null contractId: String = null first: PaginationAmount = null invoiceNumber: String = null last: PaginationAmount = null order_by: CustomerEnvelopeSort = null processId: String = null where: CustomerEnvelopeFilter = null): CustomerEnvelopeConnection +} + +type Mutation { + requestDocumentDownload(id: ID! = null): String! + requestEnvelopeDownload(fileFormat: FileFormat! = null id: ID! = null): String! +} + +type NvsDocument implements Document { + contractId: String + contractNumber: String + "Translated" + documentCategory: String! + documentCategoryCode: DocumentCategoryType! + documentDate: DateTime! + documentId: String! + documentTitle: String! + documentType: String! + documentTypeCode: String! + id: ID! + isTaxRelevant: Boolean! + processId: String +} + +type EvDocument implements Document { + contractId: String + contractNumber: String + "Translated" + documentCategory: String! + documentCategoryCode: DocumentCategoryType! + documentDate: DateTime! + documentId: String! + documentTitle: String! + "Translated" + documentType: String! + documentTypeCode: String! + id: ID! + invoiceNumber: String + isTaxRelevant: Boolean! + partnerNumber: String! +} + +type BankingDocument implements Document { + businessUnit: String! + contractId: String + contractNumber: String + "Translated" + documentCategory: String! + documentCategoryCode: DocumentCategoryType! + documentDate: DateTime! + documentId: String! + documentTitle: String! + "Translated" + documentType: String! + documentTypeCode: String! + id: ID! + isTaxRelevant: Boolean! + planNr: String! @deprecated(reason: "Use contractId instead") +} + +enum ApplyPolicy { + BEFORE_RESOLVER + AFTER_RESOLVER +} + +type CustomerEnvelope { + contractNumber: String + documents: [Document!]! + envelopeDate: DateTime! + envelopeId: String! + envelopeTitle: String + id: ID! +} + +input CustomerEnvelopeSort { + envelopeDate: SortOperationKind = null +} + +input CustomerEnvelopeFilter { + AND: [CustomerEnvelopeFilter!] = null + documents_some: DocumentFilter = null + envelopeDate: DateTime = null + envelopeDate_gt: DateTime = null + envelopeDate_gte: DateTime = null + envelopeDate_in: [DateTime!] = null + envelopeDate_lt: DateTime = null + envelopeDate_lte: DateTime = null + envelopeDate_not: DateTime = null + envelopeDate_not_gt: DateTime = null + envelopeDate_not_gte: DateTime = null + envelopeDate_not_in: [DateTime!] = null + envelopeDate_not_lt: DateTime = null + envelopeDate_not_lte: DateTime = null + isTaxRelevant: Boolean = null + OR: [CustomerEnvelopeFilter!] = null +} + +"A connection to a list of items." +type CustomerEnvelopeConnection { + contractFilterEntries: [ContractFilterEntry!]! + contractNumbers: [String!]! @deprecated(reason: "Use contractFilterEntries instead") + "Translated" + documentCategories: [TranslatedResourceOfDocumentCategoryType!]! + "A list of edges." + edges: [CustomerEnvelopeEdge!] + "A flattened list of the nodes." + nodes: [CustomerEnvelope!] + "Information to aid in pagination." + pageInfo: PageInfo! + totalCount: Int! +} + +scalar PaginationAmount + +enum FileFormat { + PDF + ZIP +} + +interface Document { + contractId: String + contractNumber: String + documentCategory: String! + documentCategoryCode: DocumentCategoryType! + documentDate: DateTime! + documentId: String! + documentTitle: String! + documentType: String! + documentTypeCode: String! + id: ID! + isTaxRelevant: Boolean! +} + +"The `DateTime` scalar represents an ISO-8601 compliant date time type." +scalar DateTime + +enum DocumentCategoryType { + WERTDOKUMENTE + INKASSODOKUMENTE + KORRESPONDENZDOKUMENTE + LEISTUNGSDOKUMENTE + OFFERTDOKUMENTE + PERSONENDOKUMENTE + REPORT + SONSTIGE + STEUERDOKUMENTE + VERTRAGSANPASSUNGEN + VERTRAGSDOKUMENTE +} + +type TranslatedResourceOfDocumentCategoryType { + key: DocumentCategoryType! + label: String! +} + +type ContractFilterEntry { + key: String! + label: String! +} + +enum TranslatableLanguage { + NOTSET + DE + FR + IT + EN +} + +enum SortOperationKind { + ASC + DESC +} + +input DocumentFilter { + AND: [DocumentFilter!] = null + contractId: String = null + contractId_contains: String = null + contractId_ends_with: String = null + contractId_in: [String] = null + contractId_not: String = null + contractId_not_contains: String = null + contractId_not_ends_with: String = null + contractId_not_in: [String] = null + contractId_not_starts_with: String = null + contractId_starts_with: String = null + contractNumber: String = null + contractNumber_contains: String = null + contractNumber_ends_with: String = null + contractNumber_in: [String] = null + contractNumber_not: String = null + contractNumber_not_contains: String = null + contractNumber_not_ends_with: String = null + contractNumber_not_in: [String] = null + contractNumber_not_starts_with: String = null + contractNumber_starts_with: String = null + documentCategoryType: DocumentCategoryType = null + documentCategoryType_gt: DocumentCategoryType = null + documentCategoryType_gte: DocumentCategoryType = null + documentCategoryType_in: [DocumentCategoryType!] = null + documentCategoryType_lt: DocumentCategoryType = null + documentCategoryType_lte: DocumentCategoryType = null + documentCategoryType_not: DocumentCategoryType = null + documentCategoryType_not_gt: DocumentCategoryType = null + documentCategoryType_not_gte: DocumentCategoryType = null + documentCategoryType_not_in: [DocumentCategoryType!] = null + documentCategoryType_not_lt: DocumentCategoryType = null + documentCategoryType_not_lte: DocumentCategoryType = null + documentDate: DateTime = null + documentDate_gt: DateTime = null + documentDate_gte: DateTime = null + documentDate_in: [DateTime!] = null + documentDate_lt: DateTime = null + documentDate_lte: DateTime = null + documentDate_not: DateTime = null + documentDate_not_gt: DateTime = null + documentDate_not_gte: DateTime = null + documentDate_not_in: [DateTime!] = null + documentDate_not_lt: DateTime = null + documentDate_not_lte: DateTime = null + documentId: String = null + documentId_contains: String = null + documentId_ends_with: String = null + documentId_in: [String] = null + documentId_not: String = null + documentId_not_contains: String = null + documentId_not_ends_with: String = null + documentId_not_in: [String] = null + documentId_not_starts_with: String = null + documentId_starts_with: String = null + documentTitle: String = null + documentTitle_contains: String = null + documentTitle_ends_with: String = null + documentTitle_in: [String] = null + documentTitle_not: String = null + documentTitle_not_contains: String = null + documentTitle_not_ends_with: String = null + documentTitle_not_in: [String] = null + documentTitle_not_starts_with: String = null + documentTitle_starts_with: String = null + documentType: String = null + documentType_contains: String = null + documentType_ends_with: String = null + documentType_in: [String] = null + documentType_not: String = null + documentType_not_contains: String = null + documentType_not_ends_with: String = null + documentType_not_in: [String] = null + documentType_not_starts_with: String = null + documentType_starts_with: String = null + envelopeId: String = null + envelopeId_contains: String = null + envelopeId_ends_with: String = null + envelopeId_in: [String] = null + envelopeId_not: String = null + envelopeId_not_contains: String = null + envelopeId_not_ends_with: String = null + envelopeId_not_in: [String] = null + envelopeId_not_starts_with: String = null + envelopeId_starts_with: String = null + envelopeOrder: Int = null + envelopeOrder_gt: Int = null + envelopeOrder_gte: Int = null + envelopeOrder_in: [Int!] = null + envelopeOrder_lt: Int = null + envelopeOrder_lte: Int = null + envelopeOrder_not: Int = null + envelopeOrder_not_gt: Int = null + envelopeOrder_not_gte: Int = null + envelopeOrder_not_in: [Int!] = null + envelopeOrder_not_lt: Int = null + envelopeOrder_not_lte: Int = null + filterId: String = null + filterId_contains: String = null + filterId_ends_with: String = null + filterId_in: [String] = null + filterId_not: String = null + filterId_not_contains: String = null + filterId_not_ends_with: String = null + filterId_not_in: [String] = null + filterId_not_starts_with: String = null + filterId_starts_with: String = null + hiddenFilterId: String = null + hiddenFilterId_contains: String = null + hiddenFilterId_ends_with: String = null + hiddenFilterId_in: [String] = null + hiddenFilterId_not: String = null + hiddenFilterId_not_contains: String = null + hiddenFilterId_not_ends_with: String = null + hiddenFilterId_not_in: [String] = null + hiddenFilterId_not_starts_with: String = null + hiddenFilterId_starts_with: String = null + isTaxRelevant: Boolean = null + isTaxRelevant_not: Boolean = null + OR: [DocumentFilter!] = null + portalCustomerId: String = null + portalCustomerId_contains: String = null + portalCustomerId_ends_with: String = null + portalCustomerId_in: [String] = null + portalCustomerId_not: String = null + portalCustomerId_not_contains: String = null + portalCustomerId_not_ends_with: String = null + portalCustomerId_not_in: [String] = null + portalCustomerId_not_starts_with: String = null + portalCustomerId_starts_with: String = null +} + +"Information about pagination in a connection." +type PageInfo { + "When paginating forwards, the cursor to continue." + endCursor: String + "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 +} + +"An edge in a connection." +type CustomerEnvelopeEdge { + "A cursor for use in pagination." + cursor: String! + "The item at the end of the edge." + node: CustomerEnvelope! +} + + + + +directive @authorize("Defines when when the resolver shall be executed.By default the resolver is executed after the policy has determined that the current user is allowed to access the field." apply: ApplyPolicy! = BEFORE_RESOLVER "The name of the authorization policy that determines access to the annotated resource." policy: String = null "Roles that are allowed to access the annotated resource." roles: [String!] = null) repeatable on SCHEMA | OBJECT | FIELD_DEFINITION + + diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/DummyDirective.graphql b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/DummyDirective.graphql new file mode 100644 index 00000000000..11be1da2a6a --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/DummyDirective.graphql @@ -0,0 +1 @@ +directive @foo on FIELD_DEFINITION diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/IntrospectionWithDefaultValues.json b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/IntrospectionWithDefaultValues.json new file mode 100644 index 00000000000..44df0f38eec --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/IntrospectionWithDefaultValues.json @@ -0,0 +1,1003 @@ +{ + "data": { + "__schema": { + "queryType": { + "name": "Query" + }, + "mutationType": null, + "subscriptionType": null, + "types": [ + { + "kind": "ENUM", + "name": "__TypeKind", + "description": "An enum describing what kind of type a given __Type is", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "SCALAR", + "description": "Indicates this type is a scalar.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OBJECT", + "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INTERFACE", + "description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UNION", + "description": "Indicates this type is a union. `possibleTypes` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM", + "description": "Indicates this type is an enum. `enumValues` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_OBJECT", + "description": "Indicates this type is an input object. `inputFields` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "LIST", + "description": "Indicates this type is a list. `ofType` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "NON_NULL", + "description": "Indicates this type is a non-null. `ofType` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Field", + "description": null, + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "args", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue" + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Query", + "description": "", + "fields": [ + { + "name": "Questions", + "description": "", + "args": [ + { + "name": "skip", + "description": "", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": "0" + }, + { + "name": "first", + "description": "", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": "10" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Question", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Schema", + "description": "A GraphQL Introspection defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, the entry points for query, mutation, and subscription operations.", + "fields": [ + { + "name": "types", + "description": "A list of all types supported by this server.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type" + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "queryType", + "description": "The type that query operations will be rooted at.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "mutationType", + "description": "If this server supports mutation, the type that mutation operations will be rooted at.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "directives", + "description": "'A list of all directives supported by this server.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Directive" + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "subscriptionType", + "description": "'If this server support subscription, the type that subscription operations will be rooted at.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Type", + "description": null, + "fields": [ + { + "name": "kind", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "__TypeKind", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "fields", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Field", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "interfaces", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "possibleTypes", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "enumValues", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__EnumValue", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "inputFields", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ofType", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__EnumValue", + "description": null, + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "__DirectiveLocation", + "description": "An enum describing valid locations where a directive can be placed", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "QUERY", + "description": "Indicates the directive is valid on queries.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "MUTATION", + "description": "Indicates the directive is valid on mutations.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FIELD", + "description": "Indicates the directive is valid on fields.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FRAGMENT_DEFINITION", + "description": "Indicates the directive is valid on fragment definitions.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FRAGMENT_SPREAD", + "description": "Indicates the directive is valid on fragment spreads.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INLINE_FRAGMENT", + "description": "Indicates the directive is valid on inline fragments.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SCHEMA", + "description": "Indicates the directive is valid on a schema SDL definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SCALAR", + "description": "Indicates the directive is valid on a scalar SDL definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OBJECT", + "description": "Indicates the directive is valid on an object SDL definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FIELD_DEFINITION", + "description": "Indicates the directive is valid on a field SDL definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ARGUMENT_DEFINITION", + "description": "Indicates the directive is valid on a field argument SDL definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INTERFACE", + "description": "Indicates the directive is valid on an interface SDL definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UNION", + "description": "Indicates the directive is valid on an union SDL definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM", + "description": "Indicates the directive is valid on an enum SDL definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM_VALUE", + "description": "Indicates the directive is valid on an enum value SDL definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_OBJECT", + "description": "Indicates the directive is valid on an input object SDL definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_FIELD_DEFINITION", + "description": "Indicates the directive is valid on an input object field SDL definition.", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "String", + "description": "Built-in String", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Int", + "description": "Built-in Int", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Question", + "description": "", + "fields": [ + { + "name": "id", + "description": "", + "args": [], + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "question", + "description": "", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "userId", + "description": "", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "ID", + "description": "Built-in ID", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__InputValue", + "description": null, + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "defaultValue", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Boolean", + "description": "Built-in Boolean", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Directive", + "description": null, + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "locations", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "__DirectiveLocation", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "args", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue" + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "onOperation", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "isDeprecated": true, + "deprecationReason": "Use `locations`." + }, + { + "name": "onFragment", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "isDeprecated": true, + "deprecationReason": "Use `locations`." + }, + { + "name": "onField", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "isDeprecated": true, + "deprecationReason": "Use `locations`." + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + } + ], + "directives": [ + { + "name": "include", + "description": "Directs the executor to include this field or fragment only when the `if` argument is true", + "args": [ + { + "name": "if", + "description": "Included when true.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + } + ] + }, + { + "name": "skip", + "description": "Directs the executor to skip this field or fragment when the `if`'argument is true.", + "args": [ + { + "name": "if", + "description": "Skipped when true.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + } + ] + }, + { + "name": "defer", + "description": "This directive allows results to be deferred during execution", + "args": [] + } + ] + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/MergeQueryWithVariable.graphql b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/MergeQueryWithVariable.graphql new file mode 100644 index 00000000000..a93df9dc208 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/MergeQueryWithVariable.graphql @@ -0,0 +1,21 @@ +query customer_query($fields_customerId: ID!, $global: ID!) { + customer(id: $customerId) { + name + consultant(id: $global) { + name + } + contracts { + id + ...life + ...other + } + } +} + +fragment life on LifeInsuranceContract { + premium +} + +fragment other on SomeOtherContract { + expiryDate +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/SchemaInfoTests_Schema.graphql b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/SchemaInfoTests_Schema.graphql new file mode 100644 index 00000000000..4a6ead02fe9 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/SchemaInfoTests_Schema.graphql @@ -0,0 +1,17 @@ +schema { + query: CustomQuery + mutation: CustomMutation + subscription: CustomSubscription +} + +type CustomQuery { + abc: String +} + +type CustomMutation { + abc: String +} + +type CustomSubscription { + abc: String +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/StarWarsIntrospectionResult.json b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/StarWarsIntrospectionResult.json new file mode 100644 index 00000000000..37afa749a50 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/StarWarsIntrospectionResult.json @@ -0,0 +1,1778 @@ +{ + "data": { + "__schema": { + "queryType": { + "name": "Query" + }, + "mutationType": { + "name": "Mutation" + }, + "subscriptionType": { + "name": "Subscription" + }, + "types": [{ + "kind": "SCALAR", + "name": "String", + "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "ID", + "description": "The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Boolean", + "description": "The `Boolean` scalar type represents `true` or `false`.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Int", + "description": "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Float", + "description": "The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Decimal", + "description": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Long", + "description": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "DateTime", + "description": "The `DateTime` scalar represents an ISO-8601 compliant date time type.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Date", + "description": "The `Date` scalar represents an ISO-8601 compliant date type.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Uuid", + "description": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Url", + "description": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Directive", + "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.", + "fields": [{ + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "locations", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "__DirectiveLocation" + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "args", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue" + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "onOperation", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": true, + "deprecationReason": "Use `locations`." + }, + { + "name": "onFragment", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": true, + "deprecationReason": "Use `locations`." + }, + { + "name": "onField", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": true, + "deprecationReason": "Use `locations`." + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "__DirectiveLocation", + "description": "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [{ + "name": "QUERY", + "description": "Location adjacent to a query operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "MUTATION", + "description": "Location adjacent to a mutation operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SUBSCRIPTION", + "description": "Location adjacent to a subscription operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FIELD", + "description": "Location adjacent to a field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FRAGMENT_DEFINITION", + "description": "Location adjacent to a fragment definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FRAGMENT_SPREAD", + "description": "Location adjacent to a fragment spread.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INLINE_FRAGMENT", + "description": "Location adjacent to an inline fragment.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SCHEMA", + "description": "Location adjacent to a schema definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SCALAR", + "description": "Location adjacent to a scalar definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OBJECT", + "description": "Location adjacent to an object type definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FIELD_DEFINITION", + "description": "Location adjacent to a field definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ARGUMENT_DEFINITION", + "description": "Location adjacent to an argument definition", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INTERFACE", + "description": "Location adjacent to an interface definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UNION", + "description": "Location adjacent to a union definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM", + "description": "Location adjacent to an enum definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM_VALUE", + "description": "Location adjacent to an enum value definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_OBJECT", + "description": "Location adjacent to an input object type definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_FIELD_DEFINITION", + "description": "Location adjacent to an input object field definition.", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__EnumValue", + "description": "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.", + "fields": [{ + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Field", + "description": "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.", + "fields": [{ + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "args", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue" + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__InputValue", + "description": "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.", + "fields": [{ + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "defaultValue", + "description": "A GraphQL-formatted string representing the default value for this input value.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Schema", + "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.", + "fields": [{ + "name": "types", + "description": "A list of all types supported by this server.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type" + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "queryType", + "description": "The type that query operations will be rooted at.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "mutationType", + "description": "If this server supports mutation, the type that mutation operations will be rooted at.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "subscriptionType", + "description": "If this server support subscription, the type that subscription operations will be rooted at.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "directives", + "description": "A list of all directives supported by this server.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Directive" + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Type", + "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.", + "fields": [{ + "name": "kind", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "__TypeKind", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "fields", + "description": null, + "args": [{ + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + }], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Field", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "interfaces", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "possibleTypes", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "enumValues", + "description": null, + "args": [{ + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + }], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__EnumValue", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "inputFields", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ofType", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "__TypeKind", + "description": "An enum describing what kind of type a given `__Type` is.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [{ + "name": "SCALAR", + "description": "Indicates this type is a scalar.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OBJECT", + "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INTERFACE", + "description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UNION", + "description": "Indicates this type is a union. `possibleTypes` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM", + "description": "Indicates this type is an enum. `enumValues` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_OBJECT", + "description": "Indicates this type is an input object. `inputFields` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "LIST", + "description": "Indicates this type is a list. `ofType` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "NON_NULL", + "description": "Indicates this type is a non-null. `ofType` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Query", + "description": null, + "fields": [{ + "name": "hero", + "description": null, + "args": [{ + "name": "episode", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "Episode", + "ofType": null + } + }, + "defaultValue": null + }], + "type": { + "kind": "INTERFACE", + "name": "Character", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "character", + "description": null, + "args": [{ + "name": "characterIds", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID" + } + } + } + }, + "defaultValue": null + }], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INTERFACE", + "name": "Character" + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "human", + "description": null, + "args": [{ + "name": "id", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null + }], + "type": { + "kind": "OBJECT", + "name": "Human", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "droid", + "description": null, + "args": [{ + "name": "id", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null + }], + "type": { + "kind": "OBJECT", + "name": "Droid", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "search", + "description": null, + "args": [{ + "name": "text", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": "null" + }], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "UNION", + "name": "SearchResult", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Mutation", + "description": null, + "fields": [{ + "name": "createReview", + "description": null, + "args": [{ + "name": "episode", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "Episode", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "review", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "ReviewInput", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Review", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Subscription", + "description": null, + "fields": [{ + "name": "onReview", + "description": null, + "args": [{ + "name": "episode", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "Episode", + "ofType": null + } + }, + "defaultValue": null + }], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Review", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Human", + "description": null, + "fields": [{ + "name": "id", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "appearsIn", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "Episode", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "friends", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "INTERFACE", + "name": "Character", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "height", + "description": null, + "args": [{ + "name": "unit", + "description": null, + "type": { + "kind": "ENUM", + "name": "Unit", + "ofType": null + }, + "defaultValue": "null" + }], + "type": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "homePlanet", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [{ + "kind": "INTERFACE", + "name": "Character", + "ofType": null + }], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Droid", + "description": null, + "fields": [{ + "name": "id", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "appearsIn", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "Episode", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "friends", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "INTERFACE", + "name": "Character", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "height", + "description": null, + "args": [{ + "name": "unit", + "description": null, + "type": { + "kind": "ENUM", + "name": "Unit", + "ofType": null + }, + "defaultValue": "null" + }], + "type": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "primaryFunction", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [{ + "kind": "INTERFACE", + "name": "Character", + "ofType": null + }], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "Episode", + "description": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [{ + "name": "NEWHOPE", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "EMPIRE", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "JEDI", + "description": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "INTERFACE", + "name": "Character", + "description": null, + "fields": [{ + "name": "id", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "friends", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "INTERFACE", + "name": "Character", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "appearsIn", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "Episode", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "height", + "description": null, + "args": [{ + "name": "unit", + "description": null, + "type": { + "kind": "ENUM", + "name": "Unit", + "ofType": null + }, + "defaultValue": "null" + }], + "type": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": [{ + "kind": "OBJECT", + "name": "Human", + "ofType": null + }, + { + "kind": "OBJECT", + "name": "Droid", + "ofType": null + } + ] + }, + { + "kind": "UNION", + "name": "SearchResult", + "description": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": [{ + "kind": "OBJECT", + "name": "Starship", + "ofType": null + }, + { + "kind": "OBJECT", + "name": "Human", + "ofType": null + }, + { + "kind": "OBJECT", + "name": "Droid", + "ofType": null + } + ] + }, + { + "kind": "OBJECT", + "name": "Review", + "description": null, + "fields": [{ + "name": "stars", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "commentary", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", + "name": "ReviewInput", + "description": null, + "fields": null, + "inputFields": [{ + "name": "stars", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "commentary", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": "null" + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "Unit", + "description": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [{ + "name": "FOOT", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "METERS", + "description": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Starship", + "description": null, + "fields": [{ + "name": "id", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "length", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + } + ], + "directives": [{ + "name": "skip", + "description": "Directs the executor to skip this field or fragment when the `if` argument is true.", + "args": [{ + "name": "if", + "description": "Skipped when true.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + }], + "onOperation": false, + "onFragment": true, + "onField": true + }, + { + "name": "include", + "description": "Directs the executor to include this field or fragment only when the `if` argument is true.", + "args": [{ + "name": "if", + "description": "Included when true.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + }], + "onOperation": false, + "onFragment": true, + "onField": true + }, + { + "name": "authorize", + "description": null, + "args": [{ + "name": "policy", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": "null" + }, + { + "name": "roles", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null + } + ], + "onOperation": false, + "onFragment": false, + "onField": false + } + ] + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/Stitching.graphql b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/Stitching.graphql new file mode 100644 index 00000000000..61d0d20af40 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/Stitching.graphql @@ -0,0 +1,56 @@ +type Query { + customer(id: ID!): Customer @delegate(schema: "customer") + customerOrConsultant(id: ID!): CustomerOrConsultant + @delegate(schema: "customer") + contracts(id: ID!): [Contract!] + @delegate(schema: "contract", path: "contracts(customerId:$arguments:id)") + consultantName: String! + @delegate( + schema: "customer" + path: "customer(id:\"Q3VzdG9tZXIKZDE=\").consultant.name" + ) + consultant: Consultant + @delegate( + schema: "customer" + path: "customer(id:\"Q3VzdG9tZXIKZDE=\").consultant" + ) +} + +type Customer { + id: ID! + name: String! + complexArg(arg:ComplexInputType): String + consultant: Consultant + contracts: [Contract!] + @delegate(schema: "contract", path: "contracts(customerId:$fields:id)") + contractIds: [ID!] + @delegate(schema: "contract", path: "contracts(customerId:$fields:id).id") +} + +type Consultant { + id: ID! + name: String! +} + +interface Contract { + id: ID! +} + +type LifeInsuranceContract implements Contract { + id: ID! + premium: Float +} + +type SomeOtherContract implements Contract { + id: ID! + expiryDate: DateTime +} + +union CustomerOrConsultant = Customer | Consultant + +input ComplexInputType { + deeper: ComplexInputType + deeperArray: [ComplexInputType] + value: String + valueArray: [String] +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/StitchingComputed.graphql b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/StitchingComputed.graphql new file mode 100644 index 00000000000..bfb5bcc3ca0 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/StitchingComputed.graphql @@ -0,0 +1,49 @@ +type Query { + customer(id: ID!): Customer @delegate(schema: "customer") + customerOrConsultant(id: ID!): CustomerOrConsultant + @delegate(schema: "customer") + contracts(id: ID!): [Contract!] + @delegate(schema: "contract", path: "contracts(customerId:$arguments:id)") + consultantName: String! + @delegate( + schema: "customer" + path: "customer(id:\"Q3VzdG9tZXIKZDE=\").consultant.name" + ) + consultant: Consultant + @delegate( + schema: "customer" + path: "customer(id:\"Q3VzdG9tZXIKZDE=\").consultant" + ) +} + +type Customer { + id: ID! + name: String! + consultant: Consultant + contracts: [Contract!] + @delegate(schema: "contract", path: "contracts(customerId:$fields:id)") + contractIds: [ID!] + @delegate(schema: "contract", path: "contracts(customerId:$fields:id).id") + foo: String @computed(dependantOn: ["id", "name"]) +} + +type Consultant { + id: ID! + name: String! +} + +interface Contract { + id: ID! +} + +type LifeInsuranceContract implements Contract { + id: ID! + premium: Float +} + +type SomeOtherContract implements Contract { + id: ID! + expiryDate: DateTime +} + +union CustomerOrConsultant = Customer | Consultant diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/StitchingExtensions.graphql b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/StitchingExtensions.graphql new file mode 100644 index 00000000000..cef1de3d480 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/StitchingExtensions.graphql @@ -0,0 +1,13 @@ +extend type Customer { + contracts: [Contract!] + @delegate(schema: "contract", path: "contracts(customerId:$fields:id)") + contractIds: [ID!] + @delegate(schema: "contract", path: "contracts(customerId:$fields:id).id") + int: Int! @delegate(schema: "contract", path: "int(i:$fields:someInt)") + guid: UUID! @delegate(schema: "contract", path: "guid(guid:$fields:someGuid)") +} + +extend type Query { + customer2(customerId: ID!): Customer + @delegate(schema: "customer", path: "customer(id:$arguments:customerId)") +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/StitchingQuery.graphql b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/StitchingQuery.graphql new file mode 100644 index 00000000000..7dda7bf74d3 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/StitchingQuery.graphql @@ -0,0 +1,17 @@ +{ + customer(id: "Q3VzdG9tZXIKZDE=") { + name + consultant { + name + } + contracts { + id + ... on LifeInsuranceContract { + premium + } + ... on SomeOtherContract { + expiryDate + } + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/StitchingQueryWithSkip.graphql b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/StitchingQueryWithSkip.graphql new file mode 100644 index 00000000000..259d5d9cea3 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/StitchingQueryWithSkip.graphql @@ -0,0 +1,17 @@ +query withSkip($skip: Boolean) { + customer(id: "Q3VzdG9tZXIKZDE=") { + name + consultant @skip(if: $skip) { + name + } + contracts { + id + ... on LifeInsuranceContract { + premium + } + ... on SomeOtherContract { + expiryDate + } + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/StitchingQueryWithTypename.graphql b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/StitchingQueryWithTypename.graphql new file mode 100644 index 00000000000..ca27ef92ae1 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/StitchingQueryWithTypename.graphql @@ -0,0 +1,6 @@ +{ + consultant { + __typename + name + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_Empty_QueryRequestBuilderException.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_Empty_QueryRequestBuilderException.snap new file mode 100644 index 00000000000..673a634349b --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_Empty_QueryRequestBuilderException.snap @@ -0,0 +1 @@ +Specify a query in order to create a query request. diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_OnlyQueryIsSet_RequestHasOnlyQuery.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_OnlyQueryIsSet_RequestHasOnlyQuery.snap new file mode 100644 index 00000000000..10b271fb06d --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_OnlyQueryIsSet_RequestHasOnlyQuery.snap @@ -0,0 +1,65 @@ +{ + "Query": { + "Kind": "Document", + "Location": { + "Start": 0, + "End": 7, + "Line": 1, + "Column": 1 + }, + "Definitions": [ + { + "Kind": "OperationDefinition", + "Location": { + "Start": 0, + "End": 7, + "Line": 1, + "Column": 1 + }, + "Name": null, + "Operation": "Query", + "VariableDefinitions": [], + "Directives": [], + "SelectionSet": { + "Kind": "SelectionSet", + "Location": { + "Start": 0, + "End": 7, + "Line": 1, + "Column": 1 + }, + "Selections": [ + { + "Kind": "Field", + "Alias": null, + "Arguments": [], + "SelectionSet": null, + "Location": { + "Start": 2, + "End": 7, + "Line": 1, + "Column": 3 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 2, + "End": 7, + "Line": 1, + "Column": 3 + }, + "Value": "foo" + }, + "Directives": [] + } + ] + } + } + ] + }, + "OperationName": null, + "VariableValues": null, + "InitialValue": null, + "Properties": null, + "Services": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndAddProperties_RequestIsCreated.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndAddProperties_RequestIsCreated.snap new file mode 100644 index 00000000000..86a1a9d692b --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndAddProperties_RequestIsCreated.snap @@ -0,0 +1,68 @@ +{ + "Query": { + "Kind": "Document", + "Location": { + "Start": 0, + "End": 7, + "Line": 1, + "Column": 1 + }, + "Definitions": [ + { + "Kind": "OperationDefinition", + "Location": { + "Start": 0, + "End": 7, + "Line": 1, + "Column": 1 + }, + "Name": null, + "Operation": "Query", + "VariableDefinitions": [], + "Directives": [], + "SelectionSet": { + "Kind": "SelectionSet", + "Location": { + "Start": 0, + "End": 7, + "Line": 1, + "Column": 1 + }, + "Selections": [ + { + "Kind": "Field", + "Alias": null, + "Arguments": [], + "SelectionSet": null, + "Location": { + "Start": 2, + "End": 7, + "Line": 1, + "Column": 3 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 2, + "End": 7, + "Line": 1, + "Column": 3 + }, + "Value": "foo" + }, + "Directives": [] + } + ] + } + } + ] + }, + "OperationName": null, + "VariableValues": null, + "InitialValue": null, + "Properties": { + "one": "foo", + "two": "bar" + }, + "Services": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndAddVariables_RequestIsCreated.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndAddVariables_RequestIsCreated.snap new file mode 100644 index 00000000000..73bccf1ac9e --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndAddVariables_RequestIsCreated.snap @@ -0,0 +1,68 @@ +{ + "Query": { + "Kind": "Document", + "Location": { + "Start": 0, + "End": 7, + "Line": 1, + "Column": 1 + }, + "Definitions": [ + { + "Kind": "OperationDefinition", + "Location": { + "Start": 0, + "End": 7, + "Line": 1, + "Column": 1 + }, + "Name": null, + "Operation": "Query", + "VariableDefinitions": [], + "Directives": [], + "SelectionSet": { + "Kind": "SelectionSet", + "Location": { + "Start": 0, + "End": 7, + "Line": 1, + "Column": 1 + }, + "Selections": [ + { + "Kind": "Field", + "Alias": null, + "Arguments": [], + "SelectionSet": null, + "Location": { + "Start": 2, + "End": 7, + "Line": 1, + "Column": 3 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 2, + "End": 7, + "Line": 1, + "Column": 3 + }, + "Value": "foo" + }, + "Directives": [] + } + ] + } + } + ] + }, + "OperationName": null, + "VariableValues": { + "one": "foo", + "two": "bar" + }, + "InitialValue": null, + "Properties": null, + "Services": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndInitialValue_RequestIsCreated.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndInitialValue_RequestIsCreated.snap new file mode 100644 index 00000000000..4f9c7003006 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndInitialValue_RequestIsCreated.snap @@ -0,0 +1,67 @@ +{ + "Query": { + "Kind": "Document", + "Location": { + "Start": 0, + "End": 7, + "Line": 1, + "Column": 1 + }, + "Definitions": [ + { + "Kind": "OperationDefinition", + "Location": { + "Start": 0, + "End": 7, + "Line": 1, + "Column": 1 + }, + "Name": null, + "Operation": "Query", + "VariableDefinitions": [], + "Directives": [], + "SelectionSet": { + "Kind": "SelectionSet", + "Location": { + "Start": 0, + "End": 7, + "Line": 1, + "Column": 1 + }, + "Selections": [ + { + "Kind": "Field", + "Alias": null, + "Arguments": [], + "SelectionSet": null, + "Location": { + "Start": 2, + "End": 7, + "Line": 1, + "Column": 3 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 2, + "End": 7, + "Line": 1, + "Column": 3 + }, + "Value": "foo" + }, + "Directives": [] + } + ] + } + } + ] + }, + "OperationName": null, + "VariableValues": null, + "InitialValue": { + "a": "123" + }, + "Properties": null, + "Services": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndOperation_RequestIsCreated.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndOperation_RequestIsCreated.snap new file mode 100644 index 00000000000..23a97e67fdf --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndOperation_RequestIsCreated.snap @@ -0,0 +1,74 @@ +{ + "Query": { + "Kind": "Document", + "Location": { + "Start": 0, + "End": 17, + "Line": 1, + "Column": 1 + }, + "Definitions": [ + { + "Kind": "OperationDefinition", + "Location": { + "Start": 0, + "End": 17, + "Line": 1, + "Column": 1 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 6, + "End": 11, + "Line": 1, + "Column": 7 + }, + "Value": "bar" + }, + "Operation": "Query", + "VariableDefinitions": [], + "Directives": [], + "SelectionSet": { + "Kind": "SelectionSet", + "Location": { + "Start": 10, + "End": 17, + "Line": 1, + "Column": 11 + }, + "Selections": [ + { + "Kind": "Field", + "Alias": null, + "Arguments": [], + "SelectionSet": null, + "Location": { + "Start": 12, + "End": 17, + "Line": 1, + "Column": 13 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 12, + "End": 17, + "Line": 1, + "Column": 13 + }, + "Value": "foo" + }, + "Directives": [] + } + ] + } + } + ] + }, + "OperationName": "bar", + "VariableValues": null, + "InitialValue": null, + "Properties": null, + "Services": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndResetOperation_RequestIsCreated.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndResetOperation_RequestIsCreated.snap new file mode 100644 index 00000000000..10b271fb06d --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndResetOperation_RequestIsCreated.snap @@ -0,0 +1,65 @@ +{ + "Query": { + "Kind": "Document", + "Location": { + "Start": 0, + "End": 7, + "Line": 1, + "Column": 1 + }, + "Definitions": [ + { + "Kind": "OperationDefinition", + "Location": { + "Start": 0, + "End": 7, + "Line": 1, + "Column": 1 + }, + "Name": null, + "Operation": "Query", + "VariableDefinitions": [], + "Directives": [], + "SelectionSet": { + "Kind": "SelectionSet", + "Location": { + "Start": 0, + "End": 7, + "Line": 1, + "Column": 1 + }, + "Selections": [ + { + "Kind": "Field", + "Alias": null, + "Arguments": [], + "SelectionSet": null, + "Location": { + "Start": 2, + "End": 7, + "Line": 1, + "Column": 3 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 2, + "End": 7, + "Line": 1, + "Column": 3 + }, + "Value": "foo" + }, + "Directives": [] + } + ] + } + } + ] + }, + "OperationName": null, + "VariableValues": null, + "InitialValue": null, + "Properties": null, + "Services": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndResetProperties_RequestIsCreated.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndResetProperties_RequestIsCreated.snap new file mode 100644 index 00000000000..10b271fb06d --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndResetProperties_RequestIsCreated.snap @@ -0,0 +1,65 @@ +{ + "Query": { + "Kind": "Document", + "Location": { + "Start": 0, + "End": 7, + "Line": 1, + "Column": 1 + }, + "Definitions": [ + { + "Kind": "OperationDefinition", + "Location": { + "Start": 0, + "End": 7, + "Line": 1, + "Column": 1 + }, + "Name": null, + "Operation": "Query", + "VariableDefinitions": [], + "Directives": [], + "SelectionSet": { + "Kind": "SelectionSet", + "Location": { + "Start": 0, + "End": 7, + "Line": 1, + "Column": 1 + }, + "Selections": [ + { + "Kind": "Field", + "Alias": null, + "Arguments": [], + "SelectionSet": null, + "Location": { + "Start": 2, + "End": 7, + "Line": 1, + "Column": 3 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 2, + "End": 7, + "Line": 1, + "Column": 3 + }, + "Value": "foo" + }, + "Directives": [] + } + ] + } + } + ] + }, + "OperationName": null, + "VariableValues": null, + "InitialValue": null, + "Properties": null, + "Services": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndResetVariables_RequestIsCreated.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndResetVariables_RequestIsCreated.snap new file mode 100644 index 00000000000..10b271fb06d --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndResetVariables_RequestIsCreated.snap @@ -0,0 +1,65 @@ +{ + "Query": { + "Kind": "Document", + "Location": { + "Start": 0, + "End": 7, + "Line": 1, + "Column": 1 + }, + "Definitions": [ + { + "Kind": "OperationDefinition", + "Location": { + "Start": 0, + "End": 7, + "Line": 1, + "Column": 1 + }, + "Name": null, + "Operation": "Query", + "VariableDefinitions": [], + "Directives": [], + "SelectionSet": { + "Kind": "SelectionSet", + "Location": { + "Start": 0, + "End": 7, + "Line": 1, + "Column": 1 + }, + "Selections": [ + { + "Kind": "Field", + "Alias": null, + "Arguments": [], + "SelectionSet": null, + "Location": { + "Start": 2, + "End": 7, + "Line": 1, + "Column": 3 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 2, + "End": 7, + "Line": 1, + "Column": 3 + }, + "Value": "foo" + }, + "Directives": [] + } + ] + } + } + ] + }, + "OperationName": null, + "VariableValues": null, + "InitialValue": null, + "Properties": null, + "Services": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndServices_RequestIsCreated.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndServices_RequestIsCreated.snap new file mode 100644 index 00000000000..df5af8e9af8 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndServices_RequestIsCreated.snap @@ -0,0 +1,65 @@ +{ + "Query": { + "Kind": "Document", + "Location": { + "Start": 0, + "End": 7, + "Line": 1, + "Column": 1 + }, + "Definitions": [ + { + "Kind": "OperationDefinition", + "Location": { + "Start": 0, + "End": 7, + "Line": 1, + "Column": 1 + }, + "Name": null, + "Operation": "Query", + "VariableDefinitions": [], + "Directives": [], + "SelectionSet": { + "Kind": "SelectionSet", + "Location": { + "Start": 0, + "End": 7, + "Line": 1, + "Column": 1 + }, + "Selections": [ + { + "Kind": "Field", + "Alias": null, + "Arguments": [], + "SelectionSet": null, + "Location": { + "Start": 2, + "End": 7, + "Line": 1, + "Column": 3 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 2, + "End": 7, + "Line": 1, + "Column": 3 + }, + "Value": "foo" + }, + "Directives": [] + } + ] + } + } + ] + }, + "OperationName": null, + "VariableValues": null, + "InitialValue": null, + "Properties": null, + "Services": {} +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndSetProperties_RequestIsCreated.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndSetProperties_RequestIsCreated.snap new file mode 100644 index 00000000000..e6da226bd9b --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndSetProperties_RequestIsCreated.snap @@ -0,0 +1,67 @@ +{ + "Query": { + "Kind": "Document", + "Location": { + "Start": 0, + "End": 7, + "Line": 1, + "Column": 1 + }, + "Definitions": [ + { + "Kind": "OperationDefinition", + "Location": { + "Start": 0, + "End": 7, + "Line": 1, + "Column": 1 + }, + "Name": null, + "Operation": "Query", + "VariableDefinitions": [], + "Directives": [], + "SelectionSet": { + "Kind": "SelectionSet", + "Location": { + "Start": 0, + "End": 7, + "Line": 1, + "Column": 1 + }, + "Selections": [ + { + "Kind": "Field", + "Alias": null, + "Arguments": [], + "SelectionSet": null, + "Location": { + "Start": 2, + "End": 7, + "Line": 1, + "Column": 3 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 2, + "End": 7, + "Line": 1, + "Column": 3 + }, + "Value": "foo" + }, + "Directives": [] + } + ] + } + } + ] + }, + "OperationName": null, + "VariableValues": null, + "InitialValue": null, + "Properties": { + "three": "baz" + }, + "Services": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndSetVariables_RequestIsCreated.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndSetVariables_RequestIsCreated.snap new file mode 100644 index 00000000000..0ba3486ca59 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_QueryAndSetVariables_RequestIsCreated.snap @@ -0,0 +1,67 @@ +{ + "Query": { + "Kind": "Document", + "Location": { + "Start": 0, + "End": 7, + "Line": 1, + "Column": 1 + }, + "Definitions": [ + { + "Kind": "OperationDefinition", + "Location": { + "Start": 0, + "End": 7, + "Line": 1, + "Column": 1 + }, + "Name": null, + "Operation": "Query", + "VariableDefinitions": [], + "Directives": [], + "SelectionSet": { + "Kind": "SelectionSet", + "Location": { + "Start": 0, + "End": 7, + "Line": 1, + "Column": 1 + }, + "Selections": [ + { + "Kind": "Field", + "Alias": null, + "Arguments": [], + "SelectionSet": null, + "Location": { + "Start": 2, + "End": 7, + "Line": 1, + "Column": 3 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 2, + "End": 7, + "Line": 1, + "Column": 3 + }, + "Value": "foo" + }, + "Directives": [] + } + ] + } + } + ] + }, + "OperationName": null, + "VariableValues": { + "three": "baz" + }, + "InitialValue": null, + "Properties": null, + "Services": null +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_SetAll_RequestIsCreated.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_SetAll_RequestIsCreated.snap new file mode 100644 index 00000000000..83618c30e8d --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/RemoteRemoteQueryRequestBuilderTests.BuildRequest_SetAll_RequestIsCreated.snap @@ -0,0 +1,80 @@ +{ + "Query": { + "Kind": "Document", + "Location": { + "Start": 0, + "End": 17, + "Line": 1, + "Column": 1 + }, + "Definitions": [ + { + "Kind": "OperationDefinition", + "Location": { + "Start": 0, + "End": 17, + "Line": 1, + "Column": 1 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 6, + "End": 11, + "Line": 1, + "Column": 7 + }, + "Value": "bar" + }, + "Operation": "Query", + "VariableDefinitions": [], + "Directives": [], + "SelectionSet": { + "Kind": "SelectionSet", + "Location": { + "Start": 10, + "End": 17, + "Line": 1, + "Column": 11 + }, + "Selections": [ + { + "Kind": "Field", + "Alias": null, + "Arguments": [], + "SelectionSet": null, + "Location": { + "Start": 12, + "End": 17, + "Line": 1, + "Column": 13 + }, + "Name": { + "Kind": "Name", + "Location": { + "Start": 12, + "End": 17, + "Line": 1, + "Column": 13 + }, + "Value": "foo" + }, + "Directives": [] + } + ] + } + } + ] + }, + "OperationName": "bar", + "VariableValues": { + "two": "bar" + }, + "InitialValue": { + "a": "456" + }, + "Properties": { + "one": "foo" + }, + "Services": {} +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/StitchingBuilderTests.AddExtensionsFromFile.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/StitchingBuilderTests.AddExtensionsFromFile.snap new file mode 100644 index 00000000000..64d14b03daa --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/StitchingBuilderTests.AddExtensionsFromFile.snap @@ -0,0 +1,4 @@ +[ + "type Query {\n contract(contractId: ID!): Contract\n contracts(customerId: ID!): [Contract!]\n}\n\ninterface Contract {\n id: ID!\n}\n\ntype LifeInsuranceContract implements Contract {\n id: ID!\n premium: Float\n}\n\ntype SomeOtherContract implements Contract {\n id: ID!\n expiryDate: DateTime\n}", + "type Query {\n customer(id: ID!): Customer\n consultant(id: ID!): Consultant\n customerOrConsultant(id: ID!): CustomerOrConsultant\n}\n\ntype Customer {\n id: ID!\n name: String!\n consultant: Consultant\n complexArg(arg: ComplexInputType): String\n}\n\ntype Consultant {\n id: ID!\n name: String!\n}\n\nunion CustomerOrConsultant = Customer | Consultant\n\ninput ComplexInputType {\n deeper: ComplexInputType\n deeperArray: [ComplexInputType]\n value: String\n valueArray: [String]\n}" +] diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/StitchingBuilderTests.AddExtensionsFromString.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/StitchingBuilderTests.AddExtensionsFromString.snap new file mode 100644 index 00000000000..64d14b03daa --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/StitchingBuilderTests.AddExtensionsFromString.snap @@ -0,0 +1,4 @@ +[ + "type Query {\n contract(contractId: ID!): Contract\n contracts(customerId: ID!): [Contract!]\n}\n\ninterface Contract {\n id: ID!\n}\n\ntype LifeInsuranceContract implements Contract {\n id: ID!\n premium: Float\n}\n\ntype SomeOtherContract implements Contract {\n id: ID!\n expiryDate: DateTime\n}", + "type Query {\n customer(id: ID!): Customer\n consultant(id: ID!): Consultant\n customerOrConsultant(id: ID!): CustomerOrConsultant\n}\n\ntype Customer {\n id: ID!\n name: String!\n consultant: Consultant\n complexArg(arg: ComplexInputType): String\n}\n\ntype Consultant {\n id: ID!\n name: String!\n}\n\nunion CustomerOrConsultant = Customer | Consultant\n\ninput ComplexInputType {\n deeper: ComplexInputType\n deeperArray: [ComplexInputType]\n value: String\n valueArray: [String]\n}" +] diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/StitchingBuilderTests.AddSchema.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/StitchingBuilderTests.AddSchema.snap new file mode 100644 index 00000000000..95950c701c3 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/StitchingBuilderTests.AddSchema.snap @@ -0,0 +1,12 @@ +type Query { + a: A @delegate(schema: "a") + b: B @delegate(schema: "b") +} + +type A @source(name: "A", schema: "a") { + b: String +} + +type B @source(name: "B", schema: "b") { + c: String +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/StitchingBuilderTests.AddSchemaFromFile.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/StitchingBuilderTests.AddSchemaFromFile.snap new file mode 100644 index 00000000000..b8f9823f7eb --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/StitchingBuilderTests.AddSchemaFromFile.snap @@ -0,0 +1,42 @@ +type Query { + contract(contractId: ID!): Contract @delegate(schema: "contract") + contracts(customerId: ID!): [Contract!] @delegate(schema: "contract") + customer(id: ID!): Customer @delegate(schema: "customer") + consultant(id: ID!): Consultant @delegate(schema: "customer") + customerOrConsultant(id: ID!): CustomerOrConsultant @delegate(schema: "customer") +} + +interface Contract @source(name: "Contract", schema: "contract") { + id: ID! +} + +type LifeInsuranceContract implements Contract @source(name: "LifeInsuranceContract", schema: "contract") { + id: ID! + premium: Float +} + +type SomeOtherContract implements Contract @source(name: "SomeOtherContract", schema: "contract") { + id: ID! + expiryDate: DateTime +} + +type Customer @source(name: "Customer", schema: "customer") { + id: ID! + name: String! + consultant: Consultant + complexArg(arg: ComplexInputType): String +} + +type Consultant @source(name: "Consultant", schema: "customer") { + id: ID! + name: String! +} + +union CustomerOrConsultant @source(name: "CustomerOrConsultant", schema: "customer") = Customer | Consultant + +input ComplexInputType @source(name: "ComplexInputType", schema: "customer") { + deeper: ComplexInputType + deeperArray: [ComplexInputType] + value: String + valueArray: [String] +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/StitchingBuilderTests.AddSchemaFromHttp.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/StitchingBuilderTests.AddSchemaFromHttp.snap new file mode 100644 index 00000000000..818963dd4b6 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/StitchingBuilderTests.AddSchemaFromHttp.snap @@ -0,0 +1,143 @@ +type Query { + consultant(id: ID!): Consultant @delegate(schema: "customer") + customer(id: ID!): Customer @delegate(schema: "customer") + customerByKind(kind: CustomerKind!): Customer @delegate(schema: "customer") + customerOrConsultant(id: ID!): CustomerOrConsultant @delegate(schema: "customer") + customers(ids: [ID!]!): [Customer] @delegate(schema: "customer") + contract(contractId: ID!): Contract @delegate(schema: "contract") + contracts(customerId: ID!): [Contract!] @delegate(schema: "contract") + extendedScalar(d: DateTime): DateTime @delegate(schema: "contract") + guid(guid: UUID!): UUID! @delegate(schema: "contract") + int(i: Int!): Int! @delegate(schema: "contract") +} + +type Mutation { + createCustomer(input: CreateCustomerInput): CreateCustomerPayload @delegate(schema: "customer") + createCustomers(inputs: [CreateCustomerInput]): [CreateCustomerPayload] @delegate(schema: "customer") +} + +type Customer @source(name: "Customer", schema: "customer") { + complexArg(arg: ComplexInputType): String + consultant(customer: CustomerInput): Consultant + id: ID! + kind: CustomerKind! + name: String! + say(input: SayInput!): String + someGuid: UUID! + someInt: Int! + street: String! +} + +type Consultant @source(name: "Consultant", schema: "customer") { + customers(after: String before: String first: PaginationAmount last: PaginationAmount): CustomerConnection + id: ID! + name: String! +} + +union CustomerOrConsultant @source(name: "CustomerOrConsultant", schema: "customer") = Customer | Consultant + +input SayInput @source(name: "SayInput", schema: "customer") { + words: [String] +} + +input ComplexInputType @source(name: "ComplexInputType", schema: "customer") { + deeper: ComplexInputType + deeperArray: [ComplexInputType] + value: String + valueArray: [String] +} + +"A connection to a list of items." +type CustomerConnection @source(name: "CustomerConnection", schema: "customer") { + "A list of edges." + edges: [CustomerEdge!] + "A flattened list of the nodes." + nodes: [Customer] + "Information to aid in pagination." + pageInfo: PageInfo! + totalCount: Int! +} + +"Information about pagination in a connection." +type PageInfo @source(name: "PageInfo", schema: "customer") { + "When paginating forwards, the cursor to continue." + endCursor: String + "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 +} + +"An edge in a connection." +type CustomerEdge @source(name: "CustomerEdge", schema: "customer") { + "A cursor for use in pagination." + cursor: String! + "The item at the end of the edge." + node: Customer +} + +enum CustomerKind @source(name: "CustomerKind", schema: "customer") { + STANDARD + PREMIUM +} + +type CreateCustomerPayload @source(name: "CreateCustomerPayload", schema: "customer") { + customer: Customer +} + +input CreateCustomerInput @source(name: "CreateCustomerInput", schema: "customer") { + consultantId: String + name: String + street: String +} + +input CustomerInput @source(name: "CustomerInput", schema: "customer") { + consultantId: String + id: String + kind: CustomerKind! + name: String + someGuid: UUID! + someInt: Int! + street: String +} + +type LifeInsuranceContract implements Contract @source(name: "LifeInsuranceContract", schema: "contract") { + byte_field: Byte + customerId: ID! + date_field: Date + date_time_field: DateTime + decimal_field: Decimal + error: String + float_field: Float + foo(bar: String): String + id: ID! + id_field: ID + int_field: Int + long_field: Long + premium: Float! + string_field: String +} + +type SomeOtherContract implements Contract @source(name: "SomeOtherContract", schema: "contract") { + customerId: ID! + expiryDate: DateTime! + id: ID! +} + +interface Contract @source(name: "Contract", schema: "contract") { + customerId: ID! + id: ID! +} + +"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 + +"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 + +"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 @custom(d: DateTime) on FIELD diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/StitchingBuilderTests.AddSchemaFromString.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/StitchingBuilderTests.AddSchemaFromString.snap new file mode 100644 index 00000000000..b8f9823f7eb --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/StitchingBuilderTests.AddSchemaFromString.snap @@ -0,0 +1,42 @@ +type Query { + contract(contractId: ID!): Contract @delegate(schema: "contract") + contracts(customerId: ID!): [Contract!] @delegate(schema: "contract") + customer(id: ID!): Customer @delegate(schema: "customer") + consultant(id: ID!): Consultant @delegate(schema: "customer") + customerOrConsultant(id: ID!): CustomerOrConsultant @delegate(schema: "customer") +} + +interface Contract @source(name: "Contract", schema: "contract") { + id: ID! +} + +type LifeInsuranceContract implements Contract @source(name: "LifeInsuranceContract", schema: "contract") { + id: ID! + premium: Float +} + +type SomeOtherContract implements Contract @source(name: "SomeOtherContract", schema: "contract") { + id: ID! + expiryDate: DateTime +} + +type Customer @source(name: "Customer", schema: "customer") { + id: ID! + name: String! + consultant: Consultant + complexArg(arg: ComplexInputType): String +} + +type Consultant @source(name: "Consultant", schema: "customer") { + id: ID! + name: String! +} + +union CustomerOrConsultant @source(name: "CustomerOrConsultant", schema: "customer") = Customer | Consultant + +input ComplexInputType @source(name: "ComplexInputType", schema: "customer") { + deeper: ComplexInputType + deeperArray: [ComplexInputType] + value: String + valueArray: [String] +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/StitchingBuilderTests.AddSchema_2.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/StitchingBuilderTests.AddSchema_2.snap new file mode 100644 index 00000000000..bbb1610292f --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__snapshots__/StitchingBuilderTests.AddSchema_2.snap @@ -0,0 +1,132 @@ +type Query { + consultant(id: ID!): Consultant @delegate(schema: "customer") + customer(id: ID!): Customer @delegate(schema: "customer") + customerByKind(kind: CustomerKind!): Customer @delegate(schema: "customer") + customerOrConsultant(id: ID!): CustomerOrConsultant @delegate(schema: "customer") + customers(ids: [ID!]!): [Customer] @delegate(schema: "customer") + contract(contractId: ID!): Contract @delegate(schema: "contract") + contracts(customerId: ID!): [Contract!] @delegate(schema: "contract") + extendedScalar(d: DateTime): DateTime @delegate(schema: "contract") + guid(guid: UUID!): UUID! @delegate(schema: "contract") + int(i: Int!): Int! @delegate(schema: "contract") +} + +type Mutation { + createCustomer(input: CreateCustomerInput): CreateCustomerPayload @delegate(schema: "customer") + createCustomers(inputs: [CreateCustomerInput]): [CreateCustomerPayload] @delegate(schema: "customer") +} + +type Consultant @source(name: "Consultant", schema: "customer") { + customers(after: String before: String first: PaginationAmount last: PaginationAmount): CustomerConnection + id: ID! + name: String! +} + +type CreateCustomerPayload @source(name: "CreateCustomerPayload", schema: "customer") { + customer: Customer +} + +type Customer @source(name: "Customer", schema: "customer") { + complexArg(arg: ComplexInputType): String + consultant(customer: CustomerInput): Consultant + id: ID! + kind: CustomerKind! + name: String! + say(input: SayInput!): String + someGuid: UUID! + someInt: Int! + street: String! +} + +"A connection to a list of items." +type CustomerConnection @source(name: "CustomerConnection", schema: "customer") { + "A list of edges." + edges: [CustomerEdge!] + "A flattened list of the nodes." + nodes: [Customer] + "Information to aid in pagination." + pageInfo: PageInfo! + totalCount: Int! +} + +"An edge in a connection." +type CustomerEdge @source(name: "CustomerEdge", schema: "customer") { + "A cursor for use in pagination." + cursor: String! + "The item at the end of the edge." + node: Customer +} + +"Information about pagination in a connection." +type PageInfo @source(name: "PageInfo", schema: "customer") { + "When paginating forwards, the cursor to continue." + endCursor: String + "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 +} + +union CustomerOrConsultant @source(name: "CustomerOrConsultant", schema: "customer") = Customer | Consultant + +input ComplexInputType @source(name: "ComplexInputType", schema: "customer") { + deeper: ComplexInputType + deeperArray: [ComplexInputType] + value: String + valueArray: [String] +} + +input CreateCustomerInput @source(name: "CreateCustomerInput", schema: "customer") { + consultantId: String + name: String + street: String +} + +input CustomerInput @source(name: "CustomerInput", schema: "customer") { + consultantId: String + id: String + kind: CustomerKind! + name: String + someGuid: UUID! + someInt: Int! + street: String +} + +input SayInput @source(name: "SayInput", schema: "customer") { + words: [String] +} + +enum CustomerKind @source(name: "CustomerKind", schema: "customer") { + STANDARD + PREMIUM +} + +interface Contract @source(name: "Contract", schema: "contract") { + customerId: ID! + id: ID! +} + +type LifeInsuranceContract implements Contract @source(name: "LifeInsuranceContract", schema: "contract") { + byte_field: Byte + customerId: ID! + date_field: Date + date_time_field: DateTime + decimal_field: Decimal + error: String + float_field: Float + foo(bar: String): String + id: ID! + id_field: ID + int_field: Int + long_field: Long + premium: Float! + string_field: String +} + +type SomeOtherContract implements Contract @source(name: "SomeOtherContract", schema: "contract") { + customerId: ID! + expiryDate: DateTime! + id: ID! +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/xunit.runner.json b/src/HotChocolate/Stitching/test/Stitching.Tests/xunit.runner.json new file mode 100644 index 00000000000..be381642fe3 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/xunit.runner.json @@ -0,0 +1,3 @@ +{ + "appDomain": "denied", "maxParallelThreads": 2 +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Merge_Directives_2_Single_Document.snap b/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Merge_Directives_2_Single_Document.snap deleted file mode 100644 index a8b1efba8ef..00000000000 --- a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyExtensionsMiddlewareTests.Apply_Object_Extension_Merge_Directives_2_Single_Document.snap +++ /dev/null @@ -1,7 +0,0 @@ -schema @_hc_service(name: "abc") { - -} - -type Foo @a @b { - abc: String -} diff --git a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyRenamingMiddlewareTests.Apply_Local_Rename.snap b/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyRenamingMiddlewareTests.Apply_Local_Rename.snap deleted file mode 100644 index e06d0a42ea1..00000000000 --- a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/Pipeline/__snapshots__/ApplyRenamingMiddlewareTests.Apply_Local_Rename.snap +++ /dev/null @@ -1,9 +0,0 @@ -schema @_hc_service(name: "abc") { - -} - -type Foo { - def: String @_hc_bind(to: "abc", as: "abc") - def: Int @_hc_bind(to: "abc") - ghi: Int @_hc_bind(to: "bar", as: "baz") -} diff --git a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/__snapshots__/DefaultObjectTypeDefinitionMergerTests.Same_Field_In_Each_Schema_Version.snap b/src/HotChocolate/Stitching/test/Stitching.Types.Tests/__snapshots__/DefaultObjectTypeDefinitionMergerTests.Same_Field_In_Each_Schema_Version.snap deleted file mode 100644 index 6efbf0a0756..00000000000 --- a/src/HotChocolate/Stitching/test/Stitching.Types.Tests/__snapshots__/DefaultObjectTypeDefinitionMergerTests.Same_Field_In_Each_Schema_Version.snap +++ /dev/null @@ -1,3 +0,0 @@ -type Foo { - a: String -} diff --git a/src/HotChocolate/Utilities/src/Utilities.Introspection/BuiltInTypes.cs b/src/HotChocolate/Utilities/src/Utilities.Introspection/BuiltInTypes.cs index d1a53803aa8..119837d6620 100644 --- a/src/HotChocolate/Utilities/src/Utilities.Introspection/BuiltInTypes.cs +++ b/src/HotChocolate/Utilities/src/Utilities.Introspection/BuiltInTypes.cs @@ -36,7 +36,7 @@ public static class BuiltInTypes SpecifiedBy }; - public static bool IsBuiltInType(string name) + public static bool IsBuiltInType(string name) => _typeNames.Contains(name) || _directiveNames.Contains(name); public static DocumentNode RemoveBuiltInTypes(this DocumentNode schema) @@ -48,7 +48,7 @@ public static DocumentNode RemoveBuiltInTypes(this DocumentNode schema) var definitions = new List(); - foreach (IDefinitionNode definition in schema.Definitions) + foreach (var definition in schema.Definitions) { if (definition is INamedSyntaxNode type) { diff --git a/src/HotChocolate/Utilities/src/Utilities.Introspection/IntrospectionClient.cs b/src/HotChocolate/Utilities/src/Utilities.Introspection/IntrospectionClient.cs index 127503b37de..ecad86632e4 100644 --- a/src/HotChocolate/Utilities/src/Utilities.Introspection/IntrospectionClient.cs +++ b/src/HotChocolate/Utilities/src/Utilities.Introspection/IntrospectionClient.cs @@ -46,7 +46,7 @@ static IntrospectionClient() throw new ArgumentNullException(nameof(stream)); } - DocumentNode document = await DownloadSchemaAsync( + var document = await DownloadSchemaAsync( client, cancellationToken) .ConfigureAwait(false); @@ -64,13 +64,13 @@ await document throw new ArgumentNullException(nameof(client)); } - ISchemaFeatures features = await GetSchemaFeaturesAsync( + var features = await GetSchemaFeaturesAsync( client, cancellationToken) .ConfigureAwait(false); - HttpQueryRequest request = IntrospectionQueryHelper.CreateIntrospectionQuery(features); + var request = IntrospectionQueryHelper.CreateIntrospectionQuery(features); - IntrospectionResult result = await ExecuteIntrospectionAsync( + var result = await ExecuteIntrospectionAsync( client, request, cancellationToken) .ConfigureAwait(false); EnsureNoGraphQLErrors(result); @@ -87,9 +87,9 @@ await document throw new ArgumentNullException(nameof(client)); } - HttpQueryRequest request = IntrospectionQueryHelper.CreateFeatureQuery(); + var request = IntrospectionQueryHelper.CreateFeatureQuery(); - IntrospectionResult result = await ExecuteIntrospectionAsync( + var result = await ExecuteIntrospectionAsync( client, request, cancellationToken) .ConfigureAwait(false); EnsureNoGraphQLErrors(result); @@ -121,7 +121,7 @@ private void EnsureNoGraphQLErrors(IntrospectionResult result) HttpQueryRequest request, CancellationToken cancellationToken) { - byte[] serializedRequest = JsonSerializer.SerializeToUtf8Bytes( + var serializedRequest = JsonSerializer.SerializeToUtf8Bytes( request, _serializerOptions); var content = new ByteArrayContent(serializedRequest); @@ -132,12 +132,12 @@ private void EnsureNoGraphQLErrors(IntrospectionResult result) Content = content }; - using HttpResponseMessage httpResponse = + using var httpResponse = await client.SendAsync(httpRequest, cancellationToken) .ConfigureAwait(false); httpResponse.EnsureSuccessStatusCode(); - using Stream stream = + using var stream = await httpResponse.Content.ReadAsStreamAsync() .ConfigureAwait(false); diff --git a/src/HotChocolate/Utilities/src/Utilities.Introspection/IntrospectionDeserializer.cs b/src/HotChocolate/Utilities/src/Utilities.Introspection/IntrospectionDeserializer.cs index 9ff858fb159..1722d1c3ab8 100644 --- a/src/HotChocolate/Utilities/src/Utilities.Introspection/IntrospectionDeserializer.cs +++ b/src/HotChocolate/Utilities/src/Utilities.Introspection/IntrospectionDeserializer.cs @@ -14,9 +14,9 @@ public static DocumentNode Deserialize(IntrospectionResult result) typeDefinitions.Add(CreateSchema(result.Data.Schema)); typeDefinitions.AddRange(CreateTypes(result.Data.Schema.Types)); - foreach (Directive directive in result.Data.Schema.Directives) + foreach (var directive in result.Data.Schema.Directives) { - DirectiveDefinitionNode directiveDefinition = + var directiveDefinition = CreateDirectiveDefinition(directive); if (directiveDefinition.Locations.Any()) { @@ -72,7 +72,7 @@ private static SchemaDefinitionNode CreateSchema(Schema schema) private static IEnumerable CreateTypes( ICollection types) { - foreach (FullType type in types) + foreach (var type in types) { yield return CreateTypes(type); } @@ -123,7 +123,7 @@ private static EnumTypeDefinitionNode CreateEnumType(FullType type) { var values = new List(); - foreach (EnumValue value in enumValues) + foreach (var value in enumValues) { values.Add(new EnumValueDefinitionNode @@ -158,7 +158,7 @@ private static EnumTypeDefinitionNode CreateEnumType(FullType type) { var list = new List(); - foreach (InputField field in fields) + foreach (var field in fields) { list.Add(new InputValueDefinitionNode ( @@ -207,7 +207,7 @@ private static EnumTypeDefinitionNode CreateEnumType(FullType type) { var list = new List(); - foreach (Field field in fields) + foreach (var field in fields) { list.Add(new FieldDefinitionNode ( @@ -252,7 +252,7 @@ private static EnumTypeDefinitionNode CreateEnumType(FullType type) private static DirectiveDefinitionNode CreateDirectiveDefinition( Directive directive) { - IReadOnlyList locations = directive.Locations == null + var locations = directive.Locations == null ? InferDirectiveLocation(directive) : directive.Locations.Select(t => new NameNode(t)).ToList(); @@ -306,7 +306,7 @@ private static EnumTypeDefinitionNode CreateEnumType(FullType type) { var list = new List(); - foreach (TypeRef typeRef in interfaces) + foreach (var typeRef in interfaces) { list.Add(new NamedTypeNode(new NameNode(typeRef.Name))); } diff --git a/src/HotChocolate/Utilities/src/Utilities.Introspection/IntrospectionQueryHelper.cs b/src/HotChocolate/Utilities/src/Utilities.Introspection/IntrospectionQueryHelper.cs index 6fa6cccd148..bd36d8a8ea1 100644 --- a/src/HotChocolate/Utilities/src/Utilities.Introspection/IntrospectionQueryHelper.cs +++ b/src/HotChocolate/Utilities/src/Utilities.Introspection/IntrospectionQueryHelper.cs @@ -23,19 +23,19 @@ internal static class IntrospectionQueryHelper public static HttpQueryRequest CreateIntrospectionQuery(ISchemaFeatures features) { - DocumentNode document = CreateIntrospectionQueryDocument(features); - string sourceText = document.Print(false); + var document = CreateIntrospectionQueryDocument(features); + var sourceText = document.Print(false); return new HttpQueryRequest(sourceText, "introspection_phase_2"); } private static DocumentNode CreateIntrospectionQueryDocument(ISchemaFeatures features) { - DocumentNode query = Utf8GraphQLParser.Parse(GetIntrospectionQuery()); + var query = Utf8GraphQLParser.Parse(GetIntrospectionQuery()); - OperationDefinitionNode operation = + var operation = query.Definitions.OfType().First(); - FieldNode schema = + var schema = operation.SelectionSet.Selections.OfType().First(); if (schema.SelectionSet is null) @@ -43,7 +43,7 @@ private static DocumentNode CreateIntrospectionQueryDocument(ISchemaFeatures fea throw new IntrospectionException(); } - FieldNode directives = + var directives = schema.SelectionSet.Selections.OfType().First(t => t.Name.Value.Equals(_directivesField, StringComparison.Ordinal)); @@ -55,7 +55,7 @@ private static DocumentNode CreateIntrospectionQueryDocument(ISchemaFeatures fea var selections = directives.SelectionSet.Selections.ToList(); AddDirectiveFeatures(features, selections); - FieldNode newField = directives.WithSelectionSet( + var newField = directives.WithSelectionSet( directives.SelectionSet.WithSelections(selections)); selections = schema.SelectionSet.Selections.ToList(); @@ -71,7 +71,7 @@ private static DocumentNode CreateIntrospectionQueryDocument(ISchemaFeatures fea selections.Remove(schema); selections.Add(newField); - OperationDefinitionNode newOp = operation.WithSelectionSet( + var newOp = operation.WithSelectionSet( operation.SelectionSet.WithSelections(selections)); var definitions = query.Definitions.ToList(); @@ -108,7 +108,7 @@ private static DocumentNode CreateIntrospectionQueryDocument(ISchemaFeatures fea { if (!features.HasSubscriptionSupport) { - FieldNode subscriptionField = selections.OfType() + var subscriptionField = selections.OfType() .First(t => t.Name.Value.Equals( SchemaFeatures.SubscriptionType, StringComparison.Ordinal)); @@ -132,7 +132,7 @@ private static DocumentNode CreateIntrospectionQueryDocument(ISchemaFeatures fea private static string GetQueryFile(string fileName) { #pragma warning disable CS8600 - Stream? stream = typeof(IntrospectionClient).Assembly + var stream = typeof(IntrospectionClient).Assembly .GetManifestResourceStream($"{_resourceNamespace}.{fileName}"); #pragma warning restore CS8600 diff --git a/src/HotChocolate/Utilities/src/Utilities.Introspection/SchemaFeatures.cs b/src/HotChocolate/Utilities/src/Utilities.Introspection/SchemaFeatures.cs index d475ab3bdd5..be7bea664d9 100644 --- a/src/HotChocolate/Utilities/src/Utilities.Introspection/SchemaFeatures.cs +++ b/src/HotChocolate/Utilities/src/Utilities.Introspection/SchemaFeatures.cs @@ -25,7 +25,7 @@ private SchemaFeatures() { var features = new SchemaFeatures(); - FullType? directive = result.Data.Schema.Types.FirstOrDefault(t => + var directive = result.Data.Schema.Types.FirstOrDefault(t => t.Name.Equals(__Directive, StringComparison.Ordinal)); if (directive is not null) { @@ -35,7 +35,7 @@ private SchemaFeatures() t.Name.Equals(Locations, StringComparison.Ordinal)); } - FullType? schema = result.Data.Schema.Types.FirstOrDefault(t => + var schema = result.Data.Schema.Types.FirstOrDefault(t => t.Name.Equals(__Schema, StringComparison.Ordinal)); if (schema is not null) {