< Summary

Information
Class: Chronicler.MemoryPackRecordSerializer
Assembly: Chronicler
File(s): /home/runner/work/Chronicler/Chronicler/src/Chronicler/Serialization/MemoryPack/MemoryPackRecordSerializer.cs
Line coverage
100%
Covered lines: 168
Uncovered lines: 0
Coverable lines: 168
Total lines: 296
Line coverage: 100%
Branch coverage
88%
Covered branches: 62
Total branches: 70
Branch coverage: 88.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
Serialize(...)100%11100%
Serialize(...)100%44100%
Populate(...)100%11100%
Populate(...)100%22100%
Populate(...)100%11100%
Populate(...)100%66100%
.ctor(...)50%22100%
get_Context()100%11100%
get_Mode()100%11100%
LookValue(...)75%44100%
LookDeep(...)100%22100%
LookDeepStruct(...)100%11100%
LookNullableDeep(...)100%22100%
LookLink(...)75%44100%
ToArray()100%11100%
.ctor(...)50%66100%
get_Context()100%11100%
get_Mode()100%11100%
LookValue(...)100%66100%
LookDeep(...)100%66100%
LookDeepStruct(...)100%44100%
LookNullableDeep(...)100%44100%
LookLink(...)93.75%1616100%
CreateDefaultDeepStruct()100%11100%
FormatSlot(...)50%22100%

File(s)

/home/runner/work/Chronicler/Chronicler/src/Chronicler/Serialization/MemoryPack/MemoryPackRecordSerializer.cs

