Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/All.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
<Project Path="HotChocolate/Core/src/Types.Scalars.Upload/HotChocolate.Types.Scalars.Upload.csproj" />
<Project Path="HotChocolate/Core/src/Types.Scalars/HotChocolate.Types.Scalars.csproj" />
<Project Path="HotChocolate/Core/src/Types.Shared/HotChocolate.Types.Shared.csproj" />
<Project Path="HotChocolate/Core/src/Types.Validation/HotChocolate.Types.Validation.csproj" />
<Project Path="HotChocolate/Core/src/Types/HotChocolate.Types.csproj" />
<Project Path="HotChocolate/Core/src/Validation/HotChocolate.Validation.csproj" />
</Folder>
Expand Down Expand Up @@ -137,6 +138,7 @@
<Project Path="HotChocolate/Core/test/Types.Scalars.Tests/HotChocolate.Types.Scalars.Tests.csproj" />
<Project Path="HotChocolate/Core/test/Types.Tests.Documentation/HotChocolate.Types.Tests.Documentation.csproj" />
<Project Path="HotChocolate/Core/test/Types.Tests/HotChocolate.Types.Tests.csproj" />
<Project Path="HotChocolate/Core/test/Types.Validation.Tests/HotChocolate.Types.Validation.Tests.csproj" />
<Project Path="HotChocolate/Core/test/Utilities/HotChocolate.Tests.Utilities.csproj" />
<Project Path="HotChocolate/Core/test/Validation.Tests/HotChocolate.Validation.Tests.csproj" />
</Folder>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,45 @@ public static void MatchInlineSnapshot(
ISnapshotValueFormatter? formatter = null)
=> Snapshot.Create().Add(value, formatter: formatter).MatchInline(snapshot);

