Skip to content

Commit

Permalink
Forked Feature Collection from ASP.NET Core (#7067)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelstaib authored Apr 19, 2024
1 parent 1c80423 commit 723733e
Show file tree
Hide file tree
Showing 13 changed files with 767 additions and 0 deletions.
30 changes: 30 additions & 0 deletions src/HotChocolate/Core/HotChocolate.Core.sln
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Types.Queries"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Types.Queries.Tests", "test\Types.Queries.Tests\HotChocolate.Types.Queries.Tests.csproj", "{AE9AF1C7-578A-46A5-84FD-9BBA8EB8DE22}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Features", "src\Features\HotChocolate.Features.csproj", "{669FA147-3B41-4841-921A-55B019C3AF26}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Features.Tests", "test\Features.Tests\HotChocolate.Features.Tests.csproj", "{EA77D317-8767-4DDE-8038-820D582C52D6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -971,6 +975,30 @@ Global
{AE9AF1C7-578A-46A5-84FD-9BBA8EB8DE22}.Release|x64.Build.0 = Release|Any CPU
{AE9AF1C7-578A-46A5-84FD-9BBA8EB8DE22}.Release|x86.ActiveCfg = Release|Any CPU
{AE9AF1C7-578A-46A5-84FD-9BBA8EB8DE22}.Release|x86.Build.0 = Release|Any CPU
{669FA147-3B41-4841-921A-55B019C3AF26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{669FA147-3B41-4841-921A-55B019C3AF26}.Debug|Any CPU.Build.0 = Debug|Any CPU
{669FA147-3B41-4841-921A-55B019C3AF26}.Debug|x64.ActiveCfg = Debug|Any CPU
{669FA147-3B41-4841-921A-55B019C3AF26}.Debug|x64.Build.0 = Debug|Any CPU
{669FA147-3B41-4841-921A-55B019C3AF26}.Debug|x86.ActiveCfg = Debug|Any CPU
{669FA147-3B41-4841-921A-55B019C3AF26}.Debug|x86.Build.0 = Debug|Any CPU
{669FA147-3B41-4841-921A-55B019C3AF26}.Release|Any CPU.ActiveCfg = Release|Any CPU
{669FA147-3B41-4841-921A-55B019C3AF26}.Release|Any CPU.Build.0 = Release|Any CPU
{669FA147-3B41-4841-921A-55B019C3AF26}.Release|x64.ActiveCfg = Release|Any CPU
{669FA147-3B41-4841-921A-55B019C3AF26}.Release|x64.Build.0 = Release|Any CPU
{669FA147-3B41-4841-921A-55B019C3AF26}.Release|x86.ActiveCfg = Release|Any CPU
{669FA147-3B41-4841-921A-55B019C3AF26}.Release|x86.Build.0 = Release|Any CPU
{EA77D317-8767-4DDE-8038-820D582C52D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EA77D317-8767-4DDE-8038-820D582C52D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EA77D317-8767-4DDE-8038-820D582C52D6}.Debug|x64.ActiveCfg = Debug|Any CPU
{EA77D317-8767-4DDE-8038-820D582C52D6}.Debug|x64.Build.0 = Debug|Any CPU
{EA77D317-8767-4DDE-8038-820D582C52D6}.Debug|x86.ActiveCfg = Debug|Any CPU
{EA77D317-8767-4DDE-8038-820D582C52D6}.Debug|x86.Build.0 = Debug|Any CPU
{EA77D317-8767-4DDE-8038-820D582C52D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EA77D317-8767-4DDE-8038-820D582C52D6}.Release|Any CPU.Build.0 = Release|Any CPU
{EA77D317-8767-4DDE-8038-820D582C52D6}.Release|x64.ActiveCfg = Release|Any CPU
{EA77D317-8767-4DDE-8038-820D582C52D6}.Release|x64.Build.0 = Release|Any CPU
{EA77D317-8767-4DDE-8038-820D582C52D6}.Release|x86.ActiveCfg = Release|Any CPU
{EA77D317-8767-4DDE-8038-820D582C52D6}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1044,6 +1072,8 @@ Global
{78979585-F881-4ACD-9E83-CCB866EB971C} = {37B9D3B1-CA34-4720-9A0B-CFF1E64F52C2}
{655739D7-87E9-43EF-B522-AC421F7451A4} = {37B9D3B1-CA34-4720-9A0B-CFF1E64F52C2}
{AE9AF1C7-578A-46A5-84FD-9BBA8EB8DE22} = {7462D089-D350-44D6-8131-896D949A65B7}
{669FA147-3B41-4841-921A-55B019C3AF26} = {37B9D3B1-CA34-4720-9A0B-CFF1E64F52C2}
{EA77D317-8767-4DDE-8038-820D582C52D6} = {7462D089-D350-44D6-8131-896D949A65B7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E4D94C77-6657-4630-9D42-0A9AC5153A1B}
Expand Down
161 changes: 161 additions & 0 deletions src/HotChocolate/Core/src/Features/FeatureCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// This code was originally forked of https://github.com/dotnet/aspnetcore/tree/c7aae8ff34dce81132d0fb3a976349dcc01ff903/src/Extensions/Features/src

// ReSharper disable NonAtomicCompoundOperator
using System.Collections;

namespace HotChocolate.Features;

/// <summary>
/// Default implementation for <see cref="IFeatureCollection"/>.
/// </summary>
public class FeatureCollection : IFeatureCollection
{
private static readonly KeyComparer _featureKeyComparer = new();
private readonly IFeatureCollection? _defaults;
private readonly int _initialCapacity;
private Dictionary<Type, object>? _features;
private volatile int _containerRevision;

/// <summary>
/// Initializes a new instance of <see cref="FeatureCollection"/>.
/// </summary>
public FeatureCollection()
{
}

/// <summary>
/// Initializes a new instance of <see cref="FeatureCollection"/> with the specified initial capacity.
/// </summary>
/// <param name="initialCapacity">
/// The initial number of elements that the collection can contain.
/// </param>
/// <exception cref="System.ArgumentOutOfRangeException">
/// <paramref name="initialCapacity"/> is less than 0
/// </exception>
public FeatureCollection(int initialCapacity)
{
if (initialCapacity < 0)
{
throw new ArgumentOutOfRangeException(nameof(initialCapacity));
}

_initialCapacity = initialCapacity;
}

/// <summary>
/// Initializes a new instance of <see cref="FeatureCollection"/> with the specified defaults.
/// </summary>
/// <param name="defaults">
/// The feature defaults.
/// </param>
public FeatureCollection(IFeatureCollection defaults)
{
_defaults = defaults;
}

/// <inheritdoc />
public virtual int Revision
{
get { return _containerRevision + (_defaults?.Revision ?? 0); }
}

/// <inheritdoc />
public bool IsReadOnly { get { return false; } }

/// <inheritdoc />
public object? this[Type key]
{
get
{
if (key is null)
{
throw new ArgumentNullException(nameof(key));
}

return _features != null && _features.TryGetValue(key, out var result) ? result : _defaults?[key];
}
set
{
if (key is null)
{
throw new ArgumentNullException(nameof(key));
}

if (value == null)
{
if (_features != null && _features.Remove(key))
{
_containerRevision++;
}
return;
}

if (_features == null)
{
_features = new Dictionary<Type, object>(_initialCapacity);
}
_features[key] = value;
_containerRevision++;
}
}

IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}

/// <inheritdoc />
public IEnumerator<KeyValuePair<Type, object>> GetEnumerator()
{
if (_features != null)
{
foreach (var pair in _features)
{
yield return pair;
}
}

if (_defaults != null)
{
// Don't return features masked by the wrapper.
foreach (var pair in _features == null ? _defaults : _defaults.Except(_features, _featureKeyComparer))
{
yield return pair;
}
}
}

/// <inheritdoc />
public TFeature? Get<TFeature>()
{
if (typeof(TFeature).IsValueType)
{
var feature = this[typeof(TFeature)];
if (feature is null && Nullable.GetUnderlyingType(typeof(TFeature)) is null)
{
throw new InvalidOperationException(
$"{typeof(TFeature).FullName} does not exist in the feature collection " +
$"and because it is a struct the method can't return null. " +
$"Use 'featureCollection[typeof({typeof(TFeature).FullName})] is not null' " +
$"to check if the feature exists.");
}
return (TFeature?)feature;
}
return (TFeature?)this[typeof(TFeature)];
}

/// <inheritdoc />
public void Set<TFeature>(TFeature? instance)
{
this[typeof(TFeature)] = instance;
}

private sealed class KeyComparer : IEqualityComparer<KeyValuePair<Type, object>>
{
public bool Equals(KeyValuePair<Type, object> x, KeyValuePair<Type, object> y) =>
x.Key.Equals(y.Key);

public int GetHashCode(KeyValuePair<Type, object> obj) =>
obj.Key.GetHashCode();
}
}
73 changes: 73 additions & 0 deletions src/HotChocolate/Core/src/Features/FeatureCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// This code was originally forked of https://github.com/dotnet/aspnetcore/tree/c7aae8ff34dce81132d0fb3a976349dcc01ff903/src/Extensions/Features/src

namespace HotChocolate.Features;

/// <summary>
/// Extension methods for getting feature from <see cref="IFeatureCollection"/>
/// </summary>
public static class FeatureCollectionExtensions
{
/// <summary>
/// Retrieves the requested feature from the collection.
/// Throws an <see cref="InvalidOperationException"/> if the feature is not present.
/// </summary>
/// <param name="featureCollection">The <see cref="IFeatureCollection"/>.</param>
/// <typeparam name="TFeature">The feature key.</typeparam>
/// <returns>The requested feature.</returns>
public static TFeature GetRequiredFeature<TFeature>(this IFeatureCollection featureCollection)
where TFeature : notnull
{
if(featureCollection is null)
{
throw new ArgumentNullException(nameof(featureCollection));
}

return featureCollection.Get<TFeature>() ??
throw new InvalidOperationException($"Feature '{typeof(TFeature)}' is not present.");
}

/// <summary>
/// Retrieves the requested feature from the collection.
/// Throws an <see cref="InvalidOperationException"/> if the feature is not present.
/// </summary>
/// <param name="featureCollection">feature collection</param>
/// <param name="key">The feature key.</param>
/// <returns>The requested feature.</returns>
public static object GetRequiredFeature(this IFeatureCollection featureCollection, Type key)
{
if(featureCollection is null)
{
throw new ArgumentNullException(nameof(featureCollection));
}

if(key is null)
{
throw new ArgumentNullException(nameof(key));
}

return featureCollection[key] ??
throw new InvalidOperationException($"Feature '{key}' is not present.");
}

/// <summary>
/// Creates a readonly collection of features.
/// </summary>
/// <param name="featureCollection">
/// The <see cref="IFeatureCollection"/> to make readonly.
/// </param>
/// <returns>
/// A readonly <see cref="IFeatureCollection"/>.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="featureCollection"/> is <c>null</c>.
/// </exception>
public static IFeatureCollection ToReadOnly(this IFeatureCollection featureCollection)
{
if(featureCollection is null)
{
throw new ArgumentNullException(nameof(featureCollection));
}

return new ReadOnlyFeatureCollection(featureCollection);
}
}
54 changes: 54 additions & 0 deletions src/HotChocolate/Core/src/Features/FeatureReference.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// This code was originally forked of https://github.com/dotnet/aspnetcore/tree/c7aae8ff34dce81132d0fb3a976349dcc01ff903/src/Extensions/Features/src

namespace HotChocolate.Features;

/// <summary>
/// A cached reference to a feature.
/// </summary>
/// <typeparam name="T">The feature type.</typeparam>
public struct FeatureReference<T>
{
private T? _feature;
private int _revision;

private FeatureReference(T? feature, int revision)
{
_feature = feature;
_revision = revision;
}

/// <summary>
/// Gets the default <see cref="FeatureReference{T}"/>.
/// </summary>
public static readonly FeatureReference<T> Default = new(default, -1);

/// <summary>
/// Gets the feature of type <typeparamref name="T"/> from <paramref name="features"/>.
/// </summary>
/// <param name="features">The <see cref="IFeatureCollection"/>.</param>
/// <returns>The feature.</returns>
public T? Fetch(IFeatureCollection features)
{
if (_revision == features.Revision)
{
return _feature;
}
_feature = (T?)features[typeof(T)];
_revision = features.Revision;
return _feature;
}

/// <summary>
/// Updates the reference to the feature.
/// </summary>
/// <param name="features">The <see cref="IFeatureCollection"/> to update.</param>
/// <param name="feature">The instance of the feature.</param>
/// <returns>A reference to <paramref name="feature"/> after the operation has completed.</returns>
public T Update(IFeatureCollection features, T feature)
{
features[typeof(T)] = feature;
_feature = feature;
_revision = features.Revision;
return feature;
}
}
Loading

0 comments on commit 723733e

Please sign in to comment.