#LineLine coverage
 1using MemoryPack;
 2using SwiftCollections;
 3using System;
 4using System.Collections.Generic;
 5
 6namespace Chronicler;
 7
 8/// <summary>
 9/// Serializes <see cref="IRecordable"/> state graphs to and from MemoryPack through the chronicler API.
 10/// </summary>
 11public static class MemoryPackRecordSerializer
 12{
 113    private static readonly byte[] EmptyRecordBytes = MemoryPackSerializer.Serialize(new MemoryPackRecordEnvelope());
 14
 15    /// <summary>
 16    /// Serializes the current state of a recordable instance into MemoryPack bytes.
 17    /// </summary>
 18    public static byte[] Serialize(IRecordable target)
 619        => Serialize(target, context: null);
 20
 21    /// <summary>
 22    /// Serializes the current state of a recordable instance into MemoryPack bytes.
 23    /// </summary>
 24    public static byte[] Serialize(IRecordable target, ChronicleContext? context)
 2925    {
 2926        if (target == null)
 127            throw new ArgumentNullException(nameof(target));
 28
 2829        context ??= new ChronicleContext();
 30
 2831        var chronicler = new MemoryPackRecordWriter(context);
 2832        target.RecordData(chronicler);
 2733        return chronicler.ToArray();
 2734    }
 35
 36    /// <summary>
 37    /// Loads MemoryPack state into an existing recordable instance.
 38    /// </summary>
 39    public static void Populate(IRecordable target, byte[] data)
 340        => Populate(target, data, context: null);
 41
 42    /// <summary>
 43    /// Loads MemoryPack state into an existing recordable instance.
 44    /// </summary>
 45    public static void Populate(IRecordable target, byte[] data, ChronicleContext? context)
 2446    {
 2447        if (data == null)
 148            throw new ArgumentNullException(nameof(data));
 49
 2350        Populate(target, data.AsSpan(), context);
 1751    }
 52
 53    /// <summary>
 54    /// Loads MemoryPack state into an existing recordable instance.
 55    /// </summary>
 56    public static void Populate(IRecordable target, ReadOnlySpan<byte> data)
 257        => Populate(target, data, context: null);
 58
 59    /// <summary>
 60    /// Loads MemoryPack state into an existing recordable instance.
 61    /// </summary>
 62    public static void Populate(IRecordable target, ReadOnlySpan<byte> data, ChronicleContext? context)
 2563    {
 2564        if (target == null)
 165            throw new ArgumentNullException(nameof(target));
 2466        if (data.IsEmpty)
 167            throw new ArgumentException("Serialized bytes must not be empty.", nameof(data));
 68
 2369        context ??= new ChronicleContext();
 70
 2371        var chronicler = new MemoryPackRecordReader(data, context);
 2372        target.RecordData(chronicler);
 2073        context.ResolveDeferredLinks();
 1874    }
 75
 76    private sealed class MemoryPackRecordWriter : IChronicler
 77    {
 4378        private readonly SwiftDictionary<string, byte[]?> _entries = new(8, StringComparer.Ordinal);
 79
 4380        public MemoryPackRecordWriter(ChronicleContext context)
 4381        {
 4382            Context = context ?? throw new ArgumentNullException(nameof(context));
 4383        }
 84
 2785        public ChronicleContext Context { get; }
 86
 1687        public SerializationMode Mode => SerializationMode.Saving;
 88
 89        public void LookValue<T>(ref T value, string name, T? defaultValue = default)
 4390        {
 4391            if (value is null || EqualityComparer<T>.Default.Equals(value, defaultValue!))
 992                return;
 93
 3494            _entries[name] = MemoryPackSerializer.Serialize(value);
 4395        }
 96
 97        public void LookDeep<T>(ref T value, string name) where T : class, IRecordable
 1098        {
 1099            if (value == null)
 1100            {
 1101                _entries[name] = null;
 1102                return;
 103            }
 104
 9105            var nested = new MemoryPackRecordWriter(Context);
 9106            value.RecordData(nested);
 9107            _entries[name] = nested.ToArray();
 10108        }
 109
 110        public void LookDeepStruct<T>(ref T value, string name) where T : struct, IRecordable
 4111        {
 4112            var nested = new MemoryPackRecordWriter(Context);
 4113            value.RecordData(nested);
 4114            _entries[name] = nested.ToArray();
 4115        }
 116
 117        public void LookNullableDeep<T>(ref T? value, string name) where T : struct, IRecordable
 4118        {
 4119            if (!value.HasValue)
 2120                return;
 121
 2122            T nestedValue = value.Value;
 2123            var nested = new MemoryPackRecordWriter(Context);
 2124            nestedValue.RecordData(nested);
 2125            _entries[name] = nested.ToArray();
 4126        }
 127
 128        public void LookLink<T>(
 129            ref T value,
 130            string name,
 131            string? slot = null,
 132            RecordLinkResolveMode resolveMode = RecordLinkResolveMode.Immediate,
 133            Action<T>? assignLoadedValue = null)
 12134        {
 12135            string? id = null;
 12136            if (value is not null
 12137                && !Context.Links.TryGetReferenceId(value, out id, slot))
 1138            {
 1139                throw new InvalidOperationException(
 1140                    $"Unable to save link '{name}' of type {typeof(T).Name} because no stable id could be produced{Forma
 141            }
 142
 11143            _entries[name] = MemoryPackSerializer.Serialize(id);
 11144        }
 145
 146        public byte[] ToArray()
 42147        {
 42148            return MemoryPackSerializer.Serialize(new MemoryPackRecordEnvelope()
 42149            {
 42150                Entries = _entries
 42151            });
 42152        }
 153    }
 154
 155    private sealed class MemoryPackRecordReader : IChronicler
 156    {
 157        private readonly SwiftDictionary<string, byte[]?> _entries;
 158
 38159        public MemoryPackRecordReader(ReadOnlySpan<byte> data, ChronicleContext context)
 38160        {
 38161            MemoryPackRecordEnvelope? envelope = MemoryPackSerializer.Deserialize<MemoryPackRecordEnvelope>(data);
 38162            _entries = envelope?.Entries ?? new SwiftDictionary<string, byte[]?>(8, StringComparer.Ordinal);
 38163            Context = context ?? throw new ArgumentNullException(nameof(context));
 38164        }
 165
 26166        public ChronicleContext Context { get; }
 167
 12168        public SerializationMode Mode => SerializationMode.Loading;
 169
 170        public void LookValue<T>(ref T value, string name, T? defaultValue = default)
 40171        {
 40172            if (!_entries.TryGetValue(name, out byte[]? entry)
 40173                || entry == null)
 20174            {
 20175                value = defaultValue!;
 20176                return;
 177            }
 178
 20179            T? loadedValue = MemoryPackSerializer.Deserialize<T>(entry);
 20180            if (loadedValue is null)
 1181            {
 1182                value = defaultValue!;
 1183                return;
 184            }
 185
 19186            value = loadedValue;
 40187        }
 188
 189        public void LookDeep<T>(ref T value, string name) where T : class, IRecordable
 9190        {
 9191            if (!_entries.TryGetValue(name, out byte[]? entry)
 9192                || entry == null)
 2193                return;
 194
 7195            if (value == null)
 1196                throw new InvalidOperationException(
 1197                    $"Unable to load '{name}' because {typeof(T).Name} must already be instantiated for a deep chronicle
 198
 6199            var nested = new MemoryPackRecordReader(entry, Context);
 6200            value.RecordData(nested);
 8201        }
 202
 203        public void LookDeepStruct<T>(ref T value, string name) where T : struct, IRecordable
 4204        {
 4205            value = CreateDefaultDeepStruct<T>();
 206
 4207            if (!_entries.TryGetValue(name, out byte[]? entry)
 4208                || entry == null)
 1209                return;
 210
 3211            var nested = new MemoryPackRecordReader(entry, Context);
 3212            value.RecordData(nested);
 4213        }
 214
 215        public void LookNullableDeep<T>(ref T? value, string name) where T : struct, IRecordable
 4216        {
 4217            if (!_entries.TryGetValue(name, out byte[]? entry)
 4218                || entry == null)
 3219            {
 3220                value = null;
 3221                return;
 222            }
 223
 1224            T nestedValue = CreateDefaultDeepStruct<T>();
 1225            var nested = new MemoryPackRecordReader(entry, Context);
 1226            nestedValue.RecordData(nested);
 1227            value = nestedValue;
 4228        }
 229
 230        public void LookLink<T>(
 231            ref T value,
 232            string name,
 233            string? slot = null,
 234            RecordLinkResolveMode resolveMode = RecordLinkResolveMode.Immediate,
 235            Action<T>? assignLoadedValue = null)
 10236        {
 10237            if (!_entries.TryGetValue(name, out byte[]? entry)
 10238                || entry == null)
 1239            {
 1240                value = default!;
 1241                return;
 242            }
 243
 9244            string? id = MemoryPackSerializer.Deserialize<string>(entry);
 9245            if (id == null)
 1246            {
 1247                value = default!;
 1248                return;
 249            }
 250
 8251            if (resolveMode == RecordLinkResolveMode.Deferred)
 5252            {
 5253                if (assignLoadedValue == null)
 1254                    throw new InvalidOperationException(
 1255                        $"Deferred link '{name}' of type {typeof(T).Name} requires an assignment callback.");
 256
 257                T? deferredValue;
 4258                if (Context.Links.TryResolve(id, out deferredValue, slot))
 1259                {
 1260                    value = deferredValue!;
 1261                    assignLoadedValue(deferredValue!);
 1262                    return;
 263                }
 264
 3265                Context.QueueDeferredLink(name, id, slot, assignLoadedValue);
 3266                value = default!;
 3267                return;
 268            }
 269
 270            T? resolvedValue;
 3271            if (!Context.Links.TryResolve(id, out resolvedValue, slot))
 1272            {
 1273                throw new InvalidOperationException(
 1274                    $"Unable to load link '{name}' of type {typeof(T).Name} with id '{id}'{FormatSlot(slot)}.");
 275            }
 276
 2277            value = resolvedValue!;
 2278            assignLoadedValue?.Invoke(resolvedValue!);
 8279        }
 280
 281        private T CreateDefaultDeepStruct<T>() where T : struct, IRecordable
 5282        {
 5283            T defaultValue = new();
 5284            var nested = new MemoryPackRecordReader(EmptyRecordBytes, Context);
 5285            defaultValue.RecordData(nested);
 5286            return defaultValue;
 5287        }
 288    }
 289
 290    private static string FormatSlot(string? slot)
 2291    {
 2292        return string.IsNullOrEmpty(slot)
 2293            ? string.Empty
 2294            : $" in slot '{slot}'";
 2295    }
 296}