Skip to content

Commit

Permalink
feat: add IContentComponent to C&S contents
Browse files Browse the repository at this point in the history
tests: add FooterLinkTagHelper changes unit tests

chore: Linted code for plan-technology-for-your-school.sln solution

chore: sonarcloud fix

chore: add .sonarqube to gitignore

fix: handle prefixed "/"

chore formatting

fix: expected url

fix: expected url

fix: test

fix: Look in all assemblies for IContentComponent types

wip

chore: add text

fix: unit tests, tweak validation

fix(redis): Handle serialisation of IContentComponent properties

chore: formatting

fix: handle redisc onnection failures better

fix tests

fixes + refactor
  • Loading branch information
jimwashbrook committed Nov 26, 2024
1 parent cf8ba9f commit 15fbc03
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,53 +9,37 @@ namespace Dfe.PlanTech.Domain.Content.Models;
/// </summary>
public static class ContentComponentJsonExtensions
{
private const string TypeDiscriminatorName = "$contentcomponenttype";

/// <summary>
/// Gets all classes that inherit from <see cref="ContentComponent"/> and creates <see cref="JsonDerivedType"/> mapping information for deserialisation
/// Gets all types inheriting <see cref="Type"/> that would be valid for serialisation
/// </summary>
private static readonly List<JsonDerivedType> ContentComponentTypes = ReflectionHelpers
.GetTypesInheritingFrom<ContentComponent>()
.Select(type => new JsonDerivedType(type, type.Name))
.ToList();

private static readonly Type ContentComponentType = typeof(ContentComponent);

private static JsonPolymorphismOptions? _contentComponentPolymorphismOptions = null;

private static JsonPolymorphismOptions ContentComponentPolymorphismOptions =>
_contentComponentPolymorphismOptions ??= CreateJsonPolymorphismOptions();
/// <param name="type"></param>
/// <returns></returns>
private static List<JsonDerivedType> GetInheritingTypes(Type type) => [.. ReflectionHelpers
.GetTypesInheritingFrom(type)
.Where(derivedType => derivedType != type && derivedType.IsConcreteClass() && derivedType.HasParameterlessConstructor())
.Select(type => new JsonDerivedType(type, type.Name))];

/// <summary>
/// Creates polymorphism support for the <see cref="ContentComponent"/> class.
/// Adds polymorphism support for the <see cref="ContentComponent"/> class.
/// </summary>
/// <returns></returns>
private static JsonPolymorphismOptions CreateJsonPolymorphismOptions()
/// <param name="jsonTypeInfo"></param>
public static void AddContentComponentPolymorphicInfo<TType>(JsonTypeInfo jsonTypeInfo)
{
var options = new JsonPolymorphismOptions()
if (jsonTypeInfo.Type != typeof(TType))
return;

var options = new JsonPolymorphismOptions
{
TypeDiscriminatorPropertyName = TypeDiscriminatorName,
TypeDiscriminatorPropertyName = $"${typeof(TType).Name.ToLower()}",
IgnoreUnrecognizedTypeDiscriminators = true,
UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization,
};

foreach (var derivedType in ContentComponentTypes)
foreach (var derivedType in GetInheritingTypes(typeof(TType)))
{
options.DerivedTypes.Add(derivedType);
}

return options;
}

/// <summary>
/// Adds polymorphism support for the <see cref="ContentComponent"/> class.
/// </summary>
/// <param name="jsonTypeInfo"></param>
public static void AddContentComponentPolymorphicInfo(JsonTypeInfo jsonTypeInfo)
{
if (jsonTypeInfo.Type != ContentComponentType)
return;

jsonTypeInfo.PolymorphismOptions = ContentComponentPolymorphismOptions;
jsonTypeInfo.PolymorphismOptions = options;
}
}
27 changes: 19 additions & 8 deletions src/Dfe.PlanTech.Domain/Helpers/ReflectionHelpers.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
using System.Reflection;

namespace Dfe.PlanTech.Domain.Helpers;

public static class ReflectionHelpers
{
public static IEnumerable<Type> GetTypesInheritingFrom<TBase>()
{
var baseType = typeof(TBase);

return AppDomain.CurrentDomain.GetAssemblies()
.SelectMany((assembley) => assembley.GetTypes())
.Where((type) => baseType.IsAssignableFrom(type) && type != baseType);
}
public static IEnumerable<Type> GetTypesInheritingFrom<TBase>() => GetTypesInheritingFrom(typeof(TBase));

public static IEnumerable<Type> GetTypesInheritingFrom(Type baseType, bool internalProjectsOnly = true) => AppDomain.CurrentDomain.GetAssemblies()
.Where(assembly => IsNotTestProject(assembly, internalProjectsOnly))
.SelectMany(AssemblyTypes)
.Where(InheritsBaseType(baseType));

private static Func<Type, bool> InheritsBaseType(Type baseType) => (type) => baseType.IsAssignableFrom(type) && type != baseType;

private static Type[] AssemblyTypes(Assembly assembly) => assembly.GetTypes();

private static bool IsNotTestProject(Assembly assembly, bool internalProjectsOnly)
=> assembly.FullName != null && !assembly.FullName.Contains("Test") && (!internalProjectsOnly || assembly.FullName.Contains("Dfe.PlanTech."));

public static bool HasParameterlessConstructor(this Type type) => type.GetConstructor(Type.EmptyTypes) != null || type.GetConstructors().Length == 0;

public static bool IsConcreteClass(this Type type) => !type.IsAbstract && !type.IsInterface;
}
5 changes: 3 additions & 2 deletions src/Dfe.PlanTech.Infrastructure.Redis/JsonSerialiser.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using Dfe.PlanTech.Domain.Content.Interfaces;
using Dfe.PlanTech.Domain.Content.Models;
using StackExchange.Redis;

