Skip to content

Commit

Permalink
Rebase
Browse files Browse the repository at this point in the history
  • Loading branch information
rickardp committed Sep 24, 2024
1 parent 8444c21 commit a7d8a79
Show file tree
Hide file tree
Showing 34 changed files with 537 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,13 @@ public async ValueTask FormatAsync(
response.GetTypedHeaders().CacheControl = cacheControlHeaderValue;
}

if (result.ContextData is not null
&& result.ContextData.TryGetValue(VaryHeaderValue, out var varyValue)
&& varyValue is string varyHeaderValue)
{
response.Headers.Vary = varyHeaderValue;
}

OnWriteResponseHeaders(operationResult, format, response.Headers);

await format.Formatter.FormatAsync(result, response.Body, cancellationToken);
Expand Down
28 changes: 23 additions & 5 deletions src/HotChocolate/Caching/src/Caching/CacheControlAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ namespace HotChocolate.Caching;
public sealed class CacheControlAttribute : DescriptorAttribute
{
private int? _maxAge;
private int? _sharedMaxAge;
private CacheControlScope? _scope;
private bool? _inheritMaxAge;
private string[]? _vary;

public CacheControlAttribute()
{
Expand All @@ -41,16 +43,16 @@ protected internal override void TryConfigure(
switch (descriptor)
{
case IObjectFieldDescriptor objectField:
objectField.CacheControl(_maxAge, _scope, _inheritMaxAge);
objectField.CacheControl(_maxAge, _scope, _inheritMaxAge, _sharedMaxAge, _vary);
break;
case IObjectTypeDescriptor objectType:
objectType.CacheControl(_maxAge, _scope);
objectType.CacheControl(_maxAge, _scope, _sharedMaxAge, _vary);
break;
case IInterfaceTypeDescriptor interfaceType:
interfaceType.CacheControl(_maxAge, _scope);
interfaceType.CacheControl(_maxAge, _scope, _sharedMaxAge, _vary);
break;
case IUnionTypeDescriptor unionType:
unionType.CacheControl(_maxAge, _scope);
unionType.CacheControl(_maxAge, _scope, _sharedMaxAge, _vary);
break;
}
}
Expand All @@ -60,6 +62,12 @@ protected internal override void TryConfigure(
/// </summary>
public int MaxAge { get => _maxAge ?? CacheControlDefaults.MaxAge; set => _maxAge = value; }

/// <summary>
/// The maximum time, in seconds, this resource can be cached on CDNs and other shared caches.
/// If not set, the value of <c>MaxAge</c> is used for shared caches too.
/// </summary>
public int SharedMaxAge { get => _sharedMaxAge ?? 0; set => _sharedMaxAge = value; }

/// <summary>
/// The scope of this resource.
/// </summary>
Expand All @@ -70,12 +78,22 @@ public CacheControlScope Scope
}

/// <summary>
/// Whether this resource should inherit the <c>MaxAge</c>
/// Whether this resource should inherit the <c>MaxAge</c> and <c>SharedMaxAge</c>
/// of its parent.
/// </summary>
public bool InheritMaxAge
{
get => _inheritMaxAge ?? false;
set => _inheritMaxAge = value;
}

/// <summary>
/// List of headers that might affect the value of this resource. Typically, these headers becomes part
/// of the cache key.
/// </summary>
public string[]? Vary
{
get => _vary ?? Array.Empty<string>();
set => _vary = value;
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using HotChocolate.Execution.Processing;
using HotChocolate.Language;
using HotChocolate.Types;
using HotChocolate.Types.Introspection;
using HotChocolate.Utilities;
using Microsoft.Net.Http.Headers;
using Microsoft.Extensions.Primitives;
using IHasDirectives = HotChocolate.Types.IHasDirectives;

namespace HotChocolate.Caching;
Expand All @@ -26,23 +29,25 @@ public void OptimizeOperation(OperationOptimizerContext context)

var constraints = ComputeCacheControlConstraints(context.CreateOperation());

if (constraints.MaxAge is not null)
if (constraints.MaxAge is not null || constraints.SharedMaxAge is not null)
{
var headerValue = new CacheControlHeaderValue
{
Private = constraints.Scope == CacheControlScope.Private,
MaxAge = TimeSpan.FromSeconds(constraints.MaxAge.Value),
MaxAge = constraints.MaxAge is not null ? TimeSpan.FromSeconds(constraints.MaxAge.Value) : null,
SharedMaxAge = constraints.SharedMaxAge is not null ? TimeSpan.FromSeconds(constraints.SharedMaxAge.Value) : null,
};

context.ContextData.Add(
WellKnownContextData.CacheControlConstraints,
new ImmutableCacheConstraints(
constraints.MaxAge.Value,
constraints.Scope));
WellKnownContextData.CacheControlHeaderValue,
headerValue.ToString());
}

if (constraints.Vary is { Length: > 0 })
{
context.ContextData.Add(
WellKnownContextData.CacheControlHeaderValue,
headerValue);
WellKnownContextData.VaryHeaderValue,
string.Join(", ", constraints.Vary));
}
}

