From 9021541db4e54d34edd2a8809515d20022ea7c16 Mon Sep 17 00:00:00 2001 From: Glen Date: Fri, 13 Dec 2024 10:42:38 +0200 Subject: [PATCH] Refactored Fusion Composition validation (#7821) --- .../Collections/MultiValueDictionary.cs | 28 ++++ .../Fusion.Composition/Errors/ErrorHelper.cs | 5 +- .../src/Fusion.Composition/Events/IEvent.cs | 3 + .../Events/IEventHandler.cs | 6 + .../Logging/CompositionLog.cs | 12 +- .../Logging/Contracts/ICompositionLog.cs | 14 +- .../Logging/LogEntryHelper.cs | 4 +- .../Logging/LoggingSession.cs | 37 ----- .../Contracts/IPreMergeValidationRule.cs | 8 -- .../PreMergeValidation/Events.cs | 44 ++++++ .../PreMergeValidationContext.cs | 77 ---------- .../PreMergeValidation/PreMergeValidator.cs | 133 +++++++++++++++--- .../DisallowedInaccessibleElementsRule.cs | 127 ++++++++--------- .../Rules/OutputFieldTypesMergeableRule.cs | 29 +--- .../CompositionResources.Designer.cs | 24 ++-- .../Properties/CompositionResources.resx | 6 +- .../Fusion.Composition/SourceSchemaMerger.cs | 10 +- ...DisallowedInaccessibleElementsRuleTests.cs | 16 +-- .../OutputFieldTypesMergeableRuleTests.cs | 16 +-- 19 files changed, 318 insertions(+), 281 deletions(-) create mode 100644 src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Collections/MultiValueDictionary.cs create mode 100644 src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Events/IEvent.cs create mode 100644 src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Events/IEventHandler.cs delete mode 100644 src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LoggingSession.cs delete mode 100644 src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Contracts/IPreMergeValidationRule.cs create mode 100644 src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Events.cs delete mode 100644 src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/PreMergeValidationContext.cs diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Collections/MultiValueDictionary.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Collections/MultiValueDictionary.cs new file mode 100644 index 00000000000..1be86756585 --- /dev/null +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Collections/MultiValueDictionary.cs @@ -0,0 +1,28 @@ +namespace HotChocolate.Fusion.Collections; + +internal sealed class MultiValueDictionary + : Dictionary> where TKey : notnull +{ + public void Add(TKey key, TValue value) + { + ArgumentNullException.ThrowIfNull(key); + + if (!TryGetValue(key, out var list)) + { + list = []; + Add(key, list); + } + + list.Add(value); + } + + public void Remove(TKey key, TValue value) + { + ArgumentNullException.ThrowIfNull(key); + + if (TryGetValue(key, out var list)) + { + list.Remove(value); + } + } +} diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Errors/ErrorHelper.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Errors/ErrorHelper.cs index 4841f244835..800b1db42f9 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Errors/ErrorHelper.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Errors/ErrorHelper.cs @@ -1,10 +1,9 @@ -using HotChocolate.Fusion.PreMergeValidation.Contracts; using static HotChocolate.Fusion.Properties.CompositionResources; namespace HotChocolate.Fusion.Errors; internal static class ErrorHelper { - public static CompositionError PreMergeValidationRuleFailed(IPreMergeValidationRule rule) - => new(string.Format(ErrorHelper_PreMergeValidationRuleFailed, rule.GetType().Name)); + public static CompositionError PreMergeValidationFailed() + => new(ErrorHelper_PreMergeValidationFailed); } diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Events/IEvent.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Events/IEvent.cs new file mode 100644 index 00000000000..fc4b2354184 --- /dev/null +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Events/IEvent.cs @@ -0,0 +1,3 @@ +namespace HotChocolate.Fusion.Events; + +internal interface IEvent; diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Events/IEventHandler.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Events/IEventHandler.cs new file mode 100644 index 00000000000..9c648ef6313 --- /dev/null +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Events/IEventHandler.cs @@ -0,0 +1,6 @@ +namespace HotChocolate.Fusion.Events; + +internal interface IEventHandler where TEvent : IEvent +{ + void Handle(TEvent @event, CompositionContext context); +} diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/CompositionLog.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/CompositionLog.cs index 2f9cc4b9cea..3f51bdfed5f 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/CompositionLog.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/CompositionLog.cs @@ -5,6 +5,8 @@ namespace HotChocolate.Fusion.Logging; public sealed class CompositionLog : ICompositionLog, IEnumerable { + public bool HasErrors { get; private set; } + public bool IsEmpty => _entries.Count == 0; private readonly List _entries = []; @@ -13,12 +15,12 @@ public void Write(LogEntry entry) { ArgumentNullException.ThrowIfNull(entry); - _entries.Add(entry); - } + if (entry.Severity == LogSeverity.Error) + { + HasErrors = true; + } - public ILoggingSession CreateSession() - { - return new LoggingSession(this); + _entries.Add(entry); } public IEnumerator GetEnumerator() => _entries.GetEnumerator(); diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/Contracts/ICompositionLog.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/Contracts/ICompositionLog.cs index 1251c6adf64..179cd3971a7 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/Contracts/ICompositionLog.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/Contracts/ICompositionLog.cs @@ -5,6 +5,14 @@ namespace HotChocolate.Fusion.Logging.Contracts; /// public interface ICompositionLog { + /// + /// Gets a value indicating whether the log contains errors. + /// + bool HasErrors { get; } + + /// + /// Gets a value indicating whether the log is empty. + /// bool IsEmpty { get; } /// @@ -12,10 +20,4 @@ public interface ICompositionLog /// /// The to write. void Write(LogEntry entry); - - /// - /// Creates a new logging session that keeps track of the number of info, warning, and error - /// entries logged. - /// - ILoggingSession CreateSession(); } diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryHelper.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryHelper.cs index f678870839e..a026e7d3468 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryHelper.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryHelper.cs @@ -5,11 +5,11 @@ namespace HotChocolate.Fusion.Logging; internal static class LogEntryHelper { - public static LogEntry DisallowedInaccessibleScalar( + public static LogEntry DisallowedInaccessibleBuiltInScalar( ScalarTypeDefinition scalar, SchemaDefinition schema) => new( - string.Format(LogEntryHelper_DisallowedInaccessibleScalar, scalar.Name), + string.Format(LogEntryHelper_DisallowedInaccessibleBuiltInScalar, scalar.Name), LogEntryCodes.DisallowedInaccessible, LogSeverity.Error, new SchemaCoordinate(scalar.Name), diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LoggingSession.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LoggingSession.cs deleted file mode 100644 index 6ee1b23df63..00000000000 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LoggingSession.cs +++ /dev/null @@ -1,37 +0,0 @@ -using HotChocolate.Fusion.Logging.Contracts; - -namespace HotChocolate.Fusion.Logging; - -public sealed class LoggingSession(ICompositionLog compositionLog) : ILoggingSession -{ - public int InfoCount { get; private set; } - - public int WarningCount { get; private set; } - - public int ErrorCount { get; private set; } - - public void Write(LogEntry entry) - { - ArgumentNullException.ThrowIfNull(entry); - - switch (entry.Severity) - { - case LogSeverity.Info: - InfoCount++; - break; - - case LogSeverity.Warning: - WarningCount++; - break; - - case LogSeverity.Error: - ErrorCount++; - break; - - default: - throw new InvalidOperationException(); - } - - compositionLog.Write(entry); - } -} diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Contracts/IPreMergeValidationRule.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Contracts/IPreMergeValidationRule.cs deleted file mode 100644 index a9a60320a91..00000000000 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Contracts/IPreMergeValidationRule.cs +++ /dev/null @@ -1,8 +0,0 @@ -using HotChocolate.Fusion.Results; - -namespace HotChocolate.Fusion.PreMergeValidation.Contracts; - -internal interface IPreMergeValidationRule -{ - CompositionResult Run(PreMergeValidationContext context); -} diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Events.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Events.cs new file mode 100644 index 00000000000..cd5be8eb6a1 --- /dev/null +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Events.cs @@ -0,0 +1,44 @@ +using System.Collections.Immutable; +using HotChocolate.Fusion.Events; +using HotChocolate.Skimmed; + +namespace HotChocolate.Fusion.PreMergeValidation; + +internal record DirectiveArgumentEvent( + InputFieldDefinition Argument, + DirectiveDefinition Directive, + SchemaDefinition Schema) : IEvent; + +internal record DirectiveEvent( + DirectiveDefinition Directive, + SchemaDefinition Schema) : IEvent; + +internal record FieldArgumentEvent( + InputFieldDefinition Argument, + OutputFieldDefinition Field, + INamedTypeDefinition Type, + SchemaDefinition Schema) : IEvent; + +internal record FieldArgumentGroupEvent( + string ArgumentName, + ImmutableArray ArgumentGroup, + string FieldName, + string TypeName) : IEvent; + +internal record OutputFieldEvent( + OutputFieldDefinition Field, + INamedTypeDefinition Type, + SchemaDefinition Schema) : IEvent; + +internal record OutputFieldGroupEvent( + string FieldName, + ImmutableArray FieldGroup, + string TypeName) : IEvent; + +internal record TypeEvent( + INamedTypeDefinition Type, + SchemaDefinition Schema) : IEvent; + +internal record TypeGroupEvent( + string TypeName, + ImmutableArray TypeGroup) : IEvent; diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/PreMergeValidationContext.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/PreMergeValidationContext.cs deleted file mode 100644 index 538df65d0ca..00000000000 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/PreMergeValidationContext.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System.Collections.Immutable; -using HotChocolate.Fusion.Logging.Contracts; -using HotChocolate.Skimmed; - -namespace HotChocolate.Fusion.PreMergeValidation; - -internal sealed class PreMergeValidationContext(CompositionContext context) -{ - public ImmutableArray SchemaDefinitions => context.SchemaDefinitions; - public ICompositionLog Log => context.Log; - public ImmutableArray OutputTypeInfo = []; - - public void Initialize() - { - InitializeOutputTypeInfo(); - } - - /// - /// Initializes a structure that makes it easier to access combined output types, fields, and - /// arguments for validation purposes. - /// - private void InitializeOutputTypeInfo() - { - OutputTypeInfo = - [ - .. SchemaDefinitions - .SelectMany(s => s.Types) - .Where(t => t.IsOutputType()) - .OfType() - .GroupBy( - t => t.Name, - (typeName, types) => - { - types = types.ToImmutableArray(); - - var fieldInfo = types - .SelectMany(t => t.Fields) - .GroupBy( - f => f.Name, - (fieldName, fields) => - { - fields = fields.ToImmutableArray(); - - var argumentInfo = fields - .SelectMany(f => f.Arguments) - .GroupBy( - a => a.Name, - (argumentName, arguments) => - new OutputArgumentInfo( - argumentName, - [.. arguments])); - - return new OutputFieldInfo( - fieldName, - [.. fields], - [.. argumentInfo]); - }); - - return new OutputTypeInfo(typeName, [.. types], [.. fieldInfo]); - }) - ]; - } -} - -internal record OutputTypeInfo( - string TypeName, - ImmutableArray Types, - ImmutableArray FieldInfo); - -internal record OutputFieldInfo( - string FieldName, - ImmutableArray Fields, - ImmutableArray Arguments); - -internal record OutputArgumentInfo( - string ArgumentName, - ImmutableArray Arguments); diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/PreMergeValidator.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/PreMergeValidator.cs index 1d812523f12..e630a9edc13 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/PreMergeValidator.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/PreMergeValidator.cs @@ -1,36 +1,135 @@ using System.Collections.Immutable; +using HotChocolate.Fusion.Collections; using HotChocolate.Fusion.Errors; -using HotChocolate.Fusion.PreMergeValidation.Contracts; -using HotChocolate.Fusion.PreMergeValidation.Rules; +using HotChocolate.Fusion.Events; using HotChocolate.Fusion.Results; +using HotChocolate.Skimmed; namespace HotChocolate.Fusion.PreMergeValidation; -internal sealed class PreMergeValidator +internal sealed class PreMergeValidator(IEnumerable rules) { - private readonly ImmutableArray _validationRules = - [ - new DisallowedInaccessibleElementsRule(), - new OutputFieldTypesMergeableRule() - ]; + private readonly ImmutableArray _rules = [.. rules]; - public CompositionResult Validate(CompositionContext compositionContext) + public CompositionResult Validate(CompositionContext context) { - var preMergeValidationContext = new PreMergeValidationContext(compositionContext); - preMergeValidationContext.Initialize(); + PublishEvents(context); - var errors = new List(); + return context.Log.HasErrors + ? ErrorHelper.PreMergeValidationFailed() + : CompositionResult.Success(); + } + + private void PublishEvents(CompositionContext context) + { + MultiValueDictionary typeGroupByName = []; - foreach (var validationRule in _validationRules) + foreach (var schema in context.SchemaDefinitions) { - var result = validationRule.Run(preMergeValidationContext); + foreach (var type in schema.Types) + { + PublishEvent(new TypeEvent(type, schema), context); + + typeGroupByName.Add(type.Name, new TypeInfo(type, schema)); + + if (type is ComplexTypeDefinition complexType) + { + foreach (var field in complexType.Fields) + { + PublishEvent(new OutputFieldEvent(field, type, schema), context); + + foreach (var argument in field.Arguments) + { + PublishEvent( + new FieldArgumentEvent(argument, field, type, schema), context); + } + } + } + } - if (result.IsFailure) + foreach (var directive in schema.DirectiveDefinitions) { - errors.AddRange(result.Errors); + PublishEvent(new DirectiveEvent(directive, schema), context); + + foreach (var argument in directive.Arguments) + { + PublishEvent(new DirectiveArgumentEvent(argument, directive, schema), context); + } } } - return errors; + foreach (var (typeName, typeGroup) in typeGroupByName) + { + PublishEvent(new TypeGroupEvent(typeName, [.. typeGroup]), context); + + MultiValueDictionary fieldGroupByName = []; + + foreach (var (type, schema) in typeGroup) + { + if (type is ComplexTypeDefinition complexType) + { + foreach (var field in complexType.Fields) + { + fieldGroupByName.Add(field.Name, new OutputFieldInfo(field, type, schema)); + } + } + } + + foreach (var (fieldName, fieldGroup) in fieldGroupByName) + { + PublishEvent( + new OutputFieldGroupEvent(fieldName, [.. fieldGroup], typeName), context); + + MultiValueDictionary argumentGroupByName = []; + + foreach (var (field, type, schema) in fieldGroup) + { + foreach (var argument in field.Arguments) + { + argumentGroupByName.Add( + argument.Name, + new FieldArgumentInfo(argument, field, type, schema)); + } + } + + foreach (var (argumentName, argumentGroup) in argumentGroupByName) + { + PublishEvent( + new FieldArgumentGroupEvent( + argumentName, + [.. argumentGroup], + fieldName, + typeName), + context); + } + } + } + } + + private void PublishEvent(TEvent @event, CompositionContext context) + where TEvent : IEvent + { + foreach (var rule in _rules) + { + if (rule is IEventHandler handler) + { + handler.Handle(@event, context); + } + } } } + +internal record TypeInfo( + INamedTypeDefinition Type, + SchemaDefinition Schema); + +internal record OutputFieldInfo( + OutputFieldDefinition Field, + INamedTypeDefinition Type, + SchemaDefinition Schema); + +internal record FieldArgumentInfo( + InputFieldDefinition Argument, + OutputFieldDefinition Field, + INamedTypeDefinition Type, + SchemaDefinition Schema); diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Rules/DisallowedInaccessibleElementsRule.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Rules/DisallowedInaccessibleElementsRule.cs index b01d1f11c78..36a229b4f8b 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Rules/DisallowedInaccessibleElementsRule.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Rules/DisallowedInaccessibleElementsRule.cs @@ -1,6 +1,4 @@ -using HotChocolate.Fusion.Errors; -using HotChocolate.Fusion.PreMergeValidation.Contracts; -using HotChocolate.Fusion.Results; +using HotChocolate.Fusion.Events; using HotChocolate.Skimmed; using static HotChocolate.Fusion.Logging.LogEntryHelper; @@ -15,80 +13,73 @@ namespace HotChocolate.Fusion.PreMergeValidation.Rules; /// /// Specification /// -internal sealed class DisallowedInaccessibleElementsRule : IPreMergeValidationRule +internal sealed class DisallowedInaccessibleElementsRule + : IEventHandler + , IEventHandler + , IEventHandler + , IEventHandler { - public CompositionResult Run(PreMergeValidationContext context) + public void Handle(TypeEvent @event, CompositionContext context) { - var loggingSession = context.Log.CreateSession(); + var (type, schema) = @event; - foreach (var schema in context.SchemaDefinitions) + // Built-in scalar types must be accessible. + if (type is ScalarTypeDefinition { IsSpecScalar: true } scalar + && !ValidationHelper.IsAccessible(scalar)) { - foreach (var type in schema.Types) - { - if (type is ScalarTypeDefinition { IsSpecScalar: true } scalar - && !ValidationHelper.IsAccessible(type)) - { - loggingSession.Write(DisallowedInaccessibleScalar(scalar, schema)); - } + context.Log.Write(DisallowedInaccessibleBuiltInScalar(scalar, schema)); + } + + // Introspection types must be accessible. + if (type.IsIntrospectionType && !ValidationHelper.IsAccessible(type)) + { + context.Log.Write(DisallowedInaccessibleIntrospectionType(type, schema)); + } + } - if (type.IsIntrospectionType) - { - if (!ValidationHelper.IsAccessible(type)) - { - loggingSession.Write(DisallowedInaccessibleIntrospectionType(type, schema)); - } + public void Handle(OutputFieldEvent @event, CompositionContext context) + { + var (field, type, schema) = @event; - if (type is ComplexTypeDefinition complexType) - { - foreach (var field in complexType.Fields) - { - if (!ValidationHelper.IsAccessible(field)) - { - loggingSession.Write( - DisallowedInaccessibleIntrospectionField( - field, - type.Name, - schema)); - } + // Introspection fields must be accessible. + if (type.IsIntrospectionType && !ValidationHelper.IsAccessible(field)) + { + context.Log.Write( + DisallowedInaccessibleIntrospectionField( + field, + type.Name, + schema)); + } + } - foreach (var argument in field.Arguments) - { - if (!ValidationHelper.IsAccessible(argument)) - { - loggingSession.Write( - DisallowedInaccessibleIntrospectionArgument( - argument, - field.Name, - type.Name, - schema)); - } - } - } - } - } - } + public void Handle(FieldArgumentEvent @event, CompositionContext context) + { + var (argument, field, type, schema) = @event; - foreach (var directive in schema.DirectiveDefinitions) - { - if (BuiltIns.IsBuiltInDirective(directive.Name)) - { - foreach (var argument in directive.Arguments) - { - if (!ValidationHelper.IsAccessible(argument)) - { - loggingSession.Write( - DisallowedInaccessibleDirectiveArgument( - argument, - directive.Name, - schema)); - } - } - } - } + // Introspection arguments must be accessible. + if (type.IsIntrospectionType && !ValidationHelper.IsAccessible(argument)) + { + context.Log.Write( + DisallowedInaccessibleIntrospectionArgument( + argument, + field.Name, + type.Name, + schema)); } + } - return loggingSession.ErrorCount == 0 - ? CompositionResult.Success() - : ErrorHelper.PreMergeValidationRuleFailed(this); + public void Handle(DirectiveArgumentEvent @event, CompositionContext context) + { + var (argument, directive, schema) = @event; + + // Built-in directive arguments must be accessible. + if (BuiltIns.IsBuiltInDirective(directive.Name) && !ValidationHelper.IsAccessible(argument)) + { + context.Log.Write( + DisallowedInaccessibleDirectiveArgument( + argument, + directive.Name, + schema)); + } } } diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Rules/OutputFieldTypesMergeableRule.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Rules/OutputFieldTypesMergeableRule.cs index e93c6e91b14..f85f44b8aef 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Rules/OutputFieldTypesMergeableRule.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Rules/OutputFieldTypesMergeableRule.cs @@ -1,7 +1,5 @@ -using HotChocolate.Fusion.Errors; -using HotChocolate.Fusion.Logging; -using HotChocolate.Fusion.PreMergeValidation.Contracts; -using HotChocolate.Fusion.Results; +using HotChocolate.Fusion.Events; +using static HotChocolate.Fusion.Logging.LogEntryHelper; namespace HotChocolate.Fusion.PreMergeValidation.Rules; @@ -12,28 +10,15 @@ namespace HotChocolate.Fusion.PreMergeValidation.Rules; /// /// Specification /// -internal sealed class OutputFieldTypesMergeableRule : IPreMergeValidationRule +internal sealed class OutputFieldTypesMergeableRule : IEventHandler { - public CompositionResult Run(PreMergeValidationContext context) + public void Handle(OutputFieldGroupEvent @event, CompositionContext context) { - var loggingSession = context.Log.CreateSession(); + var (fieldName, fieldGroup, typeName) = @event; - foreach (var outputTypeInfo in context.OutputTypeInfo) + if (!ValidationHelper.FieldsAreMergeable([.. fieldGroup.Select(i => i.Field)])) { - foreach (var fieldInfo in outputTypeInfo.FieldInfo) - { - if (!ValidationHelper.FieldsAreMergeable(fieldInfo.Fields)) - { - loggingSession.Write( - LogEntryHelper.OutputFieldTypesNotMergeable( - fieldInfo.FieldName, - outputTypeInfo.TypeName)); - } - } + context.Log.Write(OutputFieldTypesNotMergeable(fieldName, typeName)); } - - return loggingSession.ErrorCount == 0 - ? CompositionResult.Success() - : ErrorHelper.PreMergeValidationRuleFailed(this); } } diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.Designer.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.Designer.cs index b3a9cb1b02d..8a2c9d46b34 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.Designer.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.Designer.cs @@ -60,11 +60,20 @@ internal CompositionResources() { } /// - /// Looks up a localized string similar to Pre-merge validation rule '{0}' failed. View the composition log for details.. + /// Looks up a localized string similar to Pre-merge validation failed. View the composition log for details.. /// - internal static string ErrorHelper_PreMergeValidationRuleFailed { + internal static string ErrorHelper_PreMergeValidationFailed { get { - return ResourceManager.GetString("ErrorHelper_PreMergeValidationRuleFailed", resourceCulture); + return ResourceManager.GetString("ErrorHelper_PreMergeValidationFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The built-in scalar type '{0}' is not accessible.. + /// + internal static string LogEntryHelper_DisallowedInaccessibleBuiltInScalar { + get { + return ResourceManager.GetString("LogEntryHelper_DisallowedInaccessibleBuiltInScalar", resourceCulture); } } @@ -104,15 +113,6 @@ internal static string LogEntryHelper_DisallowedInaccessibleIntrospectionType { } } - /// - /// Looks up a localized string similar to The built-in scalar type '{0}' is not accessible.. - /// - internal static string LogEntryHelper_DisallowedInaccessibleScalar { - get { - return ResourceManager.GetString("LogEntryHelper_DisallowedInaccessibleScalar", resourceCulture); - } - } - /// /// Looks up a localized string similar to Field '{0}' on type '{1}' is not mergeable.. /// diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.resx b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.resx index d291d0f232a..78fde65b49b 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.resx +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.resx @@ -18,10 +18,10 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Pre-merge validation rule '{0}' failed. View the composition log for details. + + Pre-merge validation failed. View the composition log for details. - + The built-in scalar type '{0}' is not accessible. diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/SourceSchemaMerger.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/SourceSchemaMerger.cs index eb2266bc711..61e5b4658f7 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/SourceSchemaMerger.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/SourceSchemaMerger.cs @@ -1,5 +1,6 @@ using HotChocolate.Fusion.PostMergeValidation; using HotChocolate.Fusion.PreMergeValidation; +using HotChocolate.Fusion.PreMergeValidation.Rules; using HotChocolate.Fusion.Results; using HotChocolate.Skimmed; @@ -10,7 +11,8 @@ internal sealed class SourceSchemaMerger public CompositionResult Merge(CompositionContext context) { // Pre Merge Validation - var preMergeValidationResult = new PreMergeValidator().Validate(context); + var preMergeValidationResult = + new PreMergeValidator(_preMergeValidationRules).Validate(context); if (preMergeValidationResult.IsFailure) { @@ -41,4 +43,10 @@ private CompositionResult MergeSchemaDefinitions(CompositionCo // FIXME: Implement. return new SchemaDefinition(); } + + private static readonly List _preMergeValidationRules = + [ + new DisallowedInaccessibleElementsRule(), + new OutputFieldTypesMergeableRule() + ]; } diff --git a/src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/PreMergeValidation/Rules/DisallowedInaccessibleElementsRuleTests.cs b/src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/PreMergeValidation/Rules/DisallowedInaccessibleElementsRuleTests.cs index b46fee89348..3789b4ad1b3 100644 --- a/src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/PreMergeValidation/Rules/DisallowedInaccessibleElementsRuleTests.cs +++ b/src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/PreMergeValidation/Rules/DisallowedInaccessibleElementsRuleTests.cs @@ -14,13 +14,11 @@ public async Task Examples_Valid(string[] sdl) { // arrange var log = new CompositionLog(); - var context = new PreMergeValidationContext( - new CompositionContext([.. sdl.Select(SchemaParser.Parse)], log)); - - context.Initialize(); + var context = new CompositionContext([.. sdl.Select(SchemaParser.Parse)], log); + var preMergeValidator = new PreMergeValidator([new DisallowedInaccessibleElementsRule()]); // act - var result = new DisallowedInaccessibleElementsRule().Run(context); + var result = preMergeValidator.Validate(context); // assert await Assert.That(result.IsSuccess).IsTrue(); @@ -33,13 +31,11 @@ public async Task Examples_Invalid(string[] sdl) { // arrange var log = new CompositionLog(); - var context = new PreMergeValidationContext( - new CompositionContext([.. sdl.Select(SchemaParser.Parse)], log)); - - context.Initialize(); + var context = new CompositionContext([.. sdl.Select(SchemaParser.Parse)], log); + var preMergeValidator = new PreMergeValidator([new DisallowedInaccessibleElementsRule()]); // act - var result = new DisallowedInaccessibleElementsRule().Run(context); + var result = preMergeValidator.Validate(context); // assert await Assert.That(result.IsFailure).IsTrue(); diff --git a/src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/PreMergeValidation/Rules/OutputFieldTypesMergeableRuleTests.cs b/src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/PreMergeValidation/Rules/OutputFieldTypesMergeableRuleTests.cs index 998ee86ef0d..c87318a0e0a 100644 --- a/src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/PreMergeValidation/Rules/OutputFieldTypesMergeableRuleTests.cs +++ b/src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/PreMergeValidation/Rules/OutputFieldTypesMergeableRuleTests.cs @@ -14,13 +14,11 @@ public async Task Examples_Valid(string[] sdl) { // arrange var log = new CompositionLog(); - var context = new PreMergeValidationContext( - new CompositionContext([.. sdl.Select(SchemaParser.Parse)], log)); - - context.Initialize(); + var context = new CompositionContext([.. sdl.Select(SchemaParser.Parse)], log); + var preMergeValidator = new PreMergeValidator([new OutputFieldTypesMergeableRule()]); // act - var result = new OutputFieldTypesMergeableRule().Run(context); + var result = preMergeValidator.Validate(context); // assert await Assert.That(result.IsSuccess).IsTrue(); @@ -33,13 +31,11 @@ public async Task Examples_Invalid(string[] sdl) { // arrange var log = new CompositionLog(); - var context = new PreMergeValidationContext( - new CompositionContext([.. sdl.Select(SchemaParser.Parse)], log)); - - context.Initialize(); + var context = new CompositionContext([.. sdl.Select(SchemaParser.Parse)], log); + var preMergeValidator = new PreMergeValidator([new OutputFieldTypesMergeableRule()]); // act - var result = new OutputFieldTypesMergeableRule().Run(context); + var result = preMergeValidator.Validate(context); // assert await Assert.That(result.IsFailure).IsTrue();