Skip to content

Commit

Permalink
Make it easier to write to the result state and the result extensions. (
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelstaib authored Dec 15, 2022
1 parent 64c5ab9 commit baf13ae
Show file tree
Hide file tree
Showing 11 changed files with 247 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ public DirectiveContext(IMiddlewareContext middlewareContext, IDirective directi

public IOperation Operation => _middlewareContext.Operation;

public IOperationResultBuilder OperationResult => _middlewareContext.OperationResult;

public ISelection Selection => _middlewareContext.Selection;

public IVariableValueCollection Variables => _middlewareContext.Variables;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Threading;
using System.Threading.Tasks;
using HotChocolate.Execution.Properties;
using HotChocolate.Execution.Serialization;
using HotChocolate.Resolvers;
using HotChocolate.Types;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -12,6 +13,7 @@ namespace HotChocolate.Execution.Processing;

internal partial class MiddlewareContext : IMiddlewareContext
{
private readonly OperationResultBuilderFacade _operationResultBuilder = new();
private readonly List<Func<ValueTask>> _cleanupTasks = new();
private OperationContext _operationContext = default!;
private IServiceProvider _services = default!;
Expand All @@ -29,6 +31,8 @@ public IServiceProvider Services

public IOperation Operation => _operationContext.Operation;

public IOperationResultBuilder OperationResult => _operationResultBuilder;

public IDictionary<string, object?> ContextData => _operationContext.ContextData;

public IVariableValueCollection Variables => _operationContext.Variables;
Expand Down Expand Up @@ -69,6 +73,7 @@ public IReadOnlyList<ISelection> GetSelections(
for (var i = 0; i < selectionCount; i++)
{
var childSelection = Unsafe.Add(ref selectionRef, i);

if (childSelection.IsIncluded(operationIncludeFlags, allowInternals))
{
finalFields.Add(childSelection);
Expand All @@ -90,11 +95,12 @@ public void ReportError(string errorMessage)
nameof(errorMessage));
}

ReportError(ErrorBuilder.New()
.SetMessage(errorMessage)
.SetPath(Path)
.AddLocation(_selection.SyntaxNode)
.Build());
ReportError(
ErrorBuilder.New()
.SetMessage(errorMessage)
.SetPath(Path)
.AddLocation(_selection.SyntaxNode)
.Build());
}

public void ReportError(Exception exception, Action<IErrorBuilder>? configure = null)
Expand Down Expand Up @@ -182,7 +188,9 @@ public async ValueTask<T> ResolveAsync<T>()
_hasResolverResult = true;
}

return _resolverResult is null ? default! : (T)_resolverResult;
return _resolverResult is null
? default!
: (T)_resolverResult;
}

public T Resolver<T>() =>
Expand Down Expand Up @@ -257,4 +265,15 @@ public IMiddlewareContext Clone()

IResolverContext IResolverContext.Clone()
=> Clone();

private sealed class OperationResultBuilderFacade : IOperationResultBuilder
{
public OperationContext Context { get; set; } = default!;

public void SetResultState(string key, object? value)
=> Context.Result.SetContextData(key, value);

public void SetExtension<TValue>(string key, TValue value)
=> Context.Result.SetExtension(key, new NeedsFormatting<TValue>(value));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public void Initialize(
IImmutableDictionary<string, object?> scopedContextData)
{
_operationContext = operationContext;
_operationResultBuilder.Context = _operationContext;
_services = operationContext.Services;
_selection = selection;
ParentResult = parentResult;
Expand All @@ -49,6 +50,7 @@ public void Clean()
_hasResolverResult = false;
_result = default;
_parser = default!;
_operationResultBuilder.Context = default!;

Path = default!;
ScopedContextData = default!;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using HotChocolate.Execution.Processing;
using HotChocolate.Utilities;
using static System.Text.Json.JsonSerializerDefaults;
using static HotChocolate.Execution.ThrowHelper;

namespace HotChocolate.Execution.Serialization;
Expand All @@ -22,6 +22,7 @@ namespace HotChocolate.Execution.Serialization;
public sealed partial class JsonResultFormatter : IQueryResultFormatter, IExecutionResultFormatter
{
private readonly JsonWriterOptions _options;
private readonly JsonSerializerOptions _serializerOptions;

/// <summary>
/// Initializes a new instance of <see cref="JsonResultFormatter"/>.
Expand All @@ -34,7 +35,8 @@ public sealed partial class JsonResultFormatter : IQueryResultFormatter, IExecut
/// </param>
public JsonResultFormatter(bool indented = false, JavaScriptEncoder? encoder = null)
{
_options = new JsonWriterOptions { Indented = indented, Encoder = encoder };
_options = new() { Indented = indented, Encoder = encoder };
_serializerOptions = new(Web) { WriteIndented = indented, Encoder = encoder };
}

/// <inheritdoc cref="IExecutionResultFormatter.FormatAsync"/>
Expand Down Expand Up @@ -606,7 +608,7 @@ private void WriteFieldValue(
WriteListResult(writer, resultMapList);
break;

#if NET5_0_OR_GREATER
#if NET6_0_OR_GREATER
case JsonElement element:
WriteJsonElement(writer, element);
break;
Expand All @@ -615,6 +617,10 @@ private void WriteFieldValue(
writer.WriteRawValue(rawJsonValue.Value.Span, true);
break;
#endif
case NeedsFormatting unformatted:
unformatted.FormatValue(writer, _serializerOptions);
break;

case Dictionary<string, object?> dict:
WriteDictionary(writer, dict);
break;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System.Text.Json;

namespace HotChocolate.Execution.Serialization;

/// <summary>
/// This helper class allows us to indicate to the formatters that the inner value
/// has a custom formatter.
/// </summary>
/// <remarks>
/// The downside of this helper is that we bind it explicitly to JSON.
/// If there were alternative query formatter that use different formats we would get
/// into trouble with this.
///
/// This is also the reason for keeping this internal.
/// </remarks>
internal abstract class NeedsFormatting
{
/// <summary>
/// Formats the value as JSON
/// </summary>
/// <param name="writer">
/// The JSON writer.
/// </param>
/// <param name="options">
/// The JSON serializer options.
/// </param>
public abstract void FormatValue(Utf8JsonWriter writer, JsonSerializerOptions options);
}

/// <summary>
/// This helper class allows us to indicate to the formatters that the inner value
/// has a custom formatter.
/// </summary>
/// <remarks>
/// The downside of this helper is that we bind it explicitly to JSON.
/// If there were alternative query formatter that use different formats we would get
/// into trouble with this.
///
/// This is also the reason for keeping this internal.
/// </remarks>
internal sealed class NeedsFormatting<TValue> : NeedsFormatting
{
private readonly TValue _value;

public NeedsFormatting(TValue value)
{
_value = value;
}

/// <summary>
/// Formats the value as JSON
/// </summary>
/// <param name="writer">
/// The JSON writer.
/// </param>
/// <param name="options">
/// The JSON serializer options.
/// </param>
public override void FormatValue(Utf8JsonWriter writer, JsonSerializerOptions options)
=> JsonSerializer.Serialize(writer, _value, options);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Text.Json;

namespace HotChocolate.Execution.Serialization;

Expand All @@ -7,6 +8,13 @@ namespace HotChocolate.Execution.Serialization;
/// The JSON query result formatter will take the inner <see cref="Value"/>
/// and writes it without validation to the JSON response object.
/// </summary>
/// <remarks>
/// The downside of this helper is that we bind it explicitly to JSON.
/// If there were alternative query formatter that use different formats we would get
/// into trouble with this.
///
/// This is also the reason for keeping this internal.
/// </remarks>
internal readonly struct RawJsonValue
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using HotChocolate.Execution;
using HotChocolate.Types;

#nullable enable
Expand Down Expand Up @@ -30,6 +29,11 @@ public interface IMiddlewareContext : IResolverContext
/// <value></value>
bool IsResultModified { get; }

/// <summary>
/// Allows to modify some aspects of the overall operation result.
/// </summary>
IOperationResultBuilder OperationResult { get; }

/// <summary>
/// Executes the field resolver and returns its result.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#nullable enable
namespace HotChocolate.Resolvers;

/// <summary>
/// This helper allows modifying some aspects of the overall operation result object.
/// </summary>
public interface IOperationResultBuilder
{
/// <summary>
/// Sets a property on the result context data which can be used for further processing
/// in the request pipeline.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="value">The value.</param>
void SetResultState(string key, object? value);

/// <summary>
/// Sets a property on the result extension data which will
/// be serialized and send to the consumer.
///
/// <code>
/// {
/// ...
/// "extensions": {
/// "yourKey": "yourValue"
/// }
/// }
/// </code>
///
/// </summary>
/// <param name="key">The key.</param>
/// <param name="value">The value.</param>
void SetExtension<TValue>(string key, TValue value);
}
Loading

0 comments on commit baf13ae

Please sign in to comment.