mirror of https://github.com/Squidex/squidex.git
Browse Source
* GraphQL Data loader fix. * Fix tests * Fix event store. * Simplify testspull/1009/head
committed by
GitHub
114 changed files with 1366 additions and 2036 deletions
@ -1,93 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Text; |
|||
|
|||
namespace Squidex.Domain.Apps.Core.Scripting; |
|||
|
|||
public class ScriptContext : IEnumerable<KeyValuePair<string, (object? Value, bool IsReadonly)>> |
|||
{ |
|||
private readonly Dictionary<string, (object? Value, bool IsReadonly)> values = new Dictionary<string, (object? Value, bool IsReadonly)>(StringComparer.OrdinalIgnoreCase); |
|||
|
|||
public void CopyFrom(ScriptVars vars) |
|||
{ |
|||
Guard.NotNull(vars); |
|||
|
|||
foreach (var (key, item) in vars) |
|||
{ |
|||
if (!values.ContainsKey(key)) |
|||
{ |
|||
SetItem(key, item); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void SetItem(string? key, (object? Value, bool IsReadonly) item) |
|||
{ |
|||
Set(key, item.Value, item.IsReadonly); |
|||
} |
|||
|
|||
public void Set(string? key, object? value, bool isReadonly = false) |
|||
{ |
|||
if (string.IsNullOrWhiteSpace(key)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var finalKey = key.ToCamelCase(); |
|||
|
|||
if (values.TryGetValue(finalKey, out var existing) && existing.IsReadonly) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
values[finalKey] = (value, isReadonly); |
|||
} |
|||
|
|||
public bool TryGetValue(string key, [MaybeNullWhen(false)] out object? value) |
|||
{ |
|||
Guard.NotNull(key); |
|||
|
|||
value = default!; |
|||
|
|||
if (values.TryGetValue(key, out var item)) |
|||
{ |
|||
value = item.Value; |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public bool TryGetValue<T>(string key, [MaybeNullWhen(false)] out T value) |
|||
{ |
|||
Guard.NotNull(key); |
|||
|
|||
value = default!; |
|||
|
|||
if (values.TryGetValue(key, out var item) && item.Value is T typed) |
|||
{ |
|||
value = typed; |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public IEnumerator<KeyValuePair<string, (object? Value, bool IsReadonly)>> GetEnumerator() |
|||
{ |
|||
return values.GetEnumerator(); |
|||
} |
|||
|
|||
IEnumerator IEnumerable.GetEnumerator() |
|||
{ |
|||
return values.GetEnumerator(); |
|||
} |
|||
} |
|||
@ -1,124 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using Squidex.Domain.Apps.Core.Assets; |
|||
using Squidex.Infrastructure.Json.Objects; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards; |
|||
|
|||
public sealed class ScriptMetadataWrapper : IDictionary<string, object?> |
|||
{ |
|||
private readonly AssetMetadata metadata; |
|||
|
|||
public int Count |
|||
{ |
|||
get => metadata.Count; |
|||
} |
|||
|
|||
public ICollection<string> Keys |
|||
{ |
|||
get => metadata.Keys; |
|||
} |
|||
|
|||
public ICollection<object?> Values |
|||
{ |
|||
get => metadata.Values.Cast<object?>().ToList(); |
|||
} |
|||
|
|||
public object? this[string key] |
|||
{ |
|||
get => metadata[key]; |
|||
set => metadata[key] = JsonValue.Create(value); |
|||
} |
|||
|
|||
public bool IsReadOnly |
|||
{ |
|||
get => false; |
|||
} |
|||
|
|||
public ScriptMetadataWrapper(AssetMetadata metadata) |
|||
{ |
|||
this.metadata = metadata; |
|||
} |
|||
|
|||
public bool TryGetValue(string key, [MaybeNullWhen(false)] out object? value) |
|||
{ |
|||
if (metadata.TryGetValue(key, out var temp)) |
|||
{ |
|||
value = temp; |
|||
return true; |
|||
} |
|||
else |
|||
{ |
|||
value = null; |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
public void Add(string key, object? value) |
|||
{ |
|||
metadata.Add(key, JsonValue.Create(value)); |
|||
} |
|||
|
|||
public void Add(KeyValuePair<string, object?> item) |
|||
{ |
|||
Add(item.Key, item.Value); |
|||
} |
|||
|
|||
public bool Remove(string key) |
|||
{ |
|||
return metadata.Remove(key); |
|||
} |
|||
|
|||
public bool Remove(KeyValuePair<string, object?> item) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
public void Clear() |
|||
{ |
|||
metadata.Clear(); |
|||
} |
|||
|
|||
public bool Contains(KeyValuePair<string, object?> item) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
public bool ContainsKey(string key) |
|||
{ |
|||
return metadata.ContainsKey(key); |
|||
} |
|||
|
|||
public void CopyTo(KeyValuePair<string, object?>[] array, int arrayIndex) |
|||
{ |
|||
var i = arrayIndex; |
|||
|
|||
foreach (var item in metadata) |
|||
{ |
|||
if (i >= array.Length) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
array[i] = new KeyValuePair<string, object?>(item.Key, item.Value); |
|||
i++; |
|||
} |
|||
} |
|||
|
|||
public IEnumerator<KeyValuePair<string, object?>> GetEnumerator() |
|||
{ |
|||
return metadata.Select(x => new KeyValuePair<string, object?>(x.Key, x.Value)).GetEnumerator(); |
|||
} |
|||
|
|||
IEnumerator IEnumerable.GetEnumerator() |
|||
{ |
|||
return ((IEnumerable)metadata).GetEnumerator(); |
|||
} |
|||
} |
|||
@ -0,0 +1,70 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using GraphQL.DataLoader; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Caching; |
|||
|
|||
#pragma warning disable MA0048 // File name must match type name
|
|||
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
|
|||
#pragma warning disable RECS0082 // Parameter has the same name as a member and hides it
|
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Cache; |
|||
|
|||
record struct CacheableId<T>(T Id, TimeSpan CacheDuration = default); |
|||
|
|||
internal class CachingBatchDataLoader<TKey, T> : DataLoaderBase<CacheableId<TKey>, T> where TKey : notnull where T : class |
|||
{ |
|||
private readonly IQueryCache<TKey, T> queryCache; |
|||
private readonly Func<IEnumerable<TKey>, CancellationToken, Task<IDictionary<TKey, T>>> queryDelegate; |
|||
|
|||
public CachingBatchDataLoader(IQueryCache<TKey, T> queryStore, |
|||
Func<IEnumerable<TKey>, CancellationToken, Task<IDictionary<TKey, T>>> queryDelegate, bool canCache = true, int maxBatchSize = int.MaxValue) |
|||
: base(canCache, maxBatchSize) |
|||
{ |
|||
this.queryCache = queryStore; |
|||
this.queryDelegate = queryDelegate; |
|||
} |
|||
|
|||
protected override async Task FetchAsync(IEnumerable<DataLoaderPair<CacheableId<TKey>, T>> list, |
|||
CancellationToken cancellationToken) |
|||
{ |
|||
var unmatched = new List<DataLoaderPair<CacheableId<TKey>, T>>(list.Count()); |
|||
|
|||
foreach (var entry in list) |
|||
{ |
|||
if (entry.Key.CacheDuration != default && queryCache.TryGet(entry.Key.Id, out var cached)) |
|||
{ |
|||
entry.SetResult(cached); |
|||
} |
|||
else |
|||
{ |
|||
unmatched.Add(entry); |
|||
} |
|||
} |
|||
|
|||
if (unmatched.Count == 0) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var ids = unmatched.Select(x => x.Key.Id).Distinct(); |
|||
|
|||
var entries = await queryDelegate(ids, cancellationToken); |
|||
|
|||
foreach (var entry in unmatched) |
|||
{ |
|||
entries.TryGetValue(entry.Key.Id, out var value); |
|||
entry.SetResult(value!); |
|||
|
|||
if (value != null && entry.Key.CacheDuration != default) |
|||
{ |
|||
queryCache.Set(entry.Key.Id, value, entry.Key.CacheDuration); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using GraphQL.DataLoader; |
|||
using Squidex.Infrastructure.Caching; |
|||
using Squidex.Infrastructure.Translations; |
|||
using TagLib.IFD.Tags; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Cache; |
|||
|
|||
internal static class CachingDataLoaderExtensions |
|||
{ |
|||
public static IDataLoader<CacheableId<TKey>, T> GetOrAddCachingLoader<TKey, T>(this DataLoaderContext dataLoaderContext, IQueryCache<TKey, T> queryCache, string loaderKey, |
|||
Func<IEnumerable<TKey>, CancellationToken, Task<IDictionary<TKey, T>>> queryDelegate, bool canCache = true, int maxBatchSize = int.MaxValue) |
|||
where TKey : notnull where T : class |
|||
{ |
|||
return dataLoaderContext.GetOrAdd(loaderKey, () => |
|||
{ |
|||
return new CachingBatchDataLoader<TKey, T>(queryCache, queryDelegate, canCache, maxBatchSize); |
|||
}); |
|||
} |
|||
|
|||
public static IDataLoader<TKey, T> GetOrAddNonCachingBatchLoader<TKey, T>(this DataLoaderContext dataLoaderContext, string loaderKey, |
|||
Func<IEnumerable<TKey>, CancellationToken, Task<IDictionary<TKey, T>>> queryDelegate, int maxBatchSize = int.MaxValue) |
|||
where TKey : notnull where T : class |
|||
{ |
|||
return dataLoaderContext.GetOrAdd(loaderKey, () => |
|||
{ |
|||
return new NonCachingBatchLoader<TKey, T>(queryDelegate, maxBatchSize); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using GraphQL.DataLoader; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Cache; |
|||
|
|||
public sealed class EmptyDataLoaderResult<T> : IDataLoaderResult<T[]> |
|||
{ |
|||
public Task<T[]> GetResultAsync( |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
return Task.FromResult(Array.Empty<T>()); |
|||
} |
|||
|
|||
Task<object?> IDataLoaderResult.GetResultAsync( |
|||
CancellationToken cancellationToken) |
|||
{ |
|||
return Task.FromResult<object?>(Array.Empty<T>()); |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using GraphQL.DataLoader; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Cache; |
|||
|
|||
internal class NonCachingBatchLoader<TKey, T> : DataLoaderBase<TKey, T> where TKey : notnull where T : class |
|||
{ |
|||
private readonly Func<IEnumerable<TKey>, CancellationToken, Task<IDictionary<TKey, T>>> queryDelegate; |
|||
|
|||
public NonCachingBatchLoader(Func<IEnumerable<TKey>, CancellationToken, Task<IDictionary<TKey, T>>> queryDelegate, int maxBatchSize = int.MaxValue) |
|||
: base(false, maxBatchSize) |
|||
{ |
|||
this.queryDelegate = queryDelegate; |
|||
} |
|||
|
|||
protected override async Task FetchAsync(IEnumerable<DataLoaderPair<TKey, T>> list, |
|||
CancellationToken cancellationToken) |
|||
{ |
|||
var dictionary = await queryDelegate(list.Select(x => x.Key), cancellationToken).ConfigureAwait(false); |
|||
|
|||
foreach (var item in list) |
|||
{ |
|||
dictionary.TryGetValue(item.Key, out var value); |
|||
|
|||
item.SetResult(value!); |
|||
} |
|||
} |
|||
} |
|||
@ -1,125 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections; |
|||
|
|||
namespace Squidex.Infrastructure.Collections; |
|||
|
|||
public partial class ListDictionary<TKey, TValue> |
|||
{ |
|||
private sealed class KeyCollection : ICollection<TKey> |
|||
{ |
|||
private readonly ListDictionary<TKey, TValue> dictionary; |
|||
|
|||
public int Count |
|||
{ |
|||
get => dictionary.Count; |
|||
} |
|||
|
|||
public bool IsReadOnly |
|||
{ |
|||
get => false; |
|||
} |
|||
|
|||
public KeyCollection(ListDictionary<TKey, TValue> dictionary) |
|||
{ |
|||
this.dictionary = dictionary; |
|||
} |
|||
|
|||
public void Add(TKey item) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public void Clear() |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public void CopyTo(TKey[] array, int arrayIndex) |
|||
{ |
|||
var i = 0; |
|||
foreach (var (key, _) in dictionary.entries) |
|||
{ |
|||
array[arrayIndex + i] = key; |
|||
i++; |
|||
} |
|||
} |
|||
|
|||
public bool Remove(TKey item) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public bool Contains(TKey item) |
|||
{ |
|||
foreach (var entry in dictionary.entries) |
|||
{ |
|||
if (dictionary.comparer.Equals(entry.Key, item)) |
|||
{ |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public IEnumerator<TKey> GetEnumerator() |
|||
{ |
|||
return new Enumerator(dictionary); |
|||
} |
|||
|
|||
IEnumerator IEnumerable.GetEnumerator() |
|||
{ |
|||
return new Enumerator(dictionary); |
|||
} |
|||
|
|||
private struct Enumerator : IEnumerator<TKey>, IEnumerator |
|||
{ |
|||
private readonly ListDictionary<TKey, TValue> dictionary; |
|||
private int index = -1; |
|||
private TKey value = default!; |
|||
|
|||
readonly TKey IEnumerator<TKey>.Current |
|||
{ |
|||
get => value!; |
|||
} |
|||
|
|||
readonly object IEnumerator.Current |
|||
{ |
|||
get => value!; |
|||
} |
|||
|
|||
public Enumerator(ListDictionary<TKey, TValue> dictionary) |
|||
{ |
|||
this.dictionary = dictionary; |
|||
} |
|||
|
|||
public readonly void Dispose() |
|||
{ |
|||
} |
|||
|
|||
public bool MoveNext() |
|||
{ |
|||
if (index >= dictionary.entries.Count - 1) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
index++; |
|||
|
|||
value = dictionary.entries[index].Key; |
|||
return true; |
|||
} |
|||
|
|||
public void Reset() |
|||
{ |
|||
index = -1; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,125 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections; |
|||
|
|||
namespace Squidex.Infrastructure.Collections; |
|||
|
|||
public partial class ListDictionary<TKey, TValue> |
|||
{ |
|||
private sealed class ValueCollection : ICollection<TValue> |
|||
{ |
|||
private readonly ListDictionary<TKey, TValue> dictionary; |
|||
|
|||
public int Count |
|||
{ |
|||
get => dictionary.Count; |
|||
} |
|||
|
|||
public bool IsReadOnly |
|||
{ |
|||
get => false; |
|||
} |
|||
|
|||
public ValueCollection(ListDictionary<TKey, TValue> dictionary) |
|||
{ |
|||
this.dictionary = dictionary; |
|||
} |
|||
|
|||
public void Add(TValue item) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public void Clear() |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public void CopyTo(TValue[] array, int arrayIndex) |
|||
{ |
|||
var i = 0; |
|||
foreach (var (_, value) in dictionary.entries) |
|||
{ |
|||
array[arrayIndex + i] = value; |
|||
i++; |
|||
} |
|||
} |
|||
|
|||
public bool Remove(TValue item) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public bool Contains(TValue item) |
|||
{ |
|||
foreach (var entry in dictionary.entries) |
|||
{ |
|||
if (Equals(entry.Value, item)) |
|||
{ |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public IEnumerator<TValue> GetEnumerator() |
|||
{ |
|||
return new Enumerator(dictionary); |
|||
} |
|||
|
|||
IEnumerator IEnumerable.GetEnumerator() |
|||
{ |
|||
return new Enumerator(dictionary); |
|||
} |
|||
|
|||
private struct Enumerator : IEnumerator<TValue>, IEnumerator |
|||
{ |
|||
private readonly ListDictionary<TKey, TValue> dictionary; |
|||
private int index = -1; |
|||
private TValue value = default!; |
|||
|
|||
readonly TValue IEnumerator<TValue>.Current |
|||
{ |
|||
get => value!; |
|||
} |
|||
|
|||
readonly object IEnumerator.Current |
|||
{ |
|||
get => value!; |
|||
} |
|||
|
|||
public Enumerator(ListDictionary<TKey, TValue> dictionary) |
|||
{ |
|||
this.dictionary = dictionary; |
|||
} |
|||
|
|||
public readonly void Dispose() |
|||
{ |
|||
} |
|||
|
|||
public bool MoveNext() |
|||
{ |
|||
if (index >= dictionary.entries.Count - 1) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
index++; |
|||
|
|||
value = dictionary.entries[index].Value; |
|||
return true; |
|||
} |
|||
|
|||
public void Reset() |
|||
{ |
|||
index = -1; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,273 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
|
|||
namespace Squidex.Infrastructure.Collections; |
|||
|
|||
public partial class ListDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue> where TKey : notnull |
|||
{ |
|||
private readonly List<KeyValuePair<TKey, TValue>> entries = new List<KeyValuePair<TKey, TValue>>(); |
|||
private readonly IEqualityComparer<TKey> comparer; |
|||
|
|||
private struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>, IEnumerator |
|||
{ |
|||
private readonly ListDictionary<TKey, TValue> dictionary; |
|||
private int index = -1; |
|||
private KeyValuePair<TKey, TValue> value = default!; |
|||
|
|||
readonly KeyValuePair<TKey, TValue> IEnumerator<KeyValuePair<TKey, TValue>>.Current |
|||
{ |
|||
get => value!; |
|||
} |
|||
|
|||
readonly object IEnumerator.Current |
|||
{ |
|||
get => value!; |
|||
} |
|||
|
|||
public Enumerator(ListDictionary<TKey, TValue> dictionary) |
|||
{ |
|||
this.dictionary = dictionary; |
|||
} |
|||
|
|||
public readonly void Dispose() |
|||
{ |
|||
} |
|||
|
|||
public bool MoveNext() |
|||
{ |
|||
if (index >= dictionary.entries.Count - 1) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
index++; |
|||
|
|||
value = dictionary.entries[index]; |
|||
return true; |
|||
} |
|||
|
|||
public void Reset() |
|||
{ |
|||
index = -1; |
|||
} |
|||
} |
|||
|
|||
public TValue this[TKey key] |
|||
{ |
|||
get |
|||
{ |
|||
if (!TryGetValue(key, out var result)) |
|||
{ |
|||
ThrowHelper.KeyNotFoundException(); |
|||
return default!; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
set |
|||
{ |
|||
var index = -1; |
|||
|
|||
for (var i = 0; i < entries.Count; i++) |
|||
{ |
|||
if (comparer.Equals(entries[i].Key, key)) |
|||
{ |
|||
index = i; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (index >= 0) |
|||
{ |
|||
entries[index] = new KeyValuePair<TKey, TValue>(key, value); |
|||
} |
|||
else |
|||
{ |
|||
entries.Add(new KeyValuePair<TKey, TValue>(key, value)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public ICollection<TKey> Keys |
|||
{ |
|||
get => new KeyCollection(this); |
|||
} |
|||
|
|||
public ICollection<TValue> Values |
|||
{ |
|||
get => new ValueCollection(this); |
|||
} |
|||
|
|||
public int Count |
|||
{ |
|||
get => entries.Count; |
|||
} |
|||
|
|||
public int Capacity |
|||
{ |
|||
get => entries.Capacity; |
|||
} |
|||
|
|||
public bool IsReadOnly |
|||
{ |
|||
get => false; |
|||
} |
|||
|
|||
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys |
|||
{ |
|||
get => new KeyCollection(this); |
|||
} |
|||
|
|||
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values |
|||
{ |
|||
get => new ValueCollection(this); |
|||
} |
|||
|
|||
public ListDictionary() |
|||
: this(1, null) |
|||
{ |
|||
} |
|||
|
|||
public ListDictionary(ListDictionary<TKey, TValue> source, IEqualityComparer<TKey>? comparer = null) |
|||
{ |
|||
Guard.NotNull(source); |
|||
|
|||
entries = source.entries.ToList(); |
|||
|
|||
this.comparer = comparer ?? EqualityComparer<TKey>.Default; |
|||
} |
|||
|
|||
public ListDictionary(int capacity, IEqualityComparer<TKey>? comparer = null) |
|||
{ |
|||
Guard.GreaterEquals(capacity, 0); |
|||
|
|||
entries = new List<KeyValuePair<TKey, TValue>>(capacity); |
|||
|
|||
this.comparer = comparer ?? EqualityComparer<TKey>.Default; |
|||
} |
|||
|
|||
public void Add(TKey key, TValue value) |
|||
{ |
|||
if (ContainsKey(key)) |
|||
{ |
|||
ThrowHelper.ArgumentException("Key already exists.", nameof(key)); |
|||
} |
|||
|
|||
AddUnsafe(key, value); |
|||
} |
|||
|
|||
public void Add(KeyValuePair<TKey, TValue> item) |
|||
{ |
|||
Add(item.Key, item.Value); |
|||
} |
|||
|
|||
public void AddUnsafe(TKey key, TValue value) |
|||
{ |
|||
entries.Add(new KeyValuePair<TKey, TValue>(key, value)); |
|||
} |
|||
|
|||
public void Clear() |
|||
{ |
|||
entries.Clear(); |
|||
} |
|||
|
|||
public bool Contains(KeyValuePair<TKey, TValue> item) |
|||
{ |
|||
foreach (var entry in entries) |
|||
{ |
|||
if (comparer.Equals(entry.Key, item.Key) && Equals(entry.Value, item.Value)) |
|||
{ |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public bool ContainsKey(TKey key) |
|||
{ |
|||
foreach (var entry in entries) |
|||
{ |
|||
if (comparer.Equals(entry.Key, key)) |
|||
{ |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) |
|||
{ |
|||
foreach (var entry in entries) |
|||
{ |
|||
if (comparer.Equals(entry.Key, key)) |
|||
{ |
|||
value = entry.Value; |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
value = default; |
|||
return false; |
|||
} |
|||
|
|||
public bool Remove(TKey key) |
|||
{ |
|||
for (var i = 0; i < entries.Count; i++) |
|||
{ |
|||
var entry = entries[i]; |
|||
|
|||
if (comparer.Equals(entry.Key, key)) |
|||
{ |
|||
entries.RemoveAt(i); |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public bool Remove(KeyValuePair<TKey, TValue> item) |
|||
{ |
|||
for (var i = 0; i < entries.Count; i++) |
|||
{ |
|||
var entry = entries[i]; |
|||
|
|||
if (comparer.Equals(entry.Key, item.Key) && Equals(entry.Value, item.Value)) |
|||
{ |
|||
entries.RemoveAt(i); |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public void TrimExcess() |
|||
{ |
|||
entries.TrimExcess(); |
|||
} |
|||
|
|||
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) |
|||
{ |
|||
entries.CopyTo(array, arrayIndex); |
|||
} |
|||
|
|||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() |
|||
{ |
|||
return new Enumerator(this); |
|||
} |
|||
|
|||
IEnumerator IEnumerable.GetEnumerator() |
|||
{ |
|||
return new Enumerator(this); |
|||
} |
|||
} |
|||
@ -1,478 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections; |
|||
using Squidex.Infrastructure.TestHelpers; |
|||
|
|||
#pragma warning disable xUnit2017 // Do not use Contains() to check if a value exists in a collection
|
|||
#pragma warning disable IDE0028 // Simplify collection initialization
|
|||
#pragma warning disable CA1841 // Prefer Dictionary.Contains methods
|
|||
|
|||
namespace Squidex.Infrastructure.Collections; |
|||
|
|||
public class ListDictionaryTests |
|||
{ |
|||
[Fact] |
|||
public void Should_create_empty() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(); |
|||
|
|||
Assert.Empty(sut); |
|||
Assert.Equal(1, sut.Capacity); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_create_with_capacity() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(20); |
|||
|
|||
Assert.Empty(sut); |
|||
Assert.Equal(20, sut.Capacity); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_create_as_copy() |
|||
{ |
|||
var source = new ListDictionary<int, int>(); |
|||
|
|||
source.Add(1, 10); |
|||
source.Add(2, 20); |
|||
|
|||
var sut = new ListDictionary<int, int>(source); |
|||
|
|||
Assert.Equal(2, sut.Count); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_not_be_readonly() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(); |
|||
|
|||
Assert.False(sut.IsReadOnly); |
|||
Assert.False(sut.Keys.IsReadOnly); |
|||
Assert.False(sut.Values.IsReadOnly); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_add_item() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(); |
|||
|
|||
sut.Add(1, 10); |
|||
|
|||
Assert.Single(sut); |
|||
Assert.Equal(10, sut[1]); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_add_item_unsafe() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(); |
|||
|
|||
sut.AddUnsafe(1, 10); |
|||
|
|||
Assert.Single(sut); |
|||
Assert.Equal(10, sut[1]); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_add_item_as_pair() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(); |
|||
|
|||
sut.Add(new KeyValuePair<int, int>(1, 10)); |
|||
|
|||
Assert.Single(sut); |
|||
Assert.Equal(10, sut[1]); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_throw_exception_if_adding_existing_key() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(); |
|||
|
|||
sut.Add(1, 10); |
|||
|
|||
Assert.Throws<ArgumentException>(() => sut.Add(1, 20)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_throw_exception_if_adding_pair_with_existing_key() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(); |
|||
|
|||
sut.Add(1, 10); |
|||
|
|||
Assert.Throws<ArgumentException>(() => sut.Add(new KeyValuePair<int, int>(1, 20))); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_set_item() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(); |
|||
|
|||
sut[1] = 10; |
|||
|
|||
Assert.Single(sut); |
|||
Assert.Equal(10, sut[1]); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_override_item() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(); |
|||
|
|||
sut[1] = 20; |
|||
|
|||
Assert.Single(sut); |
|||
Assert.Equal(20, sut[1]); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_return_true_when_dictionary_contains_value() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(); |
|||
|
|||
sut.Add(1, 10); |
|||
|
|||
Assert.True(sut.Contains(new KeyValuePair<int, int>(1, 10))); |
|||
Assert.True(sut.ContainsKey(1)); |
|||
Assert.True(sut.Keys.Contains(1)); |
|||
Assert.True(sut.Values.Contains(10)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_return_false_when_dictionary_does_not_contains_value() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(); |
|||
|
|||
sut.Add(1, 10); |
|||
|
|||
Assert.False(sut.Contains(new KeyValuePair<int, int>(1, 20))); |
|||
Assert.False(sut.ContainsKey(2)); |
|||
Assert.False(sut.Keys.Contains(2)); |
|||
Assert.False(sut.Values.Contains(20)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_get_count() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(); |
|||
|
|||
sut.Add(1, 10); |
|||
sut.Add(2, 20); |
|||
sut.Add(3, 30); |
|||
|
|||
Assert.Equal(3, sut.Count); |
|||
Assert.Equal(3, sut.Keys.Count); |
|||
Assert.Equal(3, sut.Values.Count); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_clear() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(); |
|||
|
|||
sut.Add(1, 10); |
|||
sut.Add(2, 20); |
|||
sut.Add(3, 30); |
|||
sut.Clear(); |
|||
|
|||
Assert.Empty(sut); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_remove_key() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(); |
|||
|
|||
sut.Add(1, 10); |
|||
sut.Add(2, 20); |
|||
sut.Add(3, 30); |
|||
|
|||
Assert.True(sut.Remove(2)); |
|||
Assert.False(sut.ContainsKey(2)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_not_remove_key_if_not_found() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(); |
|||
|
|||
sut.Add(1, 10); |
|||
sut.Add(2, 20); |
|||
sut.Add(3, 30); |
|||
|
|||
Assert.False(sut.Remove(4)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_remove_item() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(); |
|||
|
|||
sut.Add(1, 10); |
|||
sut.Add(2, 20); |
|||
sut.Add(3, 30); |
|||
|
|||
Assert.True(sut.Remove(new KeyValuePair<int, int>(2, 20))); |
|||
Assert.False(sut.ContainsKey(2)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_not_remove_item_if_key_not_found() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(); |
|||
|
|||
sut.Add(1, 10); |
|||
sut.Add(2, 20); |
|||
sut.Add(3, 30); |
|||
|
|||
Assert.False(sut.Remove(new KeyValuePair<int, int>(4, 40))); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_not_remove_item_if_value_not_equal() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(); |
|||
|
|||
sut.Add(1, 10); |
|||
sut.Add(2, 20); |
|||
sut.Add(3, 30); |
|||
|
|||
Assert.False(sut.Remove(new KeyValuePair<int, int>(2, 40))); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_get_value_by_method_if_found() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(); |
|||
|
|||
sut.Add(1, 10); |
|||
sut.Add(2, 20); |
|||
|
|||
Assert.True(sut.TryGetValue(2, out var found)); |
|||
Assert.Equal(20, found); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_not_get_value_by_method_if_not_found() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(); |
|||
|
|||
sut.Add(1, 10); |
|||
sut.Add(3, 30); |
|||
|
|||
Assert.False(sut.TryGetValue(4, out var found)); |
|||
Assert.Equal(0, found); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_get_value_by_indexer_if_found() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(); |
|||
|
|||
sut.Add(1, 10); |
|||
sut.Add(2, 20); |
|||
|
|||
Assert.Equal(20, sut[2]); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_not_get_value_by_indexer_if_not_found() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(); |
|||
|
|||
sut.Add(1, 10); |
|||
sut.Add(2, 20); |
|||
|
|||
Assert.Throws<KeyNotFoundException>(() => sut[4]); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_loop_over_entries() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(); |
|||
|
|||
sut.Add(1, 10); |
|||
sut.Add(2, 20); |
|||
|
|||
var actual = new List<KeyValuePair<int, int>>(); |
|||
|
|||
foreach (var entry in sut) |
|||
{ |
|||
actual.Add(entry); |
|||
} |
|||
|
|||
Assert.Equal(new[] |
|||
{ |
|||
new KeyValuePair<int, int>(1, 10), |
|||
new KeyValuePair<int, int>(2, 20) |
|||
}, actual.ToArray()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_loop_over_entries_with_old_enumerator() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(); |
|||
|
|||
sut.Add(1, 10); |
|||
sut.Add(2, 20); |
|||
|
|||
var actual = new List<KeyValuePair<int, int>>(); |
|||
|
|||
foreach (KeyValuePair<int, int> entry in (IEnumerable)sut) |
|||
{ |
|||
actual.Add(entry); |
|||
} |
|||
|
|||
Assert.Equal(new[] |
|||
{ |
|||
new KeyValuePair<int, int>(1, 10), |
|||
new KeyValuePair<int, int>(2, 20) |
|||
}, actual.ToArray()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_copy_entries_to_array() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(); |
|||
|
|||
sut.Add(1, 10); |
|||
sut.Add(2, 20); |
|||
|
|||
Assert.Equal(new[] |
|||
{ |
|||
new KeyValuePair<int, int>(1, 10), |
|||
new KeyValuePair<int, int>(2, 20) |
|||
}, sut.ToArray()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_loop_over_keys() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(); |
|||
|
|||
sut.Add(1, 10); |
|||
sut.Add(2, 20); |
|||
|
|||
var actual = new List<int>(); |
|||
|
|||
foreach (var entry in sut.Keys) |
|||
{ |
|||
actual.Add(entry); |
|||
} |
|||
|
|||
Assert.Equal(new[] { 1, 2 }, actual.ToArray()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_loop_over_keys_with_old_enumerator() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(); |
|||
|
|||
sut.Add(1, 10); |
|||
sut.Add(2, 20); |
|||
|
|||
var actual = new List<int>(); |
|||
|
|||
foreach (int entry in (IEnumerable)sut.Keys) |
|||
{ |
|||
actual.Add(entry); |
|||
} |
|||
|
|||
Assert.Equal(new[] { 1, 2 }, actual.ToArray()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_copy_keys_to_array() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(); |
|||
|
|||
sut.Add(1, 10); |
|||
sut.Add(2, 20); |
|||
|
|||
Assert.Equal(new[] { 1, 2 }, sut.Keys.ToArray()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_loop_over_values() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(); |
|||
|
|||
sut.Add(1, 10); |
|||
sut.Add(2, 20); |
|||
|
|||
var actual = new List<int>(); |
|||
|
|||
foreach (var entry in sut.Values) |
|||
{ |
|||
actual.Add(entry); |
|||
} |
|||
|
|||
Assert.Equal(new[] { 10, 20 }, actual.ToArray()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_loop_over_values_with_old_enumerator() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(); |
|||
|
|||
sut.Add(1, 10); |
|||
sut.Add(2, 20); |
|||
|
|||
var actual = new List<int>(); |
|||
|
|||
foreach (int entry in (IEnumerable)sut.Values) |
|||
{ |
|||
actual.Add(entry); |
|||
} |
|||
|
|||
Assert.Equal(new[] { 10, 20 }, actual.ToArray()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_copy_values_to_array() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(); |
|||
|
|||
sut.Add(1, 10); |
|||
sut.Add(2, 20); |
|||
|
|||
Assert.Equal(new[] { 10, 20 }, sut.Values.ToArray()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_trim() |
|||
{ |
|||
var sut = new ListDictionary<int, int>(20); |
|||
|
|||
sut.Add(1, 10); |
|||
sut.Add(2, 20); |
|||
|
|||
Assert.Equal(20, sut.Capacity); |
|||
|
|||
sut.TrimExcess(); |
|||
|
|||
Assert.Equal(2, sut.Capacity); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_serialize_and_deserialize() |
|||
{ |
|||
var sut = new Dictionary<int, int> |
|||
{ |
|||
[11] = 1, |
|||
[12] = 2, |
|||
[13] = 3 |
|||
}.ToReadonlyDictionary(); |
|||
|
|||
var serialized = sut.SerializeAndDeserialize(); |
|||
|
|||
Assert.Equal(sut, serialized); |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue