From 71ec0abd2d38dbeb40cb1404ba3b200ab18dca25 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Fri, 29 Mar 2019 12:32:59 +0100 Subject: [PATCH] Stitching Introspection Fixes (#671) --- CHANGELOG.md | 5 + src/Core/Language.Tests/Lexer/LexerTests.cs | 48 +- .../Language.Tests/Parser/ValueParserTests.cs | 66 ++ src/Core/Language/AST/BooleanValueNode.cs | 2 + src/Core/Language/AST/EnumValueNode.cs | 2 + src/Core/Language/AST/FloatValueNode.cs | 2 + src/Core/Language/AST/IValueNode.cs | 3 +- src/Core/Language/AST/IntValueNode.cs | 2 + src/Core/Language/AST/ListValueNode.cs | 2 + src/Core/Language/AST/ObjectValueNode.cs | 2 + src/Core/Language/AST/StringValueNode.cs | 2 + src/Core/Language/AST/VariableNode.cs | 2 + src/Core/Language/Lexer/Lexer.cs | 13 +- src/Core/Language/Lexer/LexerState.cs | 10 + .../Introspection/IntrospectionClientTests.cs | 111 ++ .../IntrospectionDeserializerTests.cs | 14 + ...pectionWithDirectiveIsRepeatableField.snap | 91 ++ ...trospectionWithDirectiveLocationField.snap | 88 ++ ...ntTests.IntrospectionWithSubscription.snap | 93 ++ ...tionWithoutDirectiveIsRepeatableField.snap | 90 ++ ...spectionWithoutDirectiveLocationField.snap | 90 ++ ...ests.IntrospectionWithoutSubscription.snap | 90 ++ ...lizeIntrospectionWithIntDefaultValues.snap | 142 +++ .../IntrospectionWithDefaultValues.json | 1003 +++++++++++++++++ .../Delegation/ScopedVariableNode.cs | 2 + .../Introspection/IntrospectionClient.cs | 73 +- .../Stitching/Introspection/SchemaFeatures.cs | 2 + 27 files changed, 2020 insertions(+), 30 deletions(-) create mode 100644 src/Core/Language.Tests/Parser/ValueParserTests.cs create mode 100644 src/Stitching/Stitching.Tests/Introspection/IntrospectionClientTests.cs create mode 100644 src/Stitching/Stitching.Tests/Introspection/__snapshots__/IntrospectionClientTests.IntrospectionWithDirectiveIsRepeatableField.snap create mode 100644 src/Stitching/Stitching.Tests/Introspection/__snapshots__/IntrospectionClientTests.IntrospectionWithDirectiveLocationField.snap create mode 100644 src/Stitching/Stitching.Tests/Introspection/__snapshots__/IntrospectionClientTests.IntrospectionWithSubscription.snap create mode 100644 src/Stitching/Stitching.Tests/Introspection/__snapshots__/IntrospectionClientTests.IntrospectionWithoutDirectiveIsRepeatableField.snap create mode 100644 src/Stitching/Stitching.Tests/Introspection/__snapshots__/IntrospectionClientTests.IntrospectionWithoutDirectiveLocationField.snap create mode 100644 src/Stitching/Stitching.Tests/Introspection/__snapshots__/IntrospectionClientTests.IntrospectionWithoutSubscription.snap create mode 100644 src/Stitching/Stitching.Tests/Introspection/__snapshots__/IntrospectionDeserializerTests.DeserializeIntrospectionWithIntDefaultValues.snap create mode 100644 src/Stitching/Stitching.Tests/__resources__/IntrospectionWithDefaultValues.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f4cada3c58..94e1bd34d1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +## [0.8.1] - 2019-03-29 + +### Added + - Added operation start/stop event. - Added error filter support for schema stitching. @@ -31,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Non-nullable types are now validated when query uses variables. [#651](https://github.com/ChilliCream/hotchocolate/issues/651) - Variable handling im middleware does not convert the DateTime value anymore. [#664](https://github.com/ChilliCream/hotchocolate/issues/664) - Directives are now correctly merged when declared in an extension file. [#665](https://github.com/ChilliCream/hotchocolate/issues/665) +- Subscription is now optional in the 2-phase introspection call [#668](https://github.com/ChilliCream/hotchocolate/issues/668) ## [0.8.0] - 2019-03-03 diff --git a/src/Core/Language.Tests/Lexer/LexerTests.cs b/src/Core/Language.Tests/Lexer/LexerTests.cs index a489a1012dc..9119d8f46e8 100644 --- a/src/Core/Language.Tests/Lexer/LexerTests.cs +++ b/src/Core/Language.Tests/Lexer/LexerTests.cs @@ -1,4 +1,5 @@ -using Xunit; +using System; +using Xunit; namespace HotChocolate.Language { @@ -31,5 +32,48 @@ public void EnsureTokensAreDoublyLinked() Assert.Equal(token.Next.Next, token.Next.Next.Next.Previous); Assert.Null(token.Next.Next.Next.Next); } + + [Fact] + public void SourceIsNull_ArgumentNullException() + { + // arrange + Lexer lexer = new Lexer(); + + // act + Action action = () => lexer.Read(null); + + // assert + Assert.Throws(action); + } + + [Fact] + public void UnexpectedCharacter() + { + // arrange + Source source = new Source("~"); + Lexer lexer = new Lexer(); + + // act + Action action = () => lexer.Read(source); + + // assert + Assert.Equal("Unexpected character.", + Assert.Throws(action).Message); + } + + [Fact] + public void UnexpectedTokenSequence() + { + // arrange + Source source = new Source("\"foo"); + Lexer lexer = new Lexer(); + + // act + Action action = () => lexer.Read(source); + + // assert + Assert.Equal("Unexpected token sequence.", + Assert.Throws(action).Message); + } } -} \ No newline at end of file +} diff --git a/src/Core/Language.Tests/Parser/ValueParserTests.cs b/src/Core/Language.Tests/Parser/ValueParserTests.cs new file mode 100644 index 00000000000..4474da792cd --- /dev/null +++ b/src/Core/Language.Tests/Parser/ValueParserTests.cs @@ -0,0 +1,66 @@ +using System; +using Xunit; + +namespace HotChocolate.Language +{ + public class ValueParserTests + { + [InlineData("true", true, typeof(BooleanValueNode))] + [InlineData("false", false, typeof(BooleanValueNode))] + [InlineData("0", "0", typeof(IntValueNode))] + [InlineData("123", "123", typeof(IntValueNode))] + [InlineData("-123", "-123", typeof(IntValueNode))] + [InlineData("2.3e-5", "2.3e-5", typeof(FloatValueNode))] + [InlineData("2.3e+5", "2.3e+5", typeof(FloatValueNode))] + [InlineData("123.456", "123.456", typeof(FloatValueNode))] + [InlineData("\"123\"", "123", typeof(StringValueNode))] + [InlineData("\"\"\"123\n456\"\"\"", "123\n456", + typeof(StringValueNode))] + [InlineData("\"\\u0031\"", "1", typeof(StringValueNode))] + [InlineData("\"\\u004E\"", "N", typeof(StringValueNode))] + [InlineData("\"\\u0061\"", "a", typeof(StringValueNode))] + [InlineData("\"\\u003A\"", ":", typeof(StringValueNode))] + [InlineData("FOO", "FOO", typeof(EnumValueNode))] + [Theory] + public void ParseSimpleValue( + string value, + object expectedValue, + Type expectedNodeType) + { + // arrange + // act + IValueNode valueNode = ParseValue(value); + + // assert + Assert.Equal(expectedNodeType, valueNode.GetType()); + Assert.Equal(expectedValue, valueNode.Value); + } + + [Fact] + public void ZeroZeroIsNotAllowed() + { + // arrange + // act + Action action = () => ParseValue("00"); + + // assert + Assert.Throws(action); + } + + private static IValueNode ParseValue(string value) + { + SyntaxToken start = Lexer.Default.Read( + new Source(value)); + + var context = new ParserContext( + new Source(value), + start, + ParserOptions.Default, + Parser.ParseName); + context.MoveNext(); + + return Parser.ParseValueLiteral(context, true); + } + } +} + diff --git a/src/Core/Language/AST/BooleanValueNode.cs b/src/Core/Language/AST/BooleanValueNode.cs index e1271df170b..bfdbd358970 100644 --- a/src/Core/Language/AST/BooleanValueNode.cs +++ b/src/Core/Language/AST/BooleanValueNode.cs @@ -26,6 +26,8 @@ public BooleanValueNode( public bool Value { get; } + object IValueNode.Value => Value; + /// /// Determines whether the specified /// is equal to the current . diff --git a/src/Core/Language/AST/EnumValueNode.cs b/src/Core/Language/AST/EnumValueNode.cs index 644ec5f3fce..52f612053f3 100644 --- a/src/Core/Language/AST/EnumValueNode.cs +++ b/src/Core/Language/AST/EnumValueNode.cs @@ -35,6 +35,8 @@ public EnumValueNode( public string Value { get; } + object IValueNode.Value => Value; + /// /// Determines whether the specified /// is equal to the current . diff --git a/src/Core/Language/AST/FloatValueNode.cs b/src/Core/Language/AST/FloatValueNode.cs index 1f6049693db..8f8e20fbfa3 100644 --- a/src/Core/Language/AST/FloatValueNode.cs +++ b/src/Core/Language/AST/FloatValueNode.cs @@ -32,6 +32,8 @@ public FloatValueNode( public string Value { get; } + object IValueNode.Value => Value; + /// /// Determines whether the specified /// is equal to the current . diff --git a/src/Core/Language/AST/IValueNode.cs b/src/Core/Language/AST/IValueNode.cs index 80ae7ae0016..f70f88a7037 100644 --- a/src/Core/Language/AST/IValueNode.cs +++ b/src/Core/Language/AST/IValueNode.cs @@ -6,11 +6,12 @@ public interface IValueNode : ISyntaxNode , IEquatable { + object Value { get; } } public interface IValueNode : IValueNode { - T Value { get; } + new T Value { get; } } } diff --git a/src/Core/Language/AST/IntValueNode.cs b/src/Core/Language/AST/IntValueNode.cs index 20938dcfae9..d8428c89b99 100644 --- a/src/Core/Language/AST/IntValueNode.cs +++ b/src/Core/Language/AST/IntValueNode.cs @@ -38,6 +38,8 @@ public IntValueNode( public string Value { get; } + object IValueNode.Value => Value; + /// /// Determines whether the specified /// is equal to the current . diff --git a/src/Core/Language/AST/ListValueNode.cs b/src/Core/Language/AST/ListValueNode.cs index a162a5e8310..0347283f710 100644 --- a/src/Core/Language/AST/ListValueNode.cs +++ b/src/Core/Language/AST/ListValueNode.cs @@ -51,6 +51,8 @@ public ListValueNode( IReadOnlyList IValueNode>.Value => Items; + object IValueNode.Value => Items; + /// /// Determines whether the specified /// is equal to the current . diff --git a/src/Core/Language/AST/ObjectValueNode.cs b/src/Core/Language/AST/ObjectValueNode.cs index b9260d9f867..ffc266965db 100644 --- a/src/Core/Language/AST/ObjectValueNode.cs +++ b/src/Core/Language/AST/ObjectValueNode.cs @@ -38,6 +38,8 @@ public ObjectValueNode( IReadOnlyList IValueNode>.Value => Fields; + object IValueNode.Value => Fields; + /// /// Determines whether the specified /// is equal to the current . diff --git a/src/Core/Language/AST/StringValueNode.cs b/src/Core/Language/AST/StringValueNode.cs index 84bfad78ae6..ea093cb7b10 100644 --- a/src/Core/Language/AST/StringValueNode.cs +++ b/src/Core/Language/AST/StringValueNode.cs @@ -52,6 +52,8 @@ public StringValueNode( public string Value { get; } + object IValueNode.Value => Value; + /// /// Gets a value indicating whether this /// was parsed from a block string. diff --git a/src/Core/Language/AST/VariableNode.cs b/src/Core/Language/AST/VariableNode.cs index 05b41ba002e..757047f6f49 100644 --- a/src/Core/Language/AST/VariableNode.cs +++ b/src/Core/Language/AST/VariableNode.cs @@ -28,6 +28,8 @@ public VariableNode( public string Value => Name.Value; + object IValueNode.Value => Value; + /// /// Determines whether the specified /// is equal to the current . diff --git a/src/Core/Language/Lexer/Lexer.cs b/src/Core/Language/Lexer/Lexer.cs index 9dc2501cfa4..cd111d29666 100644 --- a/src/Core/Language/Lexer/Lexer.cs +++ b/src/Core/Language/Lexer/Lexer.cs @@ -57,7 +57,7 @@ public SyntaxToken Read(ISource source) return start; } - catch (Exception ex) + catch (Exception ex) when (!(ex is SyntaxException)) { throw new SyntaxException(state, "Unexpected token sequence.", @@ -299,11 +299,14 @@ private static SyntaxToken ReadNumberToken( if (code == '0') { - code = state.SourceText[++state.Position]; - if (char.IsDigit(code)) + if (!state.IsEndOfStream(++state.Position)) { - throw new SyntaxException(state, - $"Invalid number, unexpected digit after 0: {code}."); + code = state.SourceText[state.Position]; + if (char.IsDigit(code)) + { + throw new SyntaxException(state, + $"Invalid number, unexpected digit after 0: {code}."); + } } } else diff --git a/src/Core/Language/Lexer/LexerState.cs b/src/Core/Language/Lexer/LexerState.cs index 0f50a2cc7af..aa8f239bcae 100644 --- a/src/Core/Language/Lexer/LexerState.cs +++ b/src/Core/Language/Lexer/LexerState.cs @@ -86,5 +86,15 @@ public bool IsEndOfStream() { return Position >= SourceText.Length; } + + /// + /// Checks if the lexer source pointer has reached + /// the end of the GraphQL source text. + /// + /// + public bool IsEndOfStream(int position) + { + return position >= SourceText.Length; + } } } diff --git a/src/Stitching/Stitching.Tests/Introspection/IntrospectionClientTests.cs b/src/Stitching/Stitching.Tests/Introspection/IntrospectionClientTests.cs new file mode 100644 index 00000000000..0edf9ecd65a --- /dev/null +++ b/src/Stitching/Stitching.Tests/Introspection/IntrospectionClientTests.cs @@ -0,0 +1,111 @@ +using HotChocolate.Language; +using Snapshooter.Xunit; +using Xunit; + +namespace HotChocolate.Stitching.Introspection +{ + public class IntrospectionClientTests + { + [Fact] + public void IntrospectionWithSubscription() + { + // arrange + var features = new SchemaFeatures + { + HasSubscriptionSupport = true + }; + + // act + DocumentNode document = + IntrospectionClient.CreateIntrospectionQuery(features); + + // assert + QuerySyntaxSerializer.Serialize(document).MatchSnapshot(); + } + + [Fact] + public void IntrospectionWithoutSubscription() + { + // arrange + var features = new SchemaFeatures + { + HasSubscriptionSupport = false + }; + + // act + DocumentNode document = + IntrospectionClient.CreateIntrospectionQuery(features); + + // assert + QuerySyntaxSerializer.Serialize(document).MatchSnapshot(); + } + + [Fact] + public void IntrospectionWithDirectiveLocationField() + { + // arrange + var features = new SchemaFeatures + { + HasDirectiveLocations = true + }; + + // act + DocumentNode document = + IntrospectionClient.CreateIntrospectionQuery(features); + + // assert + QuerySyntaxSerializer.Serialize(document).MatchSnapshot(); + } + + [Fact] + public void IntrospectionWithoutDirectiveLocationField() + { + // arrange + var features = new SchemaFeatures + { + HasDirectiveLocations = false + }; + + // act + DocumentNode document = + IntrospectionClient.CreateIntrospectionQuery(features); + + // assert + QuerySyntaxSerializer.Serialize(document).MatchSnapshot(); + } + + [Fact] + public void IntrospectionWithDirectiveIsRepeatableField() + { + // arrange + var features = new SchemaFeatures + { + HasRepeatableDirectives = true + }; + + // act + DocumentNode document = + IntrospectionClient.CreateIntrospectionQuery(features); + + // assert + QuerySyntaxSerializer.Serialize(document).MatchSnapshot(); + } + + [Fact] + public void IntrospectionWithoutDirectiveIsRepeatableField() + { + // arrange + var features = new SchemaFeatures + { + HasRepeatableDirectives = false + }; + + // act + DocumentNode document = + IntrospectionClient.CreateIntrospectionQuery(features); + + // assert + QuerySyntaxSerializer.Serialize(document).MatchSnapshot(); + } + } +} diff --git a/src/Stitching/Stitching.Tests/Introspection/IntrospectionDeserializerTests.cs b/src/Stitching/Stitching.Tests/Introspection/IntrospectionDeserializerTests.cs index fcff458c996..cc79e94ef8a 100644 --- a/src/Stitching/Stitching.Tests/Introspection/IntrospectionDeserializerTests.cs +++ b/src/Stitching/Stitching.Tests/Introspection/IntrospectionDeserializerTests.cs @@ -31,5 +31,19 @@ public void JsonIsNull() // assert Assert.Throws(action).Message.MatchSnapshot(); } + + [Fact] + public void DeserializeIntrospectionWithIntDefaultValues() + { + // arrange + string json = FileResource.Open( + "IntrospectionWithDefaultValues.json"); + + // act + DocumentNode schema = IntrospectionDeserializer.Deserialize(json); + + // assert + SchemaSyntaxSerializer.Serialize(schema).MatchSnapshot(); + } } } diff --git a/src/Stitching/Stitching.Tests/Introspection/__snapshots__/IntrospectionClientTests.IntrospectionWithDirectiveIsRepeatableField.snap b/src/Stitching/Stitching.Tests/Introspection/__snapshots__/IntrospectionClientTests.IntrospectionWithDirectiveIsRepeatableField.snap new file mode 100644 index 00000000000..25f44f32012 --- /dev/null +++ b/src/Stitching/Stitching.Tests/Introspection/__snapshots__/IntrospectionClientTests.IntrospectionWithDirectiveIsRepeatableField.snap @@ -0,0 +1,91 @@ +query introspection_phase_2 { + __schema { + queryType { + name + } + mutationType { + name + } + types { + ... FullType + } + directives { + name + description + args { + ... InputValue + } + onField + onFragment + onOperation + isRepeatable + } + } +} + +fragment FullType on __Type { + kind + name + description + fields(includeDeprecated: true) { + name + description + args { + ... InputValue + } + type { + ... TypeRef + } + isDeprecated + deprecationReason + } + inputFields { + ... InputValue + } + interfaces { + ... TypeRef + } + enumValues(includeDeprecated: true) { + name + description + isDeprecated + deprecationReason + } + possibleTypes { + ... TypeRef + } +} + +fragment InputValue on __InputValue { + name + description + type { + ... TypeRef + } + defaultValue +} + +fragment TypeRef on __Type { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } +} diff --git a/src/Stitching/Stitching.Tests/Introspection/__snapshots__/IntrospectionClientTests.IntrospectionWithDirectiveLocationField.snap b/src/Stitching/Stitching.Tests/Introspection/__snapshots__/IntrospectionClientTests.IntrospectionWithDirectiveLocationField.snap new file mode 100644 index 00000000000..857bf96a546 --- /dev/null +++ b/src/Stitching/Stitching.Tests/Introspection/__snapshots__/IntrospectionClientTests.IntrospectionWithDirectiveLocationField.snap @@ -0,0 +1,88 @@ +query introspection_phase_2 { + __schema { + queryType { + name + } + mutationType { + name + } + types { + ... FullType + } + directives { + name + description + args { + ... InputValue + } + locations + } + } +} + +fragment FullType on __Type { + kind + name + description + fields(includeDeprecated: true) { + name + description + args { + ... InputValue + } + type { + ... TypeRef + } + isDeprecated + deprecationReason + } + inputFields { + ... InputValue + } + interfaces { + ... TypeRef + } + enumValues(includeDeprecated: true) { + name + description + isDeprecated + deprecationReason + } + possibleTypes { + ... TypeRef + } +} + +fragment InputValue on __InputValue { + name + description + type { + ... TypeRef + } + defaultValue +} + +fragment TypeRef on __Type { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } +} diff --git a/src/Stitching/Stitching.Tests/Introspection/__snapshots__/IntrospectionClientTests.IntrospectionWithSubscription.snap b/src/Stitching/Stitching.Tests/Introspection/__snapshots__/IntrospectionClientTests.IntrospectionWithSubscription.snap new file mode 100644 index 00000000000..29d77e28f7d --- /dev/null +++ b/src/Stitching/Stitching.Tests/Introspection/__snapshots__/IntrospectionClientTests.IntrospectionWithSubscription.snap @@ -0,0 +1,93 @@ +query introspection_phase_2 { + __schema { + queryType { + name + } + mutationType { + name + } + subscriptionType { + name + } + types { + ... FullType + } + directives { + name + description + args { + ... InputValue + } + onField + onFragment + onOperation + } + } +} + +fragment FullType on __Type { + kind + name + description + fields(includeDeprecated: true) { + name + description + args { + ... InputValue + } + type { + ... TypeRef + } + isDeprecated + deprecationReason + } + inputFields { + ... InputValue + } + interfaces { + ... TypeRef + } + enumValues(includeDeprecated: true) { + name + description + isDeprecated + deprecationReason + } + possibleTypes { + ... TypeRef + } +} + +fragment InputValue on __InputValue { + name + description + type { + ... TypeRef + } + defaultValue +} + +fragment TypeRef on __Type { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } +} diff --git a/src/Stitching/Stitching.Tests/Introspection/__snapshots__/IntrospectionClientTests.IntrospectionWithoutDirectiveIsRepeatableField.snap b/src/Stitching/Stitching.Tests/Introspection/__snapshots__/IntrospectionClientTests.IntrospectionWithoutDirectiveIsRepeatableField.snap new file mode 100644 index 00000000000..54a79bc51be --- /dev/null +++ b/src/Stitching/Stitching.Tests/Introspection/__snapshots__/IntrospectionClientTests.IntrospectionWithoutDirectiveIsRepeatableField.snap @@ -0,0 +1,90 @@ +query introspection_phase_2 { + __schema { + queryType { + name + } + mutationType { + name + } + types { + ... FullType + } + directives { + name + description + args { + ... InputValue + } + onField + onFragment + onOperation + } + } +} + +fragment FullType on __Type { + kind + name + description + fields(includeDeprecated: true) { + name + description + args { + ... InputValue + } + type { + ... TypeRef + } + isDeprecated + deprecationReason + } + inputFields { + ... InputValue + } + interfaces { + ... TypeRef + } + enumValues(includeDeprecated: true) { + name + description + isDeprecated + deprecationReason + } + possibleTypes { + ... TypeRef + } +} + +fragment InputValue on __InputValue { + name + description + type { + ... TypeRef + } + defaultValue +} + +fragment TypeRef on __Type { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } +} diff --git a/src/Stitching/Stitching.Tests/Introspection/__snapshots__/IntrospectionClientTests.IntrospectionWithoutDirectiveLocationField.snap b/src/Stitching/Stitching.Tests/Introspection/__snapshots__/IntrospectionClientTests.IntrospectionWithoutDirectiveLocationField.snap new file mode 100644 index 00000000000..54a79bc51be --- /dev/null +++ b/src/Stitching/Stitching.Tests/Introspection/__snapshots__/IntrospectionClientTests.IntrospectionWithoutDirectiveLocationField.snap @@ -0,0 +1,90 @@ +query introspection_phase_2 { + __schema { + queryType { + name + } + mutationType { + name + } + types { + ... FullType + } + directives { + name + description + args { + ... InputValue + } + onField + onFragment + onOperation + } + } +} + +fragment FullType on __Type { + kind + name + description + fields(includeDeprecated: true) { + name + description + args { + ... InputValue + } + type { + ... TypeRef + } + isDeprecated + deprecationReason + } + inputFields { + ... InputValue + } + interfaces { + ... TypeRef + } + enumValues(includeDeprecated: true) { + name + description + isDeprecated + deprecationReason + } + possibleTypes { + ... TypeRef + } +} + +fragment InputValue on __InputValue { + name + description + type { + ... TypeRef + } + defaultValue +} + +fragment TypeRef on __Type { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } +} diff --git a/src/Stitching/Stitching.Tests/Introspection/__snapshots__/IntrospectionClientTests.IntrospectionWithoutSubscription.snap b/src/Stitching/Stitching.Tests/Introspection/__snapshots__/IntrospectionClientTests.IntrospectionWithoutSubscription.snap new file mode 100644 index 00000000000..54a79bc51be --- /dev/null +++ b/src/Stitching/Stitching.Tests/Introspection/__snapshots__/IntrospectionClientTests.IntrospectionWithoutSubscription.snap @@ -0,0 +1,90 @@ +query introspection_phase_2 { + __schema { + queryType { + name + } + mutationType { + name + } + types { + ... FullType + } + directives { + name + description + args { + ... InputValue + } + onField + onFragment + onOperation + } + } +} + +fragment FullType on __Type { + kind + name + description + fields(includeDeprecated: true) { + name + description + args { + ... InputValue + } + type { + ... TypeRef + } + isDeprecated + deprecationReason + } + inputFields { + ... InputValue + } + interfaces { + ... TypeRef + } + enumValues(includeDeprecated: true) { + name + description + isDeprecated + deprecationReason + } + possibleTypes { + ... TypeRef + } +} + +fragment InputValue on __InputValue { + name + description + type { + ... TypeRef + } + defaultValue +} + +fragment TypeRef on __Type { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } +} diff --git a/src/Stitching/Stitching.Tests/Introspection/__snapshots__/IntrospectionDeserializerTests.DeserializeIntrospectionWithIntDefaultValues.snap b/src/Stitching/Stitching.Tests/Introspection/__snapshots__/IntrospectionDeserializerTests.DeserializeIntrospectionWithIntDefaultValues.snap new file mode 100644 index 00000000000..1b566a53c32 --- /dev/null +++ b/src/Stitching/Stitching.Tests/Introspection/__snapshots__/IntrospectionDeserializerTests.DeserializeIntrospectionWithIntDefaultValues.snap @@ -0,0 +1,142 @@ +schema { + query: Query +} + +"An enum describing what kind of type a given __Type is" +enum __TypeKind { + "Indicates this type is a scalar." + SCALAR + "Indicates this type is an object. `fields` and `interfaces` are valid fields." + OBJECT + "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields." + INTERFACE + "Indicates this type is a union. `possibleTypes` is a valid field." + UNION + "Indicates this type is an enum. `enumValues` is a valid field." + ENUM + "Indicates this type is an input object. `inputFields` is a valid field." + INPUT_OBJECT + "Indicates this type is a list. `ofType` is a valid field." + LIST + "Indicates this type is a non-null. `ofType` is a valid field." + NON_NULL +} + +type __Field { + name: String! + description: String + args: [__InputValue!]! + type: __Type! + isDeprecated: Boolean! + deprecationReason: String +} + +type Query { + Questions(skip: Int = 0 first: Int = 10): [Question] +} + +"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." +type __Schema { + "A list of all types supported by this server." + types: [__Type!]! + "The type that query operations will be rooted at." + queryType: __Type! + "If this server supports mutation, the type that mutation operations will be rooted at." + mutationType: __Type + "'A list of all directives supported by this server." + directives: [__Directive!]! + "'If this server support subscription, the type that subscription operations will be rooted at." + subscriptionType: __Type +} + +type __Type { + kind: __TypeKind! + name: String + description: String + fields(includeDeprecated: Boolean = false): [__Field!] + interfaces: [__Type!] + possibleTypes: [__Type!] + enumValues(includeDeprecated: Boolean = false): [__EnumValue!] + inputFields: [__InputValue!] + ofType: __Type +} + +type __EnumValue { + name: String! + description: String + isDeprecated: Boolean! + deprecationReason: String +} + +"An enum describing valid locations where a directive can be placed" +enum __DirectiveLocation { + "Indicates the directive is valid on queries." + QUERY + "Indicates the directive is valid on mutations." + MUTATION + "Indicates the directive is valid on fields." + FIELD + "Indicates the directive is valid on fragment definitions." + FRAGMENT_DEFINITION + "Indicates the directive is valid on fragment spreads." + FRAGMENT_SPREAD + "Indicates the directive is valid on inline fragments." + INLINE_FRAGMENT + "Indicates the directive is valid on a schema SDL definition." + SCHEMA + "Indicates the directive is valid on a scalar SDL definition." + SCALAR + "Indicates the directive is valid on an object SDL definition." + OBJECT + "Indicates the directive is valid on a field SDL definition." + FIELD_DEFINITION + "Indicates the directive is valid on a field argument SDL definition." + ARGUMENT_DEFINITION + "Indicates the directive is valid on an interface SDL definition." + INTERFACE + "Indicates the directive is valid on an union SDL definition." + UNION + "Indicates the directive is valid on an enum SDL definition." + ENUM + "Indicates the directive is valid on an enum value SDL definition." + ENUM_VALUE + "Indicates the directive is valid on an input object SDL definition." + INPUT_OBJECT + "Indicates the directive is valid on an input object field SDL definition." + INPUT_FIELD_DEFINITION +} + +"Built-in String" +scalar String + +"Built-in Int" +scalar Int + +type Question { + id: ID + question: String + userId: Int +} + +"Built-in ID" +scalar ID + +type __InputValue { + name: String! + description: String + type: __Type! + defaultValue: String +} + +"Built-in Boolean" +scalar Boolean + +type __Directive { + name: String + description: String + locations: [__DirectiveLocation!] + args: [__InputValue!]! + onOperation: Boolean + onFragment: Boolean + onField: Boolean +} diff --git a/src/Stitching/Stitching.Tests/__resources__/IntrospectionWithDefaultValues.json b/src/Stitching/Stitching.Tests/__resources__/IntrospectionWithDefaultValues.json new file mode 100644 index 00000000000..44df0f38eec --- /dev/null +++ b/src/Stitching/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/Stitching/Stitching/Delegation/ScopedVariableNode.cs b/src/Stitching/Stitching/Delegation/ScopedVariableNode.cs index 14bb9d875ab..a0d99563c45 100644 --- a/src/Stitching/Stitching/Delegation/ScopedVariableNode.cs +++ b/src/Stitching/Stitching/Delegation/ScopedVariableNode.cs @@ -27,6 +27,8 @@ public ScopedVariableNode( public string Value => Scope.Value + ":" + Name.Value; + object IValueNode.Value => Value; + /// /// Determines whether the specified /// is equal to the current . diff --git a/src/Stitching/Stitching/Introspection/IntrospectionClient.cs b/src/Stitching/Stitching/Introspection/IntrospectionClient.cs index 1558be67ba1..e15d7799f2d 100644 --- a/src/Stitching/Stitching/Introspection/IntrospectionClient.cs +++ b/src/Stitching/Stitching/Introspection/IntrospectionClient.cs @@ -20,6 +20,7 @@ public static class IntrospectionClient private const string _phase1 = "introspection_phase_1.graphql"; private const string _phase2 = "introspection_phase_2.graphql"; + private const string _schemaName = "__Schema"; private const string _directiveName = "__Directive"; private const string _locations = "locations"; private const string _isRepeatable = "isRepeatable"; @@ -27,6 +28,7 @@ public static class IntrospectionClient private const string _onFragment = "onFragment"; private const string _onField = "onField"; private const string _directivesField = "directives"; + private const string _subscriptionType = "subscriptionType"; public static DocumentNode LoadSchema( HttpClient httpClient) @@ -78,12 +80,18 @@ private static async Task GetSchemaFeaturesAsync( IntrospectionResult result = JsonConvert.DeserializeObject(json); - FullType type = result.Data.Schema.Types.First(t => + FullType directive = result.Data.Schema.Types.First(t => t.Name.Equals(_directiveName, StringComparison.Ordinal)); - features.HasRepeatableDirectives = type.Fields.Any(t => + features.HasRepeatableDirectives = directive.Fields.Any(t => t.Name.Equals(_isRepeatable, StringComparison.Ordinal)); - features.HasDirectiveLocations = type.Fields.Any(t => + features.HasDirectiveLocations = directive.Fields.Any(t => t.Name.Equals(_locations, StringComparison.Ordinal)); + + FullType schema = result.Data.Schema.Types.First(t => + t.Name.Equals(_schemaName, StringComparison.Ordinal)); + features.HasSubscriptionSupport = schema.Fields.Any(t => + t.Name.Equals(_subscriptionType, StringComparison.Ordinal)); + return features; } @@ -106,7 +114,7 @@ private static async Task ExecuteIntrospectionAsync( return IntrospectionDeserializer.Deserialize(json); } - private static DocumentNode CreateIntrospectionQuery( + internal static DocumentNode CreateIntrospectionQuery( SchemaFeatures features) { DocumentNode query = Parser.Default.Parse( @@ -120,31 +128,18 @@ private static DocumentNode CreateIntrospectionQuery( FieldNode directives = schema.SelectionSet.Selections.OfType().First(t => - t.Name.Value.Equals(_directivesField - , StringComparison.Ordinal)); + t.Name.Value.Equals(_directivesField, + StringComparison.Ordinal)); var selections = directives.SelectionSet.Selections.ToList(); - - if (features.HasDirectiveLocations) - { - selections.Add(CreateField(_locations)); - } - else - { - selections.Add(CreateField(_onField)); - selections.Add(CreateField(_onFragment)); - selections.Add(CreateField(_onOperation)); - } - - if (features.HasRepeatableDirectives) - { - selections.Add(CreateField(_isRepeatable)); - } + AddDirectiveFeatures(features, selections); FieldNode newField = directives.WithSelectionSet( directives.SelectionSet.WithSelections(selections)); selections = schema.SelectionSet.Selections.ToList(); + RemoveSubscriptionIfNotSupported(features, selections); + selections.Remove(directives); selections.Add(newField); @@ -165,6 +160,40 @@ private static DocumentNode CreateIntrospectionQuery( return query.WithDefinitions(definitions); } + private static void AddDirectiveFeatures( + SchemaFeatures features, + ICollection selections) + { + if (features.HasDirectiveLocations) + { + selections.Add(CreateField(_locations)); + } + else + { + selections.Add(CreateField(_onField)); + selections.Add(CreateField(_onFragment)); + selections.Add(CreateField(_onOperation)); + } + + if (features.HasRepeatableDirectives) + { + selections.Add(CreateField(_isRepeatable)); + } + } + + private static void RemoveSubscriptionIfNotSupported( + SchemaFeatures features, + ICollection selections) + { + if (!features.HasSubscriptionSupport) + { + FieldNode subscriptionField = selections.OfType() + .First(t => t.Name.Value.Equals(_subscriptionType, + StringComparison.Ordinal)); + selections.Remove(subscriptionField); + } + } + private static FieldNode CreateField(string name) => new FieldNode(null, new NameNode(name), null, Array.Empty(), diff --git a/src/Stitching/Stitching/Introspection/SchemaFeatures.cs b/src/Stitching/Stitching/Introspection/SchemaFeatures.cs index 26d79f32a15..87c2988f4f0 100644 --- a/src/Stitching/Stitching/Introspection/SchemaFeatures.cs +++ b/src/Stitching/Stitching/Introspection/SchemaFeatures.cs @@ -5,5 +5,7 @@ internal sealed class SchemaFeatures public bool HasDirectiveLocations { get; set; } public bool HasRepeatableDirectives { get; set; } + + public bool HasSubscriptionSupport { get; set; } } }