public static void MatchInlineSnapshots(
this IEnumerable<object?> values,
IEnumerable<string> snapshots,
ISnapshotValueFormatter? formatter = null)
{
var valuesArray = values.ToArray();
var snapshotsArray = snapshots.ToArray();

if (valuesArray.Length != snapshotsArray.Length)
{
throw new ArgumentException(
$"The number of snapshots must be the same as the number of values ({valuesArray.Length}).",
nameof(snapshots));
}

var i = 0;
List<Exception> exceptions = [];

foreach (var value in valuesArray)
{
try
{
Snapshot
.Create()
.Add(value, formatter: formatter)
.MatchInline(snapshotsArray[i++]);
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}

if (exceptions.Count != 0)
{
throw new AggregateException(exceptions);
}
}

public static void MatchSnapshot(this Snapshot value)
=> value.Match();

Expand Down
2 changes: 2 additions & 0 deletions src/HotChocolate/Core/HotChocolate.Core.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
<Project Path="src/Types.Scalars.Upload/HotChocolate.Types.Scalars.Upload.csproj" />
<Project Path="src/Types.Scalars/HotChocolate.Types.Scalars.csproj" />
<Project Path="src/Types.Shared/HotChocolate.Types.Shared.csproj" />
<Project Path="src/Types.Validation/HotChocolate.Types.Validation.csproj" />
<Project Path="src/Types/HotChocolate.Types.csproj" />
<Project Path="src/Validation/HotChocolate.Validation.csproj" />
<Project Path="src/Execution.Operation.Abstractions/HotChocolate.Execution.Operation.Abstractions.csproj" />
Expand Down Expand Up @@ -62,6 +63,7 @@
<Project Path="test/Types.Scalars.Tests/HotChocolate.Types.Scalars.Tests.csproj" />
<Project Path="test/Types.Tests.Documentation/HotChocolate.Types.Tests.Documentation.csproj" />
<Project Path="test/Types.Tests/HotChocolate.Types.Tests.csproj" />
<Project Path="test/Types.Validation.Tests/HotChocolate.Types.Validation.Tests.csproj" />
<Project Path="test/Utilities/HotChocolate.Tests.Utilities.csproj" />
<Project Path="test/Validation.Tests/HotChocolate.Validation.Tests.csproj" />
</Folder>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace HotChocolate.Events.Contracts;

/// <summary>
/// Represents an event that is triggered during schema validation.
/// </summary>
public interface IValidationEvent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace HotChocolate.Events.Contracts;

/// <summary>
/// Represents a handler for a schema validation event.
/// </summary>
/// <typeparam name="TEvent">The type of event to handle.</typeparam>
public interface IValidationEventHandler<in TEvent> where TEvent : IValidationEvent
{
/// <summary>
/// Handles the specified schema validation event.
/// </summary>
/// <param name="event">The schema validation event to handle.</param>
/// <param name="context">The validation context.</param>
void Handle(TEvent @event, ValidationContext context);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using HotChocolate.Events.Contracts;
using HotChocolate.Types;

namespace HotChocolate.Events;

/// <summary>
/// Represents an event that is triggered when an argument is encountered during schema validation.
/// </summary>
public sealed record ArgumentEvent(IInputValueDefinition Argument) : IValidationEvent;

/// <summary>
/// Represents an event that is triggered when a complex type is encountered during schema validation.
/// </summary>
public sealed record ComplexTypeEvent(IComplexTypeDefinition ComplexType) : IValidationEvent;

/// <summary>
/// Represents an event that is triggered when a directive is encountered during schema validation.
/// </summary>
public sealed record DirectiveEvent(IDirectiveDefinition Directive) : IValidationEvent;

/// <summary>
/// Represents an event that is triggered when an Enum type is encountered during schema validation.
/// </summary>
public sealed record EnumTypeEvent(IEnumTypeDefinition EnumType) : IValidationEvent;

/// <summary>
/// Represents an event that is triggered when an enum value is encountered during schema validation.
/// </summary>
public sealed record EnumValueEvent(IEnumValue EnumValue) : IValidationEvent;

/// <summary>
/// Represents an event that is triggered when a field is encountered during schema validation.
/// </summary>
public sealed record FieldEvent(IFieldDefinition Field) : IValidationEvent;

/// <summary>
/// Represents an event that is triggered when an Input Object field is encountered during schema validation.
/// </summary>
public sealed record InputFieldEvent(IInputValueDefinition InputField) : IValidationEvent;

/// <summary>
/// Represents an event that is triggered when an Input Object type is encountered during schema validation.
/// </summary>
public sealed record InputObjectTypeEvent(IInputObjectTypeDefinition InputObjectType) : IValidationEvent;

/// <summary>
/// Represents an event that is triggered when multiple Input Object types are encountered during schema validation.
/// </summary>
public sealed record InputObjectTypesEvent(IEnumerable<IInputObjectTypeDefinition> InputObjectTypes) : IValidationEvent;

/// <summary>
/// Represents an event that is triggered when an input value is encountered during schema validation.
/// </summary>
public sealed record InputValueEvent(IInputValueDefinition InputValue) : IValidationEvent;

/// <summary>
/// Represents an event that is triggered when an Interface type is encountered during schema validation.
/// </summary>
public sealed record InterfaceTypeEvent(IInterfaceTypeDefinition InterfaceType) : IValidationEvent;

/// <summary>
/// Represents an event that is triggered when a named member is encountered during schema validation.
/// </summary>
public sealed record NamedMemberEvent(INameProvider NamedMember) : IValidationEvent;

/// <summary>
/// Represents an event that is triggered when an Object type is encountered during schema validation.
/// </summary>
public sealed record ObjectTypeEvent(IObjectTypeDefinition ObjectType) : IValidationEvent;

/// <summary>
/// Represents an event that is triggered when an output field is encountered during schema validation.
/// </summary>
public sealed record OutputFieldEvent(IOutputFieldDefinition OutputField) : IValidationEvent;

/// <summary>
/// Represents an event that is triggered when a type is encountered during schema validation.
/// </summary>
public sealed record TypeEvent(ITypeDefinition Type) : IValidationEvent;

/// <summary>
/// Represents an event that is triggered when a Union type is encountered during schema validation.
/// </summary>
public sealed record UnionTypeEvent(IUnionTypeDefinition UnionType) : IValidationEvent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="Current">

<PropertyGroup>
<PackageId>HotChocolate.Types.Validation</PackageId>
<AssemblyName>HotChocolate.Types.Validation</AssemblyName>
<RootNamespace>HotChocolate</RootNamespace>
<Description>Contains the Hot Chocolate GraphQL type system validation.</Description>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Types.Abstractions\HotChocolate.Types.Abstractions.csproj" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Update="Properties\ValidationResources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>ValidationResources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>

<ItemGroup>
<Compile Update="Properties\ValidationResources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>ValidationResources.resx</DependentUpon>
</Compile>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace HotChocolate.Logging.Contracts;

/// <summary>
/// Defines an interface for logging schema validation information, warnings, and errors.
/// </summary>
public interface IValidationLog : IEnumerable<LogEntry>
{
/// <summary>
/// Gets a value indicating whether the log contains errors.
/// </summary>
bool HasErrors { get; }

/// <summary>
/// Gets a value indicating whether the log is empty.
/// </summary>
bool IsEmpty { get; }

/// <summary>
/// Writes the specified entry to the log.
/// </summary>
/// <param name="entry">The log entry to write.</param>
void Write(LogEntry entry);

/// <summary>
/// Writes the specified entries to the log.
/// </summary>
/// <param name="entries">The log entries to write.</param>
void Write(IEnumerable<LogEntry> entries);
}
124 changes: 124 additions & 0 deletions src/HotChocolate/Core/src/Types.Validation/Logging/LogEntry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
using System.Collections.Immutable;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using HotChocolate.Buffers;
using HotChocolate.Types;

namespace HotChocolate.Logging;

/// <summary>
/// Represents an entry in a schema validation log that describes an issue encountered during the
/// validation process.
/// </summary>
public sealed record LogEntry
{
/// <summary>
/// Gets the message associated with this log entry.
/// </summary>
public string Message { get; set; } = null!;

/// <summary>
/// Gets the code associated with this log entry.
/// </summary>
public string Code { get; set; } = null!;

/// <summary>
/// Gets the severity of this log entry.
/// </summary>
public LogSeverity Severity { get; set; }

/// <summary>
/// Gets the schema coordinate associated with this log entry.
/// </summary>
public SchemaCoordinate? Coordinate { get; set; }

/// <summary>
/// Gets the type system member associated with this log entry.
/// </summary>
public ITypeSystemMember TypeSystemMember { get; set; } = null!;

/// <summary>
/// Gets the extensions associated with this log entry.
/// </summary>
public ImmutableDictionary<string, object?> Extensions { get; set; }
#if NET10_0_OR_GREATER
= [];
#else
= ImmutableDictionary<string, object?>.Empty;
#endif

/// <summary>
/// Returns a JSON string representation of the log entry.
/// </summary>
public override unsafe string ToString()
{
using var buffer = new PooledArrayWriter();
using var writer = new Utf8JsonWriter(buffer, s_serializationOptions);

writer.WriteStartObject();
Serialize(writer);
writer.WriteEndObject();

writer.Flush();

fixed (byte* b = PooledArrayWriterMarshal.GetUnderlyingBuffer(buffer))
{
return Encoding.UTF8.GetString(b, buffer.Length);
}
}

private void Serialize(Utf8JsonWriter writer)
{
writer.WriteString("message", Message);
writer.WriteString("code", Code);
writer.WriteString("severity", Severity.ToString());

if (Coordinate is not null)
{
writer.WriteString("coordinate", Coordinate.ToString());
}

switch (TypeSystemMember)
{
case IDirectiveDefinition directiveDefinition:
writer.WriteString("member", $"@{directiveDefinition.Name}");
break;
case INameProvider namedMember:
writer.WriteString("member", namedMember.Name);
break;
}

writer.WritePropertyName("extensions");
writer.WriteStartObject();

foreach (var item in Extensions.OrderBy(i => i.Key))
{
writer.WritePropertyName(item.Key);

switch (item.Value)
{
case null:
writer.WriteNullValue();
break;
case IFieldDefinition field:
writer.WriteStringValue(field.Name);
break;
case ITypeDefinition type:
writer.WriteStringValue(type.Name);
break;
default:
writer.WriteStringValue(item.Value.ToString());
break;
}
}

writer.WriteEndObject();
}

private static readonly JsonWriterOptions s_serializationOptions = new()
{
Indented = true,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
}
Loading
Loading