From 804b6b186931efce5eb7e5234dcecd8c1bab8467 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Wed, 29 Jun 2022 16:49:39 +0200 Subject: [PATCH] Item converters. (#892) --- .../ConvertContent/AddSchemaNames.cs | 51 ++ .../ConvertContent/ContentConverter.cs | 264 ++++++++- .../ConvertContent/ExcludeChangedTypes.cs | 66 +++ .../ConvertContent/ExcludeHidden.cs | 32 ++ .../ConvertContent/FieldConverters.cs | 403 -------------- .../ConvertContent/IConverter.cs | 39 ++ .../ConvertContent/ResolveAssetUrls.cs | 78 +++ .../ConvertContent/ResolveInvariant.cs | 58 ++ .../ConvertContent/ResolveLanguages.cs | 104 ++++ .../ConvertContent/ValueConverters.cs | 111 ---- .../ValueReferencesConverter.cs | 27 +- .../Contents/Queries/Steps/ConvertData.cs | 59 +- backend/tests/RunCoverage.ps1 | 12 +- .../ConvertContent/ContentConversionTests.cs | 129 ++++- .../ConvertContent/FieldConvertersTests.cs | 512 +++++++++++++----- .../ConvertContent/ValueConvertersTests.cs | 188 ++++++- .../ReferenceExtractionTests.cs | 15 +- 17 files changed, 1392 insertions(+), 756 deletions(-) create mode 100644 backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/AddSchemaNames.cs create mode 100644 backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ExcludeChangedTypes.cs create mode 100644 backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ExcludeHidden.cs delete mode 100644 backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs create mode 100644 backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/IConverter.cs create mode 100644 backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ResolveAssetUrls.cs create mode 100644 backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ResolveInvariant.cs create mode 100644 backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ResolveLanguages.cs delete mode 100644 backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/AddSchemaNames.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/AddSchemaNames.cs new file mode 100644 index 0000000000..2cdfb7f5df --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/AddSchemaNames.cs @@ -0,0 +1,51 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Json.Objects; + +namespace Squidex.Domain.Apps.Core.ConvertContent +{ + public sealed class AddSchemaNames : IContentItemConverter + { + private readonly ResolvedComponents components; + + public AddSchemaNames(ResolvedComponents components) + { + this.components = components; + } + + public JsonObject ConvertItem(IField field, JsonObject source) + { + if (field is IArrayField) + { + return source; + } + + if (source.ContainsKey("schemaName")) + { + return source; + } + + if (!Component.IsValid(source, out var discriminator)) + { + return source; + } + + var id = DomainId.Create(discriminator); + + if (components.TryGetValue(id, out var schema)) + { + source["schemaName"] = schema.Name; + } + + return source; + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs index 6a2d255183..cd2a627dab 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs @@ -8,22 +8,60 @@ using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; +using Squidex.Infrastructure.Json.Objects; + +#pragma warning disable RECS0033 // Convert 'if' to '||' expression namespace Squidex.Domain.Apps.Core.ConvertContent { - public static class ContentConverter + public sealed class ContentConverter { - public static ContentData Convert(this ContentData content, Schema schema, params FieldConverter[] converters) + private readonly List itemConverters = new List(); + private readonly List fieldAfterConverters = new List(); + private readonly List fieldConverters = new List(); + private readonly List valueConverters = new List(); + private readonly ResolvedComponents components; + private readonly Schema schema; + + public ContentConverter(ResolvedComponents components, Schema schema) { - Guard.NotNull(schema); + this.components = components; + this.schema = schema; + } - var result = new ContentData(content.Count); + public ContentConverter Add(IConverter converter) + { + if (converter is IContentItemConverter itemConverter) + { + itemConverters.Add(itemConverter); + } - if (converters == null || converters.Length == 0) + if (converter is IContentFieldConverter fieldConverter) { - return result; + fieldConverters.Add(fieldConverter); } + if (converter is IContentFieldAfterConverter fieldAfterConverter) + { + fieldAfterConverters.Add(fieldAfterConverter); + } + + if (converter is IContentValueConverter valueConverter) + { + valueConverters.Add(valueConverter); + } + + return this; + } + + public ContentData Convert(ContentData content) + { + Guard.NotNull(schema); + + // The conversion process assumes that we have ownership of the data and can manipulate it. + // Clones are only created to save allocations. + var result = new ContentData(content.Count); + foreach (var (fieldName, fieldData) in content) { if (fieldData == null || !schema.FieldsByName.TryGetValue(fieldName, out var field)) @@ -31,13 +69,19 @@ public static ContentData Convert(this ContentData content, Schema schema, param continue; } - ContentFieldData? newData = fieldData; + // Some conversions are faster to do upfront, e.g. to remove hidden fields. + var newData = ConvertField(field, fieldData); - if (newData != null) + if (newData == null) { - newData = ConvertData(field, newData, converters); + continue; } + newData = ConvertValues(field, newData); + + // Some conversions are faster to do later, e.g. fallback handling for languages. + newData = ConvertFieldAfter(field, newData); + if (newData != null) { result.Add(field.Name, newData); @@ -47,16 +91,26 @@ public static ContentData Convert(this ContentData content, Schema schema, param return result; } - private static ContentFieldData? ConvertData(IRootField field, ContentFieldData data, FieldConverter[] converters) + private ContentFieldData? ConvertField(IRootField field, ContentFieldData? data) { - if (converters == null || converters.Length == 0) + foreach (var converter in fieldConverters) { - return data; + data = converter.ConvertField(field, data!); + + if (data == null) + { + break; + } } - foreach (var converter in converters) + return data; + } + + private ContentFieldData? ConvertFieldAfter(IRootField field, ContentFieldData? data) + { + foreach (var converter in fieldAfterConverters) { - data = converter(data!, field)!; + data = converter.ConvertFieldAfter(field, data!); if (data == null) { @@ -66,5 +120,187 @@ public static ContentData Convert(this ContentData content, Schema schema, param return data; } + + private (bool Remove, JsonValue) ConvertByType(T field, JsonValue value, IField? parent) where T : IField + { + switch (field) + { + case IArrayField arrayField: + return ConvertArray(arrayField, value); + + case IField: + return ConvertComponent(value, field); + + case IField: + return ConvertComponents(value, field); + + default: + return ConvertValue(field, value, parent); + } + } + + private (bool Remove, JsonValue) ConvertArray(IArrayField field, JsonValue value) + { + if (value.Value is not JsonArray array) + { + return (true, default); + } + + for (int i = 0; i < array.Count; i++) + { + var oldValue = array[i]; + + var (removed, newValue) = ConvertArrayItem(field, oldValue); + + if (removed) + { + array.RemoveAt(i); + i--; + } + else if (!ReferenceEquals(newValue.Value, oldValue.Value)) + { + array[i] = newValue; + } + } + + return (false, array); + } + + private (bool Remove, JsonValue) ConvertComponents(JsonValue value, IField parent) + { + if (value.Value is not JsonArray array) + { + return (true, default); + } + + for (int i = 0; i < array.Count; i++) + { + var oldValue = array[i]; + + var (removed, newValue) = ConvertComponent(oldValue, parent); + + if (removed) + { + array.RemoveAt(i); + i--; + } + else if (!ReferenceEquals(newValue.Value, oldValue.Value)) + { + // Faster to check for reference equality than for deep equals. + array[i] = newValue; + } + } + + return (false, array); + } + + private (bool Remove, JsonValue) ConvertComponent(JsonValue value, IField parent) + { + if (value.Value is not JsonObject obj || !obj.TryGetValue(Component.Discriminator, out var discriminator)) + { + return (true, default); + } + + if (!components.TryGetValue(DomainId.Create(discriminator.ToString()), out var component)) + { + return (true, default); + } + + return (false, ConvertNested(component.FieldCollection, obj, parent)); + } + + private (bool Remove, JsonValue) ConvertArrayItem(IArrayField field, JsonValue value) + { + if (value.Value is not JsonObject obj) + { + return (true, default); + } + + return (false, ConvertNested(field.FieldCollection, obj, field)); + } + + private ContentFieldData ConvertValues(IField field, ContentFieldData source) + { + ContentFieldData? result = null; + + foreach (var (key, oldValue) in source) + { + var (removed, newData) = ConvertByType(field, oldValue, null); + + // Create a copy to avoid allocations if nothing has been changed. + if (removed) + { + result ??= new ContentFieldData(source); + result.Remove(key); + } + else if (!ReferenceEquals(newData.Value, oldValue.Value)) + { + // Faster to check for reference equality than for deep equals. + result ??= new ContentFieldData(source); + result[key] = newData; + } + } + + return result ?? source; + } + + private JsonValue ConvertNested(FieldCollection fields, JsonObject source, IField parent) where T : IField + { + JsonObject? result = null; + + foreach (var (key, oldValue) in source) + { + var newValue = oldValue; + + var remove = false; + + if (fields.ByName.TryGetValue(key, out var field)) + { + (remove, newValue) = ConvertByType(field, oldValue, parent); + } + else if (key != Component.Discriminator) + { + remove = true; + } + + // Create a copy to avoid allocations if nothing has been changed. + if (remove) + { + result ??= new JsonObject(source); + result.Remove(key); + } + else if (!ReferenceEquals(newValue.Value, oldValue.Value)) + { + // Faster to check for reference equality than for deep equals. + result ??= new JsonObject(source); + result[key] = newValue; + } + } + + result ??= source; + + foreach (var converter in itemConverters) + { + result = converter.ConvertItem(parent, result); + } + + return result ?? source; + } + + private (bool Remove, JsonValue) ConvertValue(IField field, JsonValue value, IField? parent) + { + foreach (var converter in valueConverters) + { + // Use a tuple and not a nullable result to avoid boxing and allocations. + (var remove, value) = converter.ConvertValue(field, value, parent); + + if (remove) + { + return (true, default); + } + } + + return (false, value); + } } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ExcludeChangedTypes.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ExcludeChangedTypes.cs new file mode 100644 index 0000000000..2483dbb200 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ExcludeChangedTypes.cs @@ -0,0 +1,66 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Core.ValidateContent; +using Squidex.Infrastructure.Json; +using Squidex.Infrastructure.Json.Objects; + +namespace Squidex.Domain.Apps.Core.ConvertContent +{ + public sealed class ExcludeChangedTypes : IContentFieldConverter, IContentValueConverter + { + private readonly IJsonSerializer jsonSerializer; + + public ExcludeChangedTypes(IJsonSerializer jsonSerializer) + { + this.jsonSerializer = jsonSerializer; + } + + public ContentFieldData? ConvertField(IRootField field, ContentFieldData source) + { + foreach (var (_, value) in source) + { + if (value.Value == default) + { + continue; + } + + if (IsChangedType(field, value)) + { + return null; + } + } + + return source; + } + + public (bool Remove, JsonValue) ConvertValue(IField field, JsonValue source, IField? parent) + { + if (parent == null || source == default) + { + return (false, source); + } + + return (IsChangedType(field, source), source); + } + + private bool IsChangedType(IField field, JsonValue source) + { + try + { + return !JsonValueValidator.IsValid(field, source, jsonSerializer); + } + catch + { + return true; + } + + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ExcludeHidden.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ExcludeHidden.cs new file mode 100644 index 0000000000..68a88aaff9 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ExcludeHidden.cs @@ -0,0 +1,32 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure.Json.Objects; + +namespace Squidex.Domain.Apps.Core.ConvertContent +{ + public sealed class ExcludeHidden : IContentFieldConverter, IContentValueConverter + { + public static readonly ExcludeHidden Instance = new ExcludeHidden(); + + private ExcludeHidden() + { + } + + public ContentFieldData? ConvertField(IRootField field, ContentFieldData source) + { + return field.IsForApi() ? source : null; + } + + public (bool Remove, JsonValue) ConvertValue(IField field, JsonValue source, IField? parent) + { + return field.IsForApi() ? (false, source) : (true, default); + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs deleted file mode 100644 index ece3b30bbb..0000000000 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs +++ /dev/null @@ -1,403 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Core.Contents; -using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Core.ValidateContent; -using Squidex.Infrastructure; -using Squidex.Infrastructure.Json; -using Squidex.Infrastructure.Json.Objects; - -#pragma warning disable MA0048 // File name must match type name - -namespace Squidex.Domain.Apps.Core.ConvertContent -{ - public delegate ContentFieldData? FieldConverter(ContentFieldData data, IRootField field); - - public static class FieldConverters - { - public static readonly FieldConverter Noop = (data, _) => data; - - public static readonly FieldConverter ExcludeHidden = (data, field) => - { - return field.IsForApi() ? data : null; - }; - - public static FieldConverter ExcludeChangedTypes(IJsonSerializer jsonSerializer) - { - return (data, field) => - { - foreach (var (_, value) in data) - { - if (value.Value == default) - { - continue; - } - - try - { - if (!JsonValueValidator.IsValid(field, value, jsonSerializer)) - { - return null; - } - } - catch - { - return null; - } - } - - return data; - }; - } - - public static FieldConverter ResolveInvariant(LanguagesConfig languages) - { - var iv = InvariantPartitioning.Key; - - return (data, field) => - { - if (field.Partitioning.Equals(Partitioning.Invariant) && !data.TryGetNonNull(iv, out _)) - { - var result = new ContentFieldData(1); - - if (data.TryGetNonNull(languages.Master, out var value)) - { - result[iv] = value; - } - else if (data.Count > 0) - { - result[iv] = data.First().Value; - } - - return result; - } - - return data; - }; - } - - public static FieldConverter ResolveLanguages(LanguagesConfig languages) - { - var iv = InvariantPartitioning.Key; - - return (data, field) => - { - if (field.Partitioning.Equals(Partitioning.Language)) - { - if (data.TryGetNonNull(iv, out var value)) - { - var result = new ContentFieldData - { - [languages.Master] = value - }; - - return result; - } - - while (true) - { - var isRemoved = false; - - foreach (var (key, _) in data) - { - if (!languages.AllKeys.Contains(key)) - { - data.Remove(key); - isRemoved = true; - break; - } - } - - if (!isRemoved) - { - break; - } - } - } - - return data; - }; - } - - public static FieldConverter ResolveFallbackLanguages(LanguagesConfig languages) - { - return (data, field) => - { - if (field.Partitioning.Equals(Partitioning.Language)) - { - foreach (var languageCode in languages.AllKeys) - { - if (data.TryGetNonNull(languageCode, out _)) - { - continue; - } - - foreach (var fallback in languages.GetPriorities(languageCode)) - { - if (data.TryGetNonNull(fallback, out var value)) - { - data[languageCode] = value; - break; - } - } - } - } - - return data; - }; - } - - public static FieldConverter FilterLanguages(LanguagesConfig config, IEnumerable? languages) - { - if (languages?.Any() != true) - { - return Noop; - } - - var languageSet = new HashSet(StringComparer.OrdinalIgnoreCase); - - foreach (var language in languages) - { - if (config.Contains(language.Iso2Code)) - { - languageSet.Add(language.Iso2Code); - } - } - - if (languageSet.Count == 0) - { - languageSet.Add(config.Master); - } - - return (data, field) => - { - if (field.Partitioning.Equals(Partitioning.Language)) - { - while (true) - { - var isRemoved = false; - - foreach (var (key, _) in data) - { - if (!languageSet.Contains(key)) - { - data.Remove(key); - isRemoved = true; - break; - } - } - - if (!isRemoved) - { - break; - } - } - } - - return data; - }; - } - - public static FieldConverter ForValues(ResolvedComponents components, params ValueConverter[] converters) - { - return (data, field) => - { - ContentFieldData? newData = null; - - foreach (var (key, value) in data) - { - var newValue = ConvertByType(field, value, null, converters, components); - - if (newValue == null) - { - newData ??= new ContentFieldData(data); - newData.Remove(key); - } - else if (!ReferenceEquals(newValue.Value.Value, value.Value)) - { - newData ??= new ContentFieldData(data); - newData[key] = newValue.Value; - } - } - - return newData ?? data; - }; - } - - private static JsonValue? ConvertByType(T field, JsonValue value, IArrayField? parent, ValueConverter[] converters, - ResolvedComponents components) where T : IField - { - switch (field) - { - case IArrayField arrayField: - return ConvertArray(arrayField, value, converters, components); - - case IField: - return ConvertComponent(value, converters, components); - - case IField: - return ConvertComponents(value, converters, components); - - default: - return ConvertValue(field, value, parent, converters); - } - } - - private static JsonValue? ConvertArray(IArrayField field, JsonValue value, ValueConverter[] converters, - ResolvedComponents components) - { - if (value.Value is JsonArray a) - { - JsonArray? result = null; - - for (int i = 0, j = 0; i < a.Count; i++, j++) - { - var oldValue = a[i]; - - var newValue = ConvertArrayItem(field, oldValue, converters, components); - - if (newValue == null) - { - result ??= new JsonArray(a); - result.RemoveAt(j); - j--; - } - else if (!ReferenceEquals(newValue.Value.Value, oldValue.Value)) - { - result ??= new JsonArray(a); - result[j] = newValue.Value; - } - } - - return result ?? value; - } - - return null; - } - - private static JsonValue? ConvertComponents(JsonValue? value, ValueConverter[] converters, - ResolvedComponents components) - { - if (value?.Value is JsonArray a) - { - JsonArray? result = null; - - for (int i = 0, j = 0; i < a.Count; i++, j++) - { - var oldValue = a[i]; - - var newValue = ConvertComponent(oldValue, converters, components); - - if (newValue == null) - { - result ??= new JsonArray(a); - result.RemoveAt(j); - j--; - } - else if (!ReferenceEquals(newValue.Value.Value, a[i].Value)) - { - result ??= new JsonArray(a); - result[j] = newValue.Value; - } - } - - return result ?? value; - } - - return null; - } - - private static JsonValue? ConvertComponent(JsonValue? value, ValueConverter[] converters, - ResolvedComponents components) - { - if (value?.Value is JsonObject o && o.TryGetValue(Component.Discriminator, out var found) && found.Value is string s) - { - var id = DomainId.Create(s); - - if (components.TryGetValue(id, out var schema)) - { - return ConvertNested(schema.FieldCollection, value.Value, null, converters, components); - } - else - { - return value; - } - } - - return null; - } - - private static JsonValue? ConvertArrayItem(IArrayField field, JsonValue value, ValueConverter[] converters, - ResolvedComponents components) - { - if (value.Value is JsonObject) - { - return ConvertNested(field.FieldCollection, value, field, converters, components); - } - - return null; - } - - private static JsonValue ConvertNested(FieldCollection fields, JsonValue source, IArrayField? parent, ValueConverter[] converters, - ResolvedComponents components) where T : IField - { - JsonObject? result = null; - - var obj = source.AsObject; - - foreach (var (key, value) in obj) - { - JsonValue? newValue = value; - - if (fields.ByName.TryGetValue(key, out var field)) - { - newValue = ConvertByType(field, value, parent, converters, components); - } - else if (key != Component.Discriminator) - { - newValue = null; - } - - if (newValue == null) - { - result ??= new JsonObject(obj); - result.Remove(key); - } - else if (!ReferenceEquals(newValue.Value.Value, value.Value)) - { - result ??= new JsonObject(obj); - result[key] = newValue.Value; - } - } - - return result ?? source; - } - - private static JsonValue? ConvertValue(IField field, JsonValue value, IArrayField? parent, ValueConverter[] converters) - { - var newValue = value; - - for (var i = 0; i < converters.Length; i++) - { - var candidate = converters[i](newValue!, field, parent); - - if (candidate == null) - { - return null; - } - else - { - newValue = candidate.Value; - } - } - - return newValue; - } - } -} diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/IConverter.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/IConverter.cs new file mode 100644 index 0000000000..8dbc5476ff --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/IConverter.cs @@ -0,0 +1,39 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure.Json.Objects; + +#pragma warning disable MA0048 // File name must match type name + +namespace Squidex.Domain.Apps.Core.ConvertContent +{ + public interface IConverter + { + } + + public interface IContentFieldAfterConverter : IConverter + { + ContentFieldData? ConvertFieldAfter(IRootField field, ContentFieldData source); + } + + public interface IContentFieldConverter : IConverter + { + ContentFieldData? ConvertField(IRootField field, ContentFieldData source); + } + + public interface IContentItemConverter : IConverter + { + JsonObject ConvertItem(IField field, JsonObject source); + } + + public interface IContentValueConverter : IConverter + { + (bool Remove, JsonValue) ConvertValue(IField field, JsonValue source, IField? parent); + } +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ResolveAssetUrls.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ResolveAssetUrls.cs new file mode 100644 index 0000000000..e85a303a28 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ResolveAssetUrls.cs @@ -0,0 +1,78 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Json.Objects; + +namespace Squidex.Domain.Apps.Core.ConvertContent +{ + public sealed class ResolveAssetUrls : IContentValueConverter + { + private readonly NamedId appId; + private readonly IUrlGenerator urlGenerator; + private readonly Func shouldHandle; + + public ResolveAssetUrls(NamedId appId, IUrlGenerator urlGenerator, IReadOnlyCollection? fields) + { + this.appId = appId; + + if (fields == null || fields.Count == 0) + { + shouldHandle = (field, parent) => false; + } + else if (fields.Contains("*")) + { + shouldHandle = (field, parent) => true; + } + else + { + var paths = fields.Select(x => x.Split('.')).ToList(); + + shouldHandle = (field, parent) => + { + for (var i = 0; i < paths.Count; i++) + { + var path = paths[i]; + + if (parent != null) + { + if (path.Length == 2 && path[0] == parent.Name && path[1] == field.Name) + { + return true; + } + } + else + { + if (path.Length == 1 && path[0] == field.Name) + { + return true; + } + } + } + + return false; + }; + } + + this.urlGenerator = urlGenerator; + } + + public (bool Remove, JsonValue) ConvertValue(IField field, JsonValue source, IField? parent) + { + if (field is IField && source.Value is JsonArray a && shouldHandle(field, parent)) + { + for (var i = 0; i < a.Count; i++) + { + a[i] = urlGenerator.AssetContent(appId, a[i].ToString()); + } + } + + return (false, source); + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ResolveInvariant.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ResolveInvariant.cs new file mode 100644 index 0000000000..d57289b7f6 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ResolveInvariant.cs @@ -0,0 +1,58 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Core.Schemas; + +namespace Squidex.Domain.Apps.Core.ConvertContent +{ + public sealed class ResolveInvariant : IContentFieldAfterConverter + { + private readonly LanguagesConfig languages; + + public ResolveInvariant(LanguagesConfig languages) + { + this.languages = languages; + } + + public ContentFieldData? ConvertFieldAfter(IRootField field, ContentFieldData source) + { + if (!field.Partitioning.Equals(Partitioning.Invariant)) + { + return source; + } + + if (source.TryGetNonNull(InvariantPartitioning.Key, out _)) + { + return source; + } + + if (source.TryGetNonNull(languages.Master, out var value)) + { + source.Clear(); + source[InvariantPartitioning.Key] = value; + + return source; + } + + if (source.Count > 0) + { + var first = source.First().Value; + + source.Clear(); + source[InvariantPartitioning.Key] = first; + + return source; + } + + source.Clear(); + + return source; + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ResolveLanguages.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ResolveLanguages.cs new file mode 100644 index 0000000000..6d768b7c60 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ResolveLanguages.cs @@ -0,0 +1,104 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Core.ConvertContent +{ + public sealed class ResolveLanguages : IContentFieldAfterConverter + { + private readonly LanguagesConfig languages; + private readonly bool resolveFallback; + private readonly HashSet languageCodes; + + public ResolveLanguages(LanguagesConfig languages, bool resolveFallback = true, params Language[] filteredLanguages) + { + this.languages = languages; + + if (filteredLanguages?.Length > 0) + { + languageCodes = languages.AllKeys.Intersect(filteredLanguages.Select(x => x.Iso2Code)).ToHashSet(); + } + else + { + languageCodes = languages.AllKeys.ToHashSet(); + } + + if (languageCodes.Count == 0) + { + languageCodes.Add(languages.Master); + } + + this.resolveFallback = resolveFallback; + } + + public ContentFieldData? ConvertFieldAfter(IRootField field, ContentFieldData source) + { + if (!field.Partitioning.Equals(Partitioning.Language)) + { + return source; + } + + if (source.TryGetNonNull(InvariantPartitioning.Key, out var value)) + { + var result = new ContentFieldData + { + [languages.Master] = value + }; + + return result; + } + + while (true) + { + var isRemoved = false; + + foreach (var (key, _) in source) + { + if (!languageCodes.Contains(key)) + { + source.Remove(key); + isRemoved = true; + break; + } + } + + if (!isRemoved) + { + break; + } + } + + if (!resolveFallback) + { + return source; + } + + foreach (var languageCode in languageCodes) + { + if (source.TryGetNonNull(languageCode, out _)) + { + continue; + } + + foreach (var fallback in languages.GetPriorities(languageCode)) + { + if (source.TryGetNonNull(fallback, out var fallbackValue)) + { + source[languageCode] = fallbackValue; + break; + } + } + } + + return source; + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs deleted file mode 100644 index 888290e13c..0000000000 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs +++ /dev/null @@ -1,111 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Core.ValidateContent; -using Squidex.Infrastructure; -using Squidex.Infrastructure.Json; -using Squidex.Infrastructure.Json.Objects; - -#pragma warning disable MA0048 // File name must match type name - -namespace Squidex.Domain.Apps.Core.ConvertContent -{ - public delegate JsonValue? ValueConverter(JsonValue value, IField field, IArrayField? parent); - - public static class ValueConverters - { - public static readonly ValueConverter Noop = (value, field, parent) => value; - - public static readonly ValueConverter ExcludeHidden = (value, field, parent) => - { - return field.IsForApi() ? (JsonValue?)value : null; - }; - - public static ValueConverter ExcludeChangedTypes(IJsonSerializer jsonSerializer) - { - return (value, field, parent) => - { - if (value == default) - { - return value; - } - - try - { - if (!JsonValueValidator.IsValid(field, value, jsonSerializer)) - { - return null; - } - } - catch - { - return null; - } - - return value; - }; - } - - public static ValueConverter ResolveAssetUrls(NamedId appId, IReadOnlyCollection? fields, IUrlGenerator urlGenerator) - { - if (fields?.Any() != true) - { - return Noop; - } - - Func shouldHandle; - - if (fields.Contains("*")) - { - shouldHandle = (field, parent) => true; - } - else - { - var paths = fields.Select(x => x.Split('.')).ToList(); - - shouldHandle = (field, parent) => - { - for (var i = 0; i < paths.Count; i++) - { - var path = paths[i]; - - if (parent != null) - { - if (path.Length == 2 && path[0] == parent.Name && path[1] == field.Name) - { - return true; - } - } - else - { - if (path.Length == 1 && path[0] == field.Name) - { - return true; - } - } - } - - return false; - }; - } - - return (value, field, parent) => - { - if (field is IField && value.Value is JsonArray a && shouldHandle(field, parent)) - { - for (var i = 0; i < a.Count; i++) - { - a[i] = urlGenerator.AssetContent(appId, a[i].ToString()); - } - } - - return value; - }; - } - } -} diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ValueReferencesConverter.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ValueReferencesConverter.cs index f9358a4152..a40f4b1193 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ValueReferencesConverter.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ValueReferencesConverter.cs @@ -6,28 +6,29 @@ // ========================================================================== using Squidex.Domain.Apps.Core.ConvertContent; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; +using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Core.ExtractReferenceIds { - public static class ValueReferencesConverter + public sealed class ValueReferencesConverter : IContentValueConverter { - public static ValueConverter CleanReferences(HashSet? validIds = null) + private readonly HashSet? validIds; + + public ValueReferencesConverter(HashSet? validIds = null) { - if (validIds == null) - { - return ValueConverters.Noop; - } + this.validIds = validIds; + } - return (value, field, parent) => + public (bool Remove, JsonValue) ConvertValue(IField field, JsonValue source, IField? parent) + { + if (validIds == null || source == default) { - if (value == default) - { - return value; - } + return (false, source); + } - return ReferencesCleaner.Cleanup(field, value, validIds); - }; + return (false, ReferencesCleaner.Cleanup(field, source, validIds)); } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ConvertData.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ConvertData.cs index 9e51478932..cff201ea0d 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ConvertData.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ConvertData.cs @@ -16,6 +16,8 @@ using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Tasks; +#pragma warning disable MA0073 // Avoid comparison with bool constant + namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps { public sealed class ConvertData : IContentEnricherStep @@ -24,8 +26,7 @@ public sealed class ConvertData : IContentEnricherStep private readonly IJsonSerializer jsonSerializer; private readonly IAssetRepository assetRepository; private readonly IContentRepository contentRepository; - private readonly FieldConverter excludedChangedField; - private readonly FieldConverter excludedHiddenField; + private readonly ExcludeChangedTypes excludeChangedTypes; public ConvertData(IUrlGenerator urlGenerator, IJsonSerializer jsonSerializer, IAssetRepository assetRepository, IContentRepository contentRepository) @@ -35,8 +36,7 @@ public ConvertData(IUrlGenerator urlGenerator, IJsonSerializer jsonSerializer, this.assetRepository = assetRepository; this.contentRepository = contentRepository; - excludedChangedField = FieldConverters.ExcludeChangedTypes(jsonSerializer); - excludedHiddenField = FieldConverters.ExcludeHidden; + excludeChangedTypes = new ExcludeChangedTypes(jsonSerializer); } public async Task EnrichAsync(Context context, IEnumerable contents, ProvideSchema schemas, @@ -50,16 +50,16 @@ public async Task EnrichAsync(Context context, IEnumerable conten var (schema, components) = await schemas(group.Key); - var converters = GenerateConverters(context, components, referenceCleaner).ToArray(); + var converter = GenerateConverter(context, components, schema.SchemaDef, referenceCleaner); foreach (var content in group) { - content.Data = content.Data.Convert(schema.SchemaDef, converters); + content.Data = converter.Convert(content.Data); } } } - private async Task CleanReferencesAsync(Context context, IEnumerable contents, ProvideSchema schemas, + private async Task CleanReferencesAsync(Context context, IEnumerable contents, ProvideSchema schemas, CancellationToken ct) { if (context.ShouldSkipCleanup()) @@ -89,7 +89,7 @@ public async Task EnrichAsync(Context context, IEnumerable conten var foundIds = assets.Union(refContents).ToHashSet(); - return ValueReferencesConverter.CleanReferences(foundIds); + return new ValueReferencesConverter(foundIds); } } @@ -112,50 +112,43 @@ private async Task> QueryAssetIdsAsync(Context context, Ha return result; } - private IEnumerable GenerateConverters(Context context, ResolvedComponents components, ValueConverter? cleanReferences) + private ContentConverter GenerateConverter(Context context, ResolvedComponents components, Schema schema, ValueReferencesConverter? cleanReferences) { + var converter = new ContentConverter(components, schema); + if (!context.IsFrontendClient) { - yield return excludedHiddenField; - yield return FieldConverters.ForValues(components, ValueConverters.ExcludeHidden); + converter.Add(ExcludeHidden.Instance); } - yield return excludedChangedField; - yield return FieldConverters.ForValues(components, ValueConverters.ExcludeChangedTypes(jsonSerializer)); + converter.Add(excludeChangedTypes); if (cleanReferences != null) { - yield return FieldConverters.ForValues(components, cleanReferences); + converter.Add(cleanReferences); } - yield return FieldConverters.ResolveInvariant(context.App.Languages); - yield return FieldConverters.ResolveLanguages(context.App.Languages); + converter.Add(new ResolveInvariant(context.App.Languages)); + + converter.Add( + new ResolveLanguages(context.App.Languages, + context.IsFrontendClient == false && + context.ShouldResolveLanguages(), + context.Languages().ToArray())); if (!context.IsFrontendClient) { - if (context.ShouldResolveLanguages()) - { - yield return FieldConverters.ResolveFallbackLanguages(context.App.Languages); - } - - var languages = context.Languages(); - - if (languages.Any()) - { - yield return FieldConverters.FilterLanguages(context.App.Languages, languages); - } - var assetUrls = context.AssetUrls().ToList(); if (assetUrls.Count > 0) { - var appId = context.App.NamedId(); - - var resolveAssetUrls = ValueConverters.ResolveAssetUrls(appId, assetUrls, urlGenerator); - - yield return FieldConverters.ForValues(components, resolveAssetUrls); + converter.Add(new ResolveAssetUrls(context.App.NamedId(), urlGenerator, assetUrls)); } + + converter.Add(new AddSchemaNames(components)); } + + return converter; } } } diff --git a/backend/tests/RunCoverage.ps1 b/backend/tests/RunCoverage.ps1 index 8618837004..b0a6c57165 100644 --- a/backend/tests/RunCoverage.ps1 +++ b/backend/tests/RunCoverage.ps1 @@ -22,7 +22,7 @@ Write-Host "Recreated '$folderReports' folder" New-Item -ItemType directory -Path $folderReports if ($all -Or $infrastructure) { - &"$folderHome\.nuget\packages\OpenCover\4.7.922\tools\OpenCover.Console.exe" ` + &"$folderHome\.nuget\packages\OpenCover\4.7.1221\tools\OpenCover.Console.exe" ` -register:user ` -target:"C:\Program Files\dotnet\dotnet.exe" ` -targetargs:"test --filter Category!=Dependencies $folderWorking\Squidex.Infrastructure.Tests\Squidex.Infrastructure.Tests.csproj" ` @@ -34,7 +34,7 @@ if ($all -Or $infrastructure) { } if ($all -Or $appsCore) { - &"$folderHome\.nuget\packages\OpenCover\4.7.922\tools\OpenCover.Console.exe" ` + &"$folderHome\.nuget\packages\OpenCover\4.7.1221\tools\OpenCover.Console.exe" ` -register:user ` -target:"C:\Program Files\dotnet\dotnet.exe" ` -targetargs:"test --filter Category!=Dependencies $folderWorking\Squidex.Domain.Apps.Core.Tests\Squidex.Domain.Apps.Core.Tests.csproj" ` @@ -46,7 +46,7 @@ if ($all -Or $appsCore) { } if ($all -Or $appsEntities) { - &"$folderHome\.nuget\packages\OpenCover\4.7.922\tools\OpenCover.Console.exe" ` + &"$folderHome\.nuget\packages\OpenCover\4.7.1221\tools\OpenCover.Console.exe" ` -register:user ` -target:"C:\Program Files\dotnet\dotnet.exe" ` -targetargs:"test --filter Category!=Dependencies $folderWorking\Squidex.Domain.Apps.Entities.Tests\Squidex.Domain.Apps.Entities.Tests.csproj" ` @@ -58,7 +58,7 @@ if ($all -Or $appsEntities) { } if ($all -Or $users) { - &"$folderHome\.nuget\packages\OpenCover\4.7.922\tools\OpenCover.Console.exe" ` + &"$folderHome\.nuget\packages\OpenCover\4.7.1221\tools\OpenCover.Console.exe" ` -register:user ` -target:"C:\Program Files\dotnet\dotnet.exe" ` -targetargs:"test --filter Category!=Dependencies $folderWorking\Squidex.Domain.Users.Tests\Squidex.Domain.Users.Tests.csproj" ` @@ -70,7 +70,7 @@ if ($all -Or $users) { } if ($all -Or $web) { - &"$folderHome\.nuget\packages\OpenCover\4.7.922\tools\OpenCover.Console.exe" ` + &"$folderHome\.nuget\packages\OpenCover\4.7.1221\tools\OpenCover.Console.exe" ` -register:user ` -target:"C:\Program Files\dotnet\dotnet.exe" ` -targetargs:"test --filter Category!=Dependencies $folderWorking\Squidex.Web.Tests\Squidex.Web.Tests.csproj" ` @@ -81,6 +81,6 @@ if ($all -Or $web) { -oldStyle } -&"$folderHome\.nuget\packages\ReportGenerator\4.8.7\tools\net47\ReportGenerator.exe" ` +&"$folderHome\.nuget\packages\ReportGenerator\5.1.9\tools\net47\ReportGenerator.exe" ` -reports:"$folderWorking\$folderReports\*.xml" ` -targetdir:"$folderWorking\$folderReports\Output" \ No newline at end of file diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs index ca08b8cd42..d9f6e6e273 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs @@ -82,6 +82,11 @@ public void Should_apply_value_conversion_on_all_levels() .Add("nested", JsonValue.Array(1, 2)))) .Add(Component.Discriminator, DomainId.Empty)))); + var result = + new ContentConverter(components, schema) + .Add(new ValueConverter()) + .Convert(source); + var expected = new ContentData() .AddField("references", @@ -116,13 +121,127 @@ public void Should_apply_value_conversion_on_all_levels() new JsonObject())) .Add(Component.Discriminator, DomainId.Empty)))); - var converter = new ValueConverter((data, field, parent) => field.Name != "assets1" ? (JsonValue?)null : data); + Assert.Equal(expected, result); + } + + [Fact] + public void Should_apply_item_conversion_on_all_levels() + { + var source = + new ContentData() + .AddField("references", + new ContentFieldData() + .AddInvariant(JsonValue.Array(1, 2))) + .AddField("assets1", + new ContentFieldData() + .AddInvariant(JsonValue.Array(1))) + .AddField("array", + new ContentFieldData() + .AddInvariant( + JsonValue.Array( + new JsonObject() + .Add("nested", JsonValue.Array(1, 2))))) + .AddField("component", + new ContentFieldData() + .AddInvariant( + new JsonObject() + .Add("references", + JsonValue.Array(1, 2)) + .Add("assets1", + JsonValue.Array(1)) + .Add("array", + JsonValue.Array( + new JsonObject() + .Add("nested", JsonValue.Array(1, 2)))) + .Add(Component.Discriminator, DomainId.Empty))) + .AddField("components", + new ContentFieldData() + .AddInvariant( + JsonValue.Array( + new JsonObject() + .Add("references", + JsonValue.Array(1, 2)) + .Add("assets1", + JsonValue.Array(1)) + .Add("array", + JsonValue.Array( + new JsonObject() + .Add("nested", JsonValue.Array(1, 2)))) + .Add(Component.Discriminator, DomainId.Empty)))); + + var result = + new ContentConverter(components, schema) + .Add(new ItemConverter()) + .Convert(source); + + var expected = + new ContentData() + .AddField("references", + new ContentFieldData() + .AddInvariant(JsonValue.Array(1, 2))) + .AddField("assets1", + new ContentFieldData() + .AddInvariant(JsonValue.Array(1))) + .AddField("array", + new ContentFieldData() + .AddInvariant( + JsonValue.Array( + new JsonObject() + .Add("extraField", 42) + .Add("nested", JsonValue.Array(1, 2))))) + .AddField("component", + new ContentFieldData() + .AddInvariant( + new JsonObject() + .Add("extraField", 42) + .Add("references", + JsonValue.Array(1, 2)) + .Add("assets1", + JsonValue.Array(1)) + .Add("array", + JsonValue.Array( + new JsonObject() + .Add("extraField", 42) + .Add("nested", JsonValue.Array(1, 2)))) + .Add(Component.Discriminator, DomainId.Empty))) + .AddField("components", + new ContentFieldData() + .AddInvariant( + JsonValue.Array( + new JsonObject() + .Add("extraField", 42) + .Add("references", + JsonValue.Array(1, 2)) + .Add("assets1", + JsonValue.Array(1)) + .Add("array", + JsonValue.Array( + new JsonObject() + .Add("extraField", 42) + .Add("nested", JsonValue.Array(1, 2)))) + .Add(Component.Discriminator, DomainId.Empty)))); + + Assert.Equal(expected, result); + } + + private sealed class ItemConverter : IContentItemConverter + { + public JsonObject ConvertItem(IField field, JsonObject source) + { + source["extraField"] = 42; + + return source; + } + } - var actual = - source.Convert(schema, - FieldConverters.ForValues(components, converter)); + private sealed class ValueConverter : IContentValueConverter + { + public (bool Remove, JsonValue) ConvertValue(IField field, JsonValue source, IField? parent) + { + var remove = field.Name != "assets1"; - Assert.Equal(expected, actual); + return (remove, source); + } } } } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs index 659f98fee5..527bdcb5b6 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs @@ -27,90 +27,200 @@ public class FieldConvertersTests } [Fact] - public void Should_convert_data_with_value_converter() + public void Should_not_change_data_if_all_field_values_have_correct_type() { - var field = Fields.String(1, "string", Partitioning.Invariant); + var field1 = Fields.Number(1, "number1", Partitioning.Language); + var field2 = Fields.Number(2, "number2", Partitioning.Language); - var source = - new ContentFieldData() - .AddInvariant(new JsonObject()); + var schema = + new Schema("my-schema") + .AddField(field1) + .AddField(field2); - var result = FieldConverters.ForValues(ResolvedComponents.Empty, (value, field, parent) => null)(source, field); - - var expected = new ContentFieldData(); + var source = + new ContentData() + .AddField(field1.Name, + new ContentFieldData() + .AddLocalized("en", 1)) + .AddField(field2.Name, + new ContentFieldData() + .AddLocalized("en", JsonValue.Null) + .AddLocalized("de", 1)); + + var result = + new ContentConverter(ResolvedComponents.Empty, schema) + .Add(new ExcludeChangedTypes(TestUtils.DefaultSerializer)) + .Convert(source); + + var expected = source; Assert.Equal(expected, result); } [Fact] - public void Should_return_field_data_if_excluding_changed_types_and_all_values_are_valid() + public void Should_remove_fields_with_invalid_data() { - var field = Fields.Number(1, "number", Partitioning.Invariant); + var field1 = Fields.Number(1, "number1", Partitioning.Language); + var field2 = Fields.Number(2, "number2", Partitioning.Language); + + var schema = + new Schema("my-schema") + .AddField(field1) + .AddField(field2); var source = - new ContentFieldData() - .AddLocalized("en", JsonValue.Null) - .AddLocalized("de", 1); + new ContentData() + .AddField(field1.Name, + new ContentFieldData() + .AddLocalized("en", 1)) + .AddField(field2.Name, + new ContentFieldData() + .AddLocalized("en", "2") + .AddLocalized("de", 2)); + + var result = + new ContentConverter(ResolvedComponents.Empty, schema) + .Add(new ExcludeChangedTypes(TestUtils.DefaultSerializer)) + .Convert(source); - var result = FieldConverters.ExcludeChangedTypes(TestUtils.DefaultSerializer)(source, field); + var expected = + new ContentData() + .AddField(field1.Name, + new ContentFieldData() + .AddLocalized("en", 1)); - Assert.Equal(source, result); + Assert.Equal(expected, result); } [Fact] - public void Should_return_null_if_excluding_changed_types_and_one_value_is_invalid() + public void Should_remove_hidden_fields() { - var field = Fields.Number(1, "number", Partitioning.Invariant); + var field1 = Fields.Number(1, "number1", Partitioning.Language); + var field2 = Fields.Number(2, "number2", Partitioning.Language).Hide(); + + var schema = + new Schema("my-schema") + .AddField(field1) + .AddField(field2); var source = - new ContentFieldData() - .AddLocalized("en", "EN") - .AddLocalized("de", 0); + new ContentData() + .AddField(field1.Name, + new ContentFieldData() + .AddLocalized("en", 1)) + .AddField(field2.Name, + new ContentFieldData() + .AddLocalized("en", JsonValue.Null) + .AddLocalized("de", 1)); + + var result = + new ContentConverter(ResolvedComponents.Empty, schema) + .Add(ExcludeHidden.Instance) + .Convert(source); - var result = FieldConverters.ExcludeChangedTypes(TestUtils.DefaultSerializer)(source, field); + var expected = + new ContentData() + .AddField(field1.Name, + new ContentFieldData() + .AddLocalized("en", 1)); - Assert.Null(result); + Assert.Equal(expected, result); } [Fact] - public void Should_return_field_data_if_field_not_hidden() + public void Should_not_remove_hidden_fields() { - var field = Fields.String(1, "string", Partitioning.Language); + var field1 = Fields.Number(1, "number1", Partitioning.Language); + var field2 = Fields.Number(2, "number2", Partitioning.Language); - var source = new ContentFieldData(); + var schema = + new Schema("my-schema") + .AddField(field1) + .AddField(field2) + .HideField(2); - var result = FieldConverters.ExcludeHidden(source, field); + var source = + new ContentData() + .AddField(field1.Name, + new ContentFieldData() + .AddLocalized("en", 1)) + .AddField(field2.Name, + new ContentFieldData() + .AddLocalized("en", "2")); + + var result = + new ContentConverter(ResolvedComponents.Empty, schema) + .Add(new ExcludeChangedTypes(TestUtils.DefaultSerializer)) + .Convert(source); - Assert.Equal(source, result); + var expected = + new ContentData() + .AddField(field1.Name, + new ContentFieldData() + .AddLocalized("en", 1)); + + Assert.Equal(expected, result); } [Fact] - public void Should_return_null_if_field_hidden() + public void Should_remove_old_languages() { - var field = Fields.String(1, "string", Partitioning.Language); + var field1 = Fields.String(1, "string1", Partitioning.Language); - var source = new ContentFieldData(); + var schema = + new Schema("my-schema") + .AddField(field1); - var result = FieldConverters.ExcludeHidden(source, field.Hide()); + var source = + new ContentData() + .AddField(field1.Name, + new ContentFieldData() + .AddLocalized("en", "EN") + .AddLocalized("de", "DE") + .AddLocalized("it", "IT")); + + var result = + new ContentConverter(ResolvedComponents.Empty, schema) + .Add(new ResolveLanguages(languagesConfig)) + .Convert(source); - Assert.Null(result); + var expected = + new ContentData() + .AddField(field1.Name, + new ContentFieldData() + .AddLocalized("en", "EN") + .AddLocalized("de", "DE")); + + Assert.Equal(expected, result); } [Fact] - public void Should_resolve_languages_and_cleanup_old_languages() + public void Should_remove_unwanted_languages() { - var field = Fields.String(1, "string", Partitioning.Language); + var field1 = Fields.String(1, "string1", Partitioning.Language); + + var schema = + new Schema("my-schema") + .AddField(field1); var source = - new ContentFieldData() - .AddLocalized("en", "EN") - .AddLocalized("it", "IT"); + new ContentData() + .AddField(field1.Name, + new ContentFieldData() + .AddLocalized("en", "EN") + .AddLocalized("de", "DE") + .AddLocalized("it", "IT")); + + var result = + new ContentConverter(ResolvedComponents.Empty, schema) + .Add(new ResolveLanguages(languagesConfig, true, new[] { Language.DE })) + .Convert(source); var expected = - new ContentFieldData() - .AddLocalized("en", "EN"); - - var result = FieldConverters.ResolveLanguages(languagesConfig)(source, field); + new ContentData() + .AddField(field1.Name, + new ContentFieldData() + .AddLocalized("de", "DE")); Assert.Equal(expected, result); } @@ -119,53 +229,99 @@ public void Should_resolve_languages_and_cleanup_old_languages() [MemberData(nameof(InvalidValues))] public void Should_resolve_master_language_from_invariant(JsonValue value) { - var field = Fields.String(1, "string", Partitioning.Language); + var field1 = Fields.String(1, "string", Partitioning.Language); + + var schema = + new Schema("my-schema") + .AddField(field1); var source = - new ContentFieldData() - .AddLocalized("iv", "A") - .AddLocalized("it", "B"); + new ContentData() + .AddField(field1.Name, + new ContentFieldData() + .AddLocalized("iv", "A") + .AddLocalized("it", "B")); if (value != false) { - source["en"] = value!; + source[field1.Name]!["en"] = value!; } - var expected = - new ContentFieldData() - .AddLocalized("en", "A"); + var result = + new ContentConverter(ResolvedComponents.Empty, schema) + .Add(new ResolveLanguages(languagesConfig)) + .Convert(source); - var result = FieldConverters.ResolveLanguages(languagesConfig)(source, field); + var expected = + new ContentData() + .AddField(field1.Name, + new ContentFieldData() + .AddLocalized("en", "A")); Assert.Equal(expected, result); } - [Fact] - public void Should_not_resolve_master_language_if_not_found() + [Theory] + [MemberData(nameof(InvalidValues))] + public void Should_not_resolve_master_language_if_not_found(JsonValue value) { - var field = Fields.String(1, "string", Partitioning.Invariant); + var field1 = Fields.String(1, "string", Partitioning.Language); - var source = new ContentFieldData(); + var schema = + new Schema("my-schema") + .AddField(field1); + + var source = + new ContentData() + .AddField(field1.Name, + new ContentFieldData() + .AddLocalized("es", "A") + .AddLocalized("it", "B")); - var result = FieldConverters.ResolveLanguages(languagesConfig)(source, field); + if (value != false) + { + source[field1.Name]!["en"] = value; + } - Assert.Equal(source, result); + var result = + new ContentConverter(ResolvedComponents.Empty, schema) + .Add(new ResolveLanguages(languagesConfig)) + .Convert(source); + + var expected = + new ContentData() + .AddField(field1.Name, + new ContentFieldData()); + + if (value != false) + { + expected[field1.Name]!["en"] = value; + } + + Assert.Equal(expected, result); } [Fact] - public void Should_resolve_invariant() + public void Should_keep_invariant() { - var field = Fields.String(1, "string", Partitioning.Invariant); + var field1 = Fields.String(1, "string", Partitioning.Invariant); + + var schema = + new Schema("my-schema") + .AddField(field1); var source = - new ContentFieldData() - .AddInvariant("A"); + new ContentData() + .AddField(field1.Name, + new ContentFieldData() + .AddInvariant("A")); - var expected = - new ContentFieldData() - .AddInvariant("A"); + var result = + new ContentConverter(ResolvedComponents.Empty, schema) + .Add(new ResolveLanguages(languagesConfig)) + .Convert(source); - var result = FieldConverters.ResolveInvariant(languagesConfig)(source, field); + var expected = source; Assert.Equal(expected, result); } @@ -174,42 +330,70 @@ public void Should_resolve_invariant() [MemberData(nameof(InvalidValues))] public void Should_resolve_invariant_from_master_language(JsonValue value) { - var field = Fields.String(1, "string", Partitioning.Invariant); + var field1 = Fields.String(1, "string", Partitioning.Invariant); + + var schema = + new Schema("my-schema") + .AddField(field1); var source = - new ContentFieldData() - .AddLocalized("de", "DE") - .AddLocalized("en", "EN"); + new ContentData() + .AddField(field1.Name, + new ContentFieldData() + .AddLocalized("de", "DE") + .AddLocalized("en", "EN")); if (value != false) { - source[InvariantPartitioning.Key] = value!; + source[field1.Name]![InvariantPartitioning.Key] = value; } - var expected = - new ContentFieldData() - .AddInvariant("EN"); + var result = + new ContentConverter(ResolvedComponents.Empty, schema) + .Add(new ResolveInvariant(languagesConfig)) + .Convert(source); - var result = FieldConverters.ResolveInvariant(languagesConfig)(source, field); + var expected = + new ContentData() + .AddField(field1.Name, + new ContentFieldData() + .AddInvariant("EN")); Assert.Equal(expected, result); } - [Fact] - public void Should_resolve_invariant_from_first_language() + [Theory] + [MemberData(nameof(InvalidValues))] + public void Should_resolve_invariant_from_first_language(JsonValue value) { - var field = Fields.String(1, "string", Partitioning.Invariant); + var field1 = Fields.String(1, "string", Partitioning.Invariant); + + var schema = + new Schema("my-schema") + .AddField(field1); var source = - new ContentFieldData() - .AddLocalized("de", "DE") - .AddLocalized("it", "IT"); + new ContentData() + .AddField(field1.Name, + new ContentFieldData() + .AddLocalized("de", "DE") + .AddLocalized("it", "IT")); - var expected = - new ContentFieldData() - .AddInvariant("DE"); + if (value != false) + { + source[field1.Name]![InvariantPartitioning.Key] = value; + } - var result = FieldConverters.ResolveInvariant(languagesConfig)(source, field); + var result = + new ContentConverter(ResolvedComponents.Empty, schema) + .Add(new ResolveInvariant(languagesConfig)) + .Convert(source); + + var expected = + new ContentData() + .AddField(field1.Name, + new ContentFieldData() + .AddInvariant("DE")); Assert.Equal(expected, result); } @@ -217,20 +401,36 @@ public void Should_resolve_invariant_from_first_language() [Fact] public void Should_not_resolve_invariant_if_not_found() { - var field = Fields.String(1, "string", Partitioning.Language); + var field1 = Fields.String(1, "string", Partitioning.Invariant); - var source = new ContentFieldData(); + var schema = + new Schema("my-schema") + .AddField(field1); - var result = FieldConverters.ResolveInvariant(languagesConfig)(source, field); + var source = + new ContentData() + .AddField(field1.Name, + new ContentFieldData()); - Assert.Same(source, result); + var result = + new ContentConverter(ResolvedComponents.Empty, schema) + .Add(new ResolveLanguages(languagesConfig)) + .Convert(source); + + var expected = source; + + Assert.Equal(expected, result); } [Theory] [MemberData(nameof(InvalidValues))] public void Should_resolve_from_fallback_language_if_found(JsonValue value) { - var field = Fields.String(1, "string", Partitioning.Language); + var field1 = Fields.String(1, "string", Partitioning.Language); + + var schema = + new Schema("my-schema") + .AddField(field1); var config = LanguagesConfig.English @@ -239,60 +439,30 @@ public void Should_resolve_from_fallback_language_if_found(JsonValue value) .Set(Language.ES, false, Language.IT); var source = - new ContentFieldData() - .AddLocalized("en", "EN") - .AddLocalized("it", "IT"); + new ContentData() + .AddField(field1.Name, + new ContentFieldData() + .AddLocalized("en", "EN") + .AddLocalized("it", "IT")); if (value != false) { - source["de"] = value!; + source[field1.Name]!["de"] = value!; } - var expected = - new ContentFieldData() - .AddLocalized("en", "EN") - .AddLocalized("it", "IT") - .AddLocalized("es", "IT") - .AddLocalized("de", "EN"); - - var result = FieldConverters.ResolveFallbackLanguages(config)(source, field); - - Assert.Equal(expected, result); - } - - [Fact] - public void Should_not_return_value_if_master_is_missing() - { - var field = Fields.String(1, "string", Partitioning.Language); - - var source = - new ContentFieldData() - .AddLocalized("de", "DE"); - - var expected = - new ContentFieldData() - .AddLocalized("de", "DE"); - - var result = FieldConverters.ResolveFallbackLanguages(languagesConfig)(source, field); - - Assert.Equal(expected, result); - } - - [Fact] - public void Should_filter_languages() - { - var field = Fields.String(1, "string", Partitioning.Language); - - var source = - new ContentFieldData() - .AddLocalized("en", "EN") - .AddLocalized("de", "DE"); + var result = + new ContentConverter(ResolvedComponents.Empty, schema) + .Add(new ResolveLanguages(config)) + .Convert(source); var expected = - new ContentFieldData() - .AddLocalized("de", "DE"); - - var result = FieldConverters.FilterLanguages(languagesConfig, new[] { Language.DE })(source, field); + new ContentData() + .AddField(field1.Name, + new ContentFieldData() + .AddLocalized("en", "EN") + .AddLocalized("it", "IT") + .AddLocalized("es", "IT") + .AddLocalized("de", "EN")); Assert.Equal(expected, result); } @@ -300,18 +470,29 @@ public void Should_filter_languages() [Fact] public void Should_return_master_language_if_languages_to_filter_are_invalid() { - var field = Fields.String(1, "string", Partitioning.Language); + var field1 = Fields.String(1, "string", Partitioning.Language); + + var schema = + new Schema("my-schema") + .AddField(field1); var source = - new ContentFieldData() - .AddLocalized("en", "EN") - .AddLocalized("de", "DE"); + new ContentData() + .AddField(field1.Name, + new ContentFieldData() + .AddLocalized("en", "EN") + .AddLocalized("de", "DE")); - var expected = - new ContentFieldData() - .AddLocalized("en", "EN"); + var result = + new ContentConverter(ResolvedComponents.Empty, schema) + .Add(new ResolveLanguages(languagesConfig, true, Language.IT)) + .Convert(source); - var result = FieldConverters.FilterLanguages(languagesConfig, new[] { Language.CA })(source, field); + var expected = + new ContentData() + .AddField(field1.Name, + new ContentFieldData() + .AddLocalized("en", "EN")); Assert.Equal(expected, result); } @@ -323,7 +504,9 @@ public void Should_return_same_values_if_resolving_fallback_languages_from_invar var source = new ContentFieldData(); - var result = FieldConverters.ResolveFallbackLanguages(languagesConfig)(source, field); + var result = + new ResolveLanguages(languagesConfig) + .ConvertFieldAfter(field, source); Assert.Same(source, result); } @@ -335,7 +518,9 @@ public void Should_return_same_values_if_filtered_languages_is_empty() var source = new ContentFieldData(); - var result = FieldConverters.FilterLanguages(languagesConfig, Enumerable.Empty())(source, field); + var result = + new ResolveLanguages(languagesConfig, true, Array.Empty()) + .ConvertFieldAfter(field, source); Assert.Same(source, result); } @@ -347,9 +532,44 @@ public void Should_return_same_values_if_filtering_languages_from_invariant_fiel var source = new ContentFieldData(); - var result = FieldConverters.FilterLanguages(languagesConfig, null)(source, field); + var result = + new ResolveLanguages(languagesConfig) + .ConvertFieldAfter(field, source); Assert.Same(source, result); } + + /* + [Fact] + public void Should_add_schema_name_to_component() + { + var field = Fields.Component(1, "component", Partitioning.Invariant); + + var componentId = DomainId.NewGuid(); + var component = new Schema("my-component"); + var components = new ResolvedComponents(new Dictionary + { + [componentId] = component + }); + + var source = + new ContentFieldData() + .AddInvariant( + new JsonObject() + .Add(Component.Discriminator, componentId)); + + var expected = + new ContentFieldData() + .AddInvariant( + new JsonObject() + .Add(Component.Discriminator, componentId) + .Add("schemaName", component.Name)); + + var result = FieldConverters.AddSchemaName(components)(data, field); + + var expected = new ContentFieldData(); + + Assert.Equal(expected, result); + }*/ } } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ValueConvertersTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ValueConvertersTests.cs index 87f71bcff3..535459c97e 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ValueConvertersTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ValueConvertersTests.cs @@ -6,6 +6,7 @@ // ========================================================================== using FakeItEasy; +using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.TestHelpers; @@ -35,23 +36,27 @@ public ValueConvertersTests() } [Fact] - public void Should_return_null_if_field_hidden() + public void Should_return_true_if_field_hidden() { var source = JsonValue.Create(123); - var result = ValueConverters.ExcludeHidden(source, stringField.Hide(), null); + var (remove, _) = + ExcludeHidden.Instance + .ConvertValue(stringField.Hide(), source, null); - Assert.Null(result); + Assert.True(remove); } [Fact] - public void Should_return_null_if_field_has_wrong_type() + public void Should_return_true_if_field_has_wrong_type() { var source = JsonValue.Create("invalid"); - var result = ValueConverters.ExcludeChangedTypes(TestUtils.DefaultSerializer)(source, numberField, null); + var (remove, _) = + new ExcludeChangedTypes(TestUtils.DefaultSerializer) + .ConvertValue(numberField, source, numberField); - Assert.Null(result); + Assert.True(remove); } [Theory] @@ -61,11 +66,19 @@ public void Should_convert_asset_ids_to_urls(string path) { var field = Fields.Assets(1, "assets", Partitioning.Invariant); - var source = JsonValue.Array(id1, id2); + var source = + JsonValue.Array( + id1, + id2); - var expected = JsonValue.Array($"url/to/{id1}", $"url/to/{id2}"); + var (_, result) = + new ResolveAssetUrls(appId, urlGenerator, HashSet.Of(path)) + .ConvertValue(field, source, null); - var result = ValueConverters.ResolveAssetUrls(appId, HashSet.Of(path), urlGenerator)(source, field, null); + var expected = + JsonValue.Array( + $"url/to/{id1}", + $"url/to/{id2}"); Assert.Equal(expected, result); } @@ -77,11 +90,16 @@ public void Should_not_convert_asset_ids_if_field_name_does_not_match(string pat { var field = Fields.Assets(1, "assets", Partitioning.Invariant); - var source = JsonValue.Array(id1, id2); + var source = + JsonValue.Array( + id1, + id2); - var expected = source; + var (_, result) = + new ResolveAssetUrls(appId, urlGenerator, HashSet.Of(path)) + .ConvertValue(field, source, null); - var result = ValueConverters.ResolveAssetUrls(appId, HashSet.Of(path), urlGenerator)(source, field, null); + var expected = source; Assert.Equal(expected, result); } @@ -93,11 +111,19 @@ public void Should_convert_nested_asset_ids_to_urls(string path) { var field = Fields.Array(1, "parent", Partitioning.Invariant, null, null, Fields.Assets(11, "assets")); - var source = JsonValue.Array(id1, id2); + var source = + JsonValue.Array( + id1, + id2); - var expected = JsonValue.Array($"url/to/{id1}", $"url/to/{id2}"); + var (_, result) = + new ResolveAssetUrls(appId, urlGenerator, HashSet.Of(path)) + .ConvertValue(field.FieldsByName["assets"], source, field); - var result = ValueConverters.ResolveAssetUrls(appId, HashSet.Of(path), urlGenerator)(source, field.Fields[0], field); + var expected = + JsonValue.Array( + $"url/to/{id1}", + $"url/to/{id2}"); Assert.Equal(expected, result); } @@ -111,11 +137,139 @@ public void Should_not_convert_nested_asset_ids_if_field_name_does_not_match(str { var field = Fields.Array(1, "parent", Partitioning.Invariant, null, null, Fields.Assets(11, "assets")); - var source = JsonValue.Array(id1, id2); + var source = + JsonValue.Array( + id1, + id2); + + var (_, result) = + new ResolveAssetUrls(appId, urlGenerator, HashSet.Of(path)) + .ConvertValue(field, source, null); + + var expected = source; + + Assert.Equal(expected, result); + } + + [Fact] + public void Should_add_schema_name_if_component() + { + var field = Fields.Component(1, "component", Partitioning.Invariant); + + var componentId = DomainId.NewGuid(); + var component = new Schema("my-component"); + var components = new ResolvedComponents(new Dictionary + { + [componentId] = component + }); + + var source = + new JsonObject() + .Add(Component.Discriminator, componentId); + + var result = + new AddSchemaNames(components) + .ConvertItem(field, source); + + var expected = + new JsonObject() + .Add(Component.Discriminator, componentId) + .Add("schemaName", component.Name); + + Assert.Equal(expected, result); + } + + [Fact] + public void Should_not_add_schema_name_if_field_already_exists() + { + var field = Fields.Component(1, "component", Partitioning.Invariant); + + var componentId = DomainId.NewGuid(); + var component = new Schema("my-component"); + var components = new ResolvedComponents(new Dictionary + { + [componentId] = component + }); + + var source = + new JsonObject() + .Add(Component.Discriminator, componentId) + .Add("schemaName", "existing"); + + var result = + new AddSchemaNames(components) + .ConvertItem(field, source); + + var expected = source; + + Assert.Equal(expected, result); + } + + [Fact] + public void Should_not_add_schema_name_if_array_field() + { + var field = Fields.Array(1, "component", Partitioning.Invariant); + + var componentId = DomainId.NewGuid(); + var component = new Schema("my-component"); + var components = new ResolvedComponents(new Dictionary + { + [componentId] = component + }); + + var source = + new JsonObject() + .Add(Component.Discriminator, componentId); + + var result = + new AddSchemaNames(components) + .ConvertItem(field, source); + + var expected = source; + + Assert.Equal(expected, result); + } + + [Fact] + public void Should_not_add_schema_name_if_not_a_component() + { + var field = Fields.Component(1, "component", Partitioning.Invariant); + + var componentId = DomainId.NewGuid(); + var component = new Schema("my-component"); + var components = new ResolvedComponents(new Dictionary + { + [componentId] = component + }); + + var source = + new JsonObject(); + + var result = + new AddSchemaNames(components) + .ConvertItem(field, source); var expected = source; - var result = ValueConverters.ResolveAssetUrls(appId, HashSet.Of(path), urlGenerator)(source, field.Fields[0], field); + Assert.Equal(expected, result); + } + + [Fact] + public void Should_not_add_schema_name_if_component_not_found() + { + var field = Fields.Component(1, "component", Partitioning.Invariant); + + var componentId = DomainId.NewGuid(); + + var source = + new JsonObject() + .Add(Component.Discriminator, componentId); + + var result = + new AddSchemaNames(ResolvedComponents.Empty) + .ConvertItem(field, source); + + var expected = source; Assert.Equal(expected, result); } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs index c4c7ea53fb..949229a413 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs @@ -190,13 +190,12 @@ public void Should_cleanup_deleted_ids() .Add("nestedAssets", JsonValue.Array(id2)))) .Add(Component.Discriminator, DomainId.Empty)))); - var converter = - FieldConverters.ForValues(components, - ValueReferencesConverter.CleanReferences(new HashSet { id2 })); + var result = + new ContentConverter(components, schema) + .Add(new ValueReferencesConverter(new HashSet { id2 })) + .Convert(source); - var actual = source.Convert(schema, converter); - - Assert.Equal(expected, actual); + Assert.Equal(expected, result); } [Fact] @@ -282,7 +281,7 @@ public void Should_return_ids_from_field(IField field) [MemberData(nameof(ReferencingFields))] public void Should_return_same_value_from_field_if_value_is_json_null(IField field) { - var result = ValueReferencesConverter.CleanReferences(RandomIds())(JsonValue.Null, field, null); + var (_, result) = new ValueReferencesConverter(RandomIds()).ConvertValue(field, JsonValue.Null, null); Assert.Equal(JsonValue.Null, result); } @@ -296,7 +295,7 @@ public void Should_remove_deleted_ids_from_field(IField field) var value = CreateValue(id1, id2); - var result = ValueReferencesConverter.CleanReferences(HashSet.Of(id1))(value, field, null); + var (_, result) = new ValueReferencesConverter(HashSet.Of(id1)).ConvertValue(field, value, null); Assert.Equal(CreateValue(id1), result); }