Expand All @@ -17,8 +18,8 @@ public static class JsonSerialiser
private static readonly JsonSerializerOptions JsonSerialiserOptions = new()
{
TypeInfoResolver =
new DefaultJsonTypeInfoResolver().WithAddedModifier(ContentComponentJsonExtensions
.AddContentComponentPolymorphicInfo),
new DefaultJsonTypeInfoResolver().WithAddedModifier(ContentComponentJsonExtensions.AddContentComponentPolymorphicInfo<IContentComponent>)
.WithAddedModifier(ContentComponentJsonExtensions.AddContentComponentPolymorphicInfo<ContentComponent>),
ReferenceHandler = ReferenceHandler.Preserve
};

Expand Down
26 changes: 17 additions & 9 deletions src/Dfe.PlanTech.Infrastructure.Redis/RedisCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,28 @@ public RedisCache(IRedisConnectionManager connectionManager, ILogger<RedisCache>
{
_logger.LogInformation("Attempting to get or create cache item with key: {Key}", key);

var db = await _connectionManager.GetDatabaseAsync(databaseId);
var redisResult = await GetAsync<T>(db, key);

if (redisResult.ExistedInCache == true)
try
{
_logger.LogTrace("Cache item with key: {Key} found", key);
return redisResult.CacheValue;
var db = await _connectionManager.GetDatabaseAsync(databaseId);
var redisResult = await GetAsync<T>(db, key);

if (redisResult.ExistedInCache == true)
{
_logger.LogTrace("Cache item with key: {Key} found", key);
return redisResult.CacheValue;
}
else if (redisResult.Errored)
{
return await action();
}

return await CreateAndCacheItemAsync(db, key, action, expiry, onCacheItemCreation);
}
else if (redisResult.Errored)
catch (RedisConnectionException redisException)
{
_logger.LogError(redisException, "Failed to connect to Redis server: \"{Message}\". Retrieving server using {Action}.", redisException.Message, nameof(Action));
return await action();
}

return await CreateAndCacheItemAsync(db, key, action, expiry, onCacheItemCreation);
}

/// <inheritdoc/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using Dfe.PlanTech.Domain.Content.Interfaces;
using Dfe.PlanTech.Domain.Content.Models;

namespace Dfe.PlanTech.Infrastructure.Redis.UnitTests;

public class ContentComponentJsonExtensionsTests
{
[Fact]
public void AddContentComponentPolymorphicInfo_ShouldSetPolymorphismOptions_WhenTypeIsContentComponent()
[Theory]
[InlineData(typeof(ContentComponent))]
[InlineData(typeof(IContentComponent))]
public void AddContentComponentPolymorphicInfo_ShouldSetPolymorphismOptions(Type type)
{
var options = new JsonSerializerOptions()
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver().WithAddedModifier(ContentComponentJsonExtensions.AddContentComponentPolymorphicInfo)
TypeInfoResolver = new DefaultJsonTypeInfoResolver().WithAddedModifier(ContentComponentJsonExtensions.AddContentComponentPolymorphicInfo<ContentComponent>)
.WithAddedModifier(ContentComponentJsonExtensions.AddContentComponentPolymorphicInfo<IContentComponent>)
};

var typeInfo = options.GetTypeInfo(typeof(ContentComponent));
var typeInfo = options.GetTypeInfo(type);
Assert.NotNull(typeInfo);
Assert.NotNull(typeInfo.PolymorphismOptions);
Assert.Equal("$contentcomponenttype", typeInfo.PolymorphismOptions.TypeDiscriminatorPropertyName);
Assert.Equal("$" + type.Name.ToLower(), typeInfo.PolymorphismOptions.TypeDiscriminatorPropertyName);
Assert.True(typeInfo.PolymorphismOptions.IgnoreUnrecognizedTypeDiscriminators);
Assert.Equal(JsonUnknownDerivedTypeHandling.FailSerialization, typeInfo.PolymorphismOptions.UnknownDerivedTypeHandling);
}
Expand Down
Empty file.

0 comments on commit 15fbc03

Please sign in to comment.