diff --git a/src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs b/src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs index 0258998e9..2ebc1d054 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs @@ -25,8 +25,8 @@ namespace Squidex.Domain.Apps.Core.Contents { } - protected ContentData(IDictionary copy, IEqualityComparer comparer) - : base(copy, comparer) + protected ContentData(int capacity, IEqualityComparer comparer) + : base(capacity, comparer) { } @@ -43,7 +43,7 @@ namespace Squidex.Domain.Apps.Core.Contents { foreach (var otherValue in source) { - var fieldValue = target.GetOrAdd(otherValue.Key, x => new ContentFieldData()); + var fieldValue = target.GetOrAddNew(otherValue.Key); foreach (var value in otherValue.Value) { diff --git a/src/Squidex.Domain.Apps.Core.Model/Contents/IdContentData.cs b/src/Squidex.Domain.Apps.Core.Model/Contents/IdContentData.cs index 6398d66d4..0ca33663e 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Contents/IdContentData.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Contents/IdContentData.cs @@ -18,8 +18,8 @@ namespace Squidex.Domain.Apps.Core.Contents { } - public IdContentData(IdContentData copy) - : base(copy, EqualityComparer.Default) + public IdContentData(int capacity) + : base(capacity, EqualityComparer.Default) { } diff --git a/src/Squidex.Domain.Apps.Core.Model/Contents/NamedContentData.cs b/src/Squidex.Domain.Apps.Core.Model/Contents/NamedContentData.cs index b7e4187a5..faa409abb 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Contents/NamedContentData.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Contents/NamedContentData.cs @@ -18,8 +18,8 @@ namespace Squidex.Domain.Apps.Core.Contents { } - public NamedContentData(NamedContentData copy) - : base(copy, EqualityComparer.Default) + public NamedContentData(int capacity) + : base(capacity, EqualityComparer.Default) { } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs index 1480a807f..ce32898f7 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs @@ -5,6 +5,9 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; +using System.Collections.Generic; +using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; @@ -13,120 +16,83 @@ namespace Squidex.Domain.Apps.Core.ConvertContent { public delegate ContentFieldData FieldConverter(ContentFieldData data, IRootField field); + public delegate JToken ValueConverter(JToken value, IRootField field); + public static class ContentConverter { - public static NamedContentData ToNameModel(this IdContentData source, Schema schema, params FieldConverter[] converters) + public static NamedContentData ToNameModel(this IdContentData content, Schema schema, params FieldConverter[] converters) { Guard.NotNull(schema, nameof(schema)); - var result = new NamedContentData(); - - foreach (var fieldValue in source) - { - if (!schema.FieldsById.TryGetValue(fieldValue.Key, out var field)) - { - continue; - } - - var fieldData = Convert(fieldValue.Value, field, converters); - - if (fieldData != null) - { - result[field.Name] = fieldData; - } - } + var result = new NamedContentData(content.Count); - return result; + return ConvertInternal(content, result, schema.FieldsById, x => x.Name, converters); } public static IdContentData ToIdModel(this NamedContentData content, Schema schema, params FieldConverter[] converters) { Guard.NotNull(schema, nameof(schema)); - var result = new IdContentData(); + var result = new IdContentData(content.Count); - foreach (var fieldValue in content) - { - if (!schema.FieldsByName.TryGetValue(fieldValue.Key, out var field)) - { - continue; - } - - var fieldData = Convert(fieldValue.Value, field, converters); - - if (fieldData != null) - { - result[field.Id] = fieldData; - } - } - - return result; + return ConvertInternal(content, result, schema.FieldsByName, x => x.Id, converters); } public static IdContentData Convert(this IdContentData content, Schema schema, params FieldConverter[] converters) { Guard.NotNull(schema, nameof(schema)); - var result = new IdContentData(); + var result = new IdContentData(content.Count); - foreach (var fieldValue in content) - { - if (!schema.FieldsById.TryGetValue(fieldValue.Key, out var field)) - { - continue; - } - - var fieldData = Convert(fieldValue.Value, field, converters); - - if (fieldData != null) - { - result[field.Id] = fieldData; - } - } - - return result; + return ConvertInternal(content, result, schema.FieldsById, x => x.Id, converters); } public static NamedContentData Convert(this NamedContentData content, Schema schema, params FieldConverter[] converters) { Guard.NotNull(schema, nameof(schema)); - var result = new NamedContentData(); + var result = new NamedContentData(content.Count); - foreach (var fieldValue in content) + return ConvertInternal(content, result, schema.FieldsByName, x => x.Name, converters); + } + + private static TDict2 ConvertInternal( + TDict1 source, + TDict2 target, + IReadOnlyDictionary fields, + Func targetKey, params FieldConverter[] converters) + where TDict1 : IDictionary + where TDict2 : IDictionary + { + foreach (var fieldKvp in source) { - if (!schema.FieldsByName.TryGetValue(fieldValue.Key, out var field)) + if (!fields.TryGetValue(fieldKvp.Key, out var field)) { continue; } - var fieldData = Convert(fieldValue.Value, field, converters); + var fieldValue = fieldKvp.Value; - if (fieldData != null) + if (converters != null) { - result[field.Name] = fieldData; - } - } + foreach (var converter in converters) + { + fieldValue = converter(fieldValue, field); - return result; - } + if (fieldValue == null) + { + break; + } + } + } - private static ContentFieldData Convert(ContentFieldData fieldData, IRootField field, FieldConverter[] converters) - { - if (converters != null) - { - foreach (var converter in converters) + if (fieldValue != null) { - fieldData = converter(fieldData, field); - - if (fieldData == null) - { - break; - } + target.Add(targetKey(field), fieldValue); } } - return fieldData; + return target; } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs index b842ef7a0..50576965e 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs @@ -8,7 +8,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; @@ -35,29 +34,23 @@ namespace Squidex.Domain.Apps.Core.ConvertContent { return (data, field) => { - var isValid = true; - foreach (var value in data.Values) { + if (value.IsNull()) + { + continue; + } + try { - if (!value.IsNull()) - { - JsonValueConverter.ConvertValue(field, value); - } + JsonValueConverter.ConvertValue(field, value); } catch { - isValid = false; - break; + return null; } } - if (!isValid) - { - return null; - } - return data; }; } @@ -173,12 +166,10 @@ namespace Squidex.Domain.Apps.Core.ConvertContent return (data, field) => data; } - var languageCodes = - new HashSet( - languages.Select(x => x.Iso2Code).Where(x => languagesConfig.Contains(x)), - StringComparer.OrdinalIgnoreCase); + var languageCodes = languages.Select(x => x.Iso2Code).Where(x => languagesConfig.Contains(x)); + var languageSet = new HashSet(languageCodes, StringComparer.OrdinalIgnoreCase); - if (languageCodes.Count == 0) + if (languageSet.Count == 0) { return (data, field) => data; } @@ -189,7 +180,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent { var result = new ContentFieldData(); - foreach (var languageCode in languageCodes) + foreach (var languageCode in languageSet) { if (data.TryGetValue(languageCode, out var value)) { @@ -204,58 +195,101 @@ namespace Squidex.Domain.Apps.Core.ConvertContent }; } - public static FieldConverter DecodeJson() + private static FieldConverter ForNested(params ValueConverter[] converters) { return (data, field) => { - if (field is IField) + if (field is IArrayField arrayField) { - var result = new ContentFieldData(); - - foreach (var partitionValue in data) + foreach (var partition in data) { - if (partitionValue.Value.IsNull()) - { - result[partitionValue.Key] = null; - } - else + if (partition.Value is JArray jArray) { - var value = Encoding.UTF8.GetString(Convert.FromBase64String(partitionValue.Value.ToString())); - - result[partitionValue.Key] = JToken.Parse(value); + for (var i = 0; i < jArray.Count; i++) + { + if (jArray[i] is JObject item) + { + var result = new JObject(); + + foreach (var kvp in item) + { + if (!arrayField.FieldsByName.TryGetValue(kvp.Key, out var nestedField)) + { + continue; + } + + var newValue = kvp.Value; + + if (converters != null) + { + foreach (var converter in converters) + { + newValue = converter(newValue, field); + + if (ReferenceEquals(newValue, Value.Unset)) + { + break; + } + } + } + + if (!ReferenceEquals(newValue, Value.Unset)) + { + result.Add(field.Id.ToString(), newValue); + } + } + + jArray[i] = item; + } + } } } - - return result; } return data; }; } - public static FieldConverter EncodeJson() + public static FieldConverter ForValues(params ValueConverter[] converters) { return (data, field) => { - if (field is IField) + if (!(field is IArrayField)) { - var result = new ContentFieldData(); + ContentFieldData result = null; - foreach (var partitionValue in data) + foreach (var partition in data) { - if (partitionValue.Value.IsNull()) + var newValue = partition.Value; + + if (converters != null) { - result[partitionValue.Key] = null; + foreach (var converter in converters) + { + newValue = converter(newValue, field); + + if (ReferenceEquals(newValue, Value.Unset)) + { + break; + } + } } - else + + if (result != null || !ReferenceEquals(newValue, partition.Value)) { - var value = Convert.ToBase64String(Encoding.UTF8.GetBytes(partitionValue.Value.ToString())); + if (result == null) + { + result = new ContentFieldData(); + } - result[partitionValue.Key] = value; + if (!ReferenceEquals(newValue, Value.Unset)) + { + result.Add(partition.Key, newValue); + } } } - return result; + return result ?? data; } return data; diff --git a/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/Value.cs b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/Value.cs new file mode 100644 index 000000000..de7e3aca1 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/Value.cs @@ -0,0 +1,20 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Newtonsoft.Json.Linq; + +namespace Squidex.Domain.Apps.Core.ConvertContent +{ + public sealed class Value + { + public static readonly JToken Unset = JValue.CreateUndefined(); + + private Value() + { + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs new file mode 100644 index 000000000..f2bbd522b --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs @@ -0,0 +1,77 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Text; +using Newtonsoft.Json.Linq; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Core.ValidateContent; +using Squidex.Infrastructure.Json; + +namespace Squidex.Domain.Apps.Core.ConvertContent +{ + public static class ValueConverters + { + public static ValueConverter DecodeJson() + { + return (value, field) => + { + if (!value.IsNull() && field is IField) + { + var decoded = Encoding.UTF8.GetString(Convert.FromBase64String(value.ToString())); + + return JToken.Parse(decoded); + } + + return value; + }; + } + + public static ValueConverter EncodeJson() + { + return (value, field) => + { + if (!value.IsNull() && field is IField) + { + var encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(value.ToString())); + + return encoded; + } + + return value; + }; + } + + public static ValueConverter ExcludeHidden() + { + return (value, field) => + { + return field.IsHidden ? null : value; + }; + } + + public static ValueConverter ExcludeChangedTypes() + { + return (value, field) => + { + try + { + if (!value.IsNull()) + { + JsonValueConverter.ConvertValue(field, value); + } + } + catch + { + return null; + } + + return value; + }; + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/FieldReferencesConverter.cs b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/FieldReferencesConverter.cs index 8f1a751cc..15292a312 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/FieldReferencesConverter.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/FieldReferencesConverter.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; -using System.Linq; using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Infrastructure.Json; @@ -15,20 +14,18 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds { public static class FieldReferencesConverter { - public static FieldConverter CleanReferences(IEnumerable deletedReferencedIds) + public static ValueConverter CleanReferences(IEnumerable deletedReferencedIds) { var ids = new HashSet(deletedReferencedIds); - return (data, field) => + return (value, field) => { - foreach (var partitionValue in data.Where(x => !x.Value.IsNull()).ToList()) + if (value.IsNull()) { - var newValue = field.CleanReferences(partitionValue.Value, ids); - - data[partitionValue.Key] = newValue; + return value; } - return data; + return field.CleanReferences(value, ids); }; } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs index 54f1e95fa..4c072e9ab 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs @@ -7,65 +7,95 @@ using System; using System.Collections.Generic; -using System.Linq; using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Infrastructure.Json; namespace Squidex.Domain.Apps.Core.ExtractReferenceIds { - public static class ReferencesCleaner + public sealed class ReferencesCleaner : IFieldVisitor { - private static readonly List EmptyIds = new List(); + private readonly JToken value; + private readonly ICollection oldReferences; - public static JToken CleanReferences(this IField field, JToken value, ISet oldReferences) + private ReferencesCleaner(JToken value, ICollection oldReferences) { - if ((field is IField || field is IField) && !value.IsNull()) - { - switch (field) - { - case IField assetsField: - return Visit(assetsField, value, oldReferences); - - case IField referencesField: - return Visit(referencesField, value, oldReferences); - } - } + this.value = value; - return value; + this.oldReferences = oldReferences; } - private static JToken Visit(IField field, JToken value, IEnumerable oldReferences) + public static JToken CleanReferences(IField field, JToken value, ICollection oldReferences) { - var oldIds = GetIds(value); - var newIds = oldIds.Except(oldReferences).ToList(); + return field.Accept(new ReferencesCleaner(value, oldReferences)); + } - return oldIds.Count != newIds.Count ? JToken.FromObject(newIds) : value; + public JToken Visit(IArrayField field) + { + return value; } - private static JToken Visit(IField field, JToken value, ICollection oldReferences) + public JToken Visit(IField field) + { + return CleanIds(); + } + + public JToken Visit(IField field) { if (oldReferences.Contains(field.Properties.SchemaId)) { return new JArray(); } - var oldIds = GetIds(value); - var newIds = oldIds.Except(oldReferences).ToList(); - - return oldIds.Count != newIds.Count ? JToken.FromObject(newIds) : value; + return CleanIds(); } - private static List GetIds(JToken value) + private JToken CleanIds() { - try - { - return value?.ToObject>() ?? EmptyIds; - } - catch + var ids = value.ToGuidSet(); + + var isRemoved = false; + + foreach (var oldReference in oldReferences) { - return EmptyIds; + isRemoved |= ids.Remove(oldReference); } + + return isRemoved ? ids.ToJToken() : value; + } + + public JToken Visit(IField field) + { + return value; + } + + public JToken Visit(IField field) + { + return value; + } + + public JToken Visit(IField field) + { + return value; + } + + public JToken Visit(IField field) + { + return value; + } + + public JToken Visit(IField field) + { + return value; + } + + public JToken Visit(IField field) + { + return value; + } + + public JToken Visit(IField field) + { + return value; } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtensions.cs b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtensions.cs new file mode 100644 index 000000000..b7170f9aa --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtensions.cs @@ -0,0 +1,69 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using Newtonsoft.Json.Linq; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure.Json; + +namespace Squidex.Domain.Apps.Core.ExtractReferenceIds +{ + public static class ReferencesExtensions + { + public static IEnumerable ExtractReferences(this IField field, JToken value) + { + return ReferencesExtractor.ExtractReferences(field, value); + } + + public static JToken CleanReferences(this IField field, JToken value, ICollection oldReferences) + { + if (value.IsNull()) + { + return value; + } + + return ReferencesCleaner.CleanReferences(field, value, oldReferences); + } + + public static JToken ToJToken(this HashSet ids) + { + var result = new JArray(); + + foreach (var id in ids) + { + result.Add(new JValue(id)); + } + + return result; + } + + public static HashSet ToGuidSet(this JToken value) + { + if (value is JArray ids) + { + var result = new HashSet(); + + foreach (var id in ids) + { + if (id.Type == JTokenType.Guid) + { + result.Add((Guid)id); + } + else if (id.Type == JTokenType.String && Guid.TryParse((string)id, out var guid)) + { + result.Add(guid); + } + } + + return result; + } + + return new HashSet(); + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs index 216172bc0..c39dc66fd 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs @@ -13,50 +13,93 @@ using Squidex.Domain.Apps.Core.Schemas; namespace Squidex.Domain.Apps.Core.ExtractReferenceIds { - public static class ReferencesExtractor + public sealed class ReferencesExtractor : IFieldVisitor> { - public static IEnumerable ExtractReferences(this IField field, JToken value) - { - switch (field) - { - case IField assetsField: - return Visit(assetsField, value); + private readonly JToken value; - case IField referencesField: - return Visit(referencesField, value); - } + private ReferencesExtractor(JToken value) + { + this.value = value; + } - return Enumerable.Empty(); + public static IEnumerable ExtractReferences(IField field, JToken value) + { + return field.Accept(new ReferencesExtractor(value)); } - public static IEnumerable Visit(IField field, JToken value) + public IEnumerable Visit(IArrayField field) { - IEnumerable result; - try - { - result = value?.ToObject>(); - } - catch + var result = new List(); + + if (value is JArray items) { - result = null; + foreach (JObject item in value) + { + foreach (var nestedField in field.Fields) + { + if (item.TryGetValue(field.Name, out var value)) + { + result.AddRange(nestedField.Accept(new ReferencesExtractor(value))); + } + } + } } - return result ?? Enumerable.Empty(); + return result; } - private static IEnumerable Visit(IField field, JToken value) + public IEnumerable Visit(IField field) { - IEnumerable result; - try - { - result = value?.ToObject>() ?? Enumerable.Empty(); - } - catch + var ids = value.ToGuidSet(); + + return ids; + } + + public IEnumerable Visit(IField field) + { + var ids = value.ToGuidSet(); + + if (field.Properties.SchemaId != Guid.Empty) { - result = Enumerable.Empty(); + ids.Add(field.Properties.SchemaId); } - return result.Union(new[] { field.Properties.SchemaId }); + return ids; + } + + public IEnumerable Visit(IField field) + { + return Enumerable.Empty(); + } + + public IEnumerable Visit(IField field) + { + return Enumerable.Empty(); + } + + public IEnumerable Visit(IField field) + { + return Enumerable.Empty(); + } + + public IEnumerable Visit(IField field) + { + return Enumerable.Empty(); + } + + public IEnumerable Visit(IField field) + { + return Enumerable.Empty(); + } + + public IEnumerable Visit(IField field) + { + return Enumerable.Empty(); + } + + public IEnumerable Visit(IField field) + { + return Enumerable.Empty(); } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataObject.cs b/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataObject.cs index 79b1e14c4..6a72f9508 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataObject.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataObject.cs @@ -95,14 +95,14 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper { EnsurePropertiesInitialized(); - fieldProperties.GetOrAdd(propertyName, x => new ContentDataProperty(this)).Value = value; + fieldProperties.GetOrAdd(propertyName, this, (k, c) => new ContentDataProperty(c)).Value = value; } public override PropertyDescriptor GetOwnProperty(string propertyName) { EnsurePropertiesInitialized(); - return fieldProperties.GetOrAdd(propertyName, x => new ContentDataProperty(this, new ContentFieldObject(this, new ContentFieldData(), false))); + return fieldProperties.GetOrAdd(propertyName, this, (k, c) => new ContentDataProperty(c, new ContentFieldObject(c, new ContentFieldData(), false))); } public override IEnumerable> GetOwnProperties() diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs index 3e4801ae0..aa3220fa9 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs @@ -160,7 +160,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL return null; } - return schema != null ? contentDataTypes.GetOrAdd(schema, s => new ContentDataGraphType()) : null; + return schema != null ? contentDataTypes.GetOrAddNew(schema) : null; } public IGraphType GetContentType(Guid schemaId) diff --git a/src/Squidex.Infrastructure.Redis/RedisPubSub.cs b/src/Squidex.Infrastructure.Redis/RedisPubSub.cs index 353f7f295..ce14d18fb 100644 --- a/src/Squidex.Infrastructure.Redis/RedisPubSub.cs +++ b/src/Squidex.Infrastructure.Redis/RedisPubSub.cs @@ -56,7 +56,7 @@ namespace Squidex.Infrastructure { var typeName = typeof(T).FullName; - return (RedisSubscription)subscriptions.GetOrAdd(typeName, c => new RedisSubscription(redisSubscriber.Value, c, log)); + return (RedisSubscription)subscriptions.GetOrAdd(typeName, this, (k, c) => new RedisSubscription(c.redisSubscriber.Value, k, c.log)); } } } diff --git a/src/Squidex.Infrastructure/CollectionExtensions.cs b/src/Squidex.Infrastructure/CollectionExtensions.cs index febc9654d..18bcc7ae9 100644 --- a/src/Squidex.Infrastructure/CollectionExtensions.cs +++ b/src/Squidex.Infrastructure/CollectionExtensions.cs @@ -172,6 +172,18 @@ namespace Squidex.Infrastructure return result; } + public static TValue GetOrAdd(this IDictionary dictionary, TKey key, TContext context, Func creator) + { + if (!dictionary.TryGetValue(key, out var result)) + { + result = creator(key, context); + + dictionary.Add(key, result); + } + + return result; + } + public static void Foreach(this IEnumerable collection, Action action) { foreach (var item in collection) diff --git a/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemaSwaggerGenerator.cs b/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemaSwaggerGenerator.cs index 03acbbdad..d52320731 100644 --- a/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemaSwaggerGenerator.cs +++ b/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemaSwaggerGenerator.cs @@ -242,7 +242,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator private SwaggerOperations AddOperation(SwaggerOperationMethod method, string entityName, string path, Action updater) { - var operations = document.Paths.GetOrAdd(path, k => new SwaggerOperations()); + var operations = document.Paths.GetOrAddNew(path); var operation = new SwaggerOperation(); updater(operation); diff --git a/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemasSwaggerGenerator.cs b/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemasSwaggerGenerator.cs index c88f8cad2..adf41afad 100644 --- a/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemasSwaggerGenerator.cs +++ b/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemasSwaggerGenerator.cs @@ -78,7 +78,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator { name = char.ToUpperInvariant(name[0]) + name.Substring(1); - return new JsonSchema4 { Reference = document.Definitions.GetOrAdd(name, x => schema) }; + return new JsonSchema4 { Reference = document.Definitions.GetOrAdd(name, schema, (k, c) => c) }; } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/ArrayFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/ArrayFieldTests.cs new file mode 100644 index 000000000..de33f9949 --- /dev/null +++ b/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/ArrayFieldTests.cs @@ -0,0 +1,222 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using Squidex.Domain.Apps.Core.Schemas; +using Xunit; + +#pragma warning disable SA1310 // Field names must not contain underscore + +namespace Squidex.Domain.Apps.Core.Model.Schemas +{ + public class ArrayfieldTests + { + private readonly JsonSerializer serializer = TestData.DefaultSerializer(); + private readonly ArrayField parent_0 = Fields.Array(100, "root", Partitioning.Invariant); + + [Fact] + public void Should_add_field() + { + var field = CreateField(1); + + var parent_1 = parent_0.AddField(field); + + Assert.Empty(parent_0.Fields); + Assert.Equal(field, parent_1.FieldsById[1]); + } + + [Fact] + public void Should_throw_exception_if_adding_field_with_name_that_already_exists() + { + var parent_1 = parent_0.AddField(CreateField(1)); + + Assert.Throws(() => parent_1.AddNumber(2, "my-field-1")); + } + + [Fact] + public void Should_throw_exception_if_adding_field_with_id_that_already_exists() + { + var parent_1 = parent_0.AddField(CreateField(1)); + + Assert.Throws(() => parent_1.AddNumber(1, "my-field-2")); + } + + [Fact] + public void Should_hide_field() + { + var parent_1 = parent_0.AddField(CreateField(1)); + + var parent_2 = parent_1.UpdateField(1, f => f.Hide()); + var parent_3 = parent_2.UpdateField(1, f => f.Hide()); + + Assert.False(parent_1.FieldsById[1].IsHidden); + Assert.True(parent_3.FieldsById[1].IsHidden); + } + + [Fact] + public void Should_return_same_parent_if_field_to_hide_does_not_exist() + { + var parent_1 = parent_0.UpdateField(1, f => f.Hide()); ; + + Assert.Same(parent_0, parent_1); + } + + [Fact] + public void Should_show_field() + { + var parent_1 = parent_0.AddField(CreateField(1)); + + var parent_2 = parent_1.UpdateField(1, f => f.Hide()); + var parent_3 = parent_2.UpdateField(1, f => f.Show()); + var parent_4 = parent_3.UpdateField(1, f => f.Show()); + + Assert.True(parent_2.FieldsById[1].IsHidden); + Assert.False(parent_4.FieldsById[1].IsHidden); + } + + [Fact] + public void Should_return_same_parent_if_field_to_show_does_not_exist() + { + var parent_1 = parent_0.UpdateField(1, f => f.Show()); + + Assert.Same(parent_0, parent_1); + } + + [Fact] + public void Should_disable_field() + { + var parent_1 = parent_0.AddField(CreateField(1)); + + var parent_2 = parent_1.UpdateField(1, f => f.Disable()); + var parent_3 = parent_2.UpdateField(1, f => f.Disable()); + + Assert.False(parent_1.FieldsById[1].IsDisabled); + Assert.True(parent_3.FieldsById[1].IsDisabled); + } + + [Fact] + public void Should_return_same_parent_if_field_to_disable_does_not_exist() + { + var parent_1 = parent_0.UpdateField(1, f => f.Disable()); + + Assert.Same(parent_0, parent_1); + } + + [Fact] + public void Should_enable_field() + { + var parent_1 = parent_0.AddField(CreateField(1)); + + var parent_2 = parent_1.UpdateField(1, f => f.Disable()); + var parent_3 = parent_2.UpdateField(1, f => f.Enable()); + var parent_4 = parent_3.UpdateField(1, f => f.Enable()); + + Assert.True(parent_2.FieldsById[1].IsDisabled); + Assert.False(parent_4.FieldsById[1].IsDisabled); + } + + [Fact] + public void Should_return_same_parent_if_field_to_enable_does_not_exist() + { + var parent_1 = parent_0.UpdateField(1, f => f.Enable()); + + Assert.Same(parent_0, parent_1); + } + + [Fact] + public void Should_update_field() + { + var properties = new NumberFieldProperties(); + + var parent_1 = parent_0.AddField(CreateField(1)); + var parent_2 = parent_1.UpdateField(1, f => f.Update(properties)); + + Assert.NotSame(properties, parent_1.FieldsById[1].RawProperties); + Assert.Same(properties, parent_2.FieldsById[1].RawProperties); + } + + [Fact] + public void Should_throw_exception_if_updating_with_invalid_properties_type() + { + var parent_1 = parent_0.AddField(CreateField(1)); + + Assert.Throws(() => parent_1.UpdateField(1, f => f.Update(new StringFieldProperties()))); + } + + [Fact] + public void Should_return_same_parent_if_field_to_update_does_not_exist() + { + var parent_1 = parent_0.UpdateField(1, f => f.Update(new StringFieldProperties())); + + Assert.Same(parent_0, parent_1); + } + + [Fact] + public void Should_delete_field() + { + var parent_1 = parent_0.AddField(CreateField(1)); + var parent_2 = parent_1.DeleteField(1); + + Assert.Empty(parent_2.FieldsById); + } + + [Fact] + public void Should_return_same_parent_if_field_to_delete_does_not_exist() + { + var parent_1 = parent_0.DeleteField(1); + + Assert.Same(parent_0, parent_1); + } + + [Fact] + public void Should_reorder_fields() + { + var field1 = CreateField(1); + var field2 = CreateField(2); + var field3 = CreateField(3); + + var parent_1 = parent_0.AddField(field1); + var parent_2 = parent_1.AddField(field2); + var parent_3 = parent_2.AddField(field3); + var parent_4 = parent_3.ReorderFields(new List { 3, 2, 1 }); + + Assert.Equal(new List { field3, field2, field1 }, parent_4.Fields.ToList()); + } + + [Fact] + public void Should_throw_exception_if_not_all_fields_are_covered_for_reordering() + { + var field1 = CreateField(1); + var field2 = CreateField(2); + + var parent_1 = parent_0.AddField(field1); + var parent_2 = parent_1.AddField(field2); + + Assert.Throws(() => parent_2.ReorderFields(new List { 1 })); + } + + [Fact] + public void Should_throw_exception_if_field_to_reorder_does_not_exist() + { + var field1 = CreateField(1); + var field2 = CreateField(2); + + var parent_1 = parent_0.AddField(field1); + var parent_2 = parent_1.AddField(field2); + + Assert.Throws(() => parent_2.ReorderFields(new List { 1, 4 })); + } + + private static NestedField CreateField(int id) + { + return Fields.Number(id, $"my-field-{id}"); + } + } +} diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs index 0a3937dca..bd25cd43c 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs @@ -21,30 +21,35 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent private readonly LanguagesConfig languagesConfig = LanguagesConfig.Build(Language.EN, Language.DE); private readonly RootField stringLanguageField = Fields.String(1, "1", Partitioning.Language); private readonly RootField stringInvariantField = Fields.String(1, "1", Partitioning.Invariant); + private readonly RootField jsonField = Fields.Json(1, "1", Partitioning.Invariant); private readonly RootField numberField = Fields.Number(1, "1", Partitioning.Invariant); [Fact] - public void Should_encode_json_values() + public void Should_encode_json_value() { - var source = - new ContentFieldData() - .AddValue("en", null) - .AddValue("de", JToken.FromObject(new { Value = 1 })); + var source = JToken.FromObject(new { Value = 1 }); - var result = FieldConverters.EncodeJson()(source, Fields.Json(1, "1", Partitioning.Invariant)); + var result = ValueConverters.EncodeJson()(source, jsonField); - Assert.Null(result["en"]); - Assert.True(result["de"].Type == JTokenType.String); + Assert.True(result.Type == JTokenType.String); } [Fact] - public void Should_return_same_values_if_encoding_non_json_field() + public void Should_return_same_value_if_encoding_null_value() { - var source = - new ContentFieldData() - .AddValue("en", null); + var source = JValue.CreateNull(); - var result = FieldConverters.EncodeJson()(source, stringLanguageField); + var result = ValueConverters.EncodeJson()(source, jsonField); + + Assert.Same(source, result); + } + + [Fact] + public void Should_return_same_value_if_encoding_non_json_field() + { + var source = (JToken)"NO-JSON"; + + var result = ValueConverters.EncodeJson()(source, stringLanguageField); Assert.Same(source, result); } @@ -52,15 +57,31 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent [Fact] public void Should_decode_json_values() { - var source = - new ContentFieldData() - .AddValue("en", null) - .AddValue("de", "e30="); + var source = "e30="; + + var result = ValueConverters.DecodeJson()(source, jsonField); + + Assert.True(result is JObject); + } + + [Fact] + public void Should_return_same_value_if_decoding_null_value() + { + var source = JValue.CreateNull(); + + var result = ValueConverters.DecodeJson()(source, jsonField); + + Assert.Same(source, result); + } - var result = FieldConverters.DecodeJson()(source, Fields.Json(1, "1", Partitioning.Invariant)); + [Fact] + public void Should_return_same_value_if_decoding_non_json_field() + { + var source = JValue.CreateNull(); - Assert.Null(result["en"]); - Assert.True(result["de"] is JObject); + var result = ValueConverters.EncodeJson()(source, stringLanguageField); + + Assert.Same(source, result); } [Fact] @@ -77,7 +98,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent } [Fact] - public void Should_return_null_values_if_all_value_is_invalid() + public void Should_return_null_values_any_value_is_invalid() { var source = new ContentFieldData() @@ -89,18 +110,6 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent Assert.Null(result); } - [Fact] - public void Should_return_same_values_if_decoding_non_json_field() - { - var source = - new ContentFieldData() - .AddValue("en", null); - - var result = FieldConverters.DecodeJson()(source, stringLanguageField); - - Assert.Same(source, result); - } - [Fact] public void Should_return_same_values_if_field_not_hidden() { diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs index 032431e77..2593369f9 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs @@ -69,7 +69,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds new ContentFieldData() .AddValue("iv", new JArray(id1.ToString(), id2.ToString()))); - var actual = input.Convert(schema, FieldReferencesConverter.CleanReferences(new[] { id2 })); + var converter = FieldConverters.ForValues(FieldReferencesConverter.CleanReferences(new[] { id2 })); + + var actual = input.Convert(schema, converter); var cleanedValue = (JArray)actual[5]["iv"]; @@ -91,7 +93,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds } [Fact] - public void Should_empty_list_from_assets_field_for_referenced_ids_when_null() + public void Should_return_empty_list_from_assets_field_for_referenced_ids_when_null() { var sut = Fields.Assets(1, "my-asset", Partitioning.Invariant); @@ -101,7 +103,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds } [Fact] - public void Should_empty_list_from_assets_field_for_referenced_ids_when_other_type() + public void Should_return_empty_list_from_assets_field_for_referenced_ids_when_other_type() { var sut = Fields.Assets(1, "my-asset", Partitioning.Invariant); @@ -111,7 +113,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds } [Fact] - public void Should_empty_list_from_non_references_field() + public void Should_return_empty_list_from_non_references_field() { var sut = Fields.String(1, "my-string", Partitioning.Invariant);