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..94f49f1c3bd --- /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/EventAggregator.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Events/EventAggregator.cs new file mode 100644 index 00000000000..496e1c60867 --- /dev/null +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Events/EventAggregator.cs @@ -0,0 +1,29 @@ +using HotChocolate.Fusion.Collections; + +namespace HotChocolate.Fusion.Events; + +internal sealed class EventAggregator +{ + private readonly MultiValueDictionary _subscribers = new(); + + public void Subscribe(Action handler) + { + _subscribers.Add(typeof(TEvent), handler); + } + + public void Unsubscribe(Action handler) + { + _subscribers.Remove(typeof(TEvent), handler); + } + + public void Publish(TEvent @event) + { + if (_subscribers.ContainsKey(typeof(TEvent))) + { + foreach (var handler in _subscribers[typeof(TEvent)]) + { + ((Action)handler)(@event); + } + } + } +} 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..84e325c8089 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,10 @@ namespace HotChocolate.Fusion.Logging.Contracts; /// public interface ICompositionLog { + // FIXME: Docs. + bool HasErrors { get; } + + // FIXME: Docs. bool IsEmpty { get; } /// @@ -12,10 +16,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/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/IEachDirectiveArgumentEventHandler.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Contracts/IEachDirectiveArgumentEventHandler.cs new file mode 100644 index 00000000000..801352fd4bb --- /dev/null +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Contracts/IEachDirectiveArgumentEventHandler.cs @@ -0,0 +1,6 @@ +namespace HotChocolate.Fusion.PreMergeValidation.Contracts; + +internal interface IEachDirectiveArgumentEventHandler +{ + void OnEachDirectiveArgument(EachDirectiveArgumentEvent @event); +} diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Contracts/IEachDirectiveEventHandler.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Contracts/IEachDirectiveEventHandler.cs new file mode 100644 index 00000000000..53cf8f5020c --- /dev/null +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Contracts/IEachDirectiveEventHandler.cs @@ -0,0 +1,6 @@ +namespace HotChocolate.Fusion.PreMergeValidation.Contracts; + +internal interface IEachDirectiveEventHandler +{ + void OnEachDirective(EachDirectiveEvent @event); +} diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Contracts/IEachFieldArgumentEventHandler.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Contracts/IEachFieldArgumentEventHandler.cs new file mode 100644 index 00000000000..f0d204a1ae3 --- /dev/null +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Contracts/IEachFieldArgumentEventHandler.cs @@ -0,0 +1,6 @@ +namespace HotChocolate.Fusion.PreMergeValidation.Contracts; + +internal interface IEachFieldArgumentEventHandler +{ + void OnEachFieldArgument(EachFieldArgumentEvent @event); +} diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Contracts/IEachOutputFieldEventHandler.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Contracts/IEachOutputFieldEventHandler.cs new file mode 100644 index 00000000000..ecc19bf9f67 --- /dev/null +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Contracts/IEachOutputFieldEventHandler.cs @@ -0,0 +1,6 @@ +namespace HotChocolate.Fusion.PreMergeValidation.Contracts; + +internal interface IEachOutputFieldEventHandler +{ + void OnEachOutputField(EachOutputFieldEvent @event); +} diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Contracts/IEachOutputFieldNameEventHandler.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Contracts/IEachOutputFieldNameEventHandler.cs new file mode 100644 index 00000000000..96d74460032 --- /dev/null +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Contracts/IEachOutputFieldNameEventHandler.cs @@ -0,0 +1,6 @@ +namespace HotChocolate.Fusion.PreMergeValidation.Contracts; + +internal interface IEachOutputFieldNameEventHandler +{ + void OnEachOutputFieldName(EachOutputFieldNameEvent @event); +} diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Contracts/IEachTypeEventHandler.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Contracts/IEachTypeEventHandler.cs new file mode 100644 index 00000000000..071d939ad13 --- /dev/null +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Contracts/IEachTypeEventHandler.cs @@ -0,0 +1,6 @@ +namespace HotChocolate.Fusion.PreMergeValidation.Contracts; + +internal interface IEachTypeEventHandler +{ + void OnEachType(EachTypeEvent @event); +} 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..fd2978f5411 --- /dev/null +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Events.cs @@ -0,0 +1,51 @@ +using System.Collections.Immutable; +using HotChocolate.Skimmed; + +namespace HotChocolate.Fusion.PreMergeValidation; + +internal record EachDirectiveArgumentEvent( + CompositionContext Context, + InputFieldDefinition Argument, + DirectiveDefinition Directive, + SchemaDefinition Schema); + +internal record EachDirectiveEvent( + CompositionContext Context, + DirectiveDefinition Directive, + SchemaDefinition Schema); + +internal record EachFieldArgumentEvent( + CompositionContext Context, + InputFieldDefinition Argument, + OutputFieldDefinition Field, + INamedTypeDefinition Type, + SchemaDefinition Schema); + +internal record EachFieldArgumentNameEvent( + CompositionContext Context, + string ArgumentName, + ImmutableArray ArgumentInfo, + string FieldName, + string TypeName); + +internal record EachOutputFieldEvent( + CompositionContext Context, + OutputFieldDefinition Field, + INamedTypeDefinition Type, + SchemaDefinition Schema); + +internal record EachOutputFieldNameEvent( + CompositionContext Context, + string FieldName, + ImmutableArray FieldInfo, + string TypeName); + +internal record EachTypeEvent( + CompositionContext Context, + INamedTypeDefinition Type, + SchemaDefinition Schema); + +internal record EachTypeNameEvent( + CompositionContext Context, + string TypeName, + ImmutableArray TypeInfo); 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..8c949f815a2 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,175 @@ -using System.Collections.Immutable; +using HotChocolate.Fusion.Collections; using HotChocolate.Fusion.Errors; +using HotChocolate.Fusion.Events; using HotChocolate.Fusion.PreMergeValidation.Contracts; -using HotChocolate.Fusion.PreMergeValidation.Rules; using HotChocolate.Fusion.Results; +using HotChocolate.Skimmed; namespace HotChocolate.Fusion.PreMergeValidation; internal sealed class PreMergeValidator { - private readonly ImmutableArray _validationRules = - [ - new DisallowedInaccessibleElementsRule(), - new OutputFieldTypesMergeableRule() - ]; + private readonly EventAggregator _eventAggregator; - public CompositionResult Validate(CompositionContext compositionContext) + public PreMergeValidator(IEnumerable rules) { - var preMergeValidationContext = new PreMergeValidationContext(compositionContext); - preMergeValidationContext.Initialize(); + _eventAggregator = new EventAggregator(); - var errors = new List(); + SubscribeRules(rules); + } + + public CompositionResult Validate(CompositionContext context) + { + PublishEvents(context); + + return context.Log.HasErrors + ? ErrorHelper.PreMergeValidationFailed() + : CompositionResult.Success(); + } + + private void SubscribeRules(IEnumerable rules) + { + foreach (var rule in rules) + { + if (rule is IEachTypeEventHandler eachTypeEventHandler) + { + _eventAggregator.Subscribe( + eachTypeEventHandler.OnEachType); + } + + if (rule is IEachOutputFieldEventHandler eachOutputFieldEventHandler) + { + _eventAggregator.Subscribe( + eachOutputFieldEventHandler.OnEachOutputField); + } + + if (rule is IEachFieldArgumentEventHandler eachFieldArgumentEventHandler) + { + _eventAggregator.Subscribe( + eachFieldArgumentEventHandler.OnEachFieldArgument); + } + + if (rule is IEachDirectiveEventHandler eachDirectiveEventHandler) + { + _eventAggregator.Subscribe( + eachDirectiveEventHandler.OnEachDirective); + } + + if (rule is IEachDirectiveArgumentEventHandler eachDirectiveArgumentEventHandler) + { + _eventAggregator.Subscribe( + eachDirectiveArgumentEventHandler.OnEachDirectiveArgument); + } + + if (rule is IEachOutputFieldNameEventHandler eachOutputFieldNameEventHandler) + { + _eventAggregator.Subscribe( + eachOutputFieldNameEventHandler.OnEachOutputFieldName); + } + } + } + + private void PublishEvents(CompositionContext context) + { + MultiValueDictionary typeInfoByName = []; - foreach (var validationRule in _validationRules) + foreach (var schema in context.SchemaDefinitions) { - var result = validationRule.Run(preMergeValidationContext); + foreach (var type in schema.Types) + { + _eventAggregator.Publish(new EachTypeEvent(context, type, schema)); + + typeInfoByName.Add(type.Name, new TypeInfo(type, schema)); + + if (type is ComplexTypeDefinition complexType) + { + foreach (var field in complexType.Fields) + { + _eventAggregator.Publish( + new EachOutputFieldEvent(context, field, type, schema)); - if (result.IsFailure) + foreach (var argument in field.Arguments) + { + _eventAggregator.Publish( + new EachFieldArgumentEvent(context, argument, field, type, schema)); + } + } + } + } + + foreach (var directive in schema.DirectiveDefinitions) { - errors.AddRange(result.Errors); + _eventAggregator.Publish( + new EachDirectiveEvent(context, directive, schema)); + + foreach (var argument in directive.Arguments) + { + _eventAggregator.Publish( + new EachDirectiveArgumentEvent(context, argument, directive, schema)); + } } } - return errors; + foreach (var (typeName, typeInfo) in typeInfoByName) + { + _eventAggregator.Publish(new EachTypeNameEvent(context, typeName, [.. typeInfo])); + + MultiValueDictionary fieldInfoByName = []; + + foreach (var (type, schema) in typeInfo) + { + if (type is ComplexTypeDefinition complexType) + { + foreach (var field in complexType.Fields) + { + fieldInfoByName.Add(field.Name, new OutputFieldInfo(field, type, schema)); + } + } + } + + foreach (var (fieldName, fieldInfo) in fieldInfoByName) + { + _eventAggregator.Publish( + new EachOutputFieldNameEvent(context, fieldName, [.. fieldInfo], typeName)); + + MultiValueDictionary argumentInfoByName = []; + + foreach (var (field, type, schema) in fieldInfo) + { + foreach (var argument in field.Arguments) + { + argumentInfoByName.Add( + argument.Name, + new FieldArgumentInfo(argument, field, type, schema)); + } + } + + foreach (var (argumentName, argumentInfo) in argumentInfoByName) + { + _eventAggregator.Publish( + new EachFieldArgumentNameEvent( + context, + argumentName, + [.. argumentInfo], + fieldName, + typeName)); + } + } + } } } + +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..1614461728e 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.Skimmed; using static HotChocolate.Fusion.Logging.LogEntryHelper; @@ -15,80 +13,76 @@ namespace HotChocolate.Fusion.PreMergeValidation.Rules; /// /// Specification /// -internal sealed class DisallowedInaccessibleElementsRule : IPreMergeValidationRule +internal sealed class DisallowedInaccessibleElementsRule + : IEachTypeEventHandler + , IEachOutputFieldEventHandler + , IEachFieldArgumentEventHandler + , IEachDirectiveArgumentEventHandler { - public CompositionResult Run(PreMergeValidationContext context) + public void OnEachType(EachTypeEvent @event) { - var loggingSession = context.Log.CreateSession(); + var (context, 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) + context.Log.Write(DisallowedInaccessibleScalar(scalar, schema)); + } + + // Introspection types must be accessible. + if (type.IsIntrospectionType) + { + if (!ValidationHelper.IsAccessible(type)) { - if (type is ScalarTypeDefinition { IsSpecScalar: true } scalar - && !ValidationHelper.IsAccessible(type)) - { - loggingSession.Write(DisallowedInaccessibleScalar(scalar, schema)); - } + context.Log.Write(DisallowedInaccessibleIntrospectionType(type, schema)); + } + } + } - if (type.IsIntrospectionType) - { - if (!ValidationHelper.IsAccessible(type)) - { - loggingSession.Write(DisallowedInaccessibleIntrospectionType(type, schema)); - } + public void OnEachOutputField(EachOutputFieldEvent @event) + { + var (context, 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 OnEachFieldArgument(EachFieldArgumentEvent @event) + { + var (context, 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)); } + } + + public void OnEachDirectiveArgument(EachDirectiveArgumentEvent @event) + { + var (context, argument, directive, schema) = @event; - return loggingSession.ErrorCount == 0 - ? CompositionResult.Success() - : ErrorHelper.PreMergeValidationRuleFailed(this); + // 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..5cec7b28ba6 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; namespace HotChocolate.Fusion.PreMergeValidation.Rules; @@ -12,28 +10,16 @@ namespace HotChocolate.Fusion.PreMergeValidation.Rules; /// /// Specification /// -internal sealed class OutputFieldTypesMergeableRule : IPreMergeValidationRule +internal sealed class OutputFieldTypesMergeableRule + : IEachOutputFieldNameEventHandler { - public CompositionResult Run(PreMergeValidationContext context) + public void OnEachOutputFieldName(EachOutputFieldNameEvent @event) { - var loggingSession = context.Log.CreateSession(); + var (context, fieldName, fieldInfo, typeName) = @event; - foreach (var outputTypeInfo in context.OutputTypeInfo) + if (!ValidationHelper.FieldsAreMergeable([.. fieldInfo.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(LogEntryHelper.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..416eba365d5 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,11 @@ 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); } } 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..6ec14bcc4c0 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.resx +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.resx @@ -18,8 +18,8 @@ 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();