From a724f9b282a6bf76f2c42ad68fa5858f4da29197 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Wed, 24 Jun 2026 07:58:28 +0200 Subject: [PATCH] Add Mutable to StrawberryShake (#9976) --- .../CodeGeneration.CSharp/CSharpGenerator.cs | 15 +- .../Generators/DataTypeGenerator.cs | 2 +- ...ultBuilderGenerator_DeserializeDataType.cs | 2 +- .../Analyzers/DirectiveValueHelper.cs | 24 ++ .../DocumentAnalyzer.CollectEnumTypes.cs | 25 +- ...ocumentAnalyzer.CollectInputObjectTypes.cs | 23 +- .../DocumentAnalyzer.CollectOutputTypes.cs | 4 +- .../Analyzers/DocumentAnalyzer.cs | 6 +- .../Analyzers/DocumentAnalyzerContext.cs | 21 +- .../Analyzers/EnumTypeUsageAnalyzer.cs | 12 +- .../Analyzers/FieldCollector.cs | 34 +- .../Analyzers/FragmentHelper.cs | 4 +- .../Analyzers/IDocumentAnalyzerContext.cs | 2 +- .../Analyzers/InputObjectTypeUsageAnalyzer.cs | 4 +- .../Analyzers/Models/ClientModel.cs | 6 +- .../Analyzers/Models/EnumValueModel.cs | 4 +- .../Analyzers/Models/InputObjectTypeModel.cs | 4 +- .../Analyzers/Models/OperationModel.cs | 4 +- .../Analyzers/Types/EnumValueDirectiveType.cs | 13 - .../Analyzers/Types/RenameDirectiveType.cs | 17 - .../Types/RuntimeTypeDirectiveType.cs | 15 - .../Types/SerializationTypeDirectiveType.cs | 14 - .../src/CodeGeneration/ErrorHelper.cs | 35 +- .../Extensions/TypeExtensions.cs | 8 +- .../Mappers/DataTypeDescriptorMapper.cs | 6 +- .../EntityIdFactoryDescriptorMapper.cs | 3 +- .../Mappers/OperationDescriptorMapper.cs | 5 +- .../TypeDescriptorMapper.InputTypes.cs | 2 +- .../Mappers/TypeDescriptorMapper.cs | 10 +- .../src/CodeGeneration/ScalarNames.cs | 30 ++ .../StrawberryShake.CodeGeneration.csproj | 5 +- .../Utilities/EntityTypeInterceptor.cs | 83 ---- .../Utilities/FragmentRewriter.cs | 3 +- .../Utilities/LeafTypeInterceptor.cs | 51 --- .../Utilities/OperationDocumentHelper.cs | 3 +- .../RemoveClientDirectivesRewriter.cs | 5 +- .../CodeGeneration/Utilities/SchemaHelper.cs | 379 +++++++++++++++--- .../Utilities/TypeNameQueryRewriter.cs | 2 +- .../CSharpCompiler.cs | 2 + ...ryShake.CodeGeneration.CSharp.Tests.csproj | 1 + .../CSharpCompiler.cs | 2 + ...rryShake.CodeGeneration.Razor.Tests.csproj | 1 + .../Analyzers/DocumentAnalyzerTests.cs | 39 +- .../Analyzers/FieldCollectorTests.cs | 30 +- .../Analyzers/FragmentHelperTests.cs | 45 +-- .../InterfaceTypeSelectionSetAnalyzerTests.cs | 46 +-- .../Mappers/TestDataHelper.cs | 17 +- .../CodeGeneration.Tests/TestSchemaHelper.cs | 31 ++ .../Utilities/QueryDocumentRewriterTests.cs | 18 +- .../Utilities/SchemaHelperTests.cs | 2 +- 50 files changed, 570 insertions(+), 549 deletions(-) create mode 100644 src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/DirectiveValueHelper.cs delete mode 100644 src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Types/EnumValueDirectiveType.cs delete mode 100644 src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Types/RenameDirectiveType.cs delete mode 100644 src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Types/RuntimeTypeDirectiveType.cs delete mode 100644 src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Types/SerializationTypeDirectiveType.cs create mode 100644 src/StrawberryShake/CodeGeneration/src/CodeGeneration/ScalarNames.cs delete mode 100644 src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/EntityTypeInterceptor.cs delete mode 100644 src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/LeafTypeInterceptor.cs create mode 100644 src/StrawberryShake/CodeGeneration/test/CodeGeneration.Tests/TestSchemaHelper.cs diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration.CSharp/CSharpGenerator.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration.CSharp/CSharpGenerator.cs index 9d1b9f01c85..62d39b0e228 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration.CSharp/CSharpGenerator.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration.CSharp/CSharpGenerator.cs @@ -2,6 +2,8 @@ using System.Text; using HotChocolate; using HotChocolate.Language; +using HotChocolate.Types; +using HotChocolate.Types.Mutable; using HotChocolate.Validation; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Formatting; @@ -92,7 +94,6 @@ public static CSharpGeneratorResult Generate( // If we cannot create a schema, we will return the schema validation errors. if (!TryCreateSchema( typeSystemFiles, - fileLookup, errors, settings.StrictSchemaValidation, settings.NoStore, @@ -404,23 +405,19 @@ private static bool TryParseDocuments( private static bool TryCreateSchema( IReadOnlyList files, - Dictionary fileLookup, ICollection errors, bool strictValidation, bool noStore, - [NotNullWhen(true)] out Schema? schema) + [NotNullWhen(true)] out MutableSchemaDefinition? schema) { try { schema = SchemaHelper.Load(files, strictValidation, noStore); return true; } - catch (SchemaException ex) + catch (SchemaInitializationException ex) { - foreach (var error in ex.Errors) - { - errors.Add(error.SchemaError(fileLookup)); - } + errors.Add(ex.SchemaError()); schema = null; return false; @@ -428,7 +425,7 @@ private static bool TryCreateSchema( } private static bool TryValidateRequestAsync( - Schema schema, + ISchemaDefinition schema, IReadOnlyList executableFiles, Dictionary fileLookup, List errors) diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration.CSharp/Generators/DataTypeGenerator.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration.CSharp/Generators/DataTypeGenerator.cs index a96b89bcc8e..d46379a65c7 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration.CSharp/Generators/DataTypeGenerator.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration.CSharp/Generators/DataTypeGenerator.cs @@ -139,7 +139,7 @@ public void ForEachProperty( { foreach (var property in descriptor.Properties) { - if (property.Name.EqualsOrdinal(WellKnownNames.TypeName)) + if (property.Name.Equals(WellKnownNames.TypeName, StringComparison.Ordinal)) { continue; } diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration.CSharp/Generators/JsonResultBuilderGenerator_DeserializeDataType.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration.CSharp/Generators/JsonResultBuilderGenerator_DeserializeDataType.cs index b1bf8a189aa..3f0687e525e 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration.CSharp/Generators/JsonResultBuilderGenerator_DeserializeDataType.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration.CSharp/Generators/JsonResultBuilderGenerator_DeserializeDataType.cs @@ -92,7 +92,7 @@ private MethodCallBuilder CreateBuildDataStatement(ObjectTypeDescriptor concrete foreach (var property in concreteType.Properties) { - if (property.Name.EqualsOrdinal(WellKnownNames.TypeName)) + if (property.Name.Equals(WellKnownNames.TypeName, StringComparison.Ordinal)) { continue; } diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/DirectiveValueHelper.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/DirectiveValueHelper.cs new file mode 100644 index 00000000000..7e7dd43f7cd --- /dev/null +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/DirectiveValueHelper.cs @@ -0,0 +1,24 @@ +using HotChocolate.Language; +using HotChocolate.Types; + +namespace StrawberryShake.CodeGeneration.Analyzers; + +internal static class DirectiveValueHelper +{ + public static string? GetStringArgument( + this IReadOnlyDirectiveCollection directives, + string directiveName, + string argumentName) + { + var directive = directives.FirstOrDefault(directiveName); + + if (directive is not null + && directive.Arguments.TryGetValue(argumentName, out var value) + && value is StringValueNode stringValue) + { + return stringValue.Value; + } + + return null; + } +} diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/DocumentAnalyzer.CollectEnumTypes.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/DocumentAnalyzer.CollectEnumTypes.cs index bd60c8217c5..e23e5d399f5 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/DocumentAnalyzer.CollectEnumTypes.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/DocumentAnalyzer.CollectEnumTypes.cs @@ -1,6 +1,5 @@ -using HotChocolate; +using HotChocolate.Types; using StrawberryShake.CodeGeneration.Analyzers.Models; -using StrawberryShake.CodeGeneration.Analyzers.Types; using static StrawberryShake.CodeGeneration.Utilities.NameUtils; namespace StrawberryShake.CodeGeneration.Analyzers; @@ -9,33 +8,29 @@ public partial class DocumentAnalyzer { private static void CollectEnumTypes(IDocumentAnalyzerContext context) { - var analyzer = new EnumTypeUsageAnalyzer((Schema)context.Schema); + var analyzer = new EnumTypeUsageAnalyzer(context.Schema); analyzer.Analyze(context.Document); foreach (var enumType in analyzer.EnumTypes) { - RenameDirective? rename; var values = new List(); foreach (var enumValue in enumType.Values) { - rename = enumValue.Directives.FirstOrDefault()?.ToValue(); - - var value = enumValue.Directives.FirstOrDefault()?.ToValue(); + var rename = enumValue.Directives.GetStringArgument("rename", "name"); + var value = enumValue.Directives.GetStringArgument("enumValue", "value"); values.Add(new EnumValueModel( - rename?.Name ?? GetEnumValue(enumValue.Name), + rename ?? GetEnumValue(enumValue.Name), enumValue.Description, enumValue, - value?.Value)); + value)); } - rename = enumType.Directives.FirstOrDefault()?.ToValue(); - - var serializationType = - enumType.Directives.FirstOrDefault()?.ToValue(); + var typeRename = enumType.Directives.GetStringArgument("rename", "name"); + var serializationType = enumType.Directives.GetStringArgument("serializationType", "name"); - var typeName = context.ResolveTypeName(rename?.Name ?? GetClassName(enumType.Name)); + var typeName = context.ResolveTypeName(typeRename ?? GetClassName(enumType.Name)); context.RegisterModel( typeName, @@ -43,7 +38,7 @@ private static void CollectEnumTypes(IDocumentAnalyzerContext context) typeName, enumType.Description, enumType, - serializationType?.Name, + serializationType, values)); } } diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/DocumentAnalyzer.CollectInputObjectTypes.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/DocumentAnalyzer.CollectInputObjectTypes.cs index 1ad24060502..7798dcbdef1 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/DocumentAnalyzer.CollectInputObjectTypes.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/DocumentAnalyzer.CollectInputObjectTypes.cs @@ -1,7 +1,5 @@ -using HotChocolate; using HotChocolate.Types; using StrawberryShake.CodeGeneration.Analyzers.Models; -using StrawberryShake.CodeGeneration.Analyzers.Types; using static StrawberryShake.CodeGeneration.Utilities.NameUtils; namespace StrawberryShake.CodeGeneration.Analyzers; @@ -10,21 +8,21 @@ public partial class DocumentAnalyzer { private static void CollectInputObjectTypes(IDocumentAnalyzerContext context) { - var analyzer = new InputObjectTypeUsageAnalyzer((Schema)context.Schema); + var analyzer = new InputObjectTypeUsageAnalyzer(context.Schema); analyzer.Analyze(context.Document); var namesOfInputTypesWithUploadScalar = CollectTypesWithUploadScalar(analyzer); foreach (var namedInputType in analyzer.InputTypes) { - if (namedInputType is InputObjectType inputObjectType) + if (namedInputType is IInputObjectTypeDefinition inputObjectType) { RegisterInputObjectType( context, inputObjectType, namesOfInputTypesWithUploadScalar.Contains(namedInputType.Name)); } - else if (namedInputType is ILeafType) + else if (namedInputType.IsLeafType()) { context.RegisterType(namedInputType); } @@ -33,18 +31,17 @@ private static void CollectInputObjectTypes(IDocumentAnalyzerContext context) private static void RegisterInputObjectType( IDocumentAnalyzerContext context, - InputObjectType inputObjectType, + IInputObjectTypeDefinition inputObjectType, bool hasUpload) { - RenameDirective? rename; var fields = new List(); foreach (var inputField in inputObjectType.Fields) { - rename = inputField.Directives.FirstOrDefault()?.ToValue(); + var rename = inputField.Directives.GetStringArgument("rename", "name"); fields.Add(new InputFieldModel( - GetClassName(rename?.Name ?? inputField.Name), + GetClassName(rename ?? inputField.Name), inputField.Description, inputField, inputField.DefaultValue is not null @@ -55,10 +52,10 @@ inputField.DefaultValue is not null context.RegisterType(inputField.Type.NamedType()); } - rename = inputObjectType.Directives.FirstOrDefault()?.ToValue(); + var typeRename = inputObjectType.Directives.GetStringArgument("rename", "name"); var typeName = context.ResolveTypeName( - GetClassName(rename?.Name ?? inputObjectType.Name)); + GetClassName(typeRename ?? inputObjectType.Name)); context.RegisterModel( typeName, @@ -86,7 +83,7 @@ private static HashSet CollectTypesWithUploadScalar( continue; } - if (namedInputType is InputObjectType type) + if (namedInputType is IInputObjectTypeDefinition type) { foreach (var field in type.Fields) { @@ -98,7 +95,7 @@ private static HashSet CollectTypesWithUploadScalar( } } } - else if (namedInputType is ScalarType { Name: "Upload" }) + else if (namedInputType is IScalarTypeDefinition { Name: "Upload" }) { detected = true; namesOfInputTypesWithUploadScalar.Add("Upload"); diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/DocumentAnalyzer.CollectOutputTypes.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/DocumentAnalyzer.CollectOutputTypes.cs index bcb130cfbf4..50f09085178 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/DocumentAnalyzer.CollectOutputTypes.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/DocumentAnalyzer.CollectOutputTypes.cs @@ -84,14 +84,14 @@ private static void VisitFieldSelectionSet( EnqueueFields(selectionSetVariants, backlog); - if (namedType is UnionType or InterfaceType) + if (namedType is IUnionTypeDefinition or IInterfaceTypeDefinition) { s_selectionAnalyzer.Analyze( context, fieldSelection, selectionSetVariants); } - else if (namedType is ObjectType) + else if (namedType is IObjectTypeDefinition) { s_selectionAnalyzer.Analyze( context, diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/DocumentAnalyzer.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/DocumentAnalyzer.cs index f0cdde90098..bb58acdea68 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/DocumentAnalyzer.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/DocumentAnalyzer.cs @@ -1,5 +1,5 @@ -using HotChocolate; using HotChocolate.Language; +using HotChocolate.Types.Mutable; using StrawberryShake.CodeGeneration.Analyzers.Models; using static StrawberryShake.CodeGeneration.Utilities.OperationDocumentHelper; @@ -8,11 +8,11 @@ namespace StrawberryShake.CodeGeneration.Analyzers; public partial class DocumentAnalyzer { private readonly List _documents = []; - private Schema? _schema; + private MutableSchemaDefinition? _schema; public static DocumentAnalyzer New() => new(); - public DocumentAnalyzer SetSchema(Schema schema) + public DocumentAnalyzer SetSchema(MutableSchemaDefinition schema) { _schema = schema; return this; diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/DocumentAnalyzerContext.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/DocumentAnalyzerContext.cs index 8fd11ad40fc..57b95c4f270 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/DocumentAnalyzerContext.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/DocumentAnalyzerContext.cs @@ -2,8 +2,9 @@ using HotChocolate; using HotChocolate.Language; using HotChocolate.Types; -using StrawberryShake.CodeGeneration.Analyzers.Models; +using HotChocolate.Types.Mutable; using StrawberryShake.CodeGeneration.Extensions; +using StrawberryShake.CodeGeneration.Analyzers.Models; using Path = HotChocolate.Path; namespace StrawberryShake.CodeGeneration.Analyzers; @@ -17,7 +18,7 @@ public class DocumentAnalyzerContext : IDocumentAnalyzerContext private readonly FieldCollector _fieldCollector; public DocumentAnalyzerContext( - Schema schema, + MutableSchemaDefinition schema, DocumentNode document) { Schema = schema ?? throw new ArgumentNullException(nameof(schema)); @@ -34,7 +35,7 @@ public DocumentAnalyzerContext( public DocumentNode Document { get; } - public ObjectType OperationType { get; } + public IObjectTypeDefinition OperationType { get; } public OperationDefinitionNode OperationDefinition { get; } @@ -99,16 +100,16 @@ public void RegisterModel(string name, ITypeModel typeModel) public void RegisterType(ITypeDefinition type) { - if (type is ILeafType leafType && _typeModels.Values.All(x => x.Type.Name != type.Name)) + if (type.IsLeafType() && _typeModels.Values.All(x => x.Type.Name != type.Name)) { _typeModels.Add( - leafType.Name, + type.Name, new LeafTypeModel( - leafType.Name, - leafType.Description, - leafType, - leafType.GetSerializationType(), - leafType.GetRuntimeType())); + type.Name, + type.Description, + type, + type.GetSerializationType(), + type.GetRuntimeType())); } } diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/EnumTypeUsageAnalyzer.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/EnumTypeUsageAnalyzer.cs index bdf041a3795..cf47b677018 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/EnumTypeUsageAnalyzer.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/EnumTypeUsageAnalyzer.cs @@ -5,13 +5,13 @@ namespace StrawberryShake.CodeGeneration.Analyzers; -internal sealed class EnumTypeUsageAnalyzer(Schema schema) : SyntaxWalker +internal sealed class EnumTypeUsageAnalyzer(ISchemaDefinition schema) : SyntaxWalker { - private readonly HashSet _enumTypes = []; + private readonly HashSet _enumTypes = []; private readonly HashSet _visitedTypes = []; private readonly Stack _typeContext = new(); - public ISet EnumTypes => _enumTypes; + public ISet EnumTypes => _enumTypes; public void Analyze(DocumentNode document) { @@ -126,11 +126,11 @@ private void VisitInputType(IInputType type) type = innerType; continue; - case InputObjectType inputObjectType: + case IInputObjectTypeDefinition inputObjectType: VisitInputObjectType(inputObjectType); break; - case EnumType enumType: + case IEnumTypeDefinition enumType: _enumTypes.Add(enumType); break; } @@ -140,7 +140,7 @@ private void VisitInputType(IInputType type) } } - private void VisitInputObjectType(InputObjectType type) + private void VisitInputObjectType(IInputObjectTypeDefinition type) { foreach (var field in type.Fields) { diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/FieldCollector.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/FieldCollector.cs index 4b012847c20..51ee57e6593 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/FieldCollector.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/FieldCollector.cs @@ -3,6 +3,7 @@ using HotChocolate.Types; using HotChocolate.Utilities; using HotChocolate.Features; +using StrawberryShake.CodeGeneration.Utilities; using static StrawberryShake.CodeGeneration.Utilities.TypeHelpers; using Path = HotChocolate.Path; using IHasDirectives = HotChocolate.Language.IHasDirectives; @@ -157,7 +158,7 @@ internal static void ResolveFieldSelection( if ((type is IComplexTypeDefinition ct && ct.Fields.TryGetField(fieldName, out field)) || fieldSyntax.Name.Value is WellKnownNames.TypeName) { - field ??= TypeNameField.Default; + field ??= TypeNameField.For(type); if (fields.TryGetValue(responseName, out var fieldSelection)) { @@ -252,7 +253,7 @@ private Fragment CreateFragment(string fragmentName) var fragmentDefinitionSyntax = _document.Definitions .OfType() - .FirstOrDefault(t => t.Name.Value.EqualsOrdinal(fragmentName)); + .FirstOrDefault(t => t.Name.Value.Equals(fragmentName, StringComparison.Ordinal)); if (fragmentDefinitionSyntax is not null) { @@ -312,25 +313,29 @@ private sealed class SelectionCache : Dictionary null; + public string? Description { get; } - public IReadOnlyDirectiveCollection Directives => throw new NotImplementedException(); + public IReadOnlyDirectiveCollection Directives => EmptyCollections.Directives; public ISyntaxNode? SyntaxNode => null; - public Type RuntimeType => typeof(string); - - public IReadOnlyDictionary ContextData { get; } = new ExtensionData(); - public bool IsIntrospectionField => true; public FieldFlags Flags => FieldFlags.Introspection | FieldFlags.TypeNameIntrospectionField; @@ -353,9 +358,14 @@ private TypeNameField() public int Index => 0; - public static TypeNameField Default { get; } = new(); + public static TypeNameField For(IOutputTypeDefinition type) + => type.Kind is TypeKind.Object ? WithDescription : WithoutDescription; + + private static TypeNameField WithDescription { get; } = new(includeDescription: true); + + private static TypeNameField WithoutDescription { get; } = new(includeDescription: false); - public IFeatureCollection Features => throw new NotImplementedException(); + public IFeatureCollection Features => _features; public FieldDefinitionNode ToSyntaxNode() => throw new NotImplementedException(); diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/FragmentHelper.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/FragmentHelper.cs index de8be54c391..1f3b40607a7 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/FragmentHelper.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/FragmentHelper.cs @@ -33,7 +33,7 @@ public static class FragmentHelper public static FragmentNode? GetFragment(FragmentNode fragmentNode, string name) { if (fragmentNode.Fragment.Kind == FragmentKind.Named - && fragmentNode.Fragment.Name.EqualsOrdinal(name)) + && fragmentNode.Fragment.Name.Equals(name, StringComparison.Ordinal)) { return fragmentNode; } @@ -606,7 +606,7 @@ private static string CreateName( private static string GetDeferLabel(DirectiveNode directive) { var argument = directive.Arguments.FirstOrDefault( - t => t.Name.Value.EqualsOrdinal(DirectiveNames.Defer.Arguments.Label)); + t => t.Name.Value.Equals(DirectiveNames.Defer.Arguments.Label, StringComparison.Ordinal)); if (argument?.Value is not StringValueNode { Value.Length: > 0 } sv) { diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/IDocumentAnalyzerContext.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/IDocumentAnalyzerContext.cs index f7618de8abd..e2bdd22c7f3 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/IDocumentAnalyzerContext.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/IDocumentAnalyzerContext.cs @@ -13,7 +13,7 @@ public interface IDocumentAnalyzerContext DocumentNode Document { get; } - ObjectType OperationType { get; } + IObjectTypeDefinition OperationType { get; } OperationDefinitionNode OperationDefinition { get; } diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/InputObjectTypeUsageAnalyzer.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/InputObjectTypeUsageAnalyzer.cs index e9eacbabd19..e4e3a19c546 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/InputObjectTypeUsageAnalyzer.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/InputObjectTypeUsageAnalyzer.cs @@ -5,7 +5,7 @@ namespace StrawberryShake.CodeGeneration.Analyzers; -internal sealed class InputObjectTypeUsageAnalyzer(Schema schema) : SyntaxWalker +internal sealed class InputObjectTypeUsageAnalyzer(ISchemaDefinition schema) : SyntaxWalker { private readonly HashSet _inputTypes = []; private readonly HashSet _visitedTypes = []; @@ -56,7 +56,7 @@ private void VisitInputType(IInputType type) private void VisitNamedInputType(IInputTypeDefinition type) { - if (_inputTypes.Add(type) && type is InputObjectType inputObjectType) + if (_inputTypes.Add(type) && type is IInputObjectTypeDefinition inputObjectType) { foreach (IInputValueDefinition field in inputObjectType.Fields) { diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Models/ClientModel.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Models/ClientModel.cs index fc380f8b0a2..79e25f5f6f5 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Models/ClientModel.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Models/ClientModel.cs @@ -1,5 +1,5 @@ -using HotChocolate; using HotChocolate.Types; +using HotChocolate.Types.Mutable; using StrawberryShake.CodeGeneration.Extensions; namespace StrawberryShake.CodeGeneration.Analyzers.Models; @@ -25,7 +25,7 @@ public class ClientModel /// The input types that could be passed in. /// public ClientModel( - Schema schema, + MutableSchemaDefinition schema, IReadOnlyList operations, IReadOnlyList leafTypes, IReadOnlyList inputObjectTypes) @@ -65,7 +65,7 @@ public ClientModel( /// /// The analyzed schema /// - public Schema Schema { get; } + public MutableSchemaDefinition Schema { get; } /// /// Gets the operations diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Models/EnumValueModel.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Models/EnumValueModel.cs index 8b252eb1aee..691339cfbb9 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Models/EnumValueModel.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Models/EnumValueModel.cs @@ -11,7 +11,7 @@ public sealed class EnumValueModel public EnumValueModel( string name, string? description, - EnumValue value, + IEnumValue value, string? underlyingValue) { Name = name.EnsureGraphQLName(); @@ -27,7 +27,7 @@ public EnumValueModel( public string? Description { get; } - public EnumValue Value { get; } + public IEnumValue Value { get; } public string? UnderlyingValue { get; } } diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Models/InputObjectTypeModel.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Models/InputObjectTypeModel.cs index af9e881a2c0..e5758706f1b 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Models/InputObjectTypeModel.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Models/InputObjectTypeModel.cs @@ -21,7 +21,7 @@ public sealed class InputObjectTypeModel : ITypeModel public InputObjectTypeModel( string name, string? description, - InputObjectType type, + IInputObjectTypeDefinition type, bool hasUpload, IReadOnlyList fields) { @@ -45,7 +45,7 @@ public InputObjectTypeModel( /// /// Gets the input object type. /// - public InputObjectType Type { get; } + public IInputObjectTypeDefinition Type { get; } /// /// Defines if this input or one of its related has an upload scalar diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Models/OperationModel.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Models/OperationModel.cs index 4b4a17c0c59..b783d4bca96 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Models/OperationModel.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Models/OperationModel.cs @@ -11,7 +11,7 @@ public class OperationModel public OperationModel( string name, - ObjectType type, + IObjectTypeDefinition type, DocumentNode document, OperationType operationType, IReadOnlyList arguments, @@ -43,7 +43,7 @@ public OperationModel( public string Name { get; } - public ObjectType Type { get; } + public IObjectTypeDefinition Type { get; } public DocumentNode Document { get; } diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Types/EnumValueDirectiveType.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Types/EnumValueDirectiveType.cs deleted file mode 100644 index 6ab28a57426..00000000000 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Types/EnumValueDirectiveType.cs +++ /dev/null @@ -1,13 +0,0 @@ -using HotChocolate.Types; - -namespace StrawberryShake.CodeGeneration.Analyzers.Types; - -public class EnumValueDirectiveType : DirectiveType -{ - protected override void Configure(IDirectiveTypeDescriptor descriptor) - { - descriptor.Name("enumValue"); - descriptor.Argument(t => t.Value).Type>(); - descriptor.Location(DirectiveLocation.EnumValue); - } -} diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Types/RenameDirectiveType.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Types/RenameDirectiveType.cs deleted file mode 100644 index afe7f4f6232..00000000000 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Types/RenameDirectiveType.cs +++ /dev/null @@ -1,17 +0,0 @@ -using HotChocolate.Types; - -namespace StrawberryShake.CodeGeneration.Analyzers.Types; - -public class RenameDirectiveType : DirectiveType -{ - protected override void Configure(IDirectiveTypeDescriptor descriptor) - { - descriptor.Name("rename"); - descriptor.Argument(t => t.Name).Type>(); - descriptor.Location( - DirectiveLocation.InputFieldDefinition - | DirectiveLocation.InputObject - | DirectiveLocation.Enum - | DirectiveLocation.EnumValue); - } -} diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Types/RuntimeTypeDirectiveType.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Types/RuntimeTypeDirectiveType.cs deleted file mode 100644 index 4682c6948e5..00000000000 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Types/RuntimeTypeDirectiveType.cs +++ /dev/null @@ -1,15 +0,0 @@ -using HotChocolate.Types; - -namespace StrawberryShake.CodeGeneration.Analyzers.Types; - -public class RuntimeTypeDirectiveType : DirectiveType -{ - protected override void Configure( - IDirectiveTypeDescriptor descriptor) - { - descriptor.Name("runtimeType"); - descriptor.Argument(t => t.Name).Type>(); - descriptor.Argument(t => t.ValueType).Type(); - descriptor.Location(DirectiveLocation.Scalar); - } -} diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Types/SerializationTypeDirectiveType.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Types/SerializationTypeDirectiveType.cs deleted file mode 100644 index 4a820b70a12..00000000000 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Types/SerializationTypeDirectiveType.cs +++ /dev/null @@ -1,14 +0,0 @@ -using HotChocolate.Types; - -namespace StrawberryShake.CodeGeneration.Analyzers.Types; - -public class SerializationTypeDirectiveType : DirectiveType -{ - protected override void Configure( - IDirectiveTypeDescriptor descriptor) - { - descriptor.Name("serializationType"); - descriptor.Argument(t => t.Name).Type>(); - descriptor.Location(DirectiveLocation.Scalar); - } -} diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/ErrorHelper.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/ErrorHelper.cs index e1e47b84c09..6ff3d741c75 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/ErrorHelper.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/ErrorHelper.cs @@ -1,6 +1,7 @@ using HotChocolate; using HotChocolate.Collections.Immutable; using HotChocolate.Language; +using HotChocolate.Types.Mutable; using static StrawberryShake.CodeGeneration.CodeGenerationErrorCodes; using Location = HotChocolate.Location; @@ -33,35 +34,13 @@ public static IError WithFileReference( return error.WithExtensions(extensions); } - public static IError SchemaError( - this ISchemaError error, - IDictionary fileLookup) - { - var builder = ErrorBuilder.New(); - - foreach (var extension in error.Extensions) - { - builder.SetExtension(extension.Key, extension.Value); - } - - builder.SetExtension(TitleExtensionKey, "Schema validation error"); - - foreach (var syntaxNode in error.SyntaxNodes) - { - // if the error has a syntax node we will try to lookup the - // document and add the filename to the error. - if (fileLookup.TryGetValue(syntaxNode, out var filename)) - { - builder.SetExtension(FileExtensionKey, filename); - } - } - - return builder - .SetMessage(error.Message) - .SetCode(error.Code) - .SetException(error.Exception) + public static IError SchemaError(this SchemaInitializationException exception) => + ErrorBuilder.New() + .SetMessage(exception.Message ?? "The GraphQL schema could not be initialized.") + .SetCode(SchemaValidationError) + .SetException(exception) + .SetExtension(TitleExtensionKey, "Schema validation error") .Build(); - } public static IError SyntaxError( this SyntaxException exception, diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Extensions/TypeExtensions.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Extensions/TypeExtensions.cs index 8caebd0555f..484aba91da1 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Extensions/TypeExtensions.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Extensions/TypeExtensions.cs @@ -13,9 +13,9 @@ public static bool IsEntity(this ITypeDefinition typeDefinition) public static SelectionSetNode GetEntityDefinition(this ITypeDefinition typeDefinition) => typeDefinition.Features.GetRequired().Pattern; - public static string GetRuntimeType(this ILeafType leafType) - => leafType.Features.GetRequired().RuntimeType ?? "String"; + public static string GetRuntimeType(this ITypeDefinition type) + => type.Features.GetRequired().RuntimeType ?? "String"; - public static string GetSerializationType(this ILeafType leafType) - => leafType.Features.GetRequired().SerializationType; + public static string GetSerializationType(this ITypeDefinition type) + => type.Features.GetRequired().SerializationType; } diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/DataTypeDescriptorMapper.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/DataTypeDescriptorMapper.cs index 02bd3c9ef53..72d17f1fac8 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/DataTypeDescriptorMapper.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/DataTypeDescriptorMapper.cs @@ -24,17 +24,17 @@ private static IEnumerable CollectDataTypes( .ToList(); var unionTypes = model.Schema.Types - .OfType() + .OfType() .ToList(); var dataTypeInfos = new Dictionary(StringComparer.Ordinal); foreach (var dataType in dataTypes) { - var objectType = model.Schema.Types.GetType(dataType.Name); + var objectType = model.Schema.Types.GetType(dataType.Name); var abstractTypes = new List(); - abstractTypes.AddRange(unionTypes.Where(t => t.ContainsType(dataType.Name))); + abstractTypes.AddRange(unionTypes.Where(t => t.Types.ContainsName(dataType.Name))); abstractTypes.AddRange(objectType.Implements); if (!dataTypeInfos.TryGetValue(dataType.Name, out var dataTypeInfo)) diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/EntityIdFactoryDescriptorMapper.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/EntityIdFactoryDescriptorMapper.cs index 02f2e54d892..7c28ce2761d 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/EntityIdFactoryDescriptorMapper.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/EntityIdFactoryDescriptorMapper.cs @@ -18,7 +18,8 @@ public static void Map(ClientModel model, IMapperContext context) foreach (var field in entity.Fields) { - if (field.Type.NamedType() is ILeafType leafType) + var leafType = field.Type.NamedType(); + if (leafType.IsLeafType()) { fields.Add( new ScalarEntityIdDescriptor( diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/OperationDescriptorMapper.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/OperationDescriptorMapper.cs index e4bfb152cb5..b8984fb39c0 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/OperationDescriptorMapper.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/OperationDescriptorMapper.cs @@ -18,10 +18,11 @@ public static void Map(ClientModel model, IMapperContext context) var arguments = modelOperation.Arguments.Select( arg => { - var typeName = arg.Type.TypeName(); + var typeName = arg.Type.NamedType().Name; var namedTypeDescriptor = - context.Types.Single(type => type.Name.EqualsOrdinal(typeName)); + context.Types.Single( + type => type.Name.Equals(typeName, StringComparison.Ordinal)); hasUpload = hasUpload || namedTypeDescriptor.HasUpload(); diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/TypeDescriptorMapper.InputTypes.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/TypeDescriptorMapper.InputTypes.cs index 4b053b0721c..8daf2c6662a 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/TypeDescriptorMapper.InputTypes.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/TypeDescriptorMapper.InputTypes.cs @@ -72,7 +72,7 @@ private static INamedTypeDescriptor GetInputTypeDescriptor( Dictionary typeDescriptors) { return typeDescriptors.Values - .First(t => t.Model.Type.Name.EqualsOrdinal(fieldNamedType.Name)) + .First(t => t.Model.Type.Name.Equals(fieldNamedType.Name, StringComparison.Ordinal)) .Descriptor; } } diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/TypeDescriptorMapper.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/TypeDescriptorMapper.cs index bada4a415c8..ecbcfdbc93c 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/TypeDescriptorMapper.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/TypeDescriptorMapper.cs @@ -164,15 +164,15 @@ private static void ExtractTypeKindAndParentRuntimeType( { // if the output type is a union of which all types are entities, // then the union is an also considered an entity. - case UnionType typeA when typeA.Types.All(t => t.IsEntity()): + case IUnionTypeDefinition typeA when typeA.Types.All(t => t.IsEntity()): fallbackKind = TypeKind.Entity; break; - case UnionType typeB when typeB.Types.Any(t => t.IsEntity()): + case IUnionTypeDefinition typeB when typeB.Types.Any(t => t.IsEntity()): fallbackKind = TypeKind.EntityOrData; parentRuntimeTypeName = GetInterfaceName(outputType.Type.Name); break; - case InterfaceType when (implementedBy?.Any(t => t.IsEntity()) == true): + case IInterfaceTypeDefinition when (implementedBy?.Any(t => t.IsEntity()) == true): fallbackKind = TypeKind.EntityOrData; parentRuntimeTypeName = GetInterfaceName(outputType.Type.Name); break; @@ -260,10 +260,10 @@ private static RuntimeTypeInfo ExtractRuntimeType( if (kind == TypeKind.Result) { var resultTypeName = CreateResultRootTypeName(outputType.Name); - if (clientModel.OutputTypes.Any(t => t.Name.EqualsOrdinal(resultTypeName))) + if (clientModel.OutputTypes.Any(t => t.Name.Equals(resultTypeName, StringComparison.Ordinal))) { resultTypeName = CreateResultRootTypeName(outputType.Name, outputType.Type); - if (clientModel.OutputTypes.Any(t => t.Name.EqualsOrdinal(resultTypeName))) + if (clientModel.OutputTypes.Any(t => t.Name.Equals(resultTypeName, StringComparison.Ordinal))) { throw ThrowHelper.ResultTypeNameCollision(resultTypeName); } diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/ScalarNames.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/ScalarNames.cs new file mode 100644 index 00000000000..245e9b88430 --- /dev/null +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/ScalarNames.cs @@ -0,0 +1,30 @@ +namespace StrawberryShake.CodeGeneration; + +public static class ScalarNames +{ + public const string Any = nameof(Any); + public const string Base64String = nameof(Base64String); + public const string Boolean = nameof(Boolean); + public const string Byte = nameof(Byte); + public const string ByteArray = nameof(ByteArray); + public const string Date = nameof(Date); + public const string DateTime = nameof(DateTime); + public const string Decimal = nameof(Decimal); + public const string Duration = nameof(Duration); + public const string Float = nameof(Float); + public const string ID = nameof(ID); + public const string Int = nameof(Int); + public const string LocalDate = nameof(LocalDate); + public const string LocalDateTime = nameof(LocalDateTime); + public const string LocalTime = nameof(LocalTime); + public const string Long = nameof(Long); + public const string Short = nameof(Short); + public const string String = nameof(String); + public const string UnsignedByte = nameof(UnsignedByte); + public const string UnsignedInt = nameof(UnsignedInt); + public const string UnsignedLong = nameof(UnsignedLong); + public const string UnsignedShort = nameof(UnsignedShort); + public const string URI = nameof(URI); + public const string URL = nameof(URL); + public const string UUID = nameof(UUID); +} diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/StrawberryShake.CodeGeneration.csproj b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/StrawberryShake.CodeGeneration.csproj index 2a8d173569f..4f9f3437e4b 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/StrawberryShake.CodeGeneration.csproj +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/StrawberryShake.CodeGeneration.csproj @@ -15,7 +15,10 @@ - + + + + diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/EntityTypeInterceptor.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/EntityTypeInterceptor.cs deleted file mode 100644 index 69656993c88..00000000000 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/EntityTypeInterceptor.cs +++ /dev/null @@ -1,83 +0,0 @@ -using HotChocolate.Configuration; -using HotChocolate.Features; -using HotChocolate.Language; -using HotChocolate.Types; -using HotChocolate.Types.Descriptors.Configurations; - -namespace StrawberryShake.CodeGeneration.Utilities; - -internal sealed class EntityTypeInterceptor : TypeInterceptor -{ - private readonly List _outputTypes = []; - private readonly IReadOnlyList _globalEntityPatterns; - private readonly IReadOnlyDictionary _typeEntityPatterns; - - public EntityTypeInterceptor( - IReadOnlyList globalEntityPatterns, - IReadOnlyDictionary typeEntityPatterns) - { - _globalEntityPatterns = globalEntityPatterns; - _typeEntityPatterns = typeEntityPatterns; - } - - public override void OnBeforeCompleteType( - ITypeCompletionContext completionContext, - TypeSystemConfiguration configuration) - { - if (completionContext.Type is IComplexTypeDefinition outputType) - { - if (_typeEntityPatterns.TryGetValue(outputType.Name, out var pattern)) - { - configuration.Features.Set(new EntityFeature(pattern)); - } - else - { - _outputTypes.Add(new TypeInfo(outputType, configuration.Features)); - } - } - } - - public override void OnAfterCompleteTypes() - { - if (_globalEntityPatterns.Count > 0) - { - foreach (var typeInfo in _outputTypes) - { - if (_globalEntityPatterns.FirstOrDefault( - pattern => DoesPatternMatch(typeInfo.Type, pattern)) is { } matchedPattern) - { - typeInfo.Features.Set(new EntityFeature(matchedPattern)); - } - } - } - } - - private bool DoesPatternMatch(IComplexTypeDefinition outputType, SelectionSetNode pattern) - { - // TODO : At the moment we just allow the first level. - - foreach (var selection in pattern.Selections.OfType()) - { - if (selection.SelectionSet is null - && outputType.Fields.TryGetField(selection.Name.Value, out var field) - && field.Type.NamedType().IsLeafType()) - { - continue; - } - - return false; - } - - return true; - } - - private readonly struct TypeInfo( - IComplexTypeDefinition type, - IFeatureCollection features) - : IFeatureProvider - { - public IComplexTypeDefinition Type { get; } = type; - - public IFeatureCollection Features { get; } = features; - } -} diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/FragmentRewriter.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/FragmentRewriter.cs index 07d48fdc4e9..00560b86841 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/FragmentRewriter.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/FragmentRewriter.cs @@ -39,7 +39,8 @@ public static DocumentNode Rewrite(DocumentNode document) .Create(node => { if (node is FragmentSpreadNode spread - && spread.Directives.Any(t => t.Name.Value.EqualsOrdinal(DirectiveNames.Defer.Name))) + && spread.Directives.Any( + t => t.Name.Value.Equals(DirectiveNames.Defer.Name, StringComparison.Ordinal))) { context.Deferred.Add(spread.Name.Value); } diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/LeafTypeInterceptor.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/LeafTypeInterceptor.cs deleted file mode 100644 index 234697effcd..00000000000 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/LeafTypeInterceptor.cs +++ /dev/null @@ -1,51 +0,0 @@ -using HotChocolate.Configuration; -using HotChocolate.Features; -using HotChocolate.Types; -using HotChocolate.Types.Descriptors.Configurations; -using StrawberryShake.CodeGeneration.Analyzers; - -namespace StrawberryShake.CodeGeneration.Utilities; - -public class LeafTypeInterceptor : TypeInterceptor -{ - private readonly Dictionary _scalarInfos; - private readonly List _leafTypes = []; - - public LeafTypeInterceptor(Dictionary scalarInfos) - { - ArgumentNullException.ThrowIfNull(scalarInfos); - - _scalarInfos = scalarInfos; - } - - public override void OnBeforeCompleteName( - ITypeCompletionContext completionContext, - TypeSystemConfiguration configuration) - { - if (completionContext.Type is ILeafType leafType) - { - _leafTypes.Add(new LeafType(leafType, configuration.Features)); - } - } - - public override void OnAfterCompleteTypeNames() - { - foreach (var leafType in _leafTypes) - { - if (_scalarInfos.TryGetValue(leafType.Type.Name, out var scalarInfo)) - { - leafType.Features.Set(leafType.Type is ScalarType - ? new LeafTypeFeature(scalarInfo.RuntimeType, scalarInfo.SerializationType) - : new LeafTypeFeature(null, scalarInfo.SerializationType)); - } - else - { - leafType.Features.Set(leafType.Type is ScalarType - ? new LeafTypeFeature(TypeNames.String, TypeNames.String) - : new LeafTypeFeature(null, TypeNames.String)); - } - } - } - - private record LeafType(ILeafType Type, IFeatureCollection Features) : IFeatureProvider; -} diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/OperationDocumentHelper.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/OperationDocumentHelper.cs index 6b61f955392..94aa5ad22c7 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/OperationDocumentHelper.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/OperationDocumentHelper.cs @@ -1,5 +1,6 @@ using HotChocolate; using HotChocolate.Language; +using HotChocolate.Types; using HotChocolate.Validation; using Microsoft.Extensions.DependencyInjection; using static StrawberryShake.CodeGeneration.Utilities.NameUtils; @@ -25,7 +26,7 @@ internal static class OperationDocumentHelper /// public static OperationDocuments CreateOperationDocuments( IEnumerable documents, - Schema? schema = null) + ISchemaDefinition? schema = null) { ArgumentNullException.ThrowIfNull(documents); diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/RemoveClientDirectivesRewriter.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/RemoveClientDirectivesRewriter.cs index a8d83a468b9..16ad781e50a 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/RemoveClientDirectivesRewriter.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/RemoveClientDirectivesRewriter.cs @@ -12,10 +12,11 @@ protected override FieldNode RewriteField(FieldNode node, object? context) { var current = node; - if (current.Directives.Any(t => t.Name.Value.EqualsOrdinal(Returns))) + if (current.Directives.Any(t => t.Name.Value.Equals(Returns, StringComparison.Ordinal))) { var directiveNodes = current.Directives.ToList(); - directiveNodes.RemoveAll(static t => t.Name.Value.EqualsOrdinal(Returns)); + directiveNodes.RemoveAll( + static t => t.Name.Value.Equals(Returns, StringComparison.Ordinal)); current = current.WithDirectives(directiveNodes); } diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/SchemaHelper.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/SchemaHelper.cs index 01a765617c6..22f1ee70f6b 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/SchemaHelper.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/SchemaHelper.cs @@ -3,6 +3,8 @@ using HotChocolate.Features; using HotChocolate.Language; using HotChocolate.Types; +using HotChocolate.Types.Mutable; +using HotChocolate.Types.Mutable.Serialization; using HotChocolate.Utilities; using StrawberryShake.CodeGeneration.Analyzers; using StrawberryShake.CodeGeneration.Analyzers.Types; @@ -12,25 +14,30 @@ namespace StrawberryShake.CodeGeneration.Utilities; public static class SchemaHelper { - public static Schema Load( + public static MutableSchemaDefinition Load( IReadOnlyCollection schemaFiles, bool strictValidation = true, bool noStore = false) { ArgumentNullException.ThrowIfNull(schemaFiles); + _ = strictValidation; var typeInfos = new TypeInfos(); var lookup = new Dictionary(); IndexSyntaxNodes(schemaFiles, lookup); - var builder = SchemaBuilder.New(); - builder.Features.Set(typeInfos); - - builder.ModifyOptions(o => o.StrictValidation = strictValidation); + var schema = new MutableSchemaDefinition(); + schema.Features.Set(typeInfos); var leafTypes = new Dictionary(StringComparer.Ordinal); var globalEntityPatterns = new List(); var typeEntityPatterns = new Dictionary(StringComparer.Ordinal); + var definitions = new List(); + var parserOptions = new SchemaParserOptions + { + IgnoreExistingTypes = true, + IgnoreExistingDirectives = true + }; foreach (var document in schemaFiles.Select(f => f.Document)) { @@ -59,56 +66,40 @@ public static Schema Load( } else { - foreach (var scalar in document.Definitions.OfType()) - { - if (!BuiltInScalarNames.IsBuiltInScalar(scalar.Name.Value)) - { - builder.AddType(new AnyType( - scalar.Name.Value, - scalar.Description?.Value)); - } - else if (scalar.Name.Value == ScalarNames.Any) - { - builder.AddType(new AnyType()); - } - else if (scalar.Name.Value == "JSON") - { - builder.AddType(new AnyType()); - } - } - - builder.AddDocument(document); + definitions.AddRange(document.Definitions.Select(NormalizeDefinition)); } } - AddDefaultScalarInfos(builder, leafTypes); + AddDefaultScalarInfos(leafTypes); + AddImplicitScalarDefinitions(schema, leafTypes.Keys, definitions); + AddBuiltInDirectiveDefinitions(schema); + SchemaParser.Parse(schema, IntrospectionSchema, parserOptions); - return builder - .ModifyOptions( - o => - { - o.EnableDefer = true; - o.EnableStream = true; - o.EnableTag = false; - o.EnableFlagEnums = false; - o.EnableSemanticIntrospection = false; - }) - .SetSchema(d => d.Extend().OnBeforeCreate( - c => c.Features.Set(typeInfos))) - .TryAddTypeInterceptor( - new LeafTypeInterceptor(leafTypes)) - .TryAddTypeInterceptor( - new EntityTypeInterceptor(globalEntityPatterns, typeEntityPatterns)) - .Use(_ => _ => throw new NotSupportedException()) - .Create(); + if (definitions.Count > 0) + { + SchemaParser.Parse(schema, new DocumentNode(definitions), parserOptions); + } + + AddIntrospectionFields(schema); + AnnotateSchema(schema, leafTypes, globalEntityPatterns, typeEntityPatterns); + + return schema; } public static RuntimeTypeInfo GetOrCreateTypeInfo( - this Schema schema, + this MutableSchemaDefinition schema, string typeName, bool valueType = false) => schema.Features.GetOrSet().GetOrAdd(typeName, valueType); + private static IDefinitionNode NormalizeDefinition(IDefinitionNode definition) + => definition is SchemaDefinitionNode schemaDefinition + ? schemaDefinition.WithOperationTypes( + schemaDefinition.OperationTypes + .Where(t => !t.Type.Name.Value.Equals("null", StringComparison.Ordinal)) + .ToArray()) + : definition; + private static void CollectScalarInfos( IEnumerable scalarTypeExtensions, Dictionary leafTypes, @@ -169,7 +160,7 @@ private static void CollectEnumInfos( string directiveName) { var directive = hasDirectives.Directives.FirstOrDefault( - t => directiveName.EqualsOrdinal(t.Name.Value)); + t => directiveName.Equals(t.Name.Value, StringComparison.Ordinal)); if (directive is { Arguments.Count: > 0 }) { @@ -217,9 +208,7 @@ private static void CollectTypeEntityPatterns( } } - private static void AddDefaultScalarInfos( - ISchemaBuilder schemaBuilder, - Dictionary leafTypes) + private static void AddDefaultScalarInfos(Dictionary leafTypes) { TryAddLeafType( leafTypes, @@ -373,17 +362,175 @@ private static void AddDefaultScalarInfos( typeName: "Uuid", runtimeType: TypeNames.Guid, serializationType: TypeNames.String); + } + + private static void AddImplicitScalarDefinitions( + MutableSchemaDefinition schema, + IEnumerable scalarNames, + IEnumerable definitions) + { + var declaredTypeNames = + definitions + .OfType() + .Select(t => t.Name.Value) + .ToHashSet(StringComparer.Ordinal); + + foreach (var scalarName in scalarNames) + { + if (!declaredTypeNames.Contains(scalarName)) + { + TryAddScalarDefinition(schema, scalarName); + } + } + } + + private static void AddBuiltInDirectiveDefinitions(MutableSchemaDefinition schema) + { + TryAddDirectiveDefinition(schema, BuiltIns.Include.Create(schema)); + TryAddDirectiveDefinition(schema, BuiltIns.Skip.Create(schema)); + TryAddDirectiveDefinition(schema, BuiltIns.Deprecated.Create(schema)); + TryAddDirectiveDefinition(schema, BuiltIns.SpecifiedBy.Create(schema)); + TryAddDirectiveDefinition(schema, BuiltIns.OneOf.Create()); + } + + private static void AddIntrospectionFields(MutableSchemaDefinition schema) + { + if (schema.QueryType is null) + { + return; + } + + var queryType = schema.QueryType; + var schemaType = schema.Types.GetType("__Schema"); + var typeType = schema.Types.GetType("__Type"); + var stringType = schema.Types.GetType(ScalarNames.String); + + if (!queryType.Fields.ContainsName("__schema")) + { + queryType.Fields.Add( + new MutableOutputFieldDefinition("__schema", new NonNullType(schemaType)) + { + Description = "Access the current type schema of this server.", + DeclaringMember = queryType, + IsIntrospectionField = true, + Flags = FieldFlags.Introspection | FieldFlags.SchemaIntrospectionField + }); + } + + if (!queryType.Fields.ContainsName("__type")) + { + var field = new MutableOutputFieldDefinition("__type", typeType) + { + Description = "Request the type information of a single type.", + DeclaringMember = queryType, + IsIntrospectionField = true, + Flags = FieldFlags.Introspection | FieldFlags.TypeIntrospectionField + }; + + field.Arguments.Add( + new MutableInputFieldDefinition("name", new NonNullType(stringType)) + { + DeclaringMember = field + }); + + queryType.Fields.Add(field); + } + } + + private static void TryAddDirectiveDefinition( + MutableSchemaDefinition schema, + MutableDirectiveDefinition directiveDefinition) + { + if (!schema.DirectiveDefinitions.ContainsName(directiveDefinition.Name)) + { + schema.DirectiveDefinitions.Add(directiveDefinition); + } + } + + private static void TryAddScalarDefinition(MutableSchemaDefinition schema, string typeName) + { + if (!schema.Types.TryGetType(typeName, out _)) + { + schema.Types.Add(new MutableScalarTypeDefinition(typeName) { IsSpecScalar = true }); + } + } + + private static void AnnotateSchema( + MutableSchemaDefinition schema, + Dictionary leafTypes, + IReadOnlyList globalEntityPatterns, + IReadOnlyDictionary typeEntityPatterns) + { + foreach (var type in schema.Types) + { + if (!type.IsLeafType()) + { + continue; + } + + if (leafTypes.TryGetValue(type.Name, out var leafType)) + { + type.Features.Set(type is IScalarTypeDefinition + ? new LeafTypeFeature(leafType.RuntimeType, leafType.SerializationType) + : new LeafTypeFeature(null, leafType.SerializationType)); + } + else + { + type.Features.Set(type is IScalarTypeDefinition + ? new LeafTypeFeature(TypeNames.String, TypeNames.String) + : new LeafTypeFeature(null, TypeNames.String)); + } + } + + var complexTypes = new List(); + + foreach (var type in schema.Types) + { + if (type is not IComplexTypeDefinition complexType) + { + continue; + } - // register aliases - schemaBuilder.AddType(new DurationType()); - schemaBuilder.AddType(new DurationType("TimeSpan")); - schemaBuilder.AddType(new UriType()); - schemaBuilder.AddType(new UriType("Uri")); - schemaBuilder.AddType(new UrlType()); - schemaBuilder.AddType(new UrlType("Url")); - schemaBuilder.AddType(new UuidType()); - schemaBuilder.AddType(new UuidType("Guid")); - schemaBuilder.AddType(new UuidType("Uuid")); + if (typeEntityPatterns.TryGetValue(complexType.Name, out var pattern)) + { + complexType.Features.Set(new EntityFeature(pattern)); + } + else + { + complexTypes.Add(complexType); + } + } + + if (globalEntityPatterns.Count == 0) + { + return; + } + + foreach (var complexType in complexTypes) + { + if (globalEntityPatterns.FirstOrDefault( + pattern => DoesPatternMatch(complexType, pattern)) is { } matchedPattern) + { + complexType.Features.Set(new EntityFeature(matchedPattern)); + } + } + } + + private static bool DoesPatternMatch(IComplexTypeDefinition outputType, SelectionSetNode pattern) + { + foreach (var selection in pattern.Selections.OfType()) + { + if (selection.SelectionSet is null + && outputType.Fields.TryGetField(selection.Name.Value, out var field) + && field.Type.NamedType().IsLeafType()) + { + continue; + } + + return false; + } + + return true; } private static bool TryGetKeys( @@ -439,4 +586,120 @@ private static void TryRegister(TypeInfos typeInfos, RuntimeTypeDirective? runti typeInfos.GetOrAdd(runtimeType); } } + + private const string IntrospectionSchema = + """ + "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." + type __Schema { + description: String + "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 + "If this server support subscription, the type that subscription operations will be rooted at." + subscriptionType: __Type + "A list of all directives supported by this server." + directives: [__Directive!]! + } + + "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." + type __Type { + kind: __TypeKind! + name: String + description: String + specifiedByURL: String + fields(includeDeprecated: Boolean! = false): [__Field!] + interfaces: [__Type!] + possibleTypes: [__Type!] + enumValues(includeDeprecated: Boolean! = false): [__EnumValue!] + inputFields(includeDeprecated: Boolean! = false): [__InputValue!] + ofType: __Type + isOneOf: Boolean + } + + "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 + } + + "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." + type __Field { + name: String! + description: String + args(includeDeprecated: Boolean! = false): [__InputValue!]! + type: __Type! + isDeprecated: Boolean! + deprecationReason: String + } + + "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." + type __InputValue { + name: String! + description: String + type: __Type! + "A GraphQL-formatted string representing the default value for this input value." + defaultValue: String + isDeprecated: Boolean! + deprecationReason: String + } + + "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." + type __EnumValue { + name: String! + description: String + isDeprecated: Boolean! + deprecationReason: String + } + + "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." + type __Directive { + name: String! + description: String + isRepeatable: Boolean! + locations: [__DirectiveLocation!]! + args(includeDeprecated: Boolean! = false): [__InputValue!]! + onOperation: Boolean! + onFragment: Boolean! + onField: Boolean! + } + + enum __DirectiveLocation { + QUERY + MUTATION + SUBSCRIPTION + FIELD + FRAGMENT_DEFINITION + FRAGMENT_SPREAD + INLINE_FRAGMENT + VARIABLE_DEFINITION + SCHEMA + SCALAR + OBJECT + FIELD_DEFINITION + ARGUMENT_DEFINITION + INTERFACE + UNION + ENUM + ENUM_VALUE + INPUT_OBJECT + INPUT_FIELD_DEFINITION + } + """; } diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/TypeNameQueryRewriter.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/TypeNameQueryRewriter.cs index 34550cf1885..95023b227c4 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/TypeNameQueryRewriter.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/TypeNameQueryRewriter.cs @@ -65,7 +65,7 @@ internal sealed class TypeNameQueryRewriter : SyntaxRewriter() - .Any(t => t.Alias is null && t.Name.Value.EqualsOrdinal(TypeName))) + .Any(t => t.Alias is null && t.Name.Value.Equals(TypeName, StringComparison.Ordinal))) { var selections = current.Selections.ToList(); selections.Insert(0, s_typeNameField); diff --git a/src/StrawberryShake/CodeGeneration/test/CodeGeneration.CSharp.Tests/CSharpCompiler.cs b/src/StrawberryShake/CodeGeneration/test/CodeGeneration.CSharp.Tests/CSharpCompiler.cs index 6cd25986ec1..085cbd226e2 100644 --- a/src/StrawberryShake/CodeGeneration/test/CodeGeneration.CSharp.Tests/CSharpCompiler.cs +++ b/src/StrawberryShake/CodeGeneration/test/CodeGeneration.CSharp.Tests/CSharpCompiler.cs @@ -18,6 +18,8 @@ internal static class CSharpCompiler private static readonly HttpConnection? s_httpConnection = null!; private static readonly WebSocketConnection? s_webSocketConnection = null!; private static readonly ServiceCollection? s_serviceCollection = null!; + private static readonly Type? s_serviceCollectionContainerBuilderExtensions = + typeof(ServiceCollectionContainerBuilderExtensions); private static readonly IHttpClientFactory? s_httpClientFactory = null!; private static readonly HttpClient? s_httpClient = null!; private static readonly InMemoryClient s_memoryClient = null!; diff --git a/src/StrawberryShake/CodeGeneration/test/CodeGeneration.CSharp.Tests/StrawberryShake.CodeGeneration.CSharp.Tests.csproj b/src/StrawberryShake/CodeGeneration/test/CodeGeneration.CSharp.Tests/StrawberryShake.CodeGeneration.CSharp.Tests.csproj index fe669f2f6e0..f8ea88574a6 100644 --- a/src/StrawberryShake/CodeGeneration/test/CodeGeneration.CSharp.Tests/StrawberryShake.CodeGeneration.CSharp.Tests.csproj +++ b/src/StrawberryShake/CodeGeneration/test/CodeGeneration.CSharp.Tests/StrawberryShake.CodeGeneration.CSharp.Tests.csproj @@ -20,6 +20,7 @@ + diff --git a/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Razor.Tests/CSharpCompiler.cs b/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Razor.Tests/CSharpCompiler.cs index 6cd25986ec1..085cbd226e2 100644 --- a/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Razor.Tests/CSharpCompiler.cs +++ b/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Razor.Tests/CSharpCompiler.cs @@ -18,6 +18,8 @@ internal static class CSharpCompiler private static readonly HttpConnection? s_httpConnection = null!; private static readonly WebSocketConnection? s_webSocketConnection = null!; private static readonly ServiceCollection? s_serviceCollection = null!; + private static readonly Type? s_serviceCollectionContainerBuilderExtensions = + typeof(ServiceCollectionContainerBuilderExtensions); private static readonly IHttpClientFactory? s_httpClientFactory = null!; private static readonly HttpClient? s_httpClient = null!; private static readonly InMemoryClient s_memoryClient = null!; diff --git a/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Razor.Tests/StrawberryShake.CodeGeneration.Razor.Tests.csproj b/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Razor.Tests/StrawberryShake.CodeGeneration.Razor.Tests.csproj index c3cfb280ed8..bb984dc15c0 100644 --- a/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Razor.Tests/StrawberryShake.CodeGeneration.Razor.Tests.csproj +++ b/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Razor.Tests/StrawberryShake.CodeGeneration.Razor.Tests.csproj @@ -18,6 +18,7 @@ + diff --git a/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Tests/Analyzers/DocumentAnalyzerTests.cs b/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Tests/Analyzers/DocumentAnalyzerTests.cs index 8a04064f8c9..857ee189eb9 100644 --- a/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Tests/Analyzers/DocumentAnalyzerTests.cs +++ b/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Tests/Analyzers/DocumentAnalyzerTests.cs @@ -1,9 +1,5 @@ -using Microsoft.Extensions.DependencyInjection; -using HotChocolate.Execution; using HotChocolate.Language; -using HotChocolate.StarWars; using HotChocolate.Utilities; -using StrawberryShake.CodeGeneration.Utilities; namespace StrawberryShake.CodeGeneration.Analyzers; @@ -13,20 +9,8 @@ public class DocumentAnalyzerTests public async Task One_Document_One_Op_One_Field_No_Fragments() { // arrange - var schema = - await new ServiceCollection() - .AddStarWarsRepositories() - .AddGraphQL() - .AddStarWars() - .BuildSchemaAsync(cancellationToken: TestContext.Current.CancellationToken); - - schema = - SchemaHelper.Load( - [ - new(schema.ToSyntaxNode()), - new(Utf8GraphQLParser.Parse( - @"extend scalar String @runtimeType(name: ""Abc"")")) - ]); + var schema = await TestSchemaHelper.CreateStarWarsSchemaAsync( + @"extend scalar String @runtimeType(name: ""Abc"")"); var document = Utf8GraphQLParser.Parse(@" @@ -75,22 +59,9 @@ query GetHero { public async Task One_Fragment_One_Deferred_Fragment() { // arrange - var schema = - await new ServiceCollection() - .AddStarWarsRepositories() - .AddGraphQL() - .AddStarWars() - .BuildSchemaAsync(cancellationToken: TestContext.Current.CancellationToken); - - schema = - SchemaHelper.Load( - [ - new(schema.ToSyntaxNode()), - new(Utf8GraphQLParser.Parse( - @"extend scalar String @runtimeType(name: ""Abc"")")), - new(Utf8GraphQLParser.Parse( - "extend schema @key(fields: \"id\")")) - ]); + var schema = await TestSchemaHelper.CreateStarWarsSchemaAsync( + @"extend scalar String @runtimeType(name: ""Abc"")", + "extend schema @key(fields: \"id\")"); var document = Utf8GraphQLParser.Parse( diff --git a/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Tests/Analyzers/FieldCollectorTests.cs b/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Tests/Analyzers/FieldCollectorTests.cs index 4475e79b535..4fae0c0237b 100644 --- a/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Tests/Analyzers/FieldCollectorTests.cs +++ b/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Tests/Analyzers/FieldCollectorTests.cs @@ -1,7 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; -using HotChocolate.Execution; using HotChocolate.Language; -using HotChocolate.StarWars; using HotChocolate.Types; using Path = HotChocolate.Path; @@ -13,12 +10,7 @@ public class FieldCollectorTests public async Task Collect_First_Level_No_Fragments() { // arrange - var schema = - await new ServiceCollection() - .AddStarWarsRepositories() - .AddGraphQL() - .AddStarWars() - .BuildSchemaAsync(cancellationToken: TestContext.Current.CancellationToken); + var schema = await TestSchemaHelper.CreateStarWarsSchemaAsync(); var document = Utf8GraphQLParser.Parse(@" @@ -35,11 +27,13 @@ ... on Droid { .Definitions .OfType() .First(); + var queryType = schema.QueryType + ?? throw new InvalidOperationException("The Star Wars schema must define a query type."); // act var selectionSetVariants = new FieldCollector(schema, document) - .CollectFields(operation.SelectionSet, schema.QueryType, Path.Root); + .CollectFields(operation.SelectionSet, queryType, Path.Root); // assert Assert.Collection( @@ -51,14 +45,10 @@ ... on Droid { public async Task Collect_Second_Level_Fragments() { // arrange - var schema = - await new ServiceCollection() - .AddStarWarsRepositories() - .AddGraphQL() - .AddStarWars() - .BuildSchemaAsync(cancellationToken: TestContext.Current.CancellationToken); + var schema = await TestSchemaHelper.CreateStarWarsSchemaAsync(); - var character = schema.Types.GetType("Character"); + var character = schema.Types.GetType("Character") + ?? throw new InvalidOperationException("The Star Wars schema must define Character."); var document = Utf8GraphQLParser.Parse(@" @@ -95,11 +85,11 @@ ... on Droid { selectionSetVariants.ReturnType.Fields, field => Assert.Equal("name", field.ResponseName)); Assert.Equal("Character", selectionSetVariants.ReturnType.Type.Name); - Assert.Equal("Human", selectionSetVariants.Variants[0].Type.Name); - Assert.Equal("Droid", selectionSetVariants.Variants[1].Type.Name); + Assert.Equal("Droid", selectionSetVariants.Variants[0].Type.Name); + Assert.Equal("Human", selectionSetVariants.Variants[1].Type.Name); Assert.Collection( - selectionSetVariants.Variants[1].FragmentNodes, + selectionSetVariants.Variants[0].FragmentNodes, fragmentNode => Assert.Equal(FragmentKind.Inline, fragmentNode.Fragment.Kind)); } } diff --git a/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Tests/Analyzers/FragmentHelperTests.cs b/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Tests/Analyzers/FragmentHelperTests.cs index 5cf14b6fdff..a0edcce2a47 100644 --- a/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Tests/Analyzers/FragmentHelperTests.cs +++ b/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Tests/Analyzers/FragmentHelperTests.cs @@ -1,7 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; -using HotChocolate.Execution; using HotChocolate.Language; -using HotChocolate.StarWars; using StrawberryShake.CodeGeneration.Analyzers.Models; namespace StrawberryShake.CodeGeneration.Analyzers; @@ -12,12 +9,7 @@ public class FragmentHelperTests public async Task GetReturnTypeName_Found() { // arrange - var schema = - await new ServiceCollection() - .AddStarWarsRepositories() - .AddGraphQL() - .AddStarWars() - .BuildSchemaAsync(cancellationToken: TestContext.Current.CancellationToken); + var schema = await TestSchemaHelper.CreateStarWarsSchemaAsync(); var document = Utf8GraphQLParser.Parse( @@ -63,12 +55,7 @@ ... Hero public async Task GetReturnTypeName_Not_Found() { // arrange - var schema = - await new ServiceCollection() - .AddStarWarsRepositories() - .AddGraphQL() - .AddStarWars() - .BuildSchemaAsync(cancellationToken: TestContext.Current.CancellationToken); + var schema = await TestSchemaHelper.CreateStarWarsSchemaAsync(); var document = Utf8GraphQLParser.Parse(@" @@ -112,12 +99,7 @@ ... Hero public async Task GetFragment_From_FragmentTree_Found() { // arrange - var schema = - await new ServiceCollection() - .AddStarWarsRepositories() - .AddGraphQL() - .AddStarWars() - .BuildSchemaAsync(cancellationToken: TestContext.Current.CancellationToken); + var schema = await TestSchemaHelper.CreateStarWarsSchemaAsync(); var document = Utf8GraphQLParser.Parse( @@ -172,12 +154,7 @@ ... Hero public async Task Create_TypeModels_Infer_TypeStructure() { // arrange - var schema = - await new ServiceCollection() - .AddStarWarsRepositories() - .AddGraphQL() - .AddStarWars() - .BuildSchemaAsync(cancellationToken: TestContext.Current.CancellationToken); + var schema = await TestSchemaHelper.CreateStarWarsSchemaAsync(); var document = Utf8GraphQLParser.Parse(@" @@ -289,12 +266,7 @@ query GetHero { public async Task Create_TypeModels_Infer_From_Fragments() { // arrange - var schema = - await new ServiceCollection() - .AddStarWarsRepositories() - .AddGraphQL() - .AddStarWars() - .BuildSchemaAsync(cancellationToken: TestContext.Current.CancellationToken); + var schema = await TestSchemaHelper.CreateStarWarsSchemaAsync(); var document = Utf8GraphQLParser.Parse(@" @@ -424,12 +396,7 @@ fragment Droid on Droid { public async Task Create_TypeModels_Infer_From_Fragments_With_HoistedFragment() { // arrange - var schema = - await new ServiceCollection() - .AddStarWarsRepositories() - .AddGraphQL() - .AddStarWars() - .BuildSchemaAsync(cancellationToken: TestContext.Current.CancellationToken); + var schema = await TestSchemaHelper.CreateStarWarsSchemaAsync(); var document = Utf8GraphQLParser.Parse( diff --git a/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Tests/Analyzers/InterfaceTypeSelectionSetAnalyzerTests.cs b/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Tests/Analyzers/InterfaceTypeSelectionSetAnalyzerTests.cs index 10cdd85adfb..ea7053e49f6 100644 --- a/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Tests/Analyzers/InterfaceTypeSelectionSetAnalyzerTests.cs +++ b/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Tests/Analyzers/InterfaceTypeSelectionSetAnalyzerTests.cs @@ -1,6 +1,3 @@ -using Microsoft.Extensions.DependencyInjection; -using HotChocolate.StarWars; -using HotChocolate.Execution; using HotChocolate.Language; namespace StrawberryShake.CodeGeneration.Analyzers; @@ -11,12 +8,7 @@ public class InterfaceTypeSelectionSetAnalyzerTests public async Task Interface_With_Default_Names_One_Models() { // arrange - var schema = - await new ServiceCollection() - .AddStarWarsRepositories() - .AddGraphQL() - .AddStarWars() - .BuildSchemaAsync(cancellationToken: TestContext.Current.CancellationToken); + var schema = await TestSchemaHelper.CreateStarWarsSchemaAsync(); var document = Utf8GraphQLParser.Parse(@" @@ -52,12 +44,7 @@ query GetHero { public async Task Interface_With_Default_Names_Two_Models() { // arrange - var schema = - await new ServiceCollection() - .AddStarWarsRepositories() - .AddGraphQL() - .AddStarWars() - .BuildSchemaAsync(cancellationToken: TestContext.Current.CancellationToken); + var schema = await TestSchemaHelper.CreateStarWarsSchemaAsync(); var document = Utf8GraphQLParser.Parse(@" @@ -84,8 +71,8 @@ ... on Droid { Assert.Collection( context.GetImplementations(result), - model => Assert.Equal("IGetHero_Hero_Human", model.Name), - model => Assert.Equal("IGetHero_Hero_Droid", model.Name)); + model => Assert.Equal("IGetHero_Hero_Droid", model.Name), + model => Assert.Equal("IGetHero_Hero_Human", model.Name)); Assert.Collection( result.Fields, @@ -96,12 +83,7 @@ ... on Droid { public async Task Interface_With_Fragment_Definition_One_Model() { // arrange - var schema = - await new ServiceCollection() - .AddStarWarsRepositories() - .AddGraphQL() - .AddStarWars() - .BuildSchemaAsync(cancellationToken: TestContext.Current.CancellationToken); + var schema = await TestSchemaHelper.CreateStarWarsSchemaAsync(); var document = Utf8GraphQLParser.Parse(@" @@ -139,12 +121,7 @@ fragment Hero on Character { public async Task Interface_With_Fragment_Definition_Two_Models() { // arrange - var schema = - await new ServiceCollection() - .AddStarWarsRepositories() - .AddGraphQL() - .AddStarWars() - .BuildSchemaAsync(cancellationToken: TestContext.Current.CancellationToken); + var schema = await TestSchemaHelper.CreateStarWarsSchemaAsync(); var document = Utf8GraphQLParser.Parse(@" @@ -182,8 +159,8 @@ fragment Droid on Droid { Assert.Collection( context.GetImplementations(result), - model => Assert.Equal("IGetHero_Hero_Human", model.Name), - model => Assert.Equal("IGetHero_Hero_Droid", model.Name)); + model => Assert.Equal("IGetHero_Hero_Droid", model.Name), + model => Assert.Equal("IGetHero_Hero_Human", model.Name)); Assert.Empty(result.Fields); } @@ -192,12 +169,7 @@ fragment Droid on Droid { public async Task Union_With_Fragment_Definition_Two_Models() { // arrange - var schema = - await new ServiceCollection() - .AddStarWarsRepositories() - .AddGraphQL() - .AddStarWars() - .BuildSchemaAsync(cancellationToken: TestContext.Current.CancellationToken); + var schema = await TestSchemaHelper.CreateStarWarsSchemaAsync(); var document = Utf8GraphQLParser.Parse( diff --git a/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Tests/Mappers/TestDataHelper.cs b/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Tests/Mappers/TestDataHelper.cs index ca1db15b673..164e31da264 100644 --- a/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Tests/Mappers/TestDataHelper.cs +++ b/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Tests/Mappers/TestDataHelper.cs @@ -1,8 +1,5 @@ using System.Diagnostics.CodeAnalysis; -using Microsoft.Extensions.DependencyInjection; -using HotChocolate.Execution; using HotChocolate.Language; -using HotChocolate.StarWars; using StrawberryShake.CodeGeneration.Analyzers; using StrawberryShake.CodeGeneration.Analyzers.Models; using StrawberryShake.CodeGeneration.Utilities; @@ -33,18 +30,8 @@ public static ClientModel CreateClientModelAsync( public static async Task CreateClientModelAsync([StringSyntax("graphql")] string query) { - var schema = - await new ServiceCollection() - .AddStarWarsRepositories() - .AddGraphQL() - .AddStarWars() - .BuildSchemaAsync(); - - schema = SchemaHelper.Load( - [ - new(schema.ToSyntaxNode()), - new(Utf8GraphQLParser.Parse("extend schema @key(fields: \"id\")")) - ]); + var schema = await TestSchemaHelper.CreateStarWarsSchemaAsync( + "extend schema @key(fields: \"id\")"); var document = Utf8GraphQLParser.Parse(query); diff --git a/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Tests/TestSchemaHelper.cs b/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Tests/TestSchemaHelper.cs new file mode 100644 index 00000000000..d9524662267 --- /dev/null +++ b/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Tests/TestSchemaHelper.cs @@ -0,0 +1,31 @@ +using HotChocolate.Execution; +using HotChocolate.Language; +using HotChocolate.StarWars; +using HotChocolate.Types.Mutable; +using Microsoft.Extensions.DependencyInjection; +using StrawberryShake.CodeGeneration.Utilities; + +namespace StrawberryShake.CodeGeneration; + +internal static class TestSchemaHelper +{ + public static async Task CreateStarWarsSchemaAsync( + params string[] extensions) + { + var schema = + await new ServiceCollection() + .AddStarWarsRepositories() + .AddGraphQL() + .AddStarWars() + .BuildSchemaAsync(cancellationToken: TestContext.Current.CancellationToken); + + var files = new List { new(schema.ToSyntaxNode()) }; + + foreach (var extension in extensions) + { + files.Add(new GraphQLFile(Utf8GraphQLParser.Parse(extension))); + } + + return SchemaHelper.Load(files); + } +} diff --git a/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Tests/Utilities/QueryDocumentRewriterTests.cs b/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Tests/Utilities/QueryDocumentRewriterTests.cs index f3842807943..aad6f15d84d 100644 --- a/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Tests/Utilities/QueryDocumentRewriterTests.cs +++ b/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Tests/Utilities/QueryDocumentRewriterTests.cs @@ -1,8 +1,5 @@ -using HotChocolate.Execution; using HotChocolate.Language; using HotChocolate.Language.Utilities; -using HotChocolate.StarWars; -using Microsoft.Extensions.DependencyInjection; namespace StrawberryShake.CodeGeneration.Utilities; @@ -12,19 +9,8 @@ public class QueryDocumentRewriterTests public async Task GetReturnTypeName() { // arrange - var schema = - await new ServiceCollection() - .AddStarWarsRepositories() - .AddGraphQL() - .AddStarWars() - .BuildSchemaAsync(cancellationToken: TestContext.Current.CancellationToken); - - schema = - SchemaHelper.Load( - [ - new(schema.ToSyntaxNode()), - new(Utf8GraphQLParser.Parse("extend schema @key(fields: \"id\")")) - ]); + var schema = await TestSchemaHelper.CreateStarWarsSchemaAsync( + "extend schema @key(fields: \"id\")"); var document = Utf8GraphQLParser.Parse( diff --git a/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Tests/Utilities/SchemaHelperTests.cs b/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Tests/Utilities/SchemaHelperTests.cs index 3ff109307c2..2d455526e2e 100644 --- a/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Tests/Utilities/SchemaHelperTests.cs +++ b/src/StrawberryShake/CodeGeneration/test/CodeGeneration.Tests/Utilities/SchemaHelperTests.cs @@ -22,7 +22,7 @@ public void LoadGitHubSchema() ]); // assert - var scalarType = schema.Types.GetType("X509Certificate"); + var scalarType = schema.Types.GetType("X509Certificate"); Assert.Equal( "global::System.String",