Expand All @@ -67,11 +72,25 @@ private static void ProcessSelection(
{
var field = selection.Field;
var maxAgeSet = false;
var sharedMaxAgeSet = false;
var scopeSet = false;
var varySet = false;

ExtractCacheControlDetailsFromDirectives(field.Directives);

if (!maxAgeSet || !scopeSet)
if (!maxAgeSet || !sharedMaxAgeSet || !scopeSet || !varySet)
{
// Either maxAge or scope have not been specified by the @cacheControl
// directive on the field, so we try to infer these details
// from the type of the field.

if (field.Type is IHasDirectives type)
{
// The type of the field is complex and can therefore be
// annotated with a @cacheControl directive.
ExtractCacheControlDetailsFromDirectives(type.Directives);
}
}
{
// Either maxAge or scope have not been specified by the @cacheControl
// directive on the field, so we try to infer these details
Expand Down Expand Up @@ -127,6 +146,22 @@ void ExtractCacheControlDetailsFromDirectives(
maxAgeSet = true;
}

if (!sharedMaxAgeSet &&
directive.SharedMaxAge.HasValue &&
(!constraints.SharedMaxAge.HasValue || directive.SharedMaxAge < constraints.SharedMaxAge.Value))
{
// The maxAge of the @cacheControl directive is lower
// than the previously lowest maxAge value.
constraints.SharedMaxAge = directive.SharedMaxAge.Value;
sharedMaxAgeSet = true;
}
else if (directive.InheritMaxAge == true)
{
// If inheritMaxAge is set, we keep the
// computed maxAge value as is.
sharedMaxAgeSet = true;
}

if (directive.Scope.HasValue &&
directive.Scope < constraints.Scope)
{
Expand All @@ -135,6 +170,19 @@ void ExtractCacheControlDetailsFromDirectives(
constraints.Scope = directive.Scope.Value;
scopeSet = true;
}

if (directive.Vary is { Length: > 0 })
{
if (constraints.Vary != null)
{
constraints.Vary = constraints.Vary.Concat(directive.Vary.Select(x => x.ToLowerInvariant())).Distinct().OrderBy(x => x).ToArray();
}
else
{
constraints.Vary = directive.Vary.Select(x => x.ToLowerInvariant()).Distinct().OrderBy(x => x).ToArray();
}
varySet = true;
}
}
}
}
Expand Down Expand Up @@ -163,5 +211,9 @@ private sealed class CacheControlConstraints
public CacheControlScope Scope { get; set; } = CacheControlScope.Public;

internal int? MaxAge { get; set; }

internal int? SharedMaxAge { get; set; }

internal string[]? Vary { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,5 +133,20 @@ private static void ValidateCacheControlOnField(
validationContext.ReportError(error);
}
}

if (directive.SharedMaxAge.HasValue)
{
if (directive.SharedMaxAge.Value < 0)
{
var error = ErrorHelper.CacheControlNegativeSharedMaxAge(obj, field);
validationContext.ReportError(error);
}

if (inheritMaxAge)
{
var error = ErrorHelper.CacheControlBothSharedMaxAgeAndInheritMaxAge(obj, field);
validationContext.ReportError(error);
}
}
}
}
22 changes: 22 additions & 0 deletions src/HotChocolate/Caching/src/Caching/ErrorHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@ public static ISchemaError CacheControlNegativeMaxAge(
.SetTypeSystemObject(type)
.Build();

public static ISchemaError CacheControlNegativeSharedMaxAge(
ITypeSystemObject type,
IField field)
=> SchemaErrorBuilder.New()
.SetMessage(
ErrorHelper_CacheControlNegativeSharedMaxAge,
field.Coordinate.ToString())
.SetTypeSystemObject(type)
.AddSyntaxNode(field.SyntaxNode)
.Build();

public static ISchemaError CacheControlBothMaxAgeAndInheritMaxAge(
ITypeSystemObject type,
IField field)
Expand All @@ -53,4 +64,15 @@ public static ISchemaError CacheControlBothMaxAgeAndInheritMaxAge(
field.Coordinate.ToString())
.SetTypeSystemObject(type)
.Build();

public static ISchemaError CacheControlBothSharedMaxAgeAndInheritMaxAge(
ITypeSystemObject type,
IField field)
=> SchemaErrorBuilder.New()
.SetMessage(
ErrorHelper_CacheControlBothSharedMaxAgeAndInheritMaxAge,
field.Coordinate.ToString())
.SetTypeSystemObject(type)
.AddSyntaxNode(field.SyntaxNode)
.Build();
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,25 @@ public static class CacheControlInterfaceTypeDescriptorExtensions
/// <param name="scope">
/// The scope of fields of this type.
/// </param>
/// <param name="sharedMaxAge">
/// The maximum time, in seconds, fields of this
/// type should be cached in a shared cache.
/// </param>
/// <param name="vary">
/// List of headers that might affect the value of this resource.
/// </param>
public static IInterfaceTypeDescriptor CacheControl(
this IInterfaceTypeDescriptor descriptor,
int? maxAge = null, CacheControlScope? scope = null)
int? maxAge = null, CacheControlScope? scope = null,
int? sharedMaxAge = null, string[]? vary = null)
{
if (descriptor is null)
{
throw new ArgumentNullException(nameof(descriptor));
}

return descriptor.Directive(
new CacheControlDirective(maxAge, scope));
new CacheControlDirective(maxAge, scope, null, sharedMaxAge, vary));
}
/// <summary>
/// Specifies the caching rules for this interface type.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,25 @@ public static class CacheControlObjectFieldDescriptorExtensions
/// Whether this field should inherit the <c>MaxAge</c>
/// from its parent.
/// </param>
/// <param name="sharedMaxAge">
/// The maximum time, in seconds, fields of this
/// type should be cached in a shared cache.
/// </param>
/// <param name="vary">
/// List of headers that might affect the value of this resource.
/// </param>
///
public static IObjectFieldDescriptor CacheControl(
this IObjectFieldDescriptor descriptor,
int? maxAge = null, CacheControlScope? scope = null,
bool? inheritMaxAge = null)
bool? inheritMaxAge = null, int? sharedMaxAge = null, string[]? vary = null)
{
if (descriptor is null)
{
throw new ArgumentNullException(nameof(descriptor));
}

return descriptor.Directive(
new CacheControlDirective(maxAge, scope, inheritMaxAge));
new CacheControlDirective(maxAge, scope, inheritMaxAge, sharedMaxAge, vary));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,25 @@ public static class CacheControlObjectTypeDescriptorExtensions
/// <param name="scope">
/// The scope of fields of this type.
/// </param>
/// <param name="sharedMaxAge">
/// The maximum time, in seconds, fields of this
/// type should be cached in a shared cache.
/// </param>
/// <param name="vary">
/// List of headers that might affect the value of this resource.
/// </param>
public static IObjectTypeDescriptor CacheControl(
this IObjectTypeDescriptor descriptor,
int? maxAge = null, CacheControlScope? scope = null)
int? maxAge = null, CacheControlScope? scope = null,
int? sharedMaxAge = null, string[]? vary = null)
{
if (descriptor is null)
{
throw new ArgumentNullException(nameof(descriptor));
}

return descriptor.Directive(
new CacheControlDirective(maxAge, scope));
new CacheControlDirective(maxAge, scope, null, sharedMaxAge, vary));
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,24 @@ public static class CacheControlUnionTypeDescriptorExtensions
/// <param name="scope">
/// The scope of fields of this type.
/// </param>
/// <param name="sharedMaxAge">
/// The maximum time, in seconds, fields of this
/// type should be cached in a shared cache.
/// </param>
/// <param name="vary">
/// List of headers that might affect the value of this resource.
/// </param>
public static IUnionTypeDescriptor CacheControl(
this IUnionTypeDescriptor descriptor,
int? maxAge = null, CacheControlScope? scope = null)
int? maxAge = null, CacheControlScope? scope = null,
int? sharedMaxAge = null, string[]? vary = null)
{
if (descriptor is null)
{
throw new ArgumentNullException(nameof(descriptor));
}

return descriptor.Directive(
new CacheControlDirective(maxAge, scope));
new CacheControlDirective(maxAge, scope, null, sharedMaxAge, vary));
}
}
Loading

0 comments on commit a7d8a79

Please sign in to comment.