// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Text.Json;
using System.Threading.Tasks;
using EpicGames.Core;
using EpicGames.Redis.Converters;
using EpicGames.Serialization;
using Google.Protobuf;
using ProtoBuf;
using StackExchange.Redis;
namespace EpicGames.Redis
{
///
/// Attribute specifying the converter type to use for a class
///
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public sealed class RedisConverterAttribute : Attribute
{
///
/// Type of the converter to use
///
public Type ConverterType { get; }
///
/// Constructor
///
/// The converter type
public RedisConverterAttribute(Type converterType)
{
ConverterType = converterType;
}
}
///
/// Converter to and from RedisValue types
///
public interface IRedisConverter
{
///
/// Serailize an object to a RedisValue
///
///
///
RedisValue ToRedisValue(T value);
///
/// Deserialize an object from a RedisValue
///
///
///
T FromRedisValue(RedisValue value);
}
///
/// Redis serializer that uses compact binary to serialize objects
///
///
public sealed class RedisCbConverter : IRedisConverter
{
///
public RedisValue ToRedisValue(T value)
{
return CbSerializer.Serialize(value).GetView();
}
///
public T FromRedisValue(RedisValue value)
{
return CbSerializer.Deserialize(new CbField((byte[])value!));
}
}
///
/// Redis serializer that uses JSON to serialize objects
///
public sealed class RedisJsonConverter : IRedisConverter
{
static readonly JsonSerializerOptions s_options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
///
public RedisValue ToRedisValue(T value)
{
return JsonSerializer.Serialize(value, s_options);
}
///
public T FromRedisValue(RedisValue value)
{
return JsonSerializer.Deserialize((string?)value ?? String.Empty, s_options) ?? throw new FormatException("Expected non-null value");
}
}
///
/// Handles serialization of types to RedisValue instances
///
public static class RedisSerializer
{
class RedisStringConverter : IRedisConverter
{
readonly TypeConverter _typeConverter;
public RedisStringConverter(TypeConverter typeConverter)
{
_typeConverter = typeConverter;
}
public RedisValue ToRedisValue(T value) => (string?)_typeConverter.ConvertTo(value, typeof(string));
public T FromRedisValue(RedisValue value) => (T)_typeConverter.ConvertFrom((string)value!)!;
}
class RedisUtf8StringConverter : IRedisConverter
{
readonly TypeConverter _typeConverter;
public RedisUtf8StringConverter(TypeConverter typeConverter)
{
_typeConverter = typeConverter;
}
public RedisValue ToRedisValue(T value) => ((Utf8String)_typeConverter.ConvertTo(value, typeof(Utf8String))!).Memory;
public T FromRedisValue(RedisValue value) => (T)_typeConverter.ConvertFrom(new Utf8String((ReadOnlyMemory)value!))!;
}
class RedisNativeConverter : IRedisConverter
{
readonly Func _fromRedisValueFunc;
readonly Func _toRedisValueFunc;
public RedisNativeConverter(Func fromRedisValueFunc, Func toRedisValueFunc)
{
_fromRedisValueFunc = fromRedisValueFunc;
_toRedisValueFunc = toRedisValueFunc;
}
public T FromRedisValue(RedisValue value) => _fromRedisValueFunc(value);
public RedisValue ToRedisValue(T value) => _toRedisValueFunc(value);
}
static readonly Dictionary s_nativeConverters = CreateNativeConverterLookup();
static Dictionary CreateNativeConverterLookup()
{
KeyValuePair[] converters =
{
CreateNativeConverter(x => x, x => x),
CreateNativeConverter(x => (bool)x, x => x),
CreateNativeConverter(x => (int)x, x => x),
CreateNativeConverter(x => (int?)x, x => x),
CreateNativeConverter(x => (uint)x, x => x),
CreateNativeConverter(x => (uint?)x, x => x),
CreateNativeConverter(x => (long)x, x => x),
CreateNativeConverter(x => (long?)x, x => x),
CreateNativeConverter(x => (ulong)x, x => x),
CreateNativeConverter(x => (ulong?)x, x => x),
CreateNativeConverter(x => (double)x, x => x),
CreateNativeConverter(x => (double?)x, x => x),
CreateNativeConverter(x => (ReadOnlyMemory)x, x => x),
CreateNativeConverter(x => (byte[])x!, x => x),
CreateNativeConverter(x => (string)x!, x => x),
CreateNativeConverter(x => new DateTime((long)x, DateTimeKind.Utc), x => x.ToUniversalTime().Ticks)
};
return new Dictionary(converters);
}
static KeyValuePair CreateNativeConverter(Func fromRedisValueFunc, Func toRedisValueFunc)
{
return new KeyValuePair(typeof(T), new RedisNativeConverter(fromRedisValueFunc, toRedisValueFunc));
}
static readonly Dictionary s_typeToConverterType = new Dictionary();
class RedisObjectConverter : IRedisConverter