Skip to content

Commit

Permalink
Fix merge conflicts.
Browse files Browse the repository at this point in the history
  • Loading branch information
SeanFarrow committed Sep 15, 2023
2 parents 727537c + 98d11e7 commit 5a1adf7
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 38 deletions.
4 changes: 2 additions & 2 deletions global.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"sdk": {
"version": "7.0.306",
"rollForward": "latestMinor",
"version": "7.0.400",
"rollForward": "latestPatch",
"allowPrerelease": false
}
}
30 changes: 26 additions & 4 deletions src/Marvin.Cache.Headers/Extensions/ServicesExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using Marvin.Cache.Headers;
using Marvin.Cache.Headers.Interfaces;
using Marvin.Cache.Headers.Serialization;
using Marvin.Cache.Headers.Stores;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection.Extensions;
Expand All @@ -25,6 +26,7 @@ public static class ServicesExtensions
/// <param name="dateParserFunc">Func to provide a custom <see cref="IDateParser" /></param>
/// <param name="validatorValueStoreFunc">Func to provide a custom <see cref="IValidatorValueStore" /></param>
/// <param name="storeKeyGeneratorFunc">Func to provide a custom <see cref="IStoreKeyGenerator" /></param>
/// <param name="storeKeySerializerFunc">Func to provide a custom <see cref="IStoreKeySerializer" /></param>
/// <param name="eTagGeneratorFunc">Func to provide a custom <see cref="IETagGenerator" /></param>
/// <param name="lastModifiedInjectorFunc">Func to provide a custom <see cref="ILastModifiedInjector" /></param>
public static IServiceCollection AddHttpCacheHeaders(
Expand All @@ -35,6 +37,7 @@ public static IServiceCollection AddHttpCacheHeaders(
Func<IServiceProvider, IDateParser> dateParserFunc = null,
Func<IServiceProvider, IValidatorValueStore> validatorValueStoreFunc = null,
Func<IServiceProvider, IStoreKeyGenerator> storeKeyGeneratorFunc = null,
Func<IServiceProvider, IStoreKeySerializer> storeKeySerializerFunc = null,
Func<IServiceProvider, IETagGenerator> eTagGeneratorFunc = null,
Func<IServiceProvider, ILastModifiedInjector> lastModifiedInjectorFunc = null)
{
Expand All @@ -50,23 +53,25 @@ public static IServiceCollection AddHttpCacheHeaders(
dateParserFunc,
validatorValueStoreFunc,
storeKeyGeneratorFunc,
storeKeySerializerFunc,
eTagGeneratorFunc,
lastModifiedInjectorFunc);

return services;
}

private static void AddModularParts(
IServiceCollection services,
private static void AddModularParts(IServiceCollection services,
Func<IServiceProvider, IDateParser> dateParserFunc,
Func<IServiceProvider, IValidatorValueStore> validatorValueStoreFunc,
Func<IServiceProvider, IStoreKeyGenerator> storeKeyGeneratorFunc,
Func<IServiceProvider, IStoreKeySerializer> storeKeySerializerFunc,
Func<IServiceProvider, IETagGenerator> eTagGeneratorFunc,
Func<IServiceProvider, ILastModifiedInjector> lastModifiedInjectorFunc)
{
AddDateParser(services, dateParserFunc);
AddValidatorValueStore(services, validatorValueStoreFunc);
AddStoreKeyGenerator(services, storeKeyGeneratorFunc);
AddStoreKeySerializer(services, storeKeySerializerFunc);
AddETagGenerator(services, eTagGeneratorFunc);
AddLastModifiedInjector(services, lastModifiedInjectorFunc);

Expand Down Expand Up @@ -124,7 +129,7 @@ private static void AddValidatorValueStore(

if (validatorValueStoreFunc == null)
{
validatorValueStoreFunc = _ => new InMemoryValidatorValueStore();
validatorValueStoreFunc = services => new InMemoryValidatorValueStore(services.GetRequiredService<IStoreKeySerializer>());
}

services.Add(ServiceDescriptor.Singleton(typeof(IValidatorValueStore), validatorValueStoreFunc));
Expand All @@ -146,7 +151,24 @@ private static void AddStoreKeyGenerator(

services.Add(ServiceDescriptor.Singleton(typeof(IStoreKeyGenerator), storeKeyGeneratorFunc));
}


private static void AddStoreKeySerializer(
IServiceCollection services,
Func<IServiceProvider, IStoreKeySerializer> storeKeySerializerFunc)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}

if (storeKeySerializerFunc == null)
{
storeKeySerializerFunc = _ => new DefaultStoreKeySerializer();
}

services.Add(ServiceDescriptor.Singleton(typeof(IStoreKeySerializer), storeKeySerializerFunc));
}

private static void AddETagGenerator(
IServiceCollection services,
Func<IServiceProvider, IETagGenerator> eTagGeneratorFunc)
Expand Down
33 changes: 33 additions & 0 deletions src/Marvin.Cache.Headers/Interfaces/IStoreKeySerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Any comments, input: @KevinDockx
// Any issues, requests: https://github.com/KevinDockx/HttpCacheHeaders


using System;
using System.Text.Json;

namespace Marvin.Cache.Headers.Interfaces
{
/// <summary>
/// Contract for a key serializer, used to serialize a <see cref="StoreKey" />
/// </summary>
public interface IStoreKeySerializer
{
/// <summary>
/// Serialize a <see cref="StoreKey"/>.
/// </summary>
/// <param name="keyToSerialize">The <see cref="StoreKey"/> to be serialized.</param>
/// <returns>The <param name="keyToSerialize"/> serialized to a <see cref="string"/>.</returns>
///<exception cref="ArgumentNullException">thrown when the <paramref name="keyToSerialize"/> passed in is <c>null</c>.</exception>
string SerializeStoreKey(StoreKey keyToSerialize);

/// <summary>
/// Deserialize a <see cref="StoreKey"/> from a <see cref="string"/>.
/// </summary>
/// <param name="storeKeyJson">The Json representation of a <see cref="StoreKey"/> to be deserialized.</param>
/// <returns>The <param name="storeKeyJson"/> deserialized to a <see cref="StoreKey"/>.</returns>
///<exception cref="ArgumentNullException">thrown when the <paramref name="storeKeyJson"/> passed in is <c>null</c>.</exception>
///<exception cref="ArgumentException">thrown when the <paramref name="storeKeyJson"/> passed in is an empty string.</exception>
///<exception cref="JsonException">thrown when the <paramref name="storeKeyJson"/> passed in cannot be deserialized to a <see cref="StoreKey"/>.</exception>
StoreKey DeserializeStoreKey(string storeKeyJson);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Any comments, input: @KevinDockx
// Any issues, requests: https://github.com/KevinDockx/HttpCacheHeaders

using System;
using System.Text.Json;
using Marvin.Cache.Headers.Interfaces;

namespace Marvin.Cache.Headers.Serialization
{
/// <summary>
/// Serializes a <see cref="StoreKey"/> to JSON./// </summary>
public class DefaultStoreKeySerializer : IStoreKeySerializer
{
///<inheritDoc/>
public string SerializeStoreKey(StoreKey keyToSerialize)
{
ArgumentNullException.ThrowIfNull(keyToSerialize);
return JsonSerializer.Serialize(keyToSerialize);
}

///<inheritDoc/>
public StoreKey DeserializeStoreKey(string storeKeyJson)
{
if (storeKeyJson == null)
{
throw new ArgumentNullException(nameof(storeKeyJson));
}
else if (storeKeyJson.Length == 0)
{
throw new ArgumentException("The storeKeyJson parameter cannot be an empty string.", nameof(storeKeyJson));
}

return JsonSerializer.Deserialize<StoreKey>(storeKeyJson);
}
}
}
53 changes: 25 additions & 28 deletions src/Marvin.Cache.Headers/Stores/InMemoryValidatorValueStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,17 @@ private readonly ConcurrentDictionary<string, ValidatorValue> _store
private readonly ConcurrentDictionary<string, StoreKey> _storeKeyStore
= new ConcurrentDictionary<string, StoreKey>();

public Task<ValidatorValue> GetAsync(StoreKey key) => GetAsync(key.ToString());
//Serializer for StoreKeys.
private readonly IStoreKeySerializer _storeKeySerializer;

public InMemoryValidatorValueStore(IStoreKeySerializer storeKeySerializer) => _storeKeySerializer =
storeKeySerializer ?? throw new ArgumentNullException(nameof(storeKeySerializer));

public Task<ValidatorValue> GetAsync(StoreKey key)
{
var keyJson = _storeKeySerializer.SerializeStoreKey(key);
return GetAsync(keyJson);
}

private Task<ValidatorValue> GetAsync(string key)
{
Expand All @@ -41,9 +51,10 @@ private Task<ValidatorValue> GetAsync(string key)
public Task SetAsync(StoreKey key, ValidatorValue eTag)
{
// store the validator value
_store[key.ToString()] = eTag;
var keyJson = _storeKeySerializer.SerializeStoreKey(key);
_store[keyJson] = eTag;
// save the key itself as well, with an easily searchable stringified key
_storeKeyStore[key.ToString()] = key;
_storeKeyStore[keyJson] = key;
return Task.FromResult(0);
}

Expand All @@ -54,7 +65,8 @@ public Task SetAsync(StoreKey key, ValidatorValue eTag)
/// <returns></returns>
public Task<bool> RemoveAsync(StoreKey key)
{
_storeKeyStore.TryRemove(key.ToString(), out _);
var keyJson = _storeKeySerializer.SerializeStoreKey(key);
_storeKeyStore.TryRemove(keyJson, out _);
return Task.FromResult(_store.TryRemove(key.ToString(), out _));
}

Expand All @@ -74,32 +86,17 @@ public async IAsyncEnumerable<StoreKey> FindStoreKeysByKeyPartAsync(string value
if (ignoreCase)
{
valueToMatch = valueToMatch.ToLowerInvariant();

foreach (var key in _storeKeyStore.Keys
.Where(k => k.ToLowerInvariant().Contains(valueToMatch)))
{
if (_storeKeyStore.TryGetValue(key, out StoreKey storeKey))
{
lstStoreKeysToReturn.Add(storeKey);
}
}
}
else
{
foreach (var key in _storeKeyStore.Keys
.Where(k => k.Contains(valueToMatch)))

foreach (var key in _store.Keys)
{
if (_storeKeyStore.TryGetValue(key, out StoreKey storeKey))
{
lstStoreKeysToReturn.Add(storeKey);
}
var deserializedKey =_storeKeySerializer.DeserializeStoreKey(key);
var deserializedKeyValues = String.Join(',', ignoreCase ? deserializedKey.Values.Select(x => x.ToLower()) : deserializedKey.Values);
if (deserializedKeyValues.Contains(valueToMatch))
{
yield return deserializedKey;
}
}
}

for (int i = 0; i < lstStoreKeysToReturn.Count; i++)
{
yield return lstStoreKeysToReturn[i];
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.3" />
<PackageReference Include="Quibble.Xunit" Version="0.3.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand All @@ -26,6 +27,7 @@
</ItemGroup>

<ItemGroup>
<Folder Include="Serialization\" />
<Folder Include="Properties\" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System;
using System.Text.Json;
using Marvin.Cache.Headers.Serialization;
using Quibble.Xunit;
using Xunit;

namespace Marvin.Cache.Headers.Test.Serialization;

public class DefaultStoreKeySerializerFacts
{
private readonly DefaultStoreKeySerializer _storeKeySerializer =new();

[Fact]
public void SerializeStoreKey_ThrowsArgumentNullException_WhenKeyToSerializeIsNull()
{
StoreKey keyToSerialize = null;
Assert.Throws<ArgumentNullException>(() =>_storeKeySerializer.SerializeStoreKey(keyToSerialize));
}

[Fact]
public void SerializeStoreKey_ReturnsTheKeyToSerializeAsJson_WhenStoreKeyIsNotNull()
{
var keyToSerialize = new StoreKey
{
{ "testKey", "TestValue" }
};
const string expectedStoreKeyJson = "{\"testKey\":\"TestValue\"}";

var serializedStoreKey = _storeKeySerializer.
SerializeStoreKey(keyToSerialize);

JsonAssert.Equal(expectedStoreKeyJson, serializedStoreKey);
}

[Fact]
public void DeserializeStoreKey_ThrowsArgumentNullException_WhenStoreKeyJsonIsNull()
{
string storeKeyJson = null;
Assert.Throws<ArgumentNullException>(() => _storeKeySerializer.DeserializeStoreKey(storeKeyJson));
}

[Fact]
public void DeserializeStoreKey_ThrowsArgumentException_WhenStoreKeyJsonIsAnEmptyString()
{
var storeKeyJson = String.Empty;
Assert.Throws<ArgumentException>(() => _storeKeySerializer.DeserializeStoreKey(storeKeyJson));
}
[Fact]
public void DeserializeStoreKey_ThrowsJsonException_WhenStoreKeyJsonIsInvalid()
{
const string storeKeyJson = "{";
Assert.Throws<JsonException>(() => _storeKeySerializer.DeserializeStoreKey(storeKeyJson));
}

[Fact]
public void DeserializeStoreKey_ReturnsTheStoreKeyJsonAsAStoreKey_WhenTheStoreKeyJsonIsValidJson()
{
var expectedStoreKey = new StoreKey
{
{ "testKey", "TestValue" }
};
const string storeKeyJson = "{\"testKey\":\"TestValue\"}";

var deserializedStoreKey = _storeKeySerializer.DeserializeStoreKey(storeKeyJson);

Assert.Equal(expectedStoreKey, deserializedStoreKey);
}
}
Loading

0 comments on commit 5a1adf7

Please sign in